Web API中的依赖关系注入验证

Ric*_*ard 12 c# dependency-injection fluentvalidation asp.net-web-api

在MVC中,我可以创建一个可以采用依赖关系的模型验证器.我通常使用FluentValidation.例如,这允许我检查帐户注册是否未使用电子邮件地址(注意:这是一个简化的示例!):

public class RegisterModelValidator : AbstractValidator<RegisterModel> {
    private readonly MyContext _context;
    public RegisterModelValidator(MyContext context) {
        _context = context;
    }
    public override ValidationResult Validate(ValidationContext<RegisterModel> context) {
        var result = base.Validate(context);
        if (context.Accounts.Any(acc => acc.Email == context.InstanceToValidate.Email)){
            result.Errors.Add(new ValidationFailure("Email", "Email has been used"));
        }
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用FluentValidation的Web API不存在此类集成.已经有夫妇尝试,在这一点,但也没有解决的依赖注入方面,只有静态验证工作.

这很困难的原因是由于MVC和Web API之间的ModelValidatorProvider和ModelValidator的实现不同.在MVC中,这些是按请求实例化的(因此注入上下文很容易).在Web API中,它们是静态的,ModelValidatorProvider为每种类型维护ModelValidators的缓存,以避免对每个请求进行不必要的反射查找.

我一直试图自己添加必要的集成,但一直试图获得依赖范围.相反,我想我会退后一步,询问是否还有其他问题的解决方案 - 如果有人提出了执行模型验证的解决方案,可以注入依赖关系.

我不想在Controller中执行验证(我使用ValidationActionFilter来保持这一点),这意味着我无法从控制器的构造函数注入中获得任何帮助.

Opp*_*nal 6

我能够使用GetDependencyScope()扩展方法注册然后从请求访问Web API依赖项解析器.这允许在执行验证过滤器时访问模型验证器.

如果这不能解决您的依赖注入问题,请随时澄清.

Web API配置(使用Unity作为IoC容器):

public static void Register(HttpConfiguration config)
{
    config.DependencyResolver   = new UnityDependencyResolver(
        new UnityContainer()
        .RegisterInstance<MyContext>(new MyContext())
        .RegisterType<AccountValidator>()

        .RegisterType<Controllers.AccountsController>()
    );

    config.Routes.MapHttpRoute(
        name:           "DefaultApi",
        routeTemplate:  "api/{controller}/{id}",
        defaults:       new { id = RouteParameter.Optional }
    );
}
Run Code Online (Sandbox Code Playgroud)

验证操作过滤器:

public class ModelValidationFilterAttribute : ActionFilterAttribute
{
    public ModelValidationFilterAttribute() : base()
    {
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var scope   = actionContext.Request.GetDependencyScope();

        if (scope != null)
        {
            var validator   = scope.GetService(typeof(AccountValidator)) as AccountValidator;

            // validate request using validator here...
        }

        base.OnActionExecuting(actionContext);
    }
}
Run Code Online (Sandbox Code Playgroud)

模型验证器:

public class AccountValidator : AbstractValidator<Account>
{
    private readonly MyContext _context;

    public AccountValidator(MyContext context) : base()
    {
        _context = context;
    }

    public override ValidationResult Validate(ValidationContext<Account> context)
    {
        var result      = base.Validate(context);
        var resource    = context.InstanceToValidate;

        if (_context.Accounts.Any(account => String.Equals(account.EmailAddress, resource.EmailAddress)))
        {
            result.Errors.Add(
                new ValidationFailure("EmailAddress", String.Format("An account with an email address of '{0}' already exists.", resource.EmailAddress))
            );
        }

        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

API控制器操作方法:

[HttpPost(), ModelValidationFilter()]
public HttpResponseMessage Post(Account account)
{
    var scope = this.Request.GetDependencyScope();

    if(scope != null)
    {
        var accountContext = scope.GetService(typeof(MyContext)) as MyContext;
        accountContext.Accounts.Add(account);
    }

    return this.Request.CreateResponse(HttpStatusCode.Created);
}
Run Code Online (Sandbox Code Playgroud)

型号(示例):

public class Account
{
    public Account()
    {
    }

    public string FirstName
    {
        get;
        set;
    }

    public string LastName
    {
        get;
        set;
    }

    public string EmailAddress
    {
        get;
        set;
    }
}

public class MyContext
{
    public MyContext()
    {
    }

    public List<Account> Accounts
    {
        get
        {
            return _accounts;
        }
    }
    private readonly List<Account> _accounts = new List<Account>();
}
Run Code Online (Sandbox Code Playgroud)


Ric*_*ard 4

我终于成功了,但这有点困难。如前所述,ModelValidatorProvider 将保留所有验证器的 Singleton 实例,因此这是完全不合适的。相反,我按照反对派的建议使用过滤器来运行我自己的验证。该过滤器可以访问IDependencyScope并可以巧妙地实例化验证器。

在过滤器中,我遍历ActionArguments,并使它们通过验证。验证代码是从 的 Web API 运行时源中复制出来的DefaultBodyModelValidator,并进行修改以在DependencyScope.

最后,要使其与 一起工作ValidationActionFilter,您需要确保过滤器按特定顺序执行。

我已经在github上打包了我的解决方案,并在 nuget 上提供了一个版本。