Использование ModelState вне контроллера - PullRequest
0 голосов
/ 03 мая 2020

Я работаю над тем, чтобы переместить мои логики API c в моей конечной точке PATCH в команду Mediatr. При применении документа патча я обычно проверяю состояние модели, как показано ниже. Обычно я делаю это из контроллера, поэтому проблем не возникает, но при перемещении в RequestHandler у меня больше нет доступа к свойству состояния модели, поскольку я нахожусь вне контроллера.

Как бы вы порекомендовали это сделать?

Вот лог модели состояния c, который я хотел бы использовать вне контроллера:

updatePartialValueToReplaceCommand.PatchDoc.ApplyTo(valueToReplaceToPatch, ModelState); // apply patchdoc updates to the updatable valueToReplace

if (!TryValidateModel(valueToReplaceToPatch))
{
    return ValidationProblem(ModelState);
}

остаток кода для контекста:

Конечная точка исправления


        [HttpPatch("{valueToReplaceId}")]
        public IActionResult PartiallyUpdateValueToReplace(int valueToReplaceId, JsonPatchDocument<ValueToReplaceForUpdateDto> patchDoc)
        {
            var query = new UpdatePartialValueToReplaceCommand(valueToReplaceId, patchDoc);
            var result = _mediator.Send(query);

            switch (result.Result.ToUpper())
            {
                case "NOTFOUND":
                    return NotFound();
                case "NOCONTENT":
                    return NoContent();
                default:
                    return BadRequest();
            }
        }

UpdatePartialValueToReplaceCommand

public class UpdatePartialValueToReplaceCommand : IRequest<string>
    {
        public int ValueToReplaceId { get; set; }
        public JsonPatchDocument<ValueToReplaceForUpdateDto> PatchDoc { get; set; }

        public UpdatePartialValueToReplaceCommand(int valueToReplaceId, JsonPatchDocument<ValueToReplaceForUpdateDto> patchDoc)
        {
            ValueToReplaceId = valueToReplaceId;
            PatchDoc = patchDoc;
        }
    }

( BROKEN) UpdatePartialValueToReplaceHandler

    public class UpdatePartialValueToReplaceHandler : IRequestHandler<UpdatePartialValueToReplaceCommand, string>
    {
        private readonly IValueToReplaceRepository _valueToReplaceRepository;
        private readonly IMapper _mapper;

        public UpdatePartialValueToReplaceHandler(IValueToReplaceRepository valueToReplaceRepository
            , IMapper mapper)
        {
            _valueToReplaceRepository = valueToReplaceRepository ??
                throw new ArgumentNullException(nameof(valueToReplaceRepository));
            _mapper = mapper ??
                throw new ArgumentNullException(nameof(mapper));
        }

        public async Task<string> Handle(UpdatePartialValueToReplaceCommand updatePartialValueToReplaceCommand, CancellationToken cancellationToken)
        {
            if (updatePartialValueToReplaceCommand.PatchDoc == null)
            {
                return "BadRequest";
            }

            var existingValueToReplace = _valueToReplaceRepository.GetValueToReplace(updatePartialValueToReplaceCommand.ValueToReplaceId);

            if (existingValueToReplace == null)
            {
                return "NotFound";
            }

            var valueToReplaceToPatch = _mapper.Map<ValueToReplaceForUpdateDto>(existingValueToReplace); // map the valueToReplace we got from the database to an updatable valueToReplace model
            updatePartialValueToReplaceCommand.PatchDoc.ApplyTo(valueToReplaceToPatch, ModelState); // apply patchdoc updates to the updatable valueToReplace -- THIS DOESN'T WORK IN A MEDIATR COMMAND BECAUSE I DON'T HAVE CONTROLLERBASE CONTEXT

            if (!TryValidateModel(valueToReplaceToPatch))
            {
                return ValidationProblem(ModelState);
            }

            _mapper.Map(valueToReplaceToPatch, existingValueToReplace); // apply updates from the updatable valueToReplace to the db entity so we can apply the updates to the database
            _valueToReplaceRepository.UpdateValueToReplace(existingValueToReplace); // apply business updates to data if needed

            _valueToReplaceRepository.Save(); // save changes in the database

            return "NoContent";
        }
    }

Ответы [ 2 ]

0 голосов
/ 08 мая 2020

Для тех, кто заинтересован, вот что я в итоге сделал. Также избавился от этих раздражающих волшебные c струн!

[HttpPatch("{valueToReplaceId}")]
        public IActionResult PartiallyUpdateValueToReplace(int valueToReplaceId, JsonPatchDocument<ValueToReplaceForUpdateDto> patchDoc)
        {
            var query = new UpdatePartialValueToReplaceCommand(valueToReplaceId, patchDoc, this);
            var result = _mediator.Send(query);

            return result.Result;
        }
    public class UpdatePartialValueToReplaceCommand : IRequest<IActionResult>
    {
        public int ValueToReplaceId { get; set; }
        public JsonPatchDocument<ValueToReplaceForUpdateDto> PatchDoc { get; set; }
        public Controller Controller { get; set; }

        public UpdatePartialValueToReplaceCommand(int valueToReplaceId, JsonPatchDocument<ValueToReplaceForUpdateDto> patchDoc,
            Controller controller)
        {
            ValueToReplaceId = valueToReplaceId;
            PatchDoc = patchDoc;
            Controller = controller;
        }
    }
