asp.net-mvc-3c#-4.0custom-model-binder

How can I build a custom model binder which will return different types of models depending on the request context?


I have incoming requests (from Facebook for Credits handling) on a specific action which will have have different contents, so I have different model classes to handle that.

This is my action:

public ActionResult Index([ModelBinder(typeof(FacebookCreditModelBinder))] IFacebookRequest facebookRequest)
{
    if (facebookRequest is FacebookPaymentsGetItemsRequest)
    {
        // do whatever
    }
}

And this is my model binder.

public class FacebookCreditModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var binder = new DefaultModelBinder();
        // how to change the model here in the bindingContext?
        return binder.BindModel(controllerContext, bindingContext); 
    }
}

I want to create for example a FacebookPaymentsGetItemsRequest object if the incoming var "method" is "payments_get_items" and a FacebookPaymentsStatusUpdateRequest if method is "payments_status_update" and I don't know how to change the type of the model in the bindingContext. Is it possible to change the type of the model in a custom model binder?


Other approach: I tried it with BindModel also and I'm able to return the correct object but all properties are null because it is not filled by the default model binder:

public override object BindModel(ControllerContext controllerContext,
        ModelBindingContext bindingContext)
{
    NameValueCollection form = controllerContext.HttpContext.Request.Form;
    if (form.Get("method") == "payments_get_items")
    {
        return new FacebookPaymentsGetItemsRequest();
    }
    ...

Solution

  • You could do this:

    public class FacebookCreditModelBinder : DefaultModelBinder
    {
        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            var methodValue = bindingContext.ValueProvider.GetValue("method");
            if (methodValue == null || string.IsNullOrEmpty(methodValue.AttemptedValue))
            {
                throw new Exception("The method parameter was not found");
            }
    
            var method = methodValue.AttemptedValue;
            IFacebookRequest model = null;
            if (method == "payments_get_items")
            {
                model = FacebookPaymentsGetItemsRequest();
            }
            else if (method == "...")
            {
                model = ....
            }
            else
            {
                throw new NotImplementedException("Unknown method value: " + method);
            }
    
            bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
            return model;
        }
    }
    

    and register in Application_Start:

    ModelBinders.Binders.Add(typeof(IFacebookRequest), new FacebookCreditModelBinder());
    

    Then your controller action could look like this:

    public ActionResult Index(IFacebookRequest facebookRequest)
    {
        if (facebookRequest is FacebookPaymentsGetItemsRequest)
        {
            // do whatever
        }
    }