Как получить услугу от ValidationContext с помощью Simple Injector? - PullRequest
2 голосов
/ 24 апреля 2019

В моем проекте 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 возвращает null, поскольку IMyService был зарегистрирован в приложении SimpleInjector.

В моем контроллере я использую такую ​​проверку:

[HttpPost]
public async Task<IActionResult> Edit(MyViewmodel model)
{
    if (ModelState.IsValid)
    {
        //...
    }

    return View(model);
}

Итак, есть ли способ получить IMyService из Asp.Net Core IServiceProvider в ValidationContext?

1 Ответ

5 голосов
/ 25 апреля 2019

Хотя по сути нет ничего плохого в размещении логики проверки внутри самого объекта модели, проблемы начинают появляться, когда эта логика проверки требует работы сервисов.В этом случае вы в конечном итоге примените анти-шаблон 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.

...