Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I invoke a custom model binder in MVC4?

So it seems like several people (like here and here) have had issues with MVC4 model binding for ApiControllers, but none of them seem to quite address the issue I'm seeing.

All I'd really like to do is change the array binding behavior for lists of integers. So say I had a request type like this:

public class MyRequestModel
{
    public List<long> ListOfIntegers { get; set; }

    ...
}

And an API GET method like this:

public ResultsResponseModel Get(MyRequestModel request)
{
    // use request.ListOfIntegers meaningfully

    ...

    return response;
}

I basically want to be able to say /api/results/?listOfIntegers=1+2+3+4+5 and have that resolve to the List<long> property.

I've tried my usual model binding tricks, but as with most of the Web API in MVC4 it appears to have a totally separate path for model binding.

The furthest I've gotten is using a System.Web.Http.ModelBinding.ModelBinder attribute on MyRequestModel, and creating a model binder that "implemented" System.Web.Http.ModelBinding.IModelBinder. That consistently yields an object reference exception with stack traces that never touch my code.

Anyone hit this? Have thoughts on what to try next?

UPDATE: Here's a stack trace that I've captured in my custom ExceptionFilterAttribute:

Object reference not set to an instance of an object.
    at System.Web.Http.ModelBinding.DefaultActionValueBinder.BindParameterValue(HttpActionContext actionContext, HttpParameterBinding parameterBinding)
    at System.Web.Http.ModelBinding.DefaultActionValueBinder.<>c__DisplayClass1.BindValuesAsync>b__0(RequestContentReadKind contentReadKind)
    at System.Threading.Tasks.TaskHelpersExtensions.<>c__DisplayClass38.<ToAsyncVoidTask>b__37()
    at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)
like image 726
Brandon Linton Avatar asked Oct 09 '22 03:10

Brandon Linton


1 Answers

If you're talking ApiControllers, then you're trying to model bind in Web API and now MVC Here's a sample model binder

  public class MyRequestModelBinderProvider : ModelBinderProvider
    {
        MyRequestModelBinder binder = new MyRequestModelBinder();
        public IdeaModelBinderProvider()
        {          
        }

        public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType == typeof(MyRequestModel))
            {
                return binder;
            }

            return null;
        }
    } 

Here's an example of registering a custom model binder provider

 IEnumerable<object> modelBinderProviderServices = GlobalConfiguration.Configuration.ServiceResolver.GetServices(typeof(ModelBinderProvider));
 List<Object> services = new List<object>(modelBinderProviderServices);
 services.Add(new MyRequestModelBinderProvider());
 GlobalConfiguration.Configuration.ServiceResolver.SetServices(typeof(ModelBinderProvider), services.ToArray());

Now in your custom model binder you use the contexts to access the querystring values

  public class MyRequestModelBinder :  IModelBinder
    {
        public MyRequestModelBinder()
        {

        }

        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            MyRequestModel yourModel; 
            //use contexts to access query string values
            //create / update your model properties

            bindingContext.Model = yourModel;  
            //return true || false if binding is successful
        }

Make sure your using the classes and interfaces for WebAPI and not MVC. Some of the names are the same, but different namespaces and dlls

like image 115
cecilphillip Avatar answered Oct 12 '22 10:10

cecilphillip