在控制器外部使用 ModelState

Using ModelState Outside of a Controller

我正在努力将 PATCH 端点中的 API 逻辑移动到 Mediatr 命令。在应用我的补丁文档时,我通常会像下面这样检查模型状态。通常,我是从控制器执行此操作的,因此没有问题,但是当将其移至 RequestHandler 时,我无法再访问模型状态 属性,因为我在控制器之外。

您建议如何处理?

这是我想在控制器外部使用的模型状态逻辑:

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;
        }
    }

(损坏)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";
        }
    }

如果您的命令处理程序依赖于比它在命令中收到的信息更多的信息来执行操作,那么您没有在命令中提供足够的信息;在这种情况下,如果您需要应用基于 ModelState 的操作,则需要在命令中包含并传递 ModelState。

无论您能否有效地做到这一点,我都会更深入地质疑是否需要首先在此处使用 MediatR 或某种形式的命令总线;您正在同步执行操作并等待响应,加上处理程序正在尝试执行很多行为(存储库获取、模型验证、存储库保存),因此尽管您减少了控制器中的代码量,但您我真的只是将它转移到一个仍然紧密耦合的新地方,现在只是混淆了控制器的依赖关系。

您打包到 Controller -> Command -> Handler 和返回的行为似乎与通过依赖注入注入您的控制器的某种形式的提供者(或可能是多个提供者)一样好;您可以使用一个接口来保持您的代码灵活和您的依赖关系明显,同时仍然减少控制器代码本身内完成的繁重工作,以通过调用表达意图的(仍然抽象的)描述性方法来帮助保持它的清洁。

更新 1 这不是一个完全概念化的例子,但希望是说明性的。如果你想将命令总线用于横切关注点,仍然有空间可以这样做,但前提是你已经执行了输入验证等。不再需要传递任何控制器状态。

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();
    }
}

对于那些感兴趣的人,这是我最后所做的。还摆脱了那些烦人的魔术弦!

[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();
        }
    }