3

I am not able to perform property injection into a custom data annotation validation attribute

 public class CustomValidationAttribute : ValidationAttribute 
 {
    public ILogger Logger { get; set; }

    public CustomValidationAttribute(string keyPointer)
    { }

    public override bool IsValid(object value)
    {
        // Implementation here
        return true;
    }
}

Now, on my MVC Application_Start method I have the following Autofac configuration:

        // Autofac Ioc Container
        var builder = new ContainerBuilder();
        builder.RegisterType<Logger>().As<ILogger>().InstancePerHttpRequest();
        builder.RegisterType<CustomValidationAttribute>()
        .OnActivating(e =>
        {
            e.Instance.Logger = e.Context.Resolve<ILogger>();
        });
        var container = builder.Build();
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

I have also tried the autowiring capabilities:

builder.RegisterType<CustomValidationAttribute>().PropertiesAutowired();

I am guessing that the properties of an attribute on data annotations are resolved at compile time and are immune to runtime injection. This methods works fine for MVC filter attributes but does not work for data annotation attributes.

Any help is really appreciated on alternate methods to make this work.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Diego
  • 998
  • 1
  • 11
  • 20
  • related: http://stackoverflow.com/questions/5218333/asp-net-mvc3-set-custom-iserviceprovider-in-validationcontext-so-validators-can – Ruben Bartelink Feb 04 '16 at 18:47
  • (for other readers, I'm sure you know this) re "at compile time", Attributes get serialized down to blobs [effectively] representing property and ctor arguments in the Assembly binary. At run time, the Reflection APIs then dig those out and hand one an initialized version by doing lots of bespoke magic. Instantiation via `new` is a completely different thing. The key problem here is of course that no Autofac libraries do any after-the-fact injection on instances so none of the mechanisms you tried will ever do anything. Which leaves two avenues - `ValidationContext`& Service Location... – Ruben Bartelink Feb 04 '16 at 19:21
  • related: http://stackoverflow.com/questions/986019/database-injection-into-a-validation-attribute-with-asp-mvc-and-castle-windsor – Ruben Bartelink Feb 04 '16 at 19:26

4 Answers4

2

You are correct in your analysis - Autofac has a mechanism to inject into Filter Attributes [which is achieved by not instantiating them as attributes and leans on facilities exposed MVC 3].

There is no such natural extension point applicable to Validation Attributes, and Autofac doesn't make any attempt to do so.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Damian Schenkelman
  • 3,505
  • 1
  • 15
  • 19
2

For reference, we had a problem with a ValidationAttribute which needed to do some database work using a Repository, which in turn used an Entity Framework DbContext.

Our problem was that the DbContext was being cached by the attribute. This led to the data it contained being stale which affected the result of the validation!

We fixed it by doing our Repository resolve inside an Autofac Lifetimescope declaration inside the IsValid method:

using Autofac;

...

public override bool IsValid(object value)
{
    using (var lifetimeScope = MvcApplication.Container.BeginLifetimeScope())
    {
        var repo = lifetimeScope.Resolve<IMyRepo>();

        // Do your validation checks which require the repo here

    } // The repo will be released / disposed here
}

Just adding my solution here as I haven't found this solution documented for this problem anywhere else - perhaps it's just so obvious nobody else has been as dumb as me :)

Dave R
  • 1,626
  • 19
  • 28
1

I ended up using a new validator and some reflection to set an instance of the property in the data annotation.

 public class IocValidator : DataAnnotationsModelValidator<ValidationAttribute>
{
    public IocValidator(ModelMetadata metadata, ControllerContext context,
        ValidationAttribute attribute)
        : base(metadata, context, attribute) { }

    public override IEnumerable<ModelValidationResult> Validate(object container)
    {
        IEnumerable<PropertyInfo> props = 
            from p in Attribute.GetType().GetProperties()
            where p.CanRead 
                && p.CanWrite
                && (p.PropertyType.IsInterface || p.PropertyType.IsAbstract)
            select p;

        foreach (PropertyInfo prop in props)
        {
            var instance = IocHelper.Resolver.GetService(prop.PropertyType);

            if (instance != null)
                prop.SetValue(Attribute, instance, null);
        }

        return base.Validate(container);
    }
}

Then in my Application_Start I registered my new validator adapter as such:-

 DataAnnotationsModelValidatorProvider.RegisterDefaultAdapter(typeof(IocValidator));

There are definite performance implications on this approach and the dependence of the IocHelper in the Validator (ohh, the irony, dependency on the dependency injection container).

Any thoughts or better approaches are quite welcomed.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Diego
  • 998
  • 1
  • 11
  • 20
  • Most containers have an "Inject Into" operation which effectively does the above (see `ILifetimeScope.InjectProperties`), but it does make it "just work" and the Service Location isnt as blatant at the point of consumption so +1 – Ruben Bartelink Feb 04 '16 at 19:50
0

I solved it a bit differently (you're still wiring stuff via ValidationAttributes) in this answer, enabling one to write:

class MyModel 
{
    ...
    [Required, StringLength(42)]
    [ValidatorService(typeof(MyDiDependentValidator), ErrorMessage = "It's simply unacceptable")]
    public string MyProperty { get; set; }
    ....
}

public class MyDiDependentValidator : Validator<MyModel>
{
    readonly IUnitOfWork _iLoveWrappingStuff;

    public MyDiDependentValidator(IUnitOfWork iLoveWrappingStuff)
    {
        _iLoveWrappingStuff = iLoveWrappingStuff;
    }

    protected override bool IsValid(MyModel instance, object value)
    {
        var attempted = (string)value;
        return _iLoveWrappingStuff.SaysCanHazCheez(instance, attempted);
    }
}

With some helper classes (look over there), you wire it up e.g. in ASP.NET MVC like so in the Global.asax :-

DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(
    typeof(ValidatorServiceAttribute),
    (metadata, context, attribute) =>
        new DataAnnotationsModelValidatorEx(metadata, context, attribute, true));
Community
  • 1
  • 1
Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249