I was under the impression that when binding to a complex model, all public properties were processed and a match binding attempted for each.
I'm trying to resolve a variable naming problem so that a model
class Model {
public string Foo {get;set;}
public string FooBar {get;set;}
}
works nicely with a query string like
?foo=foo&foo_bar=foo_bar
Is there a better way than with a custom model binder? In any case, mine doesn't work. FooBar is simply skipped.
public class StringModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = base.BindModel(controllerContext, bindingContext);
if (model != null)
return model;
var modelName = Regex.Replace(bindingContext.ModelName, "([a-z])([A-Z])", "$1_$2").ToLowerInvariant();
var value = bindingContext.ValueProvider.GetValue(modelName);
return value;
}
}
Registered with
ModelBinders.Binders.Add(typeof(string), new StringModelBinder());
I was under the impression that when binding to a complex model, all public properties were processed and a match binding attempted for each.
No, that's a wrong impression. The default model binder will attempt to bind only the properties for which you have a corresponding value in the Request. In your case you do not have a corresponding value for the FooBar property so it won't be bound.
Actually it would be nice if we could write:
public class Model
{
public string Foo { get; set; }
[ParameterName("foo_bar")]
public string FooBar { get; set; }
}
So let's implement this. We start by writing a base attribute:
[AttributeUsageAttribute(AttributeTargets.Property)]
public abstract class PropertyBinderAttribute : Attribute, IModelBinder
{
public abstract object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
}
and a custom model binder:
public class CustomModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
var propertyBinderAttribute = propertyDescriptor
.Attributes
.OfType<PropertyBinderAttribute>()
.FirstOrDefault();
if (propertyBinderAttribute != null)
{
var value = propertyBinderAttribute.BindModel(controllerContext, bindingContext);
propertyDescriptor.SetValue(bindingContext.Model, value);
}
else
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}
As you can see this custom model analyzes the metadata of the model and if a property is decorated with an instance of the PropertyBinderAttribute it will use it.
We will then replace the default model binder with our custom one in Application_Start
:
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
and all that's left now is to implement the ParameterNameAttribute binder that we used to decorate our model property with:
public class ParameterNameAttribute : PropertyBinderAttribute
{
private readonly string parameterName;
public ParameterNameAttribute(string parameterName)
{
this.parameterName = parameterName;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(this.parameterName);
if (value != null)
{
return value.AttemptedValue;
}
return null;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With