Хотя по сути нет ничего плохого в размещении логики проверки внутри самого объекта модели, проблемы начинают появляться, когда эта логика проверки требует работы сервисов.В этом случае вы в конечном итоге примените анти-шаблон Service Locator (вызвав validationContext.GetService
).
Вместо этого, когда дело доходит до более сложных проверок, требующих запуска служб,гораздо лучше разделить данные и поведение .Это позволяет вам переместить логику проверки в отдельный класс.Этот класс может применять 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);
И вуаля!Вот она - полностью слабосвязанная структура проверки, которая может быть определена в соответствии с потребностями вашего приложения, при этом используются лучшие практики, такие как Constructor Injection, и позволяет полностью проверять код проверки без необходимости прибегать к анти-шаблонам и безтесно связаны с инфраструктурой MVC.