Click here to Skip to main content
Click here to Skip to main content

Customizing property binding through attributes

By , 15 Apr 2014
Rate this:
Please Sign up or sign in to vote.

"THE ARTICLE IS NO LONGER MAINTAINED HERE. PLEASE POST ANY FURTHER DISCUSSIONS/QUESTIONS IN MY BLOG http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes" - Author

DefaultModelBinder

I don't need to say much about model binding, most of us aware of that. The built-in DefaultModelBinder takes away most of the burden from our shoulders and it's ideal in most of the cases. But in some cases the DefaultModelBinder is not enough for binding a particular model or a property and in those cases normally we go for creating a custom model binder either by creating a brand new one by implementing IModelBinder or by extending the DefaultModelBinder.

The created custom model binder can be registered to a model by two ways either by adding into the Binders collection in Global.asax.cs or through the ModelBinderAttribute. The created custom model binder can be linked to a class but not to a property.

Listing 1. shows the ModelBinderAttribute and it's usage levels. The ModelBinderAttribute helps to specify a custom model binder at class or parameter level but not at property level. Because of this when we want to customize the binding for a single property in a model we are forced to create a custom model binder and attach that to that model instead of that property.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | 
AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter, 
AllowMultiple = false, Inherited = false)]
public sealed class ModelBinderAttribute : CustomModelBinderAttribute
{
    -
}
Listing 1. ModelBinderAttribute

In this article we will see how we can attach custom binding behaviors to a property through attributes.

Example

Let's take a simple example that helps you to clearly understand what are up to.

public class Movie
{
    public int Id { get; set; }
    public string Director { get; set; }
    public string[] Actors { get; set; }
    public DateTime ReleasedOn { get; set; }            
}
Listing 2. Movie model

We have a Movie class and a form that helps to add new movies. The user enter the details of the actors in the textbox as a comma separated list and if you use the DefaultModelBinder it just stores the complete actors list as a single value in the array but what we want is to convert the comma separated list into a string array and assign to the Actors property.

Typically what we do at this time is go and create a custom model binder and register the custom binder for the complete Movie model. The problem here is the DefaultModelBinder is a heavy class and all we want is to override the binding for the Actors property but for that we have to create a custom binder by extending the DefaultModelBinder. It would really great if we specify the framework some way to delegate the binding for only that property to another class.

Attributes are great way to customize the behaviors at runtime. So what we are going to do is create an attribute that contains a single method which takes care of the binding work for the target property. The below listing shows our attribute class PropertyBindAttribute which contains a single abstract method BindProperty.

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public abstract class PropertyBindAttribute : Attribute
{
    public abstract bool BindProperty(ControllerContext controllerContext, 
    ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor);
}
Listing 3. PropertyBindAttribute that helps to customize binding for model properties

For customize binding the Actors property in the Movie model we have to create a custom attribute by implementing the PropertyBindAttribute. The below listing shows the StringArrayPropertyBindAttribute that converts the comma separated actors list into a string array.

public class StringArrayPropertyBindAttribute : PropertyBindAttribute
{
    public override bool BindProperty(ControllerContext controllerContext, 
        ModelBindingContext bindingContext,    PropertyDescriptor propertyDescriptor)
    {
        if (propertyDescriptor.PropertyType == typeof(string[]))
        {
            var value = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);

            propertyDescriptor.SetValue(bindingContext.Model,
                value.AttemptedValue.Split(',').Select(s => s.Trim()).ToArray());

            return true;
        }

        return false;
    }
}
Listing 4. StringArrayPropertyBindAttribute that binds a comma separated string into string array

Next what we have to do is mark the Actors property with the StringArrayPropertyBindAttribute.

public class Movie
{
    ...
    [StringArrayPropertyBind]
    public string[] Actors { get; set; }
    ...
}
Listing 5. Applying StringArrayPropertyBindAttribute over property

Custom Model Binder

The DefaultModelBinder doesn't know about our PropertyBindAttribute so we have to create a custom model binder. Don't think again a custom model binder? because once we have this custom model binder we can avoid creating many model binders by extending the DefaultModelBinder class.

public class ExtendedModelBinder: DefaultModelBinder
{
    protected override void BindProperty(ControllerContext controllerContext, 
        ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
    {
        if(propertyDescriptor.Attributes.OfType<PropertyBindAttribute>().Any())
        {
            var modelBindAttr = propertyDescriptor.Attributes.OfType<PropertyBindAttribute>().FirstOrDefault();

            if(modelBindAttr.BindProperty(controllerContext, bindingContext, propertyDescriptor))
                return;
        }

        base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
    }
}
Listing 6. Custom model binder that delegates the binding work of properties to custom classes

We are nearly done! All we have to set our ExtendedModelBinder as the default in Global.asax.cs.

ModelBinders.Binders.DefaultBinder = new ExtendedModelBinder();

Summary

What we saw in this article is just an idea of how we can avoid creating more model binders when all we want is to customize the binding for a property in a model.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

After2050
Software Developer Trigent Software Private Limited
India India
I'm a software developer from south tip of India. I spent most of the time in learning new technologies. I've a keen interest in client-side technologies especially JavaScript and admire it is the most beautiful language ever seen.
 
I like sharing my knowledge and written some non-popular articles. I believe in quality and standards but blames myself for lagging them.
 
I believe in small things and they makes me happy!
Follow on   Twitter

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web02 | 2.8.140421.2 | Last Updated 15 Apr 2014
Article Copyright 2012 by After2050
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid