安装或升级程序的用户体验

视乎最近写文太少,又手痒,故而借助本次安装与升级模块所遇问题写点更多介绍。

上帝给了两只耳朵和一张嘴巴,因此文并非介绍界面,更多的是技术层面方法。

一、那个……我们用什么姿势

故事的开始,然后……直接结束,这好像是越来越多安装或升级过程的趋势。对于中间运用什么姿势完全不让客人理会,看起来很简约也很省事。

其实中间多少姿势对于技术而言是等同,不管中间要不要告诉客人进行到什么程度了,结束报告是必须的,不然怎么收费呢?

ASP.NET天生强悍以一敌多(即:多线程)。目前我们的做法是:首先将整个过程放入一个新的线程里,然后客户端通过AJAX定时拉取来报进度。

这种才算是一个完整的异步。至于C#的Asyn或Ajax请求都不是真正的异步,即使在服务端使用异常调用方法也是服务端的事,跟客户端完全无关。

而另一重要问题,线程是无法访问 System.Web.Current 属性,所以传递信息只能依靠 HttpRuntime.Cache

二、蛋疼的数据库更新

数据库安装及升级有几种办法:

1.附加,安装时很好,但升级呢?
2.通过生成脚本执行*.sql文件,很强大可以做任何你想做的事,但要注意角色。
3.利于程序来组织各种Schema,反正我看到workpress、DNN都是这种方案,但是如果代码组织能力不好反而变得更麻烦。

目前选择就是第2种办法,但会遇到一些问题,譬如:生成的脚本无法在SqlCommend来执行,原因是当遇到GO时会抛出【GO附近语法错误】异常。

解决GO问题

采用的方案是将GO行做为分段条件,然后逐一执行每一段代码,实验结果是通过SQL SERVER生成的脚本可以完整的更新。但必须注意生成脚本时一些索引、默认值等也要一并加上。

必须要加事务,否则就是灾难。

执行*.sql的其他方法

1.通过“osql.exe“来执行整个*.sql文件,这就不会有GO问题。
2.利于Microsoft SQL Server Management Studio提供的各种API接口,但分析发现不同版本有不同的DLL,所涉及的DLL合起来是MB大小,要有良好的支持,各种版本的考虑,算了,不想了。

三、资源文档部署问题

产品化会涉及的很多资源文档。如何管理这些文档让升级变得高效。这里会分为服务端【指提供者DDN部署】和宿主端【直接存放于产品端】,会有几个问题:

1.更新频率,比如我们是基于Bootstrap,那么大可放在服务端。
2.版本更新频率,对于频繁就为宿主端。
3.带有静默升级功能,大可完全基于服务端。

为什么提倡服务端部署,特别是对于有大量JavaScript脚本产品,原因是统一部署可以提供更健壮、更快速响应服务。而且如果是由JavaScript引起的又不影响业务的Bug可以直接修正,何乐不为。

暂且列为三点我认为较为需要考虑用户体验问题吧。

简单解决ASP.NET产品化自动升级模块

不知为何,似乎没有什么项目会做ASP.NET自动升级,更多的是手工、程序配合,极少有项目完全自动升级。

这里并不讨论有关于在线升级所涉及的流程及方式,理论上所有流程跟C/S完全两样。

B/S和C/S在升级模块上的区别

C/S都明白可以运行2套独立程序,即更新和宿主分开,这便没有什么问题。而对于B/S结构,也可以如此做,但有个问题,对于产品来说如果需要配置2个网站,先不排除必须在同一机器、目录访问权限等限制以外,对于虚拟机用户无疑成本上升。

因此第一前提是单一网站下产品自动升级。

bin文件夹成为升级的弊端

为何这么说?

升级避免不了bin文件夹的修改,这会引起一个问题,倘若bin文件夹较大,通过File.Copy复制一半有可能会倒置AppDomain重启,中断升级。从而引起网站无法访问。
因此需要有种方法来强制bin文件夹修改时不重启AppDomain,抑或延缓。

对于默认配置来讲,我们可以通过machine.config来强制设置restartOnExternalChanges设为false,程序就不会重启了。

<section name="appSettings" type="System.Configuration.AppSettingsSection, System.Configuration, Version=2.0.0.0,Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
restartOnExternalChanges="false" requirePermission="false" />

