Web API中的依赖项注入validation

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

public class RegisterModelValidator : AbstractValidator { private readonly MyContext _context; public RegisterModelValidator(MyContext context) { _context = context; } public override ValidationResult Validate(ValidationContext 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; } } 

使用FluentValidation的Web API不存在此类集成。 已经有过几次 尝试 ,但都没有处理dependency injection方面,只能使用静态validation器。

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

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

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

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

如果这不能解决您的dependency injection问题,请随时澄清。

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

 public static void Register(HttpConfiguration config) { config.DependencyResolver = new UnityDependencyResolver( new UnityContainer() .RegisterInstance(new MyContext()) .RegisterType() .RegisterType() ); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } 

validation操作filter:

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

模型validation器:

 public class AccountValidator : AbstractValidator { private readonly MyContext _context; public AccountValidator(MyContext context) : base() { _context = context; } public override ValidationResult Validate(ValidationContext 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; } } 

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

型号(示例):

 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 Accounts { get { return _accounts; } } private readonly List _accounts = new List(); } 

我终于有了这个工作,但这有点像一个小屋。 如前所述,ModelValidatorProvider将保留所有Validator的Singleton实例,因此这完全不合适。 相反,我正在使用filter来运行我自己的validation,正如Oppositional所建议的那样。 此filter可以访问IDependencyScope并可以整齐地实例化validation器。

在Filter中,我查看了ActionArguments ,并通过validation传递它们。 validation代码从DefaultBodyModelValidator的Web API运行时源复制DefaultBodyModelValidator ,经过修改以在DependencyScope查找Validator。

最后,要使用ValidationActionFilter进行此操作,您需要确保按特定顺序执行filter。

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

我在网络中使用DI与Fluent Validators一起工作没有任何问题。 我发现validation器被调用很多,而这些重型逻辑validation在模型validation器中没有位置。 在我看来,模型validation器应该是轻量级检查数据的形状。 Email看起来像电子邮件,并且呼叫者是否提供了FirstNameLastName以及MobileHomePhone

像是否可以注册此电子邮件的逻辑validation属于服务层,而不是控制器。 我的实现也不共享隐式数据上下文,因为我认为这是一种反模式。

我认为当前的NuGet包具有MVC3依赖关系,所以我最终只是直接查看源代码并创建自己的NinjectFluentValidatorFactory

App_Start/NinjectWebCommon.cs我们有以下内容。

  ///  /// Set up Fluent Validation for WebApi. ///  private static void FluentValidationSetup(IKernel kernel) { var ninjectValidatorFactory = new NinjectFluentValidatorFactory(kernel); // Configure MVC FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure( provider => provider.ValidatorFactory = ninjectValidatorFactory); // Configure WebApi FluentValidation.WebApi.FluentValidationModelValidatorProvider.Configure( System.Web.Http.GlobalConfiguration.Configuration, provider => provider.ValidatorFactory = ninjectValidatorFactory); DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false; } 

我相信上面唯一需要的其他包装是:

        

我花了很多时间试图找到一个好的方法,即WebApi ModelValidatorProvider将validation器存储为单例。 我不想用validationfilter来标记内容,因此我最终在validation器中注入了IKernel并使用它来获取上下文。

 public class RequestValidator : AbstractValidator{ public readonly IDbContext context; public RequestValidator(IKernel kernel) { this.context = kernel.Get(); RuleFor(r => r.Data).SetValidator(new DataMustHaveValidPartner(kernel)).When(r => r.RequestableKey == "join"); } } 

即使validation器存储为单例,这似乎也可以工作。 如果您还希望能够使用上下文调用它,则可以创建第二个构造函数,该构造IDbContext使用kernel.Get()并使IKernel构造IDbContext使用kernel.Get()传递IDbContext

这当然不建议使用,因为类是内部的,但您可以在WebApi配置中删除IModelValidatorCache服务。

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

FluentValidation已经支持WebApi很长一段时间了(不确定你的问题是否在此之前确定): https ://fluentvalidation.codeplex.com/discussions/533373

引自线程:

 { GlobalConfiguration.Configuration.Services.Add(typeof(System.Web.Http.Validation.ModelValidatorProvider), new WebApiFluentValidationModelValidatorProvider() { AddImplicitRequiredValidator = false //we need this otherwise it invalidates all not passed fields(through json). btw do it if you need }); FluentValidation.ValidatorOptions.ResourceProviderType = typeof(FluentValidationMessages); // if you have any related resource file (resx) FluentValidation.ValidatorOptions.CascadeMode = FluentValidation.CascadeMode.Continue; //if you need! 

我一直在WebApi2项目中使用它没有任何问题。