如何使用 Simple Injector 从 ValidationContext 获取服务?
How to get service from ValidationContext using Simple Injector?
在我的 Asp.Net MVC Core 项目中,我使用 SimpleInjector 作为 IoC。我使用它是因为可以注册开放泛型。
在我的一些视图模型中,我实现了 IValidatableObject
。
public class MyViewmodel: IValidatableObject
{
public string SomeProperty { get;set; }
//...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//...
IMyService service = validationContext.GetService(typeof(IMyService)) as IMyService;
}
}
和方法GetService
returnsnull
因为IMyService是通过SimpleInjector在应用程序中注册的。
在我的控制器中我使用了这样的验证:
[HttpPost]
public async Task<IActionResult> Edit(MyViewmodel model)
{
if (ModelState.IsValid)
{
//...
}
return View(model);
}
那么,有没有办法在 ValidationContext 中从 Asp.Net Core IServiceProvider
获取 IMyService?
虽然将验证逻辑放在模型对象本身内部并没有什么内在的错误,但是当验证逻辑需要服务才能工作时,问题就开始出现了。在这种情况下,您最终将应用 Service Locator anti-pattern(通过调用 validationContext.GetService
)。
相反,当涉及到需要 运行 服务的更复杂的验证时, 将数据和行为分开 会好得多。这允许您将验证逻辑移动到单独的 class。 class 可以应用 Constructor Injection,因此不必使用任何反模式。
要实现这一点,请从您自己的可以验证实例的抽象开始。例如:
public interface IValidator<T>
{
IEnumerable<string> Validate(T instance);
}
在此抽象之上,您可以定义任意多的实现,例如一个(或多个)用于验证 MyViewmodel
:
public class MyViewmodelValidator : IValidator<MyViewmodel>
{
private readonly IMyService service;
public MyViewmodelValidator(IMyService service) => this.service = service;
public IEnumerable<string> Validate(MyViewmodel instance)
{
yield return "I'm not valid.";
}
}
这就是启动程序所需的所有应用程序代码。当然你应该根据你的应用需要来建模IValidator<T>
接口。
唯一剩下的就是确保 MVC 在验证您的视图模型时使用这些验证器。这可以通过自定义 IModelValidatorProvider
实现来完成:
class SimpleInjectorModelValidatorProvider : IModelValidatorProvider
{
private readonly Container container;
public SimpleInjectorModelValidatorProvider(Container container) =>
this.container = container;
public void CreateValidators(ModelValidatorProviderContext ctx)
{
var validatorType = typeof(ModelValidator<>)
.MakeGenericType(ctx.ModelMetadata.ModelType);
var validator =
(IModelValidator)this.container.GetInstance(validatorType);
ctx.Results.Add(new ValidatorItem { Validator = validator });
}
}
// Adapter that translates calls from IModelValidator into the IValidator<T>
// application abstraction.
class ModelValidator<TModel> : IModelValidator
{
private readonly IEnumerable<IValidator<TModel>> validators;
public ModelValidator(IEnumerable<IValidator<TModel>> validators) =>
this.validators = validators;
public IEnumerable<ModelValidationResult> Validate(
ModelValidationContext ctx) =>
this.Validate((TModel)ctx.Model);
private IEnumerable<ModelValidationResult> Validate(TModel model) =>
from validator in this.validators
from errorMessage in validator.Validate(model)
select new ModelValidationResult(string.Empty, errorMessage);
}
唯一剩下要做的就是将 SimpleInjectorModelValidatorProvider
添加到 MVC 管道并进行所需的注册:
services.AddMvc(options =>
{
options.ModelValidatorProviders.Add(
new SimpleInjectorModelValidatorProvider(container));
});
// Register ModelValidator<TModel> adapter class
container.Register(typeof(ModelValidator<>), typeof(ModelValidator<>),
Lifestyle.Singleton);
// Auto-register all validator implementations
container.Collection.Register(
typeof(IValidator<>), typeof(MyViewmodelValidator).Assembly);
瞧瞧!你有它了——一个完全松散耦合的验证结构,可以根据你的应用程序的需要定义,同时使用构造函数注入等最佳实践,并允许你的验证代码得到全面测试,而不必求助于反模式,并且没有与 MVC 基础设施紧密结合。
来自@Steven 的惊人回答,但对于那些想知道如何使用 IServiceProvider
而不是来自其他库的 Container
来使其适应内置依赖注入机制的人来说,以及卡在
services.AddMvc(options =>
{
options.ModelValidatorProviders.Add(
new SimpleInjectorModelValidatorProvider(/* TODO how do I get the IServiceProvider */));
});
秘诀是创建另一个 class 配置 MvcOptions
并将 IServiceProvider
注入其中:
public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>
{
private readonly IServiceProvider provider;
public ConfigureMvcOptions(IServiceProvider provider)
{
this.provider = provider;
}
public void Configure(MvcOptions options)
{
options.ModelValidatorProviders.Add(new SimpleInjectorModelValidatorProvider(this.provider));
}
}
然后您可以按照通常的方式在 Startup.cs
中注册:
services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();
在我的 Asp.Net MVC Core 项目中,我使用 SimpleInjector 作为 IoC。我使用它是因为可以注册开放泛型。
在我的一些视图模型中,我实现了 IValidatableObject
。
public class MyViewmodel: IValidatableObject
{
public string SomeProperty { get;set; }
//...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//...
IMyService service = validationContext.GetService(typeof(IMyService)) as IMyService;
}
}
和方法GetService
returnsnull
因为IMyService是通过SimpleInjector在应用程序中注册的。
在我的控制器中我使用了这样的验证:
[HttpPost]
public async Task<IActionResult> Edit(MyViewmodel model)
{
if (ModelState.IsValid)
{
//...
}
return View(model);
}
那么,有没有办法在 ValidationContext 中从 Asp.Net Core IServiceProvider
获取 IMyService?
虽然将验证逻辑放在模型对象本身内部并没有什么内在的错误,但是当验证逻辑需要服务才能工作时,问题就开始出现了。在这种情况下,您最终将应用 Service Locator anti-pattern(通过调用 validationContext.GetService
)。
相反,当涉及到需要 运行 服务的更复杂的验证时, 将数据和行为分开 会好得多。这允许您将验证逻辑移动到单独的 class。 class 可以应用 Constructor Injection,因此不必使用任何反模式。
要实现这一点,请从您自己的可以验证实例的抽象开始。例如:
public interface IValidator<T>
{
IEnumerable<string> Validate(T instance);
}
在此抽象之上,您可以定义任意多的实现,例如一个(或多个)用于验证 MyViewmodel
:
public class MyViewmodelValidator : IValidator<MyViewmodel>
{
private readonly IMyService service;
public MyViewmodelValidator(IMyService service) => this.service = service;
public IEnumerable<string> Validate(MyViewmodel instance)
{
yield return "I'm not valid.";
}
}
这就是启动程序所需的所有应用程序代码。当然你应该根据你的应用需要来建模IValidator<T>
接口。
唯一剩下的就是确保 MVC 在验证您的视图模型时使用这些验证器。这可以通过自定义 IModelValidatorProvider
实现来完成:
class SimpleInjectorModelValidatorProvider : IModelValidatorProvider
{
private readonly Container container;
public SimpleInjectorModelValidatorProvider(Container container) =>
this.container = container;
public void CreateValidators(ModelValidatorProviderContext ctx)
{
var validatorType = typeof(ModelValidator<>)
.MakeGenericType(ctx.ModelMetadata.ModelType);
var validator =
(IModelValidator)this.container.GetInstance(validatorType);
ctx.Results.Add(new ValidatorItem { Validator = validator });
}
}
// Adapter that translates calls from IModelValidator into the IValidator<T>
// application abstraction.
class ModelValidator<TModel> : IModelValidator
{
private readonly IEnumerable<IValidator<TModel>> validators;
public ModelValidator(IEnumerable<IValidator<TModel>> validators) =>
this.validators = validators;
public IEnumerable<ModelValidationResult> Validate(
ModelValidationContext ctx) =>
this.Validate((TModel)ctx.Model);
private IEnumerable<ModelValidationResult> Validate(TModel model) =>
from validator in this.validators
from errorMessage in validator.Validate(model)
select new ModelValidationResult(string.Empty, errorMessage);
}
唯一剩下要做的就是将 SimpleInjectorModelValidatorProvider
添加到 MVC 管道并进行所需的注册:
services.AddMvc(options =>
{
options.ModelValidatorProviders.Add(
new SimpleInjectorModelValidatorProvider(container));
});
// Register ModelValidator<TModel> adapter class
container.Register(typeof(ModelValidator<>), typeof(ModelValidator<>),
Lifestyle.Singleton);
// Auto-register all validator implementations
container.Collection.Register(
typeof(IValidator<>), typeof(MyViewmodelValidator).Assembly);
瞧瞧!你有它了——一个完全松散耦合的验证结构,可以根据你的应用程序的需要定义,同时使用构造函数注入等最佳实践,并允许你的验证代码得到全面测试,而不必求助于反模式,并且没有与 MVC 基础设施紧密结合。
来自@Steven 的惊人回答,但对于那些想知道如何使用 IServiceProvider
而不是来自其他库的 Container
来使其适应内置依赖注入机制的人来说,以及卡在
services.AddMvc(options =>
{
options.ModelValidatorProviders.Add(
new SimpleInjectorModelValidatorProvider(/* TODO how do I get the IServiceProvider */));
});
秘诀是创建另一个 class 配置 MvcOptions
并将 IServiceProvider
注入其中:
public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>
{
private readonly IServiceProvider provider;
public ConfigureMvcOptions(IServiceProvider provider)
{
this.provider = provider;
}
public void Configure(MvcOptions options)
{
options.ModelValidatorProviders.Add(new SimpleInjectorModelValidatorProvider(this.provider));
}
}
然后您可以按照通常的方式在 Startup.cs
中注册:
services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();