ASP.NET Core API 控制器方法中的存储库结果和状态代码

Posted

技术标签:

【中文标题】ASP.NET Core API 控制器方法中的存储库结果和状态代码【英文标题】:Repository results and status codes in ASP.NET Core API Controller methods 【发布时间】:2020-11-18 16:41:54 【问题描述】:

我有一个 ASP.NET Core API 应用程序,我使用 IActionResult 作为返回类型。例如,我将使用来自Microsoft's documentation 的以下 sn-p:

[HttpGet("id")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)

    if (!_repository.TryGetProduct(id, out var product))
    
        return NotFound();
    

    return Ok(product);

我的控制器类似,其中路由使用IActionResult 并使用NotFound()BadRequest()Ok() 等函数,并遵循存储库模式从数据库获取数据。在微软的简单示例中,他们只是评估结果是否为null,如果是,他们将返回NotFound()

在存储库逻辑中,可能会发生任意数量的问题。例如:

数据库查询成功,但未找到产品(404 未找到) 数据库查询失败,连接字符串无效(500 服务器错误) 产品 ID 未通过某些验证测试(400 错误请求)

在上述所有情况下,您也许可以证明从 TryGetProduct() 函数返回 null 是合理的,因为在任何这些情况下它都无法检索产品。但是从存储库函数返回null 作为一般返回结果似乎没有帮助,并且可能意味着发生了任何数量的问题。 我可以从存储库返回数据以允许控制器确定要返回的适当状态代码的最佳方式是什么?最佳做法是什么?

【问题讨论】:

我认为,当在您的数据库中找不到任何结果时,返回 null 是合适的。由于查询字符串错误而导致数据库失败应该引发异常,默认中间件应该捕获并生成适当的 500。产品 ID 的验证错误确实是您应该考虑的唯一用例。您可能会使用数据注释:docs.microsoft.com/en-us/aspnet/core/mvc/models/… 返回 null 是您有史以来能做的最糟糕的事情。您的示例问题至少使一个潜在的问题显而易见(为什么它为空?)。对于您的第一个示例,例如,从 GET 返回带有 Id = -1Product。对于另外两个,您可以创建一个更复杂的对象,该对象能够封装Validation<T> 的概念,该概念可以具有结果值或验证错误列表。在任何情况下,发出连接字符串或其他数据库错误都不是一个好主意。 在没有找到对象时返回 null 就好了。如果有错误,你应该抛出一个异常,或许还有一个不同的 http 代码。另一方面,使用诸如返回“id = -1”的对象之类的技巧绝对不是一个好主意,因为理论上一个对象的 id 可能为 -1。另外我通常做的是我有一个通用的 DataResult 对象,它有一个 Content 属性、一个 Success 属性和一个 Exception 属性。这样,调用者可以很容易地知道是否有问题。 我认为您应该在控制器方法和存储库之间放置一个业务层,并将控制器方法视为与第三方(GUI 或其他)的接口。因此,只需验证为该方法提供的参数。在您的业务层内执行您的逻辑,从您的数据库加载数据等。在您的业务层内,您可以处理有关程序逻辑所需的一切。因此,在某些情况下,NULL 可能是一个有效值,而在其他情况下则不是。从业务层抛出异常并在控制器中根据需要处理它们。 您正试图让TryGetProduct 猜测您的控制器操作想要做什么。首先,TryGetProduct 就像遵循TryXXX convention 的所有方法一样,在失败时不会返回任何内容,因此它的 out 变量设置为默认值,无论是什么,并且 应该'不被使用。不过,一个简单的Try can't 返回多个失败结果。如果你想返回 multiple 结果,你应该使用不同的返回类型,例如一个枚举,一个值元组,例如 (result, message) 带有负载或失败消息,Result<T> 类型带有负载和解释。 【参考方案1】:

我想展示自己的方式。

您可以尝试配置异常处理程序。

创建新类 ExceptionMiddlewareExtension,如:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using System;
using System.Net;

namespace Project.Middleware

public static class ExceptionMiddlewareExtension
  
    public static void ConfigureExceptionHandler(this IApplicationBuilder app)
    
        app.UseExceptionHandler(appError =>
        
            appError.Run(async context =>
            
                context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
                context.Response.ContentType = "application/json";

                var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
                if (contextFeature != null)
                
                    ResponseModel<string> responseModel = new ResponseModel<string>();
                    if(contextFeature.Error is Exception)
                    
                        responseModel.Errors.Add(new ErrorModel()
                        
                            ErrorCode = context.Response.StatusCode.ToString(),
                            Message = contextFeature.Error.Message
                        );
                    
                    responseModel.Result = false;
                    await context.Response.WriteAsync(responseModel.ToString());
                
            );
        );
    
  

创建响应模型:

using System.Collections.Generic;
using Newtonsoft.Json;

namespace Project.Domain

public class ResponseModel<T>
  
    public ResponseModel()
    
        Errors = new List<ErrorModel>();
    

    public ResponseModel(T data)
    
        Data = data;
        Result = true;
    

    public ResponseModel(List<ErrorModel> errors)
    
        Errors = errors;
    

    public bool Result  get; set; 
    public T Data  get; set; 
    public List<ErrorModel> Errors  get; set; 
    

    public override string ToString()
    
        return JsonConvert.SerializeObject(this);
    
  

创建错误模型:

using Newtonsoft.Json;
namespace Projec.Domain

    public class ErrorModel
    
        public string Message  get; set; 

        public string ErrorCode  get; set; 
        
        public string Code  get; set; 
        
        public override string ToString()
        
            return JsonConvert.SerializeObject(this);
        
    

然后在您的 Startup.cs 中添加以下行到 Configure():

app.ConfigureExceptionHAndler();

另一方面,要检查您的请求参数是否有效,您可以尝试 ActionFilterAttribute,例如:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Project.Middleware

    public class ModelStateValidationAttribute : ActionFilterAttribute
    
        public override void OnActionExecuting(ActionExecutingContext context)
        
            if (!context.ModelState.IsValid)
            
                context.Result = new UnprocessableEntityObjectResult(context.ModelState);
            
        
    

简而言之,我使用这种方式并且足够清楚。您的所有请求都将通过过滤器和中间件。只需在您的管理器中使用 Try-Catch(或检查您的对象是否为空),如果出现问题则 throw new Exception(msg)

您还可以编写自定义异常模型并抛出。然后把你的模型加入middleWare查看contextFeature.Error is CustomException

希望对你有所帮助。

【讨论】:

以上是关于ASP.NET Core API 控制器方法中的存储库结果和状态代码的主要内容,如果未能解决你的问题,请参考以下文章

这是从 ASP.NET Core Web API 中的 EF Core 5 获得的啥样的响应 [关闭]

ASP.net Core API 中的路由

ASP.NET Core 中的 dotnet-trace 不显示控制器的任何方法

如何更改 ASP.NET Core API 中的默认控制器和操作?

如何将多个参数传递给 ASP.NET Core 中的 get 方法

Asp.NET Core 2.0 API 控制器中的 User.Identity.Name 为空