我正在努力将我的patch端点中的API逻辑转移到Mediatr命令中。当应用我的patch文档时,我通常会像下面一样检查模型状态。通常情况下,我从控制器中做这件事,所以没有问题,但是当把这个移动到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();
}
}
更新部分值以替换命令。
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";
}
}
如果你的命令处理程序依赖于比它在命令中接收到的信息更多的信息来对其进行操作,那么你在命令中没有提供足够的信息;在这种情况下,如果你需要基于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();
}
}