Using abstract classes as controller parameters in ASP.NET MVC4

As a part of the API for our prod­uct, we have the need to allow the UI to send us a par­cel of data, and we need to process it dif­fer­ent­ly based sole­ly on a sin­gle para­me­ter in the data.  We have decid­ed to cre­ate an abstract mod­el class to han­dle this, but MVC’s DefaultModelBinder does­n’t work out of the box on abstract class­es (or inter­faces).  There are many dis­cus­sions out there on this, and the accept­ed way to solve this prob­lem is to cre­ate a new ModelBinder class that has more knowl­edge of the inter­nals of your appli­ca­tion than the default does.  I thought about this some, and I decid­ed to try out a few tweaks to the process.

First of all, I want­ed to have a clean POCO for inter­ac­tion with the user inter­face; no func­tion­al­i­ty, just a prop­er­ty bag.  This is a com­mon pat­tern, and it works well in our project.  Most of our con­trollers are imple­ment­ed so that they take the POCO direct­ly, and objects are con­struct­ed using a fac­to­ry inject­ed into the con­troller.  This works, but I feel that it adds some unnec­es­sary noise into the con­troller code, and I set out to cre­ate a cus­tom ModelBinder to clean this up.

First, I need­ed to ensure that any POCO cre­at­ed would have some prop­er­ty that iden­ti­fies the under­ly­ing type that needs to be con­struct­ed. In this sim­ple imple­men­ta­tion, I sim­ply cre­at­ed an inter­face that all of my POCOs would implement:

public interface IPoco
{
    string Type { get; }
}

Of course, this is some­what over-sim­pli­fied, and I intend to expand this in the future, but for pur­pos­es of this dis­cus­sion, it will suffice.

I also need some way to ini­tial­ize a mod­el with the data con­tained in the POCO.  I cre­at­ed an inter­face for the mod­el class­es also:

public interface IModel
{
    void InitializeFromPoco(IPoco poco);
}

And a default imple­men­ta­tion (using AutoMapper):

public abstract class ModelBase : IModel
{
    public virtual void InitializeFromPoco(IPoco poco)
    {
        Mapper.DynamicMap(poco, this, poco.GetType(), GetType());
    }
}

Almost there, but I still need a fac­to­ry of some sort:

public interface IModelResolver
{
    IModel ResolveModel(Type modelType, IPoco poco);
}

There is one more piece that I need­ed to put in place before I could write the cus­tom ModelBinder.  I need­ed a way to link an abstract mod­el to its POCO.  I decid­ed the sim­plest way was with a cus­tom attribute:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class PocoAttribute : Attribute
{
    public Type PocoType { get; private set; }

    public PocoAttribute(Type pocoType)
    {
        PocoType = pocoType;
    }
}

And final­ly, the ModelBinder itself:

public class PocoModelBinder : DefaultModelBinder
{
    private readonly IModelResolver _modelResolver;

    public PocoModelBinder(IModelResolver modelResolver)
    {
        // Work around a bug in msbuild that causes the AutoMapper.Net4 assembly to be missed.
        var workAround = new ListSourceMapper();
        _modelResolver = modelResolver;
    }

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var pocoAttribute = bindingContext.ModelType.GetCustomAttributes(typeof(PocoAttribute), true).FirstOrDefault()
            as PocoAttribute;
        if (pocoAttribute == null)
            return base.BindModel(controllerContext, bindingContext);

        var bc = new ModelBindingContext(bindingContext);
        var modelMetaData = new ModelMetadata(
            ModelMetadataProviders.Current,
            null,
            null,
            pocoAttribute.PocoType,
            null);
        bc.ModelMetadata = modelMetaData;

        var poco = base.BindModel(controllerContext, bc) as IPoco;
        if (poco == null)
            return base.BindModel(controllerContext, bindingContext);

        IModel model = _modelResolver.ResolveModel(bindingContext.ModelType, poco);
        model.InitializeFromPoco(poco);

        return model;
    }
}

What is hap­pen­ing here?  First, in our con­struc­tor, we accept an instance of IModelResolver that is defined by the appli­ca­tion.  Note in line 8, an unused vari­able.  This is sim­ply there because AutoMapper installs a sec­ond assem­bly, AutoMapper.Net4, that is implic­it­ly ref­er­enced, and (if this code is in a library) msbuild will fail to copy it into the web appli­ca­tion’s bin direc­to­ry with­out some­thing explic­it­ly ref­er­enc­ing it.

The bulk of the work is done in the BindModel imple­men­ta­tion.  First we check to see if the mod­el type has the PocoAttribute on it.  If not, we let the default imple­men­ta­tion do the work.

