.NET

How to dynamically modify model meta data in ASP.NET MVC

Normally you just add the [Required] attribute to a view model to make it required. But I needed a way to configure whether a field to be required or not. The requirement was that it should be configured through web.config:
 
 
 
 
 
 
 
 
 
 
 

<appSettings>
    <add key='ticket-cat1-required' value='true' />
</appSettings>

Having to modify the view or the controller would not be very clean. Instead it’s much better to take advantage of the ModelValidatorProvider. I could have just done like this:

public class ConfigurableModelValidatorProvider : LocalizedModelValidatorProvider
{
    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<System.Attribute> attributes)
    {
        bool isRequired = metadata.ContainerType == typeof (CreateViewModel)
                            && ConfigurationManager.AppSettings['ticket-cat1-required'] == 'true';
        var theAttributes = attributes.ToList();
        if (!theAttributes.Any(x => x is RequiredAttribute) && isRequired)
            theAttributes.Add(new RequiredAttribute());
        return base.GetValidators(metaDataContext.Metadata, context, attributes);
    }
}

And then assigned it in global.asax:

protected void Application_Start()
{
    ModelValidatorProviders.Providers.Clear();
    ModelValidatorProviders.Providers.Add(new ConfigurableModelValidatorProvider());
    //...
}

But that would have created a tightly coupled provider.

The loosely coupled way

Instead I decided to take advantage of my inversion of control container and define some interfaces.

/// <summary>
/// Can adapt the generated metadata before it's sent to the view
/// </summary>
public interface IModelMetadataAdapter
{
    /// <summary>
    /// Adapt the meta data
    /// </summary>
    /// <param name='context'>Context information</param>
    void Adapt(MetadataContext context);
}

The context used to modify the meta data:

/// <summary>
/// context for <see cref='IModelMetadataAdapter'/>
/// </summary>
public class MetadataContext
{
    /// <summary>
    /// Initializes a new instance of the <see cref='MetadataContext'/> class.
    /// </summary>
    /// <param name='metadata'>The metadata.</param>
    public MetadataContext(ModelMetadata metadata)
    {
        if (metadata == null) throw new ArgumentNullException('metadata');
        Metadata = metadata;
    }
    /// <summary>
    /// See MSDN for info
    /// </summary>
    public ModelMetadata Metadata { get; set; }
}

Which allowed me to create this class (which is automatically registered in Griffin.Container):

[Component]
public class ToggleRequiredOnCreateModel : IModelMetadataAdapter
{
    public void Adapt(MetadataContext context)
    {
        if (context.Metadata.ContainerType != typeof(CreateViewModel))
            return;
        context.Metadata.IsRequired = false;
        if (context.Metadata.PropertyName != 'Category1')
            return;
        context.Metadata.IsRequired = ConfigurationManager.AppSettings['ticket-cat1-required'] == 'true';
    }
}

To make it all possible I’ve also have to modify the validator provider:

public class ConfigurableModelValidatorProvider : LocalizedModelValidatorProvider
{
    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<System.Attribute> attributes)
    {
        var services = DependencyResolver.Current.GetServices<IModelMetadataAdapter>();
        var metaDataContext = new MetadataContext(metadata);
        foreach (var service in services)
        {
            service.Adapt(metaDataContext);
        }
        var theAttributes = attributes.ToList();
        if (!theAttributes.Any(x => x is RequiredAttribute) && metaDataContext.Metadata.IsRequired)
            theAttributes.Add(new RequiredAttribute());
        return base.GetValidators(metaDataContext.Metadata, context, attributes);
    }
}

In my case I’m using my Griffin.MvcContrib project to handle the localization, that’s why I inherit LocalizedModelValidatorProvider and not DataAnnotationsModelValidatorProvider.
 

Reference: How to dynamically modify model meta data in ASP.NET MVC from our NGC partner Jonas Gauffin at the jgauffin’s coding den blog.

Related Articles

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button