public class UpdatePartialValueToReplaceHandler : IRequestHandler<UpdatePartialValueToReplaceCommand, IActionResult>
    {
        private readonly IValueToReplaceRepository _valueToReplaceRepository;
        private readonly IMapper _mapper;

        public UpdatePartialValueToReplaceHandler(IValueToReplaceRepository valueToReplaceRepository
            , IMapper mapper)
        {
            _valueToReplaceRepository = valueToReplaceRepository ??
                throw new ArgumentNullException(nameof(valueToReplaceRepository));
            _mapper = mapper ??
                throw new ArgumentNullException(nameof(mapper));
        }

        public async Task<IActionResult> Handle(UpdatePartialValueToReplaceCommand updatePartialValueToReplaceCommand, CancellationToken cancellationToken)
        {
            if (updatePartialValueToReplaceCommand.PatchDoc == null)
            {
                return updatePartialValueToReplaceCommand.Controller.BadRequest();
            }

            var existingValueToReplace = _valueToReplaceRepository.GetValueToReplace(updatePartialValueToReplaceCommand.ValueToReplaceId);

            if (existingValueToReplace == null)
            {
                return updatePartialValueToReplaceCommand.Controller.NotFound();
            }

            var valueToReplaceToPatch = _mapper.Map<ValueToReplaceForUpdateDto>(existingValueToReplace); // map the valueToReplace we got from the database to an updatable valueToReplace model
            updatePartialValueToReplaceCommand.PatchDoc.ApplyTo(valueToReplaceToPatch, updatePartialValueToReplaceCommand.Controller.ModelState); // apply patchdoc updates to the updatable valueToReplace

            if (!updatePartialValueToReplaceCommand.Controller.TryValidateModel(valueToReplaceToPatch))
            {
                return updatePartialValueToReplaceCommand.Controller.ValidationProblem(updatePartialValueToReplaceCommand.Controller.ModelState);
            }

            _mapper.Map(valueToReplaceToPatch, existingValueToReplace); // apply updates from the updatable valueToReplace to the db entity so we can apply the updates to the database
            _valueToReplaceRepository.UpdateValueToReplace(existingValueToReplace); // apply business updates to data if needed

            _valueToReplaceRepository.Save(); // save changes in the database

            return updatePartialValueToReplaceCommand.Controller.NoContent();
        }
    }
0 голосов
/ 03 мая 2020

Если ваш обработчик команд зависит от большего количества информации, чем он получил в команде, чтобы воздействовать на него, то вы не предоставляете достаточно информации в команде; в этом случае, если вам нужно применить операцию, основанную на ModelState, вам нужно будет включить и передать ModelState в команду.

Независимо от того, сможете ли вы сделать это эффективно или нет, я бы более глубоко позвонил под вопросом необходимость использования MediatR или какой-либо другой формы командной шины здесь, в первую очередь; вы синхронно выполняете операцию и ожидаете ответа, плюс обработчик пытается выполнить много действий (получение хранилища, проверка модели, сохранение хранилища), поэтому, хотя вы сократили объем кода в контроллере, вы на самом деле я только переместил его в новое место, которое все еще тесно связано, и теперь просто запутывает зависимости контроллера.

Поведение, которое вы упаковали в Controller -> Command -> Handler и обратно, похоже на он также будет обслуживаться какой-либо формой провайдера (или, вероятно, несколькими провайдерами), введенными в ваш контроллер посредством внедрения зависимости; вы можете использовать интерфейс для сохранения гибкости своего кода и очевидных зависимостей, при этом уменьшая грубую работу, выполняемую внутри самого кода контроллера, чтобы помочь поддерживать его в чистоте, вместо этого вызывая (все еще абстрагированные) описательные методы, которые express намерение.

Обновление 1 Это не полностью концептуализированный пример, но, надеюсь, иллюстративный. Если вы хотите использовать командную шину для сквозных задач, для этого еще есть место, но только после того, как вы выполнили проверку ввода, et c. Больше не нужно передавать состояние контроллера.

public class YourController : Controller
{
    private readonly ILogger<YourController> _logger;
    private readonly IModelPatcher<SomeInput, SomeOutput> _modelPatcher;
    private readonly IWriteRepository<SomeOutput> _writeRepository;

    public YourController(ILogger<YourController> logger, IModelPatcher<SomeInput, SomeOutput> modelPatcher, IWriteRepository<SomeOutput> writeRepository)
    {
        _logger = logger;
        _modelPatcher = modelPatcher;
        _writeRepository = writeRepository;
    }

    [HttpPatch("{valueToReplaceId}")]
    public IActionResult PartiallyUpdateValueToReplace(int valueToReplaceId, JsonPatchDocument<SomeInput> patchDoc)
    {
        if (patchDoc == null) return BadRequest();
        var result = _modelPatcher.ApplyPatch(patchDoc, valueToReplaceId);
        if (result == null) return NotFound();
        if (!TryValidateModel(result)) return ValidationProblem(ModelState);
        // var mapToDto = _mapper.Map(result); // maybe even here, before the repo...
        _writeRepository.Update(result); // <-- This could be a command! Model is ready, validation is done.
        return NoContent();
    }

}

public class SomeInput { }
public class SomeOutput { }

public interface IModelPatcher<in TInput, out TResult>
{
    TResult ApplyPatch(JsonPatchDocument<TInput> inputModel, int value);
}

public class SomeInputModelPatcher : IModelPatcher<SomeInput, SomeOutput>
{
    private readonly IReadRepository<Something> _repository;

    public SomeInputModelPatcher(IReadRepository<Something> repository)
    {
        _repository = repository;
    }

    public SomeOutput ApplyPatch(JsonPatchDocument<SomeInput> inputModel, int value)
    {
        // Do the patch related work
        return new SomeOutput();
    }
}
...