There’s a lit­tle bit of MVC mag­ic in lines 18–25.  Our goal is to trick the default ModelBinder into cre­at­ing and pop­u­lat­ing an object of our poco type.  To do this, we need to pro­vide a new ModelBindingContext to the base imple­men­ta­tion, explic­it­ly spec­i­fy­ing the poco type.  If we get a type that imple­ments IPoco, then we con­tin­ue, oth­er­wise we let the default imple­men­ta­tion do its work.

Finally, we call our instance of IModelResolver to get the con­crete imple­men­ta­tion, and copy the data in the poco into the object.  And that’s all there is to it.

Putting it together

Let’s cre­ate a POCO, an abstract mod­el, and a cou­ple of con­crete mod­els that make use of this framework:

public class TestViewModel : IPoco
{
    public string Name { get; set; }
    public string Type { get; set; }
}

[Poco(typeof(TestViewModel))]
public abstract class TestModel : ModelBase
{
    public string Name { get; set; }

    public abstract string GetString();
}

public class TestModelA : TestModel
{
    public override string GetString()
    {
        return String.Format("Hello {0}, this is Model A", Name);
    }
}

public class TestModelB : TestModel
{
    public override string GetString()
    {
        return String.Format("Hello {0}, this is Model B", Name);
    }
}

The TestViewModel class is our POCO, and it is the con­tract that the user inter­face can fol­low. Inside of TestModel, we have a sim­ple abstract method (GetString) who’s behav­ior will dif­fer depend­ing on the con­crete type ulti­mate­ly con­struct­ed (TestModelA or TestModelB). Note that there is no “Type” prop­er­ty, as that is no longer nec­es­sary. Each con­crete type will know what it needs to do.

A sim­ple controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new TestModelA());
    }

    [HttpPost]
    public ActionResult Index(TestModel model)
    {
        return View(model);
    }

    [HttpPost]
    public ActionResult TheOldWay(TestViewModel viewModel)
    {
        var someModelFactory = IocContainer.Get(); // Or use constructor injection
        TestModel model = someModelFactory.GetFromViewModel(viewModel);
 
        return View(model);
    }
}

The default Index() method in lines 3–6 just sets up a default view. The sec­ond method in lines 8–12 is the one that invokes our new mod­el binder. Note that it accepts the base class we defined above, and not the view mod­el. An exam­ple of how we could have done things with­out our cus­tom mod­el binder is in the method TheOldWay, lines 14–20. Note that this requires that we have a fac­to­ry of some sort and an explic­it con­ver­sion from the view mod­el to the model.

The fol­low­ing view needs no real expla­na­tion, I am includ­ing it for completeness:

@model TestModel
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
    <head>
        <title>Test</title>
    </head>
    <body>
        <div>
            <h1>@Model.GetString()</h1>
            @using (Html.BeginForm())
            {
                @Html.EditorForModel()
                @Html.DropDownList("Type", new [] {new SelectListItem(){Text = "TypeA", Value = "TypeA"}, new SelectListItem() {Text = "TypeB", Value="TypeB"}  })
                <input type="submit"/>
            }
        </div>
    </body>
</html>

We are almost there, but we still need to tell the MVC pipeline to use the new mod­el binder.  First, we imple­ment the binder’s depen­den­cy, IModelResolver:

public class ModelResolver : IModelResolver
{
    public IModel ResolveModel(Type modelType, IPoco poco)
    {
        if (poco.Type == "TypeA") return new TestModelA();
        return new TestModelB();
    }
}

This imple­men­ta­tion very sim­ply choos­es between TestModelA and TestModelB based on the val­ue of the Type prop­er­ty in the view mod­el. A more robust and gener­ic imple­men­ta­tion might use an IoC con­tain­er that has named instances set up, for exam­ple, or reflec­tion tech­niques could be used. The great part about this kind of pat­tern is that it is pos­si­ble to imple­ment just the right lev­el of com­plex­i­ty for your appli­ca­tion’s needs.

Finally, with an IModelResolver imple­ment­ed, we can set the DefaultBinder in Global.asax.cs:

protected void Application_Start()
{
    // Other setup stuff...
    ModelBinders.Binders.DefaultBinder = new PocoModelBinder(new ModelResolver());
}

Using this cus­tom mod­el binder as a frame­work, you can clean up your con­troller code, and make the intent of your pub­lic API more clear. It can also clean up your mod­els and help you to keep your code adher­ent to the SOLID prin­ci­ples, espe­cial­ly the Single Responsibility Principle and the Liskov Substitution Principle.

Note: Although this arti­cle was writ­ten for the MVC4 frame­work, a sim­i­lar approach can be used in the ASP.NET WebAPI. Look for a future post with those details.

[Update June, 2018: I final­ly just dumped the WebAPI ver­sion of this here.]

Share