【问题标题】:Using ModelState Outside of a Controller在控制器外部使用 ModelState
【发布时间】:2020-05-03 03:33:59
【问题描述】:

我正在将我的 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";
        }
    }

【问题讨论】:

    标签: c# api controller cqrs mediatr


    【解决方案1】:

    如果您的命令处理程序依赖的信息比命令中接收到的信息要多,那么您没有在命令中提供足够的信息;在这种情况下,如果您需要应用基于 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();
        }
    }
    

    【讨论】:

    • 是的,我已经考虑过通过一些方法来提供模型状态的方法,但没有成功。就需求本身而言,我试图将我的关注点分开,因此执行更新的实际行为存在于它需要的地方(尽可能简单)。不确定这个提供者抽象是什么样子的,但它仍然会面临同样的问题,即无法访问控制器之外的模型状态。
    • 我添加了一些代码来帮助说明这个想法。在试图分离您的关注点时,您现有的命令和处理程序仍然不是单一的责任;处理程序行为与控制器紧密耦合,它只是移动到其他地方,并且它仍然负责许多行为,例如输入模型验证和返回合理可能是控制器职责的 HTTP 响应。如果您可以将每个职责分解,则分离变得容易得多。
    【解决方案2】:

    对于那些感兴趣的人,这就是我最终所做的。也摆脱了那些烦人的魔线!

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多