I’m writing a CMS in ASP.NET MVC. One of my needs is that in a custom CRUD cycle, I can use different entities, all inherited from a base class. Because of default model binder, an action method must be of the specific type to deserialize information from the form collection into the entity. The persistence is based on generic XML serialization: how to locate the serialization is due to url information(s).

This is one of my action methods:

        [AcceptVerbs(HttpVerbs.Post)]
        [ValidateInput(false)]
        public ActionResult UpdateResource(string path, ICMSResourceInfo resource)
        {
            var instance = DataContext.Get(path);
            if (instance == null)
                return RedirectToError("Istanza da aggiornare non trovata");

            instance.Serialize(resource);

            SaveChanges();

            return RedirectToBrowse(resource.Parent);
        }

How to do it?

I have some information in the POST action, for example a “resource.TemplateName” that I use to specify the Type. This information can be used in a custom DefaultModelBinder to decide which type instantiate.

DefaultModelBinder has a method named BindModel:

        public override object BindModel(ControllerContext controllerContext, 
ModelBindingContext bindingContext)

BindModel has an argument, named bindingContext of type ModelBindingContext:

namespace System.Web.Mvc
{
    public class ModelBindingContext
    {
        //…
        public Type ModelType { get; set; }
        public IDictionary<string, ValueProviderResult> ValueProvider { get; set; }
    }
}

Two properties are interesting for our needs.

BindingContext contains the property ValueProvider which is a Dictionary that is really the FormCollection we can use in Action method. If we override BindModel method in a class derived from ModelBinder,we can find in ValueProvider our “resource.TemplateName” value.

The second is ModelType: this is the type that the DefaultModelBinder has obtained from Action method. So, if we decide that some values in ValueProvider (“resource.TemplateName” in my case) has some values, I can change the ModelType. Specifically:

    public class CMSModelBinder: DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ValueProvider.ContainsKey("resource.TemplateName"))
            {
                bindingContext.ModelType =
                    CMSTemplateFactory.Default.GetTypeOf(
                        bindingContext.ValueProvider["resource.TemplateName"].AttemptedValue);
                // CMSFolderTemplateFactory is a class in which I map my custom types (templates)
            }
            var bound = base.BindModel(controllerContext, bindingContext);
            return bound;
        }

So, bindingContext.ValueProvider["resource.TemplateName"].AttemptedValue is the key I use to decode my specifc type. I obtain (in some ways) the specific type and then DefaultModelBinder continue with its work.

To declare to ASP.NET MVC that there is a new ModelBinder, in Global.asax we need to declare:

            CMSModelBinder modelBinder = 
                new CMSModelBinder();

            ModelBinders.Binders[typeof(ICMSResourceInfo)] = modelBinder;

In general, we have to associate a ModelBinder for each specific type we need to specify a custom binding.

Inheritance is not considered: binding associate on specific types. This means that if I write:

            ModelBinders.Binders[typeof(object)] = modelBinder;

this assiociation is valid for an Action method with arguments of object type, not every type inherited from object (all!!!)

I think this is good, not bad.

Enjoy!

     Marco

About these ads