MvcExtensions – Bootstrapping
- by kazimanzurrashid
When you create a new ASP.NET MVC application you will find that the global.asax contains the following lines: namespace MvcApplication1
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
}
}
As the application grows, there are quite a lot of plumbing code gets into the global.asax which quickly becomes a design smell. Lets take a quick look at the code of one of the open source project that I recently visited:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Default","{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" });
}
protected override void OnApplicationStarted()
{
Error += OnError;
EndRequest += OnEndRequest;
var settings = new SparkSettings()
.AddNamespace("System")
.AddNamespace("System.Collections.Generic")
.AddNamespace("System.Web.Mvc")
.AddNamespace("System.Web.Mvc.Html")
.AddNamespace("MvcContrib.FluentHtml")
.AddNamespace("********")
.AddNamespace("********.Web")
.SetPageBaseType("ApplicationViewPage")
.SetAutomaticEncoding(true);
#if DEBUG
settings.SetDebug(true);
#endif
var viewFactory = new SparkViewFactory(settings);
ViewEngines.Engines.Add(viewFactory);
#if !DEBUG
PrecompileViews(viewFactory);
#endif
RegisterAllControllersIn("********.Web");
log4net.Config.XmlConfigurator.Configure();
RegisterRoutes(RouteTable.Routes);
Factory.Load(new Components.WebDependencies());
ModelBinders.Binders.DefaultBinder = new Binders.GenericBinderResolver(Factory.TryGet<IModelBinder>);
ValidatorConfiguration.Initialize("********");
HtmlValidationExtensions.Initialize(ValidatorConfiguration.Rules);
}
private void OnEndRequest(object sender, System.EventArgs e)
{
if (((HttpApplication)sender).Context.Handler is MvcHandler)
{
CreateKernel().Get<ISessionSource>().Close();
}
}
private void OnError(object sender, System.EventArgs e)
{
CreateKernel().Get<ISessionSource>().Close();
}
protected override IKernel CreateKernel()
{
return Factory.Kernel;
}
private static void PrecompileViews(SparkViewFactory viewFactory)
{
var batch = new SparkBatchDescriptor();
batch.For<HomeController>().For<ManageController>();
viewFactory.Precompile(batch);
}
As you can see there are quite a few of things going on in the above code, Registering the ViewEngine, Compiling the Views, Registering the Routes/Controllers/Model Binders, Settings up Logger, Validations and as you can imagine the more it becomes complex the more things will get added in the application start.
One of the goal of the MVCExtensions is to reduce the above design smell. Instead of writing all the plumbing code in the application start, it contains BootstrapperTask to register individual services. Out of the box, it contains BootstrapperTask to register Controllers, Controller Factory, Action Invoker, Action Filters, Model Binders, Model Metadata/Validation Providers, ValueProvideraFactory, ViewEngines etc and it is intelligent enough to automatically detect the above types and register into the ASP.NET MVC Framework. Other than the built-in tasks you can create your own custom task which will be automatically executed when the application starts. When the BootstrapperTasks are in action you will find the global.asax pretty much clean like the following:
public class MvcApplication : UnityMvcApplication
{
public void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs e)
{
Check.Argument.IsNotNull(e, "e");
HttpException exception = e.Exception.GetBaseException() as HttpException;
if ((exception != null) && (exception.GetHttpCode() == (int)HttpStatusCode.NotFound))
{
e.Dismiss();
}
}
}
The above code is taken from my another open source project Shrinkr, as you can see the global.asax is longer cluttered with any plumbing code. One special thing you have noticed that it is inherited from the UnityMvcApplication rather than regular HttpApplication. There are separate version of this class for each IoC Container like NinjectMvcApplication, StructureMapMvcApplication etc.
Other than executing the built-in tasks, the Shrinkr also has few custom tasks which gets executed when the application starts. For example, when the application starts, we want to ensure that the default users (which is specified in the web.config) are created. The following is the custom task that is used to create those default users:
public class CreateDefaultUsers : BootstrapperTask
{
protected override TaskContinuation ExecuteCore(IServiceLocator serviceLocator)
{
IUserRepository userRepository = serviceLocator.GetInstance<IUserRepository>();
IUnitOfWork unitOfWork = serviceLocator.GetInstance<IUnitOfWork>();
IEnumerable<User> users = serviceLocator.GetInstance<Settings>().DefaultUsers;
bool shouldCommit = false;
foreach (User user in users)
{
if (userRepository.GetByName(user.Name) == null)
{
user.AllowApiAccess(ApiSetting.InfiniteLimit);
userRepository.Add(user);
shouldCommit = true;
}
}
if (shouldCommit)
{
unitOfWork.Commit();
}
return TaskContinuation.Continue;
}
}
There are several other Tasks in the Shrinkr that we are also using which you will find in that project. To create a custom bootstrapping task you have create a new class which either implements the IBootstrapperTask interface or inherits from the abstract BootstrapperTask class, I would recommend to start with the BootstrapperTask as it already has the required code that you have to write in case if you choose the IBootstrapperTask interface. As you can see in the above code we are overriding the ExecuteCore to create the default users, the MVCExtensions is responsible for populating the ServiceLocator prior calling this method and in this method we are using the service locator to get the dependencies that are required to create the users (I will cover the custom dependencies registration in the next post). Once the users are created, we are returning a special enum, TaskContinuation as the return value, the TaskContinuation can have three values Continue (default), Skip and Break. The reason behind of having this enum is, in some special cases you might want to skip the next task in the chain or break the complete chain depending upon the currently running task, in those cases you will use the other two values instead of the Continue. The last thing I want to cover in the bootstrapping task is the Order. By default all the built-in tasks as well as newly created task order is set to the DefaultOrder(a static property), in some special cases you might want to execute it before/after all the other tasks, in those cases you will assign the Order in the Task constructor. For Example, in Shrinkr, we want to run few background services when the all the tasks are executed, so we assigned the order as DefaultOrder + 1. Here is the code of that Task:
public class ConfigureBackgroundServices : BootstrapperTask
{
private IEnumerable<IBackgroundService> backgroundServices;
public ConfigureBackgroundServices()
{
Order = DefaultOrder + 1;
}
protected override TaskContinuation ExecuteCore(IServiceLocator serviceLocator)
{
backgroundServices = serviceLocator.GetAllInstances<IBackgroundService>().ToList();
backgroundServices.Each(service => service.Start());
return TaskContinuation.Continue;
}
protected override void DisposeCore()
{
backgroundServices.Each(service => service.Stop());
}
}
That’s it for today, in the next post I will cover the custom service registration, so stay tuned.