Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Model Binder does not fire

I have registered a custom model binder for MyList in global.asax. However the model binder does not fire for nested properties, for simple types it works fine. In the example below, it fires for Index() but not does not fire for Index2()

Global.asax

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    ModelBinders.Binders.Add(typeof(MyList), new MyListBinder());

    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
}

Code:

public class MyListBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        return new MyList();
    }
}

public class MyList
{
    public List<int> items { get; set; }
}

public class MyListWrapper
{
    public MyList listItems { get; set; }
}

public class TestController : Controller
{
    public ActionResult Index(MyList list)  // ModelBinder fires :-)
    {            
        return View();
    }

    public ActionResult Index2(MyListWrapper wrapper) // ModelBinder does not fire! :-(
    {
        return View();
    }
}
like image 692
newbie Avatar asked Apr 05 '13 00:04

newbie


3 Answers

Model binders are used to allow actions to accept complex object types as parameters. These complex types should be generated via POST requests, for example, by submitting a form. If you have a highly complex object that cannot be binded by the default model binder (or it wouldn't be effective), you can use custom model binders.

To answer your question: if you don't add a custom model binder for the MyListWrapper class too, the BindModel(of the MyListBinder)won't be called in a GET request, this is how ASP.NET MVC works. However, if you modify your code by adding a POST action with the MyListWrapper parameter, you can see that the BindModel method is called properly.

[HttpGet]
public ActionResult Index2()  // ModelBinder doesn't fire
{
    return View();
}

[HttpPost]
public ActionResult Index2(MyListWrapper wrapper) // ModelBinder fires
{
    return View();
}

And the Index2 view

@model fun.web.MyListWrapper

@using (Html.BeginForm())
{
    @Html.HiddenFor(m => m.listItems)
    <input type="submit" value="Submit" />
}

If you'd like "control" the action parameters in a GET request, you should use action filters.

like image 114
laszlokiss88 Avatar answered Oct 31 '22 16:10

laszlokiss88


You defined binder for MyList, so it triggers only when action method input parameter is of type MyList.

ModelBinders.Binders.Add(typeof(MyList), new MyListBinder());

If you want model binder to trigger even when your MyList is nested into other model, you have to do this:

[ModelBinder(typeof(MyListBinder))] 
public class MyList
{
    public List<int> items { get; set; }
}

Then, model binder triggers whenever it encounters MyList type, even when it's nested.

like image 24
Nenad Avatar answered Oct 31 '22 18:10

Nenad


Add this to your global:

ModelBinders.Binders.Add(typeof(MyListWrapper), new MyListWrapperBinder());

And then create a MyListWrapperBinder that can handle the binding.

like image 1
Facio Ratio Avatar answered Oct 31 '22 18:10

Facio Ratio