问题
In my Asp.Net MVC Core project I use SimpleInjector as IoC. I use it because of possibility of registering open generics.
In some of my viewmodels I implement IValidatableObject
.
public class MyViewmodel: IValidatableObject
{
public string SomeProperty { get;set; }
//...
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//...
IMyService service = validationContext.GetService(typeof(IMyService)) as IMyService;
}
}
And method GetService
returns null
because IMyService was registered in application by SimpleInjector.
In my controller I use such a validation:
[HttpPost]
public async Task<IActionResult> Edit(MyViewmodel model)
{
if (ModelState.IsValid)
{
//...
}
return View(model);
}
So, is there way to get IMyService from Asp.Net Core IServiceProvider
in ValidationContext?
回答1:
Although there is nothing inherently wrong with placing validation logic inside the model object itself, problems start to appear when that validation logic requires services to work. In that case you'll end up applying the Service Locator anti-pattern (by calling validationContext.GetService
).
Instead, when it comes to more complex validations that require services to run, it's much better to separate data and behavior. This allows you to move the validation logic to a separate class. This class can apply Constructor Injection and, therefore, doesn't have to use any anti-patterns.
To achieve this, start off with your own abstraction that can validate instances. For instance:
public interface IValidator<T>
{
IEnumerable<string> Validate(T instance);
}
On top of this abstraction, you can define as many implementations as you will, for instance one (or more) for validating 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.";
}
}
This is all the application code you need to get things in motion. Of course you should model the IValidator<T>
interface according to your application needs.
Only thing left is ensure MVC uses these validators when validating your view models. This can be done with a custom IModelValidatorProvider
implementation:
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);
}
The only thing left to do is add SimpleInjectorModelValidatorProvider
to the MVC pipeline and make the required registrations:
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);
Et voila! There you have it—a completely loosely coupled validation structure that can be defined according to the needs of your application, while using best practices like Constructor Injection and allows your validation code to be fully tested without having to resort to anti-patterns, and without being tightly coupled with the MVC infrastructure.
来源:https://stackoverflow.com/questions/55835059/how-to-get-service-from-validationcontext-using-simple-injector