Monday, December 14, 2009 8:03 AM Kazi Manzur Rashid

Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

After hearing Phil Haack and Scott Hanselman in their latest podcast and PDC session I decided to give a good look at the MvcTurbine project in this weekend. I totally agree the with intend of this project, let your favorite IoC to rule everywhere, but I do not think it has been implemented in the correct way. There are three important design decisions that the MvcTurbine team should re-evaluate:

Generic Component Registration API

Rather than allowing you to use your preferred IoC registration features, it prefers to use its own generic syntax for component registration, which is simply wrong. In real application, we need more support from our IoC such as lifetime management, auto wiring, modularity like (Ninject/Autfac Modules, StructureMap Registry) etc. In case of this generic API we have to throw away the features of these IoCs.

Missing Common Service Locator

I think the Common Service Locator(CSL) has become the standard to abstract the underlying IoC from your application code and all the popular IoCs in .NET world has an adapter for CSL. But rather than using/extending it, it has its own version of service locator.

Limited support for Action Filter injection

It uses a host kinda Attribute to inject the dependencies into the actual action filter attribute and this host is not capable of passing the attribute specific values to the actual attribute and even if this feature is implemented in the future it will be based upon the string based property name which would work on top of reflection.

Now, lets see how my  new extension solves the above issues and how easily you can start plugging it in your application. First lets take a quick look of the project structure of this extension.

SWMEStructure

As you can see other than the Core, each IoC has its own implementation. Currently it has the support for Autofac, Ninject, StructureMap, Unity and Windsor. When plugging it in your application, it requires five simple steps:

  1. Add reference of the Core Project.
  2. Add reference of you prefered IoC project and binaries of that IoC.
  3. Change the Global.asax so that it inherits from your prefered IoC supported HttpApplication.
  4. Create a class that inherits from RegisterRoutesBase and register your routes over there.
  5. Create a class as per your IoC that allows component registration. For example, both Autofac and Ninject supports Module for component registration and StructureMap uses Registry. Since both Unity and Windsor does not have modules support there is an interface IModule which you have to implement for your application component registration (Check the sample application for the complete reference).

And that’s it, you are all set to go.

Now, lets see the above steps in more detail, I will use the Autofac in this case. But you can check the other examples if your prefered IoC is other than Autofac.

First, lets add the required references, once your are done, your Mvc application will have the following references:

SWMEAutofacStructure

Next, change the global.asax so that it inherits from the Autofac supported HttpApplication like the following:

namespace Demo.Web.Autofac
{
    using System.Web.Mvc.Extensibility.Autofac;

    public class MvcApplication : AutofacMvcApplication
    {
    }
}

Next, create a class to register the routes:

public class RegisterRoutes : RegisterRoutesBase
{
    public RegisterRoutes(RouteCollection routes) : base(routes)
    {
    }

    protected override void ExecuteCore(IServiceLocator locator)
    {
        Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        Routes.MapRoute( "Default", "{controller}/{action}/{id}", new { controller = "Product", action = "Index", id = string.Empty } );
    }
}

And at last an Autofac module to register your application specific components:

public class RegisterServices : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.Register<InMemoryDatabasae>().As<IDatabase>();
        builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>));
    }
}

If you browse the sample applications, you will find there are quite a few things that is going under the hood. Lets take a quick look how the Product Controller is implemented:

public class ProductController : Controller
{
    private readonly IRepository<Product> repository;

    public ProductController(IRepository<Product> repository)
    {
        this.repository = repository;
    }

    public ActionResult Index()
    {
        return View(repository.All().Select(product => product.AsDisplayModel()));
    }

    public ActionResult Details(int id)
    {
        return View(repository.Get(id).AsDisplayModel());
    }

    public ActionResult Create()
    {
        return View(new ProductEditModel());
    }

