Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finding custom attributes on view model properties when model binding

I've found a lot of information on implementing a custom model binder for validation purposes but I haven't seen much about what I'm attempting to do.

I want to be able to manipulate the values that the model binder is going to set based on attributes on the property in the view model. For instance:

public class FooViewModel : ViewModel
{
    [AddBar]
    public string Name { get; set; }
}

AddBar is just

public class AddBarAttribute : System.Attribute
{
}

I've not been able to find a clean way to find the attributes on a view model property in the custom model binder's BindModel method. This works but it feels like there should be a simpler solution:

public class FooBarModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var value = base.BindModel(controllerContext, bindingContext);

        var hasBarAttribute = false;

        if(bindingContext.ModelMetadata.ContainerType != null)
        {
            var property = bindingContext.ModelMetadata.ContainerType.GetProperties()
                .Where(x => x.Name == bindingContext.ModelMetadata.PropertyName).FirstOrDefault();
            hasBarAttribute = property != null && property.GetCustomAttributes(true).Where(x => x.GetType() == typeof(AddBarAttribute)).Count() > 0;
        }

        if(value.GetType() == typeof(String) && hasBarAttribute)
            value = ((string)value) + "Bar";

        return value;
    }
}

Is there a cleaner way to view the attributes on the view model property or a different kind of attribute I could be using? The DataAnnotation attributes really seem to be for a different problem.

UPDATE

Craig's answer got me to the right place but I thought I'd put some examples in here for others.

The metadata provider I ended up with looks like

public class FooBarModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        var metaData = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);

        if(attributes.OfType<AddBarAttribute>().Any())
            metaData.AdditionalValues.Add("AddBarKey", true);

        return metaData;
    }
}

The model binder looks like:

public class FooBarModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var value = base.BindModel(controllerContext, bindingContext);

        if(bindingContext.ModelMetadata.AdditionalValues.ContainsKey("AddBarKey"))
            value = ((string)value) + "Bar";

        return value;
    }
}
like image 862
Jared Avatar asked Jun 01 '11 17:06

Jared


1 Answers

The "correct" way (per the guy who wrote it) is to write a model metadata provider. There's an example at the link. Not precisely "simple," but it works, and you'll be doing what the rest of MVC does.

like image 160
Craig Stuntz Avatar answered Oct 18 '22 13:10

Craig Stuntz