3

I would like to isolate a controller action completely from other elements of the system, as it is a refactor to legacy code so that the action can be rendered in various places in the system. In order to isolate any overlap in property names in the partial view's model, I was hoping to prefix the form name attributes with a certain value and make use of the BindAttribute to instruct the ModelBinder to match up the model properties with the appropriately prefixed form fields.

Since I have a handful of actions that make use of this model, I was hoping I could place the [Bind] decorator directly on the model class itself, avoiding having to decorate every model parameter coming into each of those actions. However, this approach doesn't seem to register with the binder, so I end up with unpopulated properties. On the other hand, if I move the decorators to the parameters, all is well.

The documentation for BindAttribute indicates it can be used on classes. Is this not a supported scenario?

1 Answer 1

5
+200

You could write a custom model binder for the corresponding view model:

public class MyViewModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        bindingContext.ModelName = "some_prefix";
        return base.BindModel(controllerContext, bindingContext);
    }
}

which you could register in your Application_Start and associate with your view model:

ModelBinders.Binders.Add(typeof(MyViewModel), new MyViewModelBinder());

In this example, I have hardcoded the prefix, but you could make the model binder a bit more generic and reusable and take into account the BindAttribute that you could use to decorate your view model with:

public class MyViewModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // TODO: cache the result of this LINQ query to 
        // avoid using reflecting on each request. After all
        // the metadata of the view model won't change at runtime
        var bindAttribute = bindingContext
            .ModelType
            .GetCustomAttributes(typeof(BindAttribute), true)
            .OfType<BindAttribute>()
            .FirstOrDefault();

        bindingContext.ModelName = bindAttribute != null ? bindAttribute.Prefix : null;
        return base.BindModel(controllerContext, bindingContext);
    }
}

and then all that's left is to decorate your view model with the Bind attribute:

[Bind(Prefix = "some_prefix")]
public class MyViewModel
{
    public string Foo { get; set; }
    public string Bar { get; set; }
}

and request the action that is taking this view model:

/someaction?some_prefix.foo=the_foo_value&some_prefix.bar=the_bar_value
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks. I'd rather not introduce any more moving parts to the system, so since this isn't a supported scenario OOtB, I'll just decorate the parameters and move on.
One quick question before I do, is it possible that associating the binder with a base model class will allow it to be reusable across all models which inherit from it without specifying several such directives in application startup?
If I remember correctly this will work with a base model and a single registration but I am not 100% sure and it should be tested.
It does not work with base model and a single registration :(.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.