Question

First I have registered my custom attribute :

container.Register<ValidationAttribute, CustomValidationAttribute>();

I have created a custom DataAnnotationsModelValidatorProvider :

public class CustomModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
    private ValidationAttribute _customValidationAttribute;

    public CustomModelValidatorProvider(
        ValidationAttribute customValidationAttribute) : base()
    {
        _customValidationAttribute = customValidationAttribute;
    }

    protected override IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata, ControllerContext context, 
        IEnumerable<Attribute> attributes)
    {
        IList<Attribute> customAttributes = attributes.ToList();

        // It will be added for each property of the model
        customAttributes.Add(_customValidationAttribute);

        IEnumerable<ModelValidator> validators = 
            base.GetValidators(metadata, context, customAttributes);

        return validators;
    }
}

I have registered it in the container :

// Register Custom Model Validation Provider
container.Register<DataAnnotationsModelValidatorProvider, CustomModelValidatorProvider>();

I have also created a DependencyResolverModelValidatorProvider to get the provider for GetValidator methods to be able to inject other lifetime scope instances:

public class DependencyResolverModelValidatorProvider 
    : DataAnnotationsModelValidatorProvider
{
    protected override IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata, ControllerContext context, 
        IEnumerable<Attribute> attributes)
    {
        return GetProvider().GetValidators(metadata, context);
    }

    private static DataAnnotationsModelValidatorProvider GetProvider()
    {
        return (DataAnnotationsModelValidatorProvider)
            DependencyResolver.Current.GetService(
                typeof(DataAnnotationsModelValidatorProvider));
    }
}

And finally I have replaced the current DataAnnotationsModelValidatorProvider :

ModelValidatorProviders.Providers.Remove(
    ModelValidatorProviders.Providers
        .OfType<DataAnnotationsModelValidatorProvider>().First());

ModelValidatorProviders.Providers.Add(new DependencyResolverModelValidatorProvider());

Is this a good way to add custom injected validation attributes at runtime using Simple Injector?

Was it helpful?

Solution

First of all you shouldn't register attributes explicitly in the container if you want do this in run time.

In DataAnnotaionsModelMetadataProvider you should use GetRegistration method.

I'm resolve this like that:

1) My custom model metadata provider

public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    private readonly Container container;

    public CustomModelMetadataProvider(Container container)
    {
        this.container = container;
    }

    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        var dependentAttributeType = typeof(DependentAttribute);

        foreach (var attribute in attributes.Where(i => Attribute.IsDefined(i.GetType(), dependentAttributeType)))
        {
            container.GetRegistration(attribute.GetType(), true).Registration.InitializeInstance(attribute);
        }

        return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
    }
}

2) My for instance custom attribute (sign DependentAttribute), to recognize in model metadata provider

[Dependent]
public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute, IClientValidatable
{
    [Dependency]
    public IResourceReader ResourceReader { get; set; }

    public override bool IsValid(object value)
    {
        return base.IsValid(value);
    }

    public override string FormatErrorMessage(string name)
    {
        return ResourceReader.Get(ErrorMessageResourceName);
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ValidationType = "required",
            ErrorMessage = FormatErrorMessage(metadata.DisplayName)
        };
    }
}

3) I'm register only my model metadata provider in startup in my code

ModelMetadataProviders.Current = new CustomModelMetadataProvider(container);

Thats All.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top