    [HttpPost]
    public ActionResult Create(FormCollection form)
    {
        ProductEditModel model = new ProductEditModel();

        if (TryUpdateModel(model, form.ToValueProvider()))
        {
            Product product = model.AsProduct();
            product.Id = repository.All().LastOrDefault().Id + 1;

            repository.Add(product);

            return RedirectToAction("Index");
        }

        return View(model);
    }

    public ActionResult Edit(int id)
    {
        return View(repository.Get(id).AsEditModel());
    }

    [HttpPost]
    public ActionResult Edit(ProductEditModel model)
    {
        if (ModelState.IsValid)
        {
            Product product = model.AsProduct();

            repository.Update(product);

            return RedirectToAction("Index");
        }

        return View(model);
    }

    public ActionResult Delete(int id)
    {
        return View(repository.Get(id).AsDisplayModel());
    }

    [HttpPost]
    public ActionResult Delete(int id, string confirm)
    {
        repository.Delete(id);

        return RedirectToAction("Index");
    }
}

As you can see the the controller requires the repository which we have registered in the RegisterServices module, but the controller, action filters (Not decorated in the controller) and model binders are automatically registered by this extension.

Lets take a look at the Model Binder, in the sample application we have only one Model Binder which is used to create the product edit view model. Under the hood only the category id and supplier Id is passed and it uses the corresponding repositories to populate the Category and Supplier property of this view model.

[BindingTypes(typeof(ProductEditModel))]
public class ProductEditModelBinder : DefaultModelBinder
{
    private readonly IRepository<Category> categoryRepository;
    private readonly IRepository<Supplier> supplierRepository;

    public ProductEditModelBinder(IRepository<Category> categoryRepository, IRepository<Supplier> supplierRepository)
    {
        this.categoryRepository = categoryRepository;
        this.supplierRepository = supplierRepository;
    }

    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
    {
        const string CategoryProperty = "Category";
        const string SupplierProperty = "Supplier";

        if (propertyDescriptor.Name.Equals(CategoryProperty, StringComparison.OrdinalIgnoreCase))
        {
            return GetValue(CategoryProperty, categoryRepository, controllerContext, bindingContext);
        }

        if (propertyDescriptor.Name.Equals(SupplierProperty, StringComparison.OrdinalIgnoreCase))
        {
            return GetValue(SupplierProperty, supplierRepository, controllerContext, bindingContext);
        }

        return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }

    private static object GetValue<TEntity>(string propertyName, IRepository<TEntity> repository, ControllerContext controllerContext, ModelBindingContext bindingContext) where TEntity : EntityBase
    {
        ValueProviderResult result = bindingContext.ValueProvider.GetValue(controllerContext, propertyName);
        int? id = (result != null) ? (int?) result.ConvertTo(typeof(int?)) : null;

        return (id.HasValue && id.Value > 0) ? repository.Get(id.Value) : default(TEntity);
    }
}

The model binder inherits from the default model binder so that we can advantages of built-in features, the only exception is the Category and Supplier property where we are using the repository to retrieve it from our data store. One important thing I think you noticed that the Model Binder is decorated with an special attribute BindingTypes, this attribute indicates for which type(s) the binder will be used. This extension uses this attribute to register the model binders in the Model Binder collection.

And at last the Filters, the following is the code of PopulateCategoriesAttriute action filter:

public sealed class PopulateCategoriesAttribute : PopulateModelAttribute
{
    private readonly IRepository<Category> repository;

    public PopulateCategoriesAttribute(IRepository<Category> repository)
    {
        this.repository = repository;
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        ProductEditModel editModel = filterContext.Controller.ViewData.Model as ProductEditModel;

        if (editModel != null)
        {
            editModel.Categories = new SelectList(repository.All(), "Id", "Name", editModel.Category);
        }
    }
}

As we can see that the Filter accept a repository in the constructor and it has not been decorated in the Controller methods, if you are wondering then how this filter come into action, let me explain a bit. When Oxite was initially released a lot people criticized it, but in next (current) version the Oxite came up with lots of excellent ideas. The Filter registration that I about to show you is taken from the Oxite with a slight change in the implementation. Although, this extension support filter property injection, but I would suggest to use the following fluent registration. To apply filter in controller, you first have to create a class which inherits from RegisterFiltersBase class. The following shows how the PopulateCategoriesAttribute and PopulateSuppliersAttribute are applied in the product controller of the sample application.

