寂寞如此美丽:脱离Application_Start,让初始化代码更优美
这里的“寂寞”指的是将ASP.NET程序中的初始化代码从Global.asax.cs的Application_Start()方法中,移至单独的程序集中,并且这个程序集与Web项目的程序集没有任何来往。比如,初始化代码所在的程序集叫CNBlogs.BootStrapper,Web项目的程序集叫CNBlogs.Web,在Visual Studio中,这两个项目之间没有任何引用关系。
因为低耦合而变得寂寞,代码却因此变得更美。在生活中,寂寞可以让人保持内心的宁静,可以享受更多思考之美。
这篇文章通过两种方法让初始化代码变得更优美:
1)PreApplicationStartMethod(ASP.NET 4.0的新特性,详见这里)。
2)Bootstrapper(codeplex上的开源项目,详见 http://bootstrapper.codeplex.com/)。
使用Application_Start()的场景
先看一下不使用Bootstrapper,直接在Application_Start()进行初始化的示例代码:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
protected void Application_Start()
{
//MVC的注册
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
//IOC容器的注册
var container = IoCFactory.Instance.CurrentContainter;
container.RegisterType<IBlogSiteService, FakeBlogSiteService>();
container.RegisterType<IBlogPostService, FakeBlogPostService>();
}
这是一种常用场景,在Application_Start()中完成一些初始化注册与配置,但这些代码堆在一起,看着总是不顺眼。
当使用了测试驱动开发(TDD)之后,不仅看着不顺眼,而且用着也不顺手,因为在测试项目中也要进行初始化,但测试项目无法调用Application_Start()方法。
我们当时将这部分初始化代码移至独立的程序集(CNBlogs.BootStrapper),就是因为测试所需。独立出来后,Application_Start()与测试项目都通过调用CNBlogs.BootStrapper.Initializer.Initialize()方法完成初始化。
这样虽然独立了,但并不寂寞,也不美丽。因为:
1. Web项目要依赖CNBlogs.BootStrapper;
2. 这些初始化代码依然堆在一些,只是换了个地方。
解决方法
针对第一个问题 » 用ASP.NET 4.0的新特性PreApplicationStartMethod来解决。在CNBlogs.Bootstrapper项目的AssemblyInfo.cs添加如下的代码:
[assembly: PreApplicationStartMethod(typeof(CNBlogs.Bootstrapper.Initializer), "Initialize")]
注:CNBlogs.Bootstrapper.Initializer是静态类,Initialize中Initializer中的静态方法。
ASP.NET Runtime会自动在程序集中发现这个定义,并在调用Application_Start()之前执行这里指定的Initialize方法。
需要注意的是,AreaRegistration.RegisterAllAreas();只能放在Application_Start()中执行,否则会报错:
This method cannot be called during the application's pre-start initialization stage.
针对第二个问题 » 通过 Bootstrapper 将不同的初始化代码组织成不同的任务(实现IStartupTask接口),然后通过Bootstrap.Bootstrapper的Fluent API调用这些任务,并且可以指定任务的执行顺序。比如,我们有两个任务IocRegisterTask与RegisterRoutesTask,IocRegisterTask要先执行,运行这两个任务的代码如下:
public static class Initializer
{
public static void Initialize()
{
Bootstrap.Bootstrapper.With.StartupTasks()
.UsingThisExecutionOrder(s => s.First<RegisterRoutesTask>().Then<IocRegisterTask>())
.Start();
}
}
IoCRegisterTask的实现代码如下:
public class IocRegisterTask : IStartupTask
{
#region IStartupTask Members
public void Run()
{
var container = IoCFactory.Instance.CurrentContainter;
container.RegisterType<IBlogSiteService, FakeBlogSiteService>();
container.RegisterType<IBlogPostService, FakeBlogPostService>();
}
public void Reset()
{
}
#endregion
}
RegisterRoutesTask的实现代码如下:
public class RegisterRoutesTask : IStartupTask
{
#region IStartupTask Members
public void Run()
{
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
private void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
public void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public void Reset()
{
}
#endregion
}
小结
代码之美与文学、绘画、音乐一样,没有最美,只有更美。其中的过程是无止境的,其中的乐趣也是无止境的,当然前提是你真正喜欢它。有人说写程序是吃青春饭,这绝对是个错误的想法!因为文学家、画家、音乐家没有吃青春饭的,程序员也一样!写到这里,突然想到,当年老的时候,把年轻时候写的代码拿出来重构一下,一定会很有意思!