0

I'm trying to understand how custom binding should work.

Assuming a simple Action of

[HttpPost]
public ActionResult MyAction(CustomType parameter) {
    // do something
}

... and the following form data

{
    parameter : "mydata"
    parameter.Property1 : "something"
    parameter.Property2 : 3
}

... and the following, very simple custom binder

public class MyBinder : DefaultModelBinder {

        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {

            if (bindingContext.ModelType.Equals(typeof(CustomType))) {
                string parameter = controllerContext.HttpContext.Request.Form[bindingContext.ModelName];
                object model = controllerContext.HttpContext.Cache[parameter];

                return model;
            }

            return base.BindModel(controllerContext, bindingContext);

        }

        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) {
            // not called
            return base.CreateModel(controllerContext, bindingContext, modelType);
        }

        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor) {
            // not called
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }

    }

I can see that the BindModel method is called. However, once I return my custom object the BindProperty method is never called for Property1 and Property2. This makes sense, because I'm not calling base.BindModel().

So my question is: How should BindModel be implemented so that it creates CustomType and also calls BindModel in the super class?

Red Taz
  • 4,159
  • 4
  • 38
  • 60
  • What makes you think you need a custom model binder. The data you are posting has no relationship to your model. The fact you have `parameter : "mydata"` means the first thing that happens in the model binding process is a new instance of `CustomType` type is initialized and then it tries to set the value of `CustomType = "mydata"` which of course fails (you can't set a complex object to a string). The posted data needs to be `{ Property1 : "something", Property2 : 3 }` and the `DefaultModelBinder` will initialize a new `CustomType` and set the values of `Property1` and `Property2`. –  Apr 18 '15 at 00:50
  • @StephenMuecke yes you're right and I apologise for the contrived example. Please consider a scenario where `parameter : "mydata"` holds useful data pertinent to the creation of the object such as a key to a dictionary holding an exising object, or a serialsed representation of the object. I then want to follow up this object retrieval with the recursive property binding provided by the default binder. I hope that makes the question a little clearer. – Red Taz Apr 18 '15 at 07:07

2 Answers2

0

I haven't done this for a while, but IIRC, you can construct your new CustomType with the properties set, rather than return new CustomType();

Something like:

return new CustomType
{
    Property1 = request.Form.Get("Property1"),
    ...
};
MattDuFeu
  • 1,615
  • 4
  • 16
  • 25
  • I'd much rather have the DefaultModelBinder handle the property binding rather than hard-coding each of the form keys – Red Taz Apr 17 '15 at 15:04
0

I have examined the source code for DefaultModelBinder and discovered the internal method BindComplexElementalModel. The desired model binding behavior can be achieved using this custom data binder.

public class MyBinder : DefaultModelBinder {

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {

        if (bindingContext.ModelType.Equals(typeof(CustomType))) {
            // get an instance of the model using the model prefix
            string parameter = controllerContext.HttpContext.Request.Form[bindingContext.ModelName];
            object model = controllerContext.HttpContext.Cache[parameter];

            // populate the remaining model properties using reflection (yuck)
            MethodInfo bindComplexElementalModel =
                base.GetType().GetMethod("BindComplexElementalModel", BindingFlags.NonPublic | BindingFlags.Instance);

            bindComplexElementalModel.Invoke(this, new object[] { controllerContext, bindingContext, model });

            return model;
        }

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

Of course, using an internal framework method isn't a clean solution so I am leaving this question open in the hope that someone can provide a better answer.

Red Taz
  • 4,159
  • 4
  • 38
  • 60
  • Could you alternatively implement IModelBinder in a new custom binder? – MattDuFeu Apr 21 '15 at 09:54
  • I did wonder if that was required. Quite [a few resources](http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx) recommend inheriting from `DefaultModelBinder` presumably because; if you want to maintain the same level of flexibility provided by the default binder (i.e. automatic and recursive binding of all object properties) then writing your own custom `IModelBinder` would involve re-writing a lot of that default functionality? – Red Taz Apr 21 '15 at 10:24
  • The comments in [this question](http://stackoverflow.com/questions/8535802/when-to-use-imodelbinder-versus-defaultmodelbinder) agree. I don't know the answer, but at least you got it working. – MattDuFeu Apr 21 '15 at 10:35