如何在ASP.NET应用程序中初始化

每个程序都需要初始化的过程,用来读取配置或者设置一些运行环境(变量),对于ASP.NET程序来说,又该在哪里执行初始化的任务呢?

我想应该绝大多数人都知道在Global.asax中执行初始化的过程,然而有些细节是我们需要关注的。

本文用例

在这篇博客的示例代码中,AppInitializer包含了网站的初始化的实现代码:

 
 
 
 
  1. public static class AppInitializer  
  2. {  
  3.     public static ConnectionStringSettings MyNorthwindConnectionSetting { get; private set; }  
  4.  
  5.     public static void Init()  
  6.     {  
  7.         // 读取连接字符串。  
  8.         LoadConnectionString();  
  9.  
  10.         // 设置SQLSERVER缓存依赖通知。  
  11.         SetSqlDependency();  
  12.  
  13.         // 其它的初始化操作。  
  14.         OthersInit();  
  15.     }  
  16.  
  17.     static void LoadConnectionString()  
  18.     {  
  19.         ConnectionStringSettings setting = ConfigurationManager.ConnectionStrings["MyNorthwind"];  
  20.         if( setting == null )  
  21.             throw new ConfigurationException("没有配置MyNorthwind连接字符串。");  
  22.  
  23.         if( string.IsNullOrEmpty(setting.ConnectionString) )  
  24.             throw new ConfigurationException("没有为MyNorthwind连接字符串指定内容。");  
  25.  
  26.         if( string.IsNullOrEmpty(setting.ProviderName) )  
  27.             throw new ConfigurationException("没有为MyNorthwind连接字符串指定ProviderName 。");  
  28.  
  29.         // 保存读取到的连接字符串,供程序使用。  
  30.         MyNorthwindConnectionSetting = setting;  
  31.     }  
  32.  
  33.     static void SetSqlDependency()  
  34.     {  
  35.         // 判断SQLSERVER版本是否为 2005以上版本,  
  36.         // 是否开启Service Broker的检查代码就不列出了。  
  37.  
  38.         SqlDependency.Start(MyNorthwindConnectionSetting.ConnectionString);  
  39.     }  
  40.  
  41.     static void OthersInit()  
  42.     {  
  43.         // 其它的初始化操作。  
  44.  
  45.         // 例如:  
  46.         // 1. 加载必要的缓存数据。  
  47.         // 2. 检查上传目录是不存在。  
  48.         // 3. ...................  
  49.     }  

这段代码的意图很清楚,一定要确保正确的配置了数据库连接字符串,否则以异常的形式报告出来。

示例程序还有一个页面,Default.aspx

 
 
 
 
  1.  
  2.      
  3.     
     
  4.         

    User Login

     
  5.     
 
  •  
  •          
  •             UserName:  
  •             Password:  
  •              
  •         

     
  •      
  •  
  • 其实就是一个登录页面,后台代码为:

     
     
     
     
    1. protected void btnLogin_Click(object sender, EventArgs e)  
    2. {  
    3.     bool ok = false;  
    4.  
    5.     using( SqlConnection connection  
    6.         = new SqlConnection(AppInitializer.MyNorthwindConnectionSetting.ConnectionString) ) {  
    7.  
    8.         connection.Open();  
    9.  
    10.         // 其它的数据库操作。  
    11.  
    12.         ok = true;  
    13.     }  
    14.  
    15.     if( ok )  
    16.         Response.Redirect("Default2.aspx");  

    你没有想到的Global.asax怪事!

    或许有些人会这样写他们的初始化代码:

     
     
     
     
    1. void Application_Start(object sender, EventArgs e)  
    2. {  
    3.     //在应用程序启动时运行的代码  
    4.     try {  
    5.         AppInitializer.Init();  
    6.     }  
    7.     catch( Exception ex ) {  
    8.         LogException(ex);  
    9.           
    10.         // .....................  
    11.     }  
    12. }  

    这段代码有什么问题呢?

    其实问题的线索在于:为什么要加try....catch语句,是因为知道可能会发生异常吗?

    如果真有异常情况发生,这样处理后,后续的请求是不是会发生各种想像不到的错误?

    显然这里不能吃掉异常,要不然后面的请求肯定会有问题,因为它们依赖的设置没有正确的初始化。

    好吧,那我去掉 try.....catch语句,这样总该行了吧:

     
     
     
     
    1. void Application_Start(object sender, EventArgs e)  
    2. {  
    3.     //在应用程序启动时运行的代码  
    4.  
    5.     AppInitializer.Init();  
    6. }  

    还是看来一下真实的运行情况吧。

    噢,抱歉,我还真忘记了配置连接字符串,这个异常提示太给力了。

    现在就加上连接字符串吗?

    别急,想像一下,如果这个网站是一个真实的在线网站,会是什么情况呢?
    答案有二种:

    1. 另一个用户也发起了一次请求。

    2. 当前用户看到错误页面后,重新刷新了一次当前页面。

    现在我用Opera来扮演第二个浏览用户吧,还是打开同样的网址。

    太奇怪了,第二个用户居然能打开页面,好吧,让他登录试试。

    结果第二个用户看到的错误情况和第一个用户完全不同。

    如果此时第一个用户刷新他的浏览器,发现页面又可以显示了,然而登录时,会看到与第二个用户一样的异常信息。

    这个示例代码实在太简单了,我想维护人员根据NullReferenceException这个线索找下去,很快就能找到答案。如果初始化代码再复杂一些,比如SetSqlDependency()中出现异常呢,那么程序仍然能够正常运行,但是我们期望的缓存依赖可能就没有效果了,最终可能会产生性能问题,排查的难度就会大多了。

    记得以前做项目时,就遇到过这种情况,当时感到很奇怪,为什么刷新一下就没黄页了,不过后面的错误就很折腾人了,最终也让我总结了这个教训。所以我建议:如果在初始化阶段出现了异常,干脆就别让程序继续运行了,每个请求都直接显示黄页,直到排除故障为止。

    #p#

    如何保证初始化异常一直显示?

    当初始化发生异常时,如何保证初始化异常一直显示呢?

    方法其实并不难,我们需要修改一下代码:

     
     
     
     
    1. private static Exception s_initException;  
    2.  
    3. void Application_Start(object sender, EventArgs e)  
    4. {  
    5.     try {  
    6.         AppInitializer.Init();  
    7.     }  
    8.     catch( Exception ex ) {  
    9.         // 记下初始化的异常。  
    10.         s_initException = ex;  
    11.     }      
    12. }  
    13.  
    14. protected void Application_BeginRequest(object sender, EventArgs e)  
    15. {  
    16.     // 如果存在初始化异常,就抛出来。  
    17.     // 直到开发人员发现这个异常,并已解决了异常为止。  
    18.     if( s_initException != null )  
    19.         throw s_initException;  
    20. }  

    现在不管有多少个用户来访问,或者第一个访问者刷新浏览器多少次,都会看到同样的异常信息:

    说明:Global.asax的这个问题在IIS7以上版本的集成模式下并不存在。

    还有哪些初始化方法?

    除了Global.asax中的Application_Start,还有哪些方法可以在ASP.NET程序执行初始化的任务呢?

    目前我知道的还有另三种方法:

    1. App_Code中的AppInitialize方法。

    2. 写个专用的HttpModule。

    3. ASP.NET 4.0的PreApplicationStartMethodAttribute

    App_Code中的AppInitialize方法

    ASP.NET允许我们在App_Code中的任何一个类型定义一个AppInitialize方法,用它也能执行初始化的任务。

     
     
     
     
    1. public class Class1  
    2. {  
    3.     public static void appInitialize()  
    4.     {  
    5.         AppInitializer.Init();          
    6.     }  
    7. }  

    如果我此时再次运行示例程序(已注释掉Global.asax中的代码),会看到以下显示:

    显然,我们期望的初始化代码确实被调用了。

    这个AppInitialize方法有什么限制呢?

    我们还是来看一下ASP.NET的源代码吧:

     
     
     
     
    1. internal class BuildResultMainCodeAssembly : BuildResultCompiledAssembly  
    2. {  
    3.     private MethodInfo FindAppInitializeMethod(Type t)  
    4.     {  
    5.         return t.GetMethod("AppInitialize",   
    6.             BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,   
    7.             null, new Type[0], null);  
    8.     }  

    根据代码我们可以发现AppInitialize方法的特点有:

    1. 必须是一个公开的静态方法:BindingFlags.Public | BindingFlags.Static

    2. 方法名不区分大小写:BindingFlags.IgnoreCase

    3. 方法不允许有传入参数:new Type[0]

    HttpModule也能执行初始化的任务

    由于HttpModule总是会在ASP.NET管线中被调用,所以,我们还可以用它来完成初始化的操作。

     
     
     
     
    1. public class InitAppModule : IHttpModule  
    2. {  
    3.     public void Init(HttpApplication context)  
    4.     {  
    5.          //注意:Init事件可能被多次调用,所以这个方法会被多次调用。  
    6.  
    7.         AppInitializer.Init();  
    8.     }  

    正如代码注释所说的那样,这种调用代码是不对的,除非你能接受初始化代码被多次调用!

    所以,我们应该按单例模式的思路来改写代码:

     
     
     
     
    1. private static readonly object s_lock = new object();  
    2. private static bool s_initOK;  
    3.  
    4. public void Init(HttpApplication context)  
    5. {  
    6.     lock( s_lock ) {  
    7.         if( s_initOK == false ) {  
    8.             //保证初始化代码只执行一次。  
    9.  
    10.             AppInitializer.Init();  
    11.             s_initOK = true;  
    12.         }  
    13.     }  
    14. }  

    如果你希望代码简单一点,还可以这样实现:

     
     
     
     
    1. public class InitAppModule : IHttpModule  
    2. {  
    3.     static InitAppModule()  
    4.     {  
    5.         AppInitializer.Init();  
    6.     }  
    7.     public void Init(HttpApplication context)  
    8.     {  
    9.         // 留个空方法,  
    10.         // ASP.NET会调用这个方法,最后能触发静态方法的调用。  
    11.     }  

    ASP.NET 4.0新增的初始化方法

    为了让一些类库能自动执行一些初始化,ASP.NET提供了一种新方法,允许为程序集指定一个PreApplicationStartMethodAttribute

    为了演示这种用法,我将前面的示例(VS2008开发)移到一个类库中(用VS2012开发)并设置类库的命名空间为InitClassLibrary1。

    然后,我添加了一个调用类:

     
     
     
     
    1. namespace InitClassLibrary1  
    2. {  
    3.     public class Class1  
    4.     {  
    5.         public static void InitApp()  
    6.         {  
    7.             AppInitializer.Init();  
    8.         }  
    9.     }  

    最后,我们可以在InitClassLibrary1类库的AssemblyInfo.cs文件中,增加一个Attribute

     
     
     
     
    1. [assembly: System.Web.PreApplicationStartMethod(  
    2.                 typeof(InitClassLibrary1.Class1), "InitApp")]  

    当然了,你也可以直接像下面设置,免得多创建一个类型出来:

     
     
     
     
    1. [assembly: System.Web.PreApplicationStartMethod(  
    2.                 typeof(InitClassLibrary1.AppInitializer), "Init")]  

    这样设置后,再运行网站,你也能发现我们的初始化代码确实运行了:黄页仍然在显示。

    #p#

    各种初始化方法的差别

    前面介绍了4种在ASP.NET执行初始化的方法,你或许想知道它们到底有哪些区别呢?

    由于它们都能实现初始化的操作,它们的差别也只有执行的时刻不同而已,我们可以用简单的方法区分它们的调用位置:看异常的调用堆栈信息。

    AppInitialize方法异常时的调用堆栈信息:

    HttpModule异常时的调用堆栈信息:

    PreApplicationStartMethodAttribute异常时的调用堆栈信息:

    Global.asax的Application_Start事件处理器的调用方式则不同,ASP.NET采用了反射调用,当异常发生只保留了内部异常,我们看不到调用堆栈(不信的话,自己去试试)。

    没关系,既然ASP.NET不告诉我们调用堆栈信息,我们自己也可以去取,请看下面的代码:

     
     
     
     
    1. void Application_Start()  
    2. {  
    3.     System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace();  
    4.     System.IO.File.WriteAllText("h:\\Application_Start_stack.txt", stack.ToString());  
    5. }  

    再打开文件看一下吧。

    说明:Global.asax的Application_Start事件处理器还有几种等效的方法:

     
     
     
     
    1. // 这二个方法都可以实现与Application_Start(object sender, EventArgs e)相同的行为。  
    2.  
    3. void Application_OnStart()  
    4. {  
    5. }  
    6.  
    7. void Application_Start()  
    8. {  
    9. }  

    根据以上分析,可以可以得知:

    1. AppInitialize和PreApplicationStartMethodAttribute指向的方法被调用的时机发生在ASP.NET创建宿主环境时,属于比较早的时刻。

    2. Application_Start和HttpModule的调用时刻要晚一点。

    这个结论有用吗?

    其实我也感觉意义不大,不过分析它仅仅为了满足我的求知欲和好奇心而已,你是否也有这样的好奇心呢?

    如果你仍然好奇想知道这4种方法的执行时机的先后顺序,我也能告诉你:

    1. PreApplicationStartMethodAttribute指向的方法。

    2. App_Code中的appInitialize方法。

    3. Application_Start。

    4. HttpModule

    再补充一点:在开发环境中,当我们编译网站时,PreApplicationStartMethodAttribute指向的方法可能会被调用,这处决于类库的程序集是否发生了修改。

    到底该选择哪种初始化方法?

    今天给大家介绍了4种在ASP.NET中执行初始化的方法,或许有些人会想:到底该选择哪种初始化方法呢?

    的确,方法越多越让人迷惑。

    下面的观点仅代表我个人的建议,你也可以根据自己的喜好来选择。

    1. 优先选择Application_Start(虽然IIS的经典模式下需要多写点代码),因为任何人找初始化代码时都会想到那里,便于其他人维护。

    2. AppInitialize方法虽然使用简单,但它并不适合于WebApplication项目。

    3. PreApplicationStartMethodAttribute只支持ASP.NET 4.0以上版本,且尤其适合于类库的内部初始化。

    4. 当以上方法都不可行时,HttpModule将成为最后的救命稻草,它适合所有ASP.NET版本。

    原文链接:http://www.cnblogs.com/fish-li/archive/2013/03/24/2979780.html

    当前名称:如何在ASP.NET应用程序中初始化
    转载来源:http://www.mswzjz.cn/qtweb/news35/421285.html

    攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等

    广告

    声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能

    贝锐智能技术为您推荐以下文章

    Google知识

    分类信息网站