看似可行性,但有个致命就是该节点无法被继承,所以我们无法在网站web.config里面强制更改默认配置。当然同样要求每个客户强制去修改这个也是不可能。所以:

第一种办法:修改machine.config来强制修改bin文件夹时不引起application重启,不可行。

而后我尝试使用发布产品时使用第二种办法预编译。预编译根本不可能起到作用,原因是预编译对于application来说,还是需要修改bin文件夹,同样也会遇到如上问题。

对于第三种办法使用Ninject依赖注入,这毋庸置疑是可行了,但由于产品已经开发尾声,倘若做这种修改,也不太现实。

因此我继续尝试其他方法,bin文件夹是受于AppDomain的监测通知重启;因此假使可以通过配置来强制这种监测通知时间不是立即,那问题也变得简单。

终于在web.config的httpRuntime节点找到 maxWaitChangeNotificationwaitChangeNotification。注意这两个属性必须要结合使用。

通过web.config配置来延缓AppDomain重启

我将用更加白话来说明这两个属性。

maxWaitChangeNotification:从第一次文件更改开始,N秒内等待后才重启AppDomain。

waitChangeNotification:两次文件更改间隔时间超出N秒内重启AppDomain。

假如以下配置:

<httpRuntime targetFramework=”4.5″ maxWaitChangeNotification=”60″ waitChangeNotification=”15″ />

表示:如果2次文件更改时间不超过15秒,则从第一次文件更改开始60内才会重启AppDomain。

这里的时间点并不会按设置的准确来重启AppDomain,但测试下来该时间点始终会大于所设置的时间。

很明显这就是我想要的。

至此ASP.NET产品化的自动升级已经解决,望此文对各位有所帮助。

开发简单Quartz.Net远程管理遇到的些问题

