Reputation: 1691
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
Upvotes: 2
Views: 1174
Reputation: 9489
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.
Upvotes: 4