Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC - Custom Model Binder on Interface Type

I'm not sure if this behavior is expected or not, but it seems that custom model binding doesn't work when the binding is assigned to an interface type. Has anyone experimented with this?

public interface ISomeModel {}
public class SomeModel : ISomeModel {}

public class MvcApplication : HttpApplication {
    protected void Application_Start(object sender, EventArgs e) {
        ModelBinders.Binders[typeof(ISomeModel)] = new MyCustomModelBinder();
    }
}

With the above code when I bind to a model of type SomeModel, MyCustomModelBinder is never hit; however, if I change the above code and substitute typeof(ISomeModel) for typeof(SomeModel) and post the exact same form MyCustomModelBinder is called as expected. Does that seem right?


Edit

I found myself back in this predicament over a year after I originally asked this question, and now I have a solution that works. Thank you Matt Hidinger!

http://www.matthidinger.com/archive/2011/08/16/An-inheritance-aware-ModelBinderProvider-in-MVC-3.aspx

like image 698
Nathan Taylor Avatar asked Jun 04 '10 00:06

Nathan Taylor


People also ask

What interface and method needs to be implemented for a custom model binder?

To create custom model binder class, it needs to inherit from IModelBinder interface. This interface has async method named "BindModelAsync" and it has parameter of type ModelBindingContext. The ModelBindingContext class provides the context that model binder functions.

How can we use a custom model binder in asp net core?

In this article Model binding allows controller actions to work directly with model types (passed in as method arguments), rather than HTTP requests. Mapping between incoming request data and application models is handled by model binders.

What is the purpose of implementing the BindModel () method on the IModelBinder interface?

MVC uses following types for Model Binding: IModelBinder interface - This defines methods that are required for a Model Binder, like the BindModel method. This method is responsible for binding a model to some values using ControllerContext and BindingContext.


2 Answers

I was experimenting with this issue and I came up with a solution of sorts. I made a class called InterfaceModelBinder:

public class InterfaceModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        ModelBindingContext context = new ModelBindingContext(bindingContext);
        var item = Activator.CreateInstance(
            Type.GetType(controllerContext.RequestContext.HttpContext.Request.Form["AssemblyQualifiedName"]));

        Func<object> modelAccessor = () => item;
        context.ModelMetadata = new ModelMetadata(new DataAnnotationsModelMetadataProvider(),
            bindingContext.ModelMetadata.ContainerType, modelAccessor, item.GetType(), bindingContext.ModelName);

        return base.BindModel(controllerContext, context);
    }
}

Which I registered in my Application_Start as so:

ModelBinders.Binders.Add(typeof(IFormSubmission), new InterfaceModelBinder.Models.InterfaceModelBinder());

The interface and a concrete implementation look like this:

public interface IFormSubmission
{
}

public class ContactForm : IFormSubmission
{
    public string Name
    {
        get;
        set;
    }

    public string Email
    {
        get;
        set;
    }

    public string Comments
    {
        get;
        set;
    }
}

The only downside to this whole approach (as you might have gathered already) is that I need to get the AssemblyQualifiedName from somewhere, and in this example it is being stored as a hidden field on the client side, like so:

<%=Html.HiddenFor(m => m.GetType().AssemblyQualifiedName) %>

I'm not certain though that the downsides of exposing the Type name to the client are worth losing the benefits of this approach. An Action like this can handle all my form submissions:

[HttpPost]
public ActionResult Process(IFormSubmission form)
{
    if (ModelState.IsValid)
    {
        FormManager manager = new FormManager();
        manager.Process(form);
    }

    //do whatever you want
}

Any thoughts on this approach?

like image 79
Nathan Anderson Avatar answered Sep 25 '22 03:09

Nathan Anderson


Suddenly, an MVC3 solution appears:

http://www.matthidinger.com/archive/2011/08/16/An-inheritance-aware-ModelBinderProvider-in-MVC-3.aspx

like image 43
Nathan Taylor Avatar answered Sep 23 '22 03:09

Nathan Taylor