C# 如何使用MediatR正确实现结果对象程序流?

C# 如何使用MediatR正确实现结果对象程序流?,c#,asp.net-core-webapi,mediatr,C#,Asp.net Core Webapi,Mediatr,我尝试使用基于MediatR中讨论的思想的自定义结果对象,而不是对程序流使用异常。我这里有一个非常简单的例子 公共类结果 { 公共HttpStatusCode状态码{get;set;} 公共字符串错误{get;set;} 公共结果(HttpStatusCode statusCode) { StatusCode=StatusCode; } 公共结果(HttpStatusCode状态码,字符串错误) { StatusCode=StatusCode; 错误=错误; } } 公开课成绩:成绩 { 公共内

我尝试使用基于MediatR中讨论的思想的自定义结果对象,而不是对程序流使用异常。我这里有一个非常简单的例子

公共类结果
{
公共HttpStatusCode状态码{get;set;}
公共字符串错误{get;set;}
公共结果(HttpStatusCode statusCode)
{
StatusCode=StatusCode;
}
公共结果(HttpStatusCode状态码,字符串错误)
{
StatusCode=StatusCode;
错误=错误;
}
}
公开课成绩:成绩
{
公共内容内容{get;set;}
公共结果(t内容内容):基础(HttpStatusCode.OK)
{
内容=内容;
}
}
其思想是,任何失败都将使用非通用版本,而成功将使用通用版本

我遇到了以下设计问题

  • 如果响应可能是泛型或非泛型的,那么我应该为控制器方法指定什么作为返回类型
  • 例如

    [HttpGet(“{id}”)]
    公共异步任务获取(Guid id)
    {
    返回wait\u mediator.Send(new GetBlobLink.Query(){TestCaseId=id});
    }
    
    如果我只是返回类型为
    Result

  • 如何约束mediatr管道行为以潜在地处理泛型或非泛型结果响应
  • 例如,如果结果是成功的,我只想从
    result
    的通用版本返回
    内容。如果失败,我想返回result对象

    这是我刚开始想到的,但感觉非常“臭”

    公共类RESTErrorBehavior:IPipelineBehavior
    TRequest在哪里:IRequest
    {
    公共IHttpContextAccessor_httpContextAccessor{get;set;}
    公共RESTErrorBehavior(IHttpContextAccessor httpContextAccessor)
    {
    _httpContextAccessor=httpContextAccessor;
    }
    公共异步任务句柄(TRequest请求、CancellationToken CancellationToken、RequestHandlerDelegate next)
    {
    var response=wait next();
    if(response.GetType().GetGenericTypeDefinition()==typeof(Result))
    {
    _httpContextAccessor.HttpContext.Response.StatusCode=200;
    //如何返回响应中的内容值?
    }
    如果(响应为结果)
    {
    _httpContextAccessor.HttpContext.Response.StatusCode=400;
    返回响应;
    } 
    返回响应;
    }
    }
    
  • 序列化可能是一个挑战。如果有一个用例需要为成功的请求返回完整的
    结果
    对象,该怎么办?我不一定总是想显示
    “error”:null
  • 我要下兔子洞吗?有更好的方法吗

    尝试这样做的原因是什么

    • 瘦控制器
    • 对API请求的格式良好的json响应
    • 避免验证的异常控制流(从而避免需要使用.net核心中间件来处理和格式化请求异常)
    非常感谢,

    我要下兔子洞吗?有更好的方法吗

    是的,是的

    这里有几个问题。一些是技术上的困难,另一些是由于采取了错误的方法。在解决这些问题之前,我想单独解决这些问题,因此解决方案(以及背后的推理)更有意义


    1。类型滥用

    _httpContextAccessor.HttpContext.Response.StatusCode = 200;
    
    //How do I return whatever the value of Content on the response is here?
    
    您试图将
    结果
    类型(及其泛型变体)用作数据,这是在滥用它:

    我不清楚为什么要在Mediatr管道行为中设置响应的值。返回的值由控制器操作决定。这已在您的代码中:

    [HttpGet("{id}")]
    public async Task<ActionResult<Result<string>>> Get(Guid id)
    {
        return await _mediator.Send(new GetBlobLink.Query() { TestCaseId = id });
    }
    

    我提出的解决办法
    • 完全删除
      Result
      ,用未初始化的
      T
      将不良结果实例化为
      Result
    • 不要在这里使用HTTP状态代码。结果类应该只包含业务逻辑,而不是表示逻辑
    • 为结果类指定一个布尔属性来表示成功,而不是试图通过基/泛型结果类型来预测它
    • 与您发布的问题无关,但好提示:允许返回多条错误消息。这样做可能与上下文相关
    • 为了确保坏结果不包含值,而好结果必须包含值,请隐藏构造函数并依赖更具描述性的静态方法
    这只是一个基本示例,说明了如何根据返回的对象拥有多个http响应状态。根据控制器的具体操作,相关的返回语句可能会更改

    请注意,此示例将任何失败的结果视为错误请求,而不是内部服务器错误。我通常设置restapi以使用捕获所有未处理的异常,最终返回内部服务器错误(除非某个异常类型有更相关的http状态代码)

    如何(以及如果)区分内部服务器错误和错误请求错误不是您问题的重点。为了有一个具体的例子,我向您展示了一种方法,其他方法仍然存在


    您的担忧

    • 瘦控制器
    瘦控制器很好,但这里有合理的线条。您编写的用于容纳更瘦控制器的代码所带来的问题比使用稍微不那么瘦的控制器所带来的问题还要多

    如果愿意,可以通过将每个控制器操作传递到通用的
    ActionResult HandleResult(Result-Result)
    方法来避免
    If success
    检入每个控制器操作
    [HttpGet("{id}")]
    public async Task<ActionResult<Result<string>>> Get(Guid id)
    {
        return await _mediator.Send(new GetBlobLink.Query() { TestCaseId = id });
    }
    
    public async Task<IActionResult> Get(Guid id)
    {
        var result = await GetResult(id);
    
        if(result.IsSuccess)
            return Ok(result.Value);
        else
            // return a bad result
    }
    
    if(result.IsSuccess)
        return Ok(result.Value);             // type: ActionResult<Foo>
    else
        return BadRequest(result.Errors);    // type: ActionResult<string[]>
    
    public class Result<T>
    {
        public bool IsSuccess { get; private set; }
        public T Value { get; private set; }
        public string[] Errors { get; private set; }
    
        private Result() { }
    
        public static Result<T> Success(T value) => new Result<T>() { IsSuccess = true, Value = value };
        public static Result<T> Failure(params string[] errors) => new Result<T>() { IsSuccess = false, Errors = errors };
    }
    
    // inside your Mediatr request
    
    return Result<BlobLink>.Success(myBlobLink);
    
    return Result<BlobLink>.Failure("This is an error message");
    
    [HttpGet("{id}")]
    public async Task<IActionResult> Get(Guid id)
    {
        Result<BlobLink> result = await _mediator.Send(new GetBlobLink.Query() { TestCaseId = id });
    
        if(result.IsSuccess && result.Value != null)
            return Ok(result.Value);
        else if(result.IsSuccess && result.Value == null)
            return NotFound();
        else
            return BadRequest(result.Errors);
    }