继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

ASP.NET应用程序中初始化技巧

慕沐945168
关注TA
已关注
手记 26
粉丝 103
获赞 424

阅读目录

  • 开始

  • 本文用例

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

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

  • 还有哪些初始化方法?

  • App_Code中的AppInitialize方法

  • HttpModule也能执行初始化的任务

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

  • 各种初始化方法的差别

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

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

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

回到顶部

本文用例

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

public static class AppInitializer
{
    public static ConnectionStringSettings MyNorthwindConnectionSetting { get; private set; }

    public static void Init()
    {
        // 读取连接字符串。
        LoadConnectionString();

        // 设置SQLSERVER缓存依赖通知。
        SetSqlDependency();

        // 其它的初始化操作。
        OthersInit();
    }

    static void LoadConnectionString()
    {
        ConnectionStringSettings setting = ConfigurationManager.ConnectionStrings["MyNorthwind"];
        if( setting == null )
            throw new ConfigurationException("没有配置MyNorthwind连接字符串。");

        if( string.IsNullOrEmpty(setting.ConnectionString) )
            throw new ConfigurationException("没有为MyNorthwind连接字符串指定内容。");

        if( string.IsNullOrEmpty(setting.ProviderName) )
            throw new ConfigurationException("没有为MyNorthwind连接字符串指定ProviderName 。");

        // 保存读取到的连接字符串,供程序使用。
        MyNorthwindConnectionSetting = setting;
    }

    static void SetSqlDependency()
    {
        // 判断SQLSERVER版本是否为 2005以上版本,
        // 是否开启Service Broker的检查代码就不列出了。

        SqlDependency.Start(MyNorthwindConnectionSetting.ConnectionString);
    }

    static void OthersInit()
    {
        // 其它的初始化操作。

        // 例如:
        // 1. 加载必要的缓存数据。
        // 2. 检查上传目录是不存在。
        // 3. ...................
    }
}

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

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

<body>
    <form id="form1" runat="server">
    <div>
        <h1>User Login</h1>
    </div>

        <p style="line-height: 150%;">
            UserName: <asp:TextBox ID="txtUserName" runat="server" Width="200px" Text="Fish Li"></asp:TextBox><br />
            Password: <asp:TextBox ID="txtPassword" runat="server" Width="200px" TextMode="Password"></asp:TextBox><br />
            <asp:Button ID="btnLogin" runat="server" Text="登录" OnClick="btnLogin_Click" />
        </p>
    </form>
</body>

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

protected void btnLogin_Click(object sender, EventArgs e)
{
    bool ok = false;

    using( SqlConnection connection
        = new SqlConnection(AppInitializer.MyNorthwindConnectionSetting.ConnectionString) ) {

        connection.Open();

        // 其它的数据库操作。

        ok = true;
    }

    if( ok )
        Response.Redirect("Default2.aspx");
}


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

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

void Application_Start(object sender, EventArgs e)
{
    //在应用程序启动时运行的代码
    try {
        AppInitializer.Init();
    }
    catch( Exception ex ) {
        LogException(ex);
        
        // .....................
    }
}

这段代码有什么问题呢?
其实问题的线索在于:为什么要加try....catch语句,是因为知道可能会发生异常吗?
如果真有异常情况发生,这样处理后,后续的请求是不是会发生各种想像不到的错误?

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

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

void Application_Start(object sender, EventArgs e)
{
    //在应用程序启动时运行的代码

    AppInitializer.Init();
}

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

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

现在就加上连接字符串吗?
别急,想像一下,如果这个网站是一个真实的在线网站,会是什么情况呢?
答案有二种:
1. 另一个用户也发起了一次请求。
2. 当前用户看到错误页面后,重新刷新了一次当前页面。

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

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

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

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

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

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

回到顶部

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

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

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

private static Exception s_initException;

void Application_Start(object sender, EventArgs e)
{
    try {
        AppInitializer.Init();
    }
    catch( Exception ex ) {
        // 记下初始化的异常。
        s_initException = ex;
    }    
}

protected void Application_BeginRequest(object sender, EventArgs e)
{
    // 如果存在初始化异常,就抛出来。
    // 直到开发人员发现这个异常,并已解决了异常为止。
    if( s_initException != null )
        throw s_initException;
}

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


说明: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方法,用它也能执行初始化的任务。

public class Class1
{
    public static void appInitialize()
    {
        AppInitializer.Init();        
    }
}

如果我此时再次运行示例程序(已注释掉Global.asax中的代码)

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

这个AppInitialize方法有什么限制呢?
我们还是来看一下ASP.NET的源代码吧:

internal class BuildResultMainCodeAssembly : BuildResultCompiledAssembly
{
    private MethodInfo FindAppInitializeMethod(Type t)
    {
        return t.GetMethod("AppInitialize", 
            BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase, 
            null, new Type[0], null);
    }

根据代码我们可以发现AppInitialize方法的特点有:
1. 必须是一个公开的静态方法:BindingFlags.Public | BindingFlags.Static
2. 方法名不区分大小写:BindingFlags.IgnoreCase
3. 方法不允许有传入参数:new Type[0]


HttpModule也能执行初始化的任务

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

public class InitAppModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
         //注意:Init事件可能被多次调用,所以这个方法会被多次调用。

        AppInitializer.Init();
    }

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

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

private static readonly object s_lock = new object();
private static bool s_initOK;

public void Init(HttpApplication context)
{
    lock( s_lock ) {
        if( s_initOK == false ) {
            //保证初始化代码只执行一次。

            AppInitializer.Init();
            s_initOK = true;
        }
    }
}

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

public class InitAppModule : IHttpModule
{
    static InitAppModule()
    {
        AppInitializer.Init();
    }
    public void Init(HttpApplication context)
    {
        // 留个空方法,
        // ASP.NET会调用这个方法,最后能触发静态方法的调用。
    }


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

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

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

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

namespace InitClassLibrary1
{
    public class Class1
    {
        public static void InitApp()
        {
            AppInitializer.Init();
        }
    }
}

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

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

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

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

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

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP