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

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

我想应该绝大多数人都知道在Global.asax中执行初始化的过程,

然而有些细节是我们需要关注的。

本文用例

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

public static classAppInitializer{

public staticConnectionStringSettingsMyNorthwindConnectionSetting { get; private set; }

public static voidInit()

{

// 读取连接字符串。LoadConnectionString();

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

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

}

static voidLoadConnectionString()

{

ConnectionStringSettingssetting =ConfigurationManager.ConnectionStrings["MyNorthwind"];

if( setting ==null)

throw newConfigurationException("没有配置MyNorthwind连接字符串。");

if( string.IsNullOrEmpty(setting.ConnectionString) )

throw newConfigurationException("没有为MyNorthwind连接字符串指定内容。");

if( string.IsNullOrEmpty(setting.ProviderName) )

throw newConfigurationException("没有为MyNorthwind连接字符串指定ProviderName 。");

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

}

static voidSetSqlDependency()

{

// 判断SQLSERVER版本是否为 2005以上版本,

// 是否开启Service Broker的检查代码就不列出了。SqlDependency.Start(MyNorthwindConnectionSetting.ConnectionString);

}

static voidOthersInit()

{

// 其它的初始化操作。

// 例如:

// 1. 加载必要的缓存数据。

// 2. 检查上传目录是不存在。

// 3. ...................}

}

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

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

User Login

UserName:
Password:

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

protected voidbtnLogin_Click(objectsender, EventArgse)

{

boolok =false;

using( SqlConnectionconnection

=newSqlConnection(AppInitializer.MyNorthwindConnectionSetting.ConnectionString) ) {

connection.Open();

// 其它的数据库操作。ok =true;

}

if( ok )

Response.Redirect("Default2.aspx");

}

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

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

voidApplication_Start(objectsender, EventArgse)

{

//在应用程序启动时运行的代码try{

AppInitializer.Init();

}

catch( Exceptionex ) {

LogException(ex);

// .....................}

}

这段代码有什么问题呢?

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

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

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

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

voidApplication_Start(objectsender, EventArgse)

{

//在应用程序启动时运行的代码AppInitializer.Init();

}

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

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

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

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

答案有二种:

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

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

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

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

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

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

这个示例代码实在太简单了,我想维护人员根据NullReferenceException这个线索找下去,很快就能找到答案。

如果初始化代码再复杂一些,比如SetSqlDependency()中出现异常呢,那么程序仍然能够正常运行,

但是我们期望的缓存依赖可能就没有效果了,最终可能会产生性能问题,排查的难度就会大多了。

记得以前做项目时,就遇到过这种情况,当时感到很奇怪,为什么刷新一下就没黄页了,不过后面的错误就很折腾人了,

最终也让我总结了这个教训。

所以我建议:如果在初始化阶段出现了异常,干脆就别让程序继续运行了,每个请求都直接显示黄页,直到排除故障为止。

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

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

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

private staticExceptions_initException;

voidApplication_Start(objectsender, EventArgse)

{

try{

AppInitializer.Init();

}

catch( Exceptionex ) {

// 记下初始化的异常。s_initException =ex;

}

}

protected voidApplication_BeginRequest(objectsender, EventArgse)

{

// 如果存在初始化异常,就抛出来。

// 直到开发人员发现这个异常,并已解决了异常为止。if( s_initException !=null)

throws_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 classClass1{

public static voidappInitialize()

{

AppInitializer.Init();

}

}

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

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

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

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

internal classBuildResultMainCodeAssembly: BuildResultCompiledAssembly{

privateMethodInfoFindAppInitializeMethod(Typet)

{

returnt.GetMethod("AppInitialize",

BindingFlags.Public |BindingFlags.Static |BindingFlags.IgnoreCase,

null, newType[0], null);

}

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

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

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

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

HttpModule也能执行初始化的任务

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

public classInitAppModule: IHttpModule{

public voidInit(HttpApplicationcontext)

{

//注意:Init事件可能被多次调用,所以这个方法会被多次调用。AppInitializer.Init();

}

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

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

private static readonly objects_lock =new object();

private static bools_initOK;

public voidInit(HttpApplicationcontext)

{

lock( s_lock ) {

if( s_initOK ==false) {

//保证初始化代码只执行一次。AppInitializer.Init();

s_initOK =true;

}

}

}

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

public classInitAppModule: IHttpModule{

staticInitAppModule()

{

AppInitializer.Init();

}

public voidInit(HttpApplicationcontext)

{

// 留个空方法,

// ASP.NET会调用这个方法,最后能触发静态方法的调用。}

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

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

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

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

namespaceInitClassLibrary1

{public classClass1{public static voidInitApp()

{AppInitializer.Init();

}

}

}

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

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

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

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

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

各种初始化方法的差别

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

由于它们都能实现初始化的操作,它们的差别也只有执行的时刻不同而已,

我们可以用简单的方法区分它们的调用位置:看异常的调用堆栈信息。

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

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

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

Global.asax的Application_Start事件处理器的调用方式则不同,ASP.NET采用了反射调用,

当异常发生只保留了内部异常,我们看不到调用堆栈(不信的话,自己去试试)。

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

voidApplication_Start()

{

System.Diagnostics.StackTracestack =newSystem.Diagnostics.StackTrace();

System.IO.File.WriteAllText("h:\\Application_Start_stack.txt", stack.ToString());

}

再打开文件看一下吧。

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

// 这二个方法都可以实现与Application_Start(object sender, EventArgs e)相同的行为。voidApplication_OnStart()

{

}

voidApplication_Start()

{

}

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

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版本。

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】按钮。

如果,您希望更容易地发现我的新博客,不妨点击一下右下角的【关注 Fish Li】。

因为,我的写作热情也离不开您的肯定支持。

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是Fish Li 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段代码是一个网络初始化函数 `net_init()`,它用于配置网络相关的参数。 首先,在函数内部定义了一个长度为8字节的缓冲区 `buf`,用于存储MAC地址和IP地址等信息。 接下来,调用了 `netInitialize()` 函数,该函数用于初始化网络栈。 然后,调用了 `netSYS_SetHostName()` 函数,将主机名设置为 `host_name`。 注释掉了一行代码 `get_mac_addr(ip_addr)` 和 `printf("%s\n", mac_addr)`。这可能是获取MAC地址的代码和打印MAC地址的代码。 然后,调用了 `netMAC_aton()` 函数,将 `mac_addr` 的MAC地址转换为二进制表示,并存储在 `buf` 。接着,使用 `netIF_SetOption()` 函数将转换后的MAC地址设置为网络接口的MAC地址。 接下来,通过判断 `DHCP_enabled` 的值是否为假(false),来确定网络配置模式是静态配置还是使用DHCP。如果为假,即静态配置模式,则继续执行以下代码: 首先,调用 `netDHCP_Disable()` 函数禁用DHCP功能。 然后,调用 `netIP_aton()` 函数将 `ip_addr` 的IP地址转换为二进制表示,并存储在 `buf` 。接着,使用 `netIF_SetOption()` 函数将转换后的IP地址设置为网络接口的IP地址。 接下来,类似地,使用 `netIP_aton()` 函数将 `net_mask` 的子网掩码转换为二进制表示,并通过 `netIF_SetOption()` 函数设置网络接口的子网掩码。 然后,使用 `netIP_aton()` 函数将 `def_gw` 的默认网关地址转换为二进制表示,并通过 `netIF_SetOption()` 函数设置网络接口的默认网关。 接下来,使用 `netIP_aton()` 函数将 `pri_dns` 的主DNS服务器地址转换为二进制表示,并通过 `netIF_SetOption()` 函数设置网络接口的主DNS服务器地址。 最后,使用 `netIP_aton()` 函数将 `sec_dns` 的备用DNS服务器地址转换为二进制表示,并通过 `netIF_SetOption()` 函数设置网络接口的备用DNS服务器地址。 这样,该函数完成了网络初始化的配置,包括设置主机名、MAC地址、IP地址、子网掩码、默认网关和DNS服务器地址等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值