public class RegisterFilters : RegisterFiltersBase
{
    public RegisterFilters(IFilterRegistry filterRegistry) : base(filterRegistry)
    {
    }

    protected override void ExecuteCore(IServiceLocator serviceLocator)
    {
        FilterRegistry.Register<ProductController, PopulateCategoriesAttribute, PopulateSuppliersAttribute>(c => c.Create())
                      .Register<ProductController, PopulateCategoriesAttribute, PopulateSuppliersAttribute>(c => c.Create(null))
                      .Register<ProductController, PopulateCategoriesAttribute, PopulateSuppliersAttribute>(c => c.Edit(0))
                      .Register<ProductController, PopulateCategoriesAttribute, PopulateSuppliersAttribute>(c => c.Edit(null));
    }
}

There are quite a few overloads for the Filter registration, if you want to apply filters to the whole controller, just skip the action part.

And that’s it for today.

I also think this would be nice addition for the MVCContrib project. So please download the code and play with it and do let me know your feedbacks.

Source Code: github

 

Shout it
Filed under: , , , , , , , , , ,

Comments

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Monday, December 14, 2009 9:28 AM by Javier Lozano

@Kazi,

Thanks for checking out MVC Turbine! You bring up some interesting points with the current implementation of turbine that are bit too complex to address within a comment.

You and others have inspired me to write a blog post that address such concerns for the design decisions that were made during development.

Again, thank you for checking out MVC Turbine and providing great constructive feedback!

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Monday, December 14, 2009 9:40 AM by Kazi Manzur Rashid

@Javier: Sure I would love to hear your thoughts and do let me know if you need any help.

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Tuesday, December 15, 2009 12:44 PM by Javier Lozano

@Kazi,

My response is here - http://bit.ly/73oLOv

Would love you hear your feedback!

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Thursday, December 17, 2009 12:25 PM by Erik

Great post as always.

Played around with it a bit, really nice..

had to add the following to my AutofacMvcApplication to make it work with NHibernate Managed Web Session :

public override void Init()

       {

           base.Init();

           this.BeginRequest += new EventHandler(ServerApp_BeginRequest);

           this.EndRequest += new EventHandler(ServerApp_EndRequest);

       }

       void ServerApp_EndRequest(object sender, EventArgs e)

       {

           var persistance = this.Bootstrapper.ServiceLocator.GetInstance<Persistance.IMynameofPersistance>();

           ISession session = ManagedWebSessionContext.Unbind(

               System.Web.HttpContext.Current, persistance.GetSessionFactory());

           if (session != null)

           {

               if (session.Transaction != null &&

                   session.Transaction.IsActive)

               {

                   session.Transaction.Rollback();

               }

               else

                   session.Flush();

               session.Close();

           }

       }

       void ServerApp_BeginRequest(object sender, EventArgs e)

       {

           var persistance = this.Bootstrapper.ServiceLocator.GetInstance<Persistance.IMynameofPersistance>();

           ManagedWebSessionContext.Bind(

               System.Web.HttpContext.Current,

               persistance.GetSessionFactory().OpenSession());

       }  

Thanks again,

Erik

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Thursday, December 17, 2009 1:10 PM by Rei Roldan

Hello,

First of impressive work a very nice design indeed, I've been runnning around like a mad man trying to do something similar to what you you've done but never managed to quite get  it right.

There is one thing that i'm not sure how to work out though. How on earth would you go about injecting filters into types deriving from another type. Would you recommed iterating over the child types or registering them individually?

Ex: Say you had a BaseController with a filter which handles errors and all your controllers inherited from this controller. Would you register the filter on each individual controller or specify it on the BaseController and all inherited types would get the filter injected.

