2

We have a custom model binder that deserialises json into a list of objects and I want to use that model binder for several views, each of which uses a different viewmodel.

What we want to avoid is having to register the model binder for each view model as follows:

ModelBinders.Binders.Add(typeof(ViewModelOne), new JsonPropertyBinder());
ModelBinders.Binders.Add(typeof(ViewModelTwo), new JsonPropertyBinder());

What we'd like to do is have the ViewModels derive from a base class (which they do) and register that base class:

ModelBinders.Binders.Add(typeof(ViewModelBase), new JsonPropertyBinder());

where ViewModelOne and ViewModelTwo inherit form ViewModelBase. I've tried this and I didn't have any luck. The problem there, is that the property that needs to be custom-bound is not in the base ViewModel. What we really want is an elegant solution to implementing my model binder in a generic way.

We also have a custom attribute [JsonBindable] on the properties in my viewmodels that are to be custom-bound, I then check for this attribute in the binder:

public class JsonPropertyBinder : DefaultModelBinder
    {
        protected override object GetPropertyValue(ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, System.Web.Mvc.IModelBinder propertyBinder)
        {
            if (propertyDescriptor.Attributes.OfType<Attribute>().Any(x => (x is JsonBindableAttribute)))
            {
                var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
                return JsonConvert.DeserializeObject(value, propertyDescriptor.PropertyType);
            }

            return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
        }
    }

I've tried adding the [ModelBinder] attribute to my viewmodel, but without success. Although, I'm not sure I like this approach, as I'd like to keep the registration of the binder to one place, rather than spread out

-- EDIT -- I guess I could create an intermediary class (e.g. ViewModelIntermediate) that will inherit from the ViewModelBase, contain only the property I want to custom-bind, and then have ViewModelOne and ViewModelTwo inherit from ViewModelIntermediate so that I can register the binder once using the derived ViewModel e.g.

ModelBinders.Binders.Add(typeof(ViewModelIntermediate), new JsonPropertyBinder());

but that seems like a clumsy solution. I want to be able to declare the custom binder once and use it with any view model - and not have to abstract my classes to oblivion.

Right now I'm thinking that I might have to declare a new default binder which inherits from DefaultModelBinder and have some logic in there that checks for certain (or custom) attributes and processes them accordingly. something like this

Community
  • 1
  • 1
Adam Hey
  • 1,512
  • 1
  • 20
  • 24

1 Answers1

4

i think what you might be looking for is a custom Model Binding Provider.. basically an uber abstraction that determines what ModelBinder to use, given the model type.

so basically, implement the IModelBinderProvider interface, and have the GetBinder method return your model binder, based on your criteria.

public class ViewModelBaseBinderProvider
    : IModelBinderProvider
{
    public IModelBinder GetBinder(Type modelType)
    {
        // this or whatever condition you want to apply to determine
        // if your model binder needs to be used.
        if (typeof(ViewModelBase).IsAssignableFrom(modelType))
            return new JsonPropertyBinder();

        // this means, the view model did not match our criteria   
        // let it flow through the usual model binders.       
        return null;
    }
}

once you have defined the custom provider, register in your application start as follows:

ModelBinderProviders.BinderProviders.Add(new ViewModelBaseBinderProvider());

p.s. nit: i would rename the JsonPropertyBinder to JsonPropertyModelBinder for clarity.

Raja Nadar
  • 9,409
  • 2
  • 32
  • 41
  • Thanks for your answer, however it's not quite what I'm looking for. Correct me if I'm wrong, but with your solution, all of the logic to determine which binder to use will have to be together in this GetBinder method. – Adam Hey Feb 16 '16 at 10:29
  • nope. the only logic here is to determine if your model binder needs to be picked up or not. if the criteria doesn't match yours, we just return null and MVC takes care of running though all the other BindingProviders (if any) and finally the default one. note that we are just adding custom bindingprovider to MVC's list of providers.. we are not replacing anything – Raja Nadar Feb 16 '16 at 18:44
  • ok cool. thanks! Then it does sound like what I'm looking for. I'll try it out shortly – Adam Hey Feb 17 '16 at 08:02
  • @RajaNadar just wanted you to know this helped me out big time today. Thanks! – Kaleb Anderson Jan 11 '19 at 22:42