3

With ASP.NET MVC, is it possible to use constructor injection with the data annotation attributes (specifically, I'm using validation attributes)?

What I would like to be able to do is:

public class NoEmailTakenAttribute : ValidationAttribute
{
    public NoEmailTakenAttribute(IService service) { .. }
}

Is that possible?

Thanks.

Brian Mains
  • 50,520
  • 35
  • 148
  • 257
  • What are you trying to achieve? – Jeroen Dec 13 '12 at 03:44
  • Create validation attributes with DI capability, just like you could use DI with controller creation. – Brian Mains Dec 13 '12 at 03:52
  • Am I supposed to know what DI means? – Jeroen Dec 13 '12 at 04:07
  • I'm trying to implement a Dependency Injection (DI) technique. – Brian Mains Dec 13 '12 at 04:12
  • Sorry I will need a better and more extended description of what you exactly want. – Jeroen Dec 13 '12 at 04:15
  • You'd have to use an IoC container to hook up the dependencies, but yes, this should certainly be possible- have you tried it and had a problem? – Greg Smith Dec 13 '12 at 09:11
  • @GregSmith I have an IoC container setup and ready to go; I'm using IOC with my controller creation. I wasn't sure where to start with data annotation injection though, since attributes are a part of the model binding process and aren't explicitly defined as a part of the MVC process. Googling brings back articles on constructor injection. – Brian Mains Dec 13 '12 at 12:44

5 Answers5

4

You can't use Controller injection from what I see using Reflector, but it does appear possible to use property injection. By creating a class that inherits from DataAnnotationsModelValidatorProvider, and by overriding the method GetValidators, it seems plausible that the attributes can be property injected into before the validation happens... this is from an initial analysis, yet to be fully determined.

Brian Mains
  • 50,520
  • 35
  • 148
  • 257
1

The solution proposed by Brian Mains should work fine. I don't think constructor injection is an option here but property injection will do the job. You can derive from ModelValidatorProvider and your implementation might look similiar to this:

public class MyModelValidatorProvider : ModelValidatorProvider
{
    private IDiContainer _container;

    public MyModelValidatorProvider(IDiContainer container)
    {
        _container = container;
    }

    public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
    {
        List<ModelValidator> validators = new List<ModelValidator>();

        PropertyInfo targetProperty = metadata.ContainerType.GetProperty(metadata.PropertyName);
        if (targetProperty.GetCustomAttributes(false).Any(attr => attr.GetType() == typeof(NoEmailTakenAttribute)))
        {
            DataAnnotationsModelValidator<NoEmailTakenAttribute> validator = new DataAnnotationsModelValidator<NoEmailTakenAttribute>(
                metadata, context, _container.Resolve<NoEmailTakenAttribute>());

            validators.Add(validator);
        }

        return validators;
    }
}

I did not look closely into the ModelMetadata and just used refelction to decide whether to return the validator or not but it probably can be done better.

Then in the Global.asax add the following:

ModelValidatorProviders.Providers.Add(new MyModelValidatorProvider(InstanceOfContainer));

and you should be good to go. The only problem here is that your validator will get created by the default mechanism as well. Obviously this will result in your validator not having the proper dependencies injected. I have no idea how to exclude a validator from default creation but if you properly check against null values inside your validator it should work fine (a bit of a workaround i must say but maybe you'll find a better way).

dmusial
  • 1,504
  • 15
  • 14
0

You can do this by creating base controller class and declare property for IService interface and write base controller class with parameter IService. Use this in your derived class. As asp.net mvc uses the default controller constructor to initialise that time your service get instantiated by derived class parameterized constructor.

Brian Mains
  • 50,520
  • 35
  • 148
  • 257
Prashant
  • 710
  • 3
  • 7
  • 29
0

I don't think there is an easy way to do constructor injection or even property injection for custom data annotations. What @BrianMains suggested sounds plausible, but I haven't gone that far with it myself.

One way to get around the complexity is to use a service locator in the custom data annotation to get whichever dependency you need. Not as clean, but gets you the same result.

Check this link out for more info: Dependency injection in custom DataAnnotations in ASP.Net MVC 3

Community
  • 1
  • 1
Rick B
  • 1,156
  • 10
  • 11
0

Brian Mains answer is correct but bit too brief. So here is an example.

First define a provider

public class CustomProvider : DataAnnotationsModelValidatorProvider
{
    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, IEnumerable<ModelValidatorProvider> validatorProviders, IEnumerable<Attribute> attributes)
    {
        foreach (var attrib in attributes)
        {
           //if the attrib is your one set whatever you need 
        }

        return base.GetValidators(metadata, validatorProviders, attributes);
    }
}

and then register it - for MVC see dmusial answer. For Web Api(How To Add Custom ModelValidatorProviders To Web API Project? )

config.Services.Add(typeof(ModelValidatorProvider), new CustomProvider());
IvanH
  • 5,039
  • 14
  • 60
  • 81