Database injection into a validation attribute with ASP MVC and Castle Windsor
Asked Answered
C

4

7

I need some help - I am trying to use a custom validation attribute in an ASP.NET MVC web project that needs to make a database call.

I have windsor successfully working for the controllers and the IRepository interface is injected normally. The problem arrises when I need to inject the repository into the attribute class.

The attribute class has the following code:

public class ValidateUniqueUrlNodeAttribute : AbstractValidationAttribute
{
    private readonly string message;
    private readonly IArticleRepository articleRepository;

    public ValidateUniqueUrlNodeAttribute(string message)
    {
        this.message = message;
    }

    public ValidateUniqueUrlNodeAttribute(string message, IArticleRepository articleRepository):this(message)
    {
        this.articleRepository = articleRepository;
    }
    public override IValidator Build()
    {
        var validator = new UniqueUrlNodeValidator(articleRepository) { ErrorMessage = message };

        ConfigureValidatorMessage(validator);

        return validator;
    }

My problem is that I cannot seem to make Windsor intercept the contruction of the attribute to pass in the IArticleRepository

The current code in my global.asax file is as follows:

container = new WindsorContainer();
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(Container));
   container
     .RegisterControllers(Assembly.GetExecutingAssembly())
     .AddComponent<IArticleRepository, ArticleRepository>()
     .AddComponent<ValidateUniqueUrlNodeAttribute>();

Any help would be greatly appreciated.

Chivalrous answered 12/6, 2009 at 10:29 Comment(0)
E
5

AFAIK no dependency injection container can directly manage an attribute, since it's instantiated by the runtime and there's no way to intercept that.

However, they can cheat by either:

  1. Using a static gateway to the container (example), or
  2. Using a "BuildUp" feature that injects whatever dependencies are found within an already-constructed object. This is called BuildUp in Unity or InjectProperties in Autofac.

Windsor doesn't support #2 (ref1, ref2), so you can either:

  1. Try one of the hacks to make Windsor support #2 (hack1, hack2)
  2. Use a static gateway
  3. Implement your own IValidatorBuilder and make it use Windsor to create validators. I'm sure this is implemented somewhere but I can't find it right now...
Electrocardiogram answered 12/6, 2009 at 16:56 Comment(0)
S
0

Don't know if this helps, but I subclassed ValidationAttribute to expose a Resolve<T>() method like so:

public abstract class IocValidationAttribute : ValidationAttribute
{
    protected T Resolve<T>()
    {
        return IocHelper.Container().Resolve<T>();
    }
}

Then it can be used in any custom ValidatorAttribute that needs to hit a database:

public class UniqueEmailAttribute : IocValidationAttribute
{
    public override bool IsValid(object value)
    {
        ICustomerRepository customerRepository = Resolve<ICustomerRepository>();

        return customerRepository.FindByEmail(value.ToString()) == null;
    }
}

I think it's a variation of the 'Static Gateway' approach mentioned by Mauricio Scheffer. I don't know if this is a good design or not. I'm not a huge fan of it, I'd rather the dependency was injected more 'elegantly', though I can't use constructor injection obviously, I'd like to use Property injection but can't work out a way to hook into the ASP.NET MVC framework code to do this (I've even pored though the MVC2 source code).

Shermanshermie answered 28/2, 2010 at 3:17 Comment(2)
Where do you get IocHelper from?Vinny
@Vinny looks like an evil staticCrepitate
F
0

I was able to wire it up [using Autofac as it happens, but it's just constructor injection via the ASP.NET MVC DependencyResolver] 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));
Flocculent answered 5/2, 2016 at 10:13 Comment(0)
U
-1

Hmm.

Can you test the effect of removing the (string message) ctor, and see if that at least forces Castle to use the ctor with the Repostiory ?

Otherwise we call AddComponent(name, type, type). Other than that it really should work...

Also does this hint at my first idea ? How do I use Windsor to inject dependencies into ActionFilterAttributes

Unassuming answered 12/6, 2009 at 10:37 Comment(2)
Sorry, I can't remove the string CTOR as the attribute looks like this: [ValidateUniqueUrlNode("Please enter a unique Url")] public string UrlNode { get; set; } Should this be different?Chivalrous
Dahhh. Still scratching head... I'll have a searchUnassuming

© 2022 - 2024 — McMap. All rights reserved.