Actually, after writing all that down, it makes no sense whats so ever. I don't think it would be possible to inject the filters on derived types without some sort of iterating child types. :/

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Thursday, December 17, 2009 3:31 PM by Kazi Manzur Rashid

@Erik,

I think adding an PerRequestTask ala BootstrapperTask would be helpful to handle this kind of scenario, I know HttpModule does the same but due to web.config registration it is some kind of PITA.

What do you think?

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Friday, December 18, 2009 6:16 AM by Rei Roldan

Hi,

Following my previous post (which is not visible yet), I've added a method to the FilterRegistry class:

public IFilterRegistry RegisterForControllersFromAssemblyOf<Assembly, TFilter>(Func<Type, bool> where)

           where TFilter : FilterAttribute {

           var assem = typeof(Assembly).Assembly;

           var types = assem.GetExportedTypes();

           var autoMappedControllers = new List<Type>();

           types.Each(x => {

               if (where == null || !where(x))

                   return;

               autoMappedControllers.Add(x);

           });

           autoMappedControllers.Each(x => {

               var t = typeof(FilterRegistryControllerItem<>).MakeGenericType(x);

               var f = new FilterAttribute[] { ServiceLocator.GetInstance<TFilter>() };

               var item = Activator.CreateInstance(t, (IEnumerable<FilterAttribute>)f) as FilterRegistryItemBase;

               Items.Add(item);

           });

           return this;

       }

Its not the pretty but it does what I needed it to do. In fact that might actually be a nice extension method instead of a modification to the original class.

I hope someone finds this usefull.

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Friday, December 18, 2009 6:30 AM by Rei Roldan

And yet another modification... This is starting to feel like spam (sorry).

public IFilterRegistry RegisterForControllersFromAssemblyOf<Assembly, TFilter>(Func<Type, bool> where)

           where TFilter : FilterAttribute {

           var f = new FilterAttribute[] { ServiceLocator.GetInstance<TFilter>() };

           return RegisterForControllersFromAssemblyOf<Assembly>(where, f);

       }

       public IFilterRegistry RegisterForControllersFromAssemblyOf<Assembly>(Func<Type, bool> where, params FilterAttribute[] filters) {

           var assem = typeof(Assembly).Assembly;

           var types = assem.GetExportedTypes();

           var autoMappedControllers = new List<Type>();

           types.Each(x => {

               if (where == null || !where(x))

                   return;

               autoMappedControllers.Add(x);

           });

           autoMappedControllers.Each(x => {

               var t = typeof(FilterRegistryControllerItem<>).MakeGenericType(x);

               var item = Activator.CreateInstance(t, (IEnumerable<FilterAttribute>)filters) as FilterRegistryItemBase;

               Items.Add(item);

           });

           return this;

       }

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Friday, December 18, 2009 6:43 AM by Kazi Manzur Rashid

@Rei Roldan : Thanks for the snippet, it would be really handy for bulk filter registration. Sorry about the comment approval, the spam rate of weblogs now a days seems very high and interestingly it is marking the legitimate comments as spam. Do let me know if you want to join the project.

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Tuesday, December 22, 2009 10:37 AM by Erik

@Kazi,

re : PerRequestTask ala BootstrapperTask vs HttpModule

Sounds more appropriate, I'll give it a try, thanks!

Erik

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Tuesday, December 22, 2009 11:18 AM by Kazi Manzur Rashid

@Erik: Just inherit from the github.com/.../PerRequestTaskBase.cs and override the ExecuteCore.

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Tuesday, December 22, 2009 7:59 PM by Kazi Manzur Rashid

@Rei Roldan: Now it support multiple controller registration with the TypeCatalogBuilder also made most of the registration as extension as per your suggestion.

# re: Meet my new ASP.NET MVC Extension - System.Web.Mvc.Extensibility

Wednesday, December 23, 2009 4:23 AM by Rei Roldan

@kazimanzurrashid cool thanks :)

I'm surprised as this is the first time I've contributed something that someone finds "usefull".

And again, Awesome job!