之前的一些项目是依赖时IIS进程下面,所以很容易就可以获取到相关Quartz的Job、Trigger等状态信息,相关代码也是直接通过官网的示例,并无做过多研究,当然也运行得很好。直到,通过Topshelf依附到Windows Services(以下简称:QA)中,因为需要对Job管理(以下简称:QB)也是必然了,虽说有很多现成的,但出于特殊需求还是自己在内部实现了。(采用工具方式可参考张善友BLOG

环境:ASP.NET、.Net Framework 4.0、IIS6.0。

1、Failed: Scheduler with name ‘XXXScheduler’ already exists.

我想大部分人会跟我一样,在任何一个需要调度的页面,通过以下方式来创建或获取任何一个Job。

ISchedulerFactory factory = new Quartz.Impl.StdSchedulerFactory();
 factory.GetScheduler();

但其实这样默认返回IScheduler,不管是哪种方式的配置,始终是quartz.config或web.config等等为优先。

当我的Web项目里面需要同时管理QA和QB时,由于每一次页面请求都会进行实例,倒置了Scheduler重复。其实真正做法应该是在Application_Start做QA、QB实例,以便我在其他任何页面调用。示例:

protected void Application_Start(object sender, EventArgs e)
 {
   // 远程
   NameValueCollection props = new NameValueCollection();
   props["quartz.scheduler.instanceName"] = "QAScheduler";
   props["quartz.scheduler.proxy"] = "true";
   props["quartz.scheduler.proxy.address"] = string.Format("tcp://{0}:{1}/{2}", "localhost", "555", "QuartzScheduler");
   ISchedulerFactory factory = new Quartz.Impl.StdSchedulerFactory(props);
   factory.GetScheduler();
   // 本地(自动按quartz.config配置项)
   ISchedulerFactory factory_local = new Quartz.Impl.StdSchedulerFactory();
   factory_local.GetScheduler();
 }

其他需要涉及到的页面:

ISchedulerFactory factory = new Quartz.Impl.StdSchedulerFactory();
IScheduler _scheduler = factory.GetScheduler("SchedulerName");// SchedulerName可以通过factory.AllSchedulers来获取。

2、Error communicating with remote scheduler.

这个错误只有在引用远程Quartz管理时才会出现异常,我总结下来会2种情况遇到这种异常:

a、Quartz没有启动。

b、防火墙。

待补充……

生成唯一文件名(.NET版)

这里会列举一些在上传文件时生成唯一文件名的方法。

前提:在同一个文件夹目录下。

1、时间文件名

简单、方便,比如:DateTime.Now.ToString(“MMDDYYHHMMSS”),在并发率高的情况重复率还是比较高的。

2、GUID方法

理论上讲他是不重复,但是我不太喜欢TA,主要原因是他太长了。

3、GetRandomFileName()方法

这是.NET FRAMEWORK提供的,返回可用作文件夹名或文件名的加密的强随机字符串。生成类似:dcxw0x22.hlr文件名,你可以直接使用他做为你的文件名、或者去掉点号,接着加上你的上传文件的扩展名。不过呢,要注意的是,这里生成的文件名是没有检查是否唯一性,所以还需要单独处理是否已经存在这一动作。

4、test[1].jpg重复文件进行编号处理

使用windows的人会经常看到一些文件名有[数字],这里你也可以模拟类似办法,如果用户上传的文件名重复,你可以尝试加入[number]继续检查,直到唯一。

参考文章:http://www.codekeep.net/snippets/ac9e2e7a-f2d8-49b2-8f07-bf443ce93697.aspx

 

生成文件名我的观念是按具体需求,文件名跟文件夹名称都是如此。而如何有效管理上传文件也是很重要,比较乐观的比如按时间来划分、按编号来划分。我曾经升级过一些系统,旧的办法总是讲所有用户文件不管三七二十一,直接统一放置某文件夹下面,随着时间越来越久,文件夹越来越膨胀。在WINDOWS下面,当文件夹的文件数量越来越多,其IO读取速度也会越来越慢。

img空路径导致Page_Load执行两次

很难以发现这个问题:一般执行的结果肯定是一样的,只不过是Page_Load执行两次而已(不排除特殊情况下)。所以如果你的加载数据是放在Page_Load中执行那这将是产生很大的性能问题,因为该问题的产生是在执行结果后产生HTML并发送客户端后如果src=””引起的另一次GET请求。解决办法当然就是赋予src正确的数据。

我只能告诫自己,按标准堆规范做事,否则很大性能并不是框架本身而是自己。

ASP.NET与Quartz.NET计划任务

很多时候我们希望在应用程序中按指定时间执行一些任务,一种解决办法是使用客户端就用程序或WINDOWS服务来执行,较早的一些项目就是采用这些方法,写个简单的控制台应用程序,然后由WINDOWS任务计划调用。这又让我想起我的一个同学,他曾经也在他的项目中使用WINDOWS服务来解决问题。

在ASP.NET应用程序中,并不是只能采用以上那些办法。取而代之的是通过IIS线程执行你的任务。而Quartz是第三方开源的作业调度框架,而Quartz.NET是Open Symphony 的 Quartz API的.NET移植,它用C#写成,可用于winform和asp.net应用中。

在Quartz.NET有三个主要组成部分:调度,作业和触发器。写到这里感觉很多余,关于Quartz.NET方面的知识,我这里将提供一些地址。而这里我将叙述遇到的一些问题及解决方案:

1、应用池回收调度停止

Quartz.NET本身是寄托于IIS线程上面,所以一但应用池回收,导致放在内存中的数据也跟着丢失。因此对于这个问题,需要从IIS应用池配置、ASP.NET应用程序双方面结合。

IIS应用池配置主要是减少线程自动回收,主要影响因素包括:回收工作进程(分钟)、空闲超时关闭工作进程。

ASP.NET应用程序处理办法通过Application_Start和Application_End分别加载Quartz.NET和关闭Quartz.NET,特别是在Application_End时可以使用类似这种萎缩的方式,解决当由IIS应用池回收自动加载问题:

Thread.Sleep(1000);
string url = "Web Url";
HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
Stream receiveStream = myHttpWebResponse.GetResponseStream();

其实综合起来并不能很好解决所有问题,比较机器重启后等。

参考

  1. 开源的作业调度框架 – Quartz.NET
  2. Quartz.NET – Enterprise Job Scheduler for .NET Platform(官网)

© 2017 卡片机色彩 沪ICP备13032872号-3

Theme by cipchk

to top