提问者:小点点

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;
    }
}

Web API与FluentValidation不存在这样的集成。在这方面已经进行了几次尝试,但都没有解决依赖注入方面,并且只使用静态验证器。

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

我自己一直在尝试添加必要的集成,但是一直在尝试获取依赖范围。相反,我想我应该退后一步,询问是否有其他的解决方案来解决这个问题--如果有人想出了在可以注入依赖项的地方执行模型验证的解决方案。

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


共3个答案

匿名用户

我能够使用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 }
    );
}

验证操作筛选器:

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);
    }
}

模型验证程序:

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;
    }
}

null

[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);
}

null

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>();
}

匿名用户

null

null

最后,要使起作用,您需要确保筛选器以特定的顺序执行。

我已经在github上打包了我的解决方案,在NuGet上有一个版本。

匿名用户

null

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Services.Clear(Type.GetType("System.Web.Http.Validation.IModelValidatorCache, System.Web.Http"));
    }
}