ASP.NET MVC – Cannot create an instance of an interface

I came across an error message recently that I hadn’t seen before:

postback002

The error occurred when I tried submitting the following form:

postback001

The stack trace suggested it came from the model binder:


[MissingMethodException: Cannot create an instance of an interface.]
System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0
System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +113
System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +232
System.Activator.CreateInstance(Type type, Boolean nonPublic) +83
System.Activator.CreateInstance(Type type) +6
System.Web.Mvc.DefaultModelBinder.CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) +540

This actually makes a lot of sense. The MVC model binder needs to create an instance of every object in the postback model in order to bind the form values to it. If the postback model contains a property of an interface type (instead of a concrete type) then the model binder won’t be able to create an instance of that property – because it won’t know which concrete type to create, hence the error.

There are several ways to work around this issue, but first let’s review the failing code. The view model contains a list of IPeople which the view loops over, outputting a checkbox for each person in the list. Note that the checkboxes are bound to the IsChecked property on the IPerson interface.

Here’s the HTML with Razor:

@model Blog.Models.PeopleViewModel

<h2>People</h2>
@using(Html.BeginForm())
{
    for (int i = 0; i < Model.People.Count; i++)
    {
        <label class="checkbox">
            @Html.CheckBoxFor(x => Model.People[i].IsChecked) @Model.People[i].Name
        </label>
    }
    <input type="submit" class="btn" />
}

Here’s the code behind:

public class HomeController : Controller
{
    public ActionResult People()
    {
        var model = new PeopleViewModel();
        model.People = new List<IPerson>();
        model.People.Add(new Person { Name = "Dave", IsChecked  = false });
        model.People.Add(new Person { Name = "John", IsChecked  = false });
        model.People.Add(new Person { Name = "Mark", IsChecked  = false });
        return View(model);
    }

    [HttpPost]
    public ActionResult People(PeopleViewModel model)
    {
        // TODO - Do something with the selected items
        return View(model);
    }
}

public class PeopleViewModel
{
    public List<IPerson> People { get; set; }
}

public interface IPerson
{
    bool IsChecked { get; set; }
    string Name { get; set; }
}

public class Person : IPerson
{
    public bool IsChecked { get; set; }
    public string Name { get; set; }
}

The problem could be fixed by moving the IsChecked property from the IPerson interface to the PeopleViewModel (implemented as a list of booleans). My preferred approach is to create a parent view model to contain the property, and have the PeopleViewModel inherit it. This allows the IsChecked property to be re-used on other view models:

public class SelectableViewModel
{
    public List<bool> IsChecked { get; set; }
}

public class PeopleViewModel : SelectableViewModel
{
    public List<IPerson> People { get; set; }
}

Here’s the modified HTML with Razor:

@model Blog.Models.PeopleViewModel

<h2>People</h2>
@using(Html.BeginForm())
{
    for (int i = 0; i < Model.People.Count; i++)
    {
        <label class="checkbox">
            @Html.CheckBoxFor(x => Model.IsChecked[i]) @Model.People[i].Name
        </label>
    }
    <input type="submit" class="btn" />
}

Finally, the IsChecked list needs to be initialized and the POST action needs the type of the model parameter changed from PersonViewModel to SelectableViewModel.

public ActionResult People()
{
    var model = new PeopleViewModel();
    model.People = new List<IPerson>();
    model.People.Add(new Person { Name = "Dave" });
    model.People.Add(new Person { Name = "John" });
    model.People.Add(new Person { Name = "Mark" });

    model.IsChecked = new List<bool> { false, false, false };

    return View(model);
}

[HttpPost]
public ActionResult People(SelectableViewModel model)
{
    // TODO - Do something with the selected items
    return View(model);
}
This entry was posted in Tips and Tricks and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s