ASP.NET Core 和 Web API:用于管理异常和一致响应的自定义包装器





5.00/5 (16投票s)
本文将介绍如何为 ASP.NET Core 和 Web API 应用程序实现自定义包装器,以管理异常,并为消费者提供有意义且一致的响应。
引言
构建 RESTful API 在当今非常流行,我们今天构建的大多数项目都严重依赖 API/服务来通信数据。如你所知,创建 Web API 很容易,但设计一个好的 API 并不像你想象的那么容易,特别是当你处理大量暴露了某些 public
API 端点的项目或微服务时。
本文将介绍如何为 ASP.NET Core 和 Web API 应用程序实现自定义包装器,以管理异常,并为消费者提供有意义且一致的响应。
为什么?
在深入探讨之前,我们先来谈谈“为什么”。为什么我们需要实现自定义包装器,为什么它是一个好主意?
ASP.NET Core 和标准的 ASP.NET Web API 允许我们轻松创建 API;但是,它们默认不为成功请求和错误提供一致的响应。为了更清楚地说明,如果你正在采取 RESTful 方法来构建 API,那么你将使用 HTTP
动词,如 GET
、POST
、PUT
和 DELETE
。这些操作中的每一个都可能根据你的方法/操作的设计方式返回不同的类型。你的 POST
、PUT
和 DELETE
端点可能会返回数据,也可能不返回数据。你的 GET
端点可能返回一个 string
、一个 List<T>
、一个 IEnumerable
、一个自定义 class
或一个 object
。另一方面,如果你的 API 抛出错误,它将返回一个 object
,或者更糟的是一个 HTML
string
,说明错误的原因。所有这些响应之间的差异使得 API 难以使用,因为消费者需要知道在每种情况下返回的数据的类型和结构。客户端代码和服务代码都变得难以管理。
在为“实际”应用程序项目构建 API 时,许多开发人员并不关心消费者。他们最关心的是能够将数据返回给消费者,仅此而已。API 不仅仅是通过 HTTP
传递 JSON
,还包括如何向使用你的 API 的开发人员呈现有意义的响应。
请始终记住...
引用“好的 API 设计是使用它的开发人员的用户体验。”
作为重视消费者的开发者,我们希望为他们提供有意义且一致的 API 响应。这就是为什么在本帖中,我们将实现一个可跨应用程序重用的自定义包装器,它具有以下功能:
- 处理意外错误
- 处理
ModelState
验证错误 - 可配置的自定义 API 异常
- 结果和错误的统一响应对象
- 详细的结果响应
- 详细的错误响应
- 可配置的 HTTP 状态码
- 支持 Swagger
如何做到?
在处理 ASP.NET Core 或标准 Web API 时,处理异常并为 API 处理的所有请求返回一致的响应非常重要,无论成功还是失败。这使得 API 更容易使用,而无需在客户端编写复杂的代码。通过为 ASP.NET Core 和 Web API 响应使用自定义包装器,可以确保所有响应都具有一致的结构,并且所有异常都会被处理。
我们将研究如何实现一个处理上述所有功能的自定义包装器,用于 ASP.NET Core 和标准 Web API。
VMD.RESTApiResponseWrapper Nuget 包
如果你想跳过实际的代码实现,有两个独立的 Nuget 包可以直接集成到你的项目中
VMD.RESTApiResponseWrapper.Core
(适用于 ASP.NET Core 应用)VMD.RESTApiResponseWrapper.Net
(适用于标准 Web API 应用)
这些库中的每一个都是单独创建的。VMD.RESTApiResponseWrapper.Core
是使用 ASP.NET Core 2.0 和 Visual Studio 2017 构建的。它使用 中间件
来实现包装器和管理异常。而 VMD.RESTApiResponseWrapper.Net
是使用完整 .NET Framework v4.6 和 Visual Studio 2015 构建的。它使用 DelegatingHandler
来实现包装器,并使用 ExceptionFilterAttribute
来处理异常。
安装和使用
首先,你需要安装 Newtonsoft.json
包,然后再安装 VMD.RESTApiResponseWrapper
包。
ASP.NET Core 集成
对于 ASP.NET Core 应用,你可以通过 NPM 或使用以下命令安装包
PM> Install-Package VMD.RESTApiResponseWrapper.Core -Version 1.0.3
安装完成后,你可以按照以下步骤开始将包装器集成到你的 ASP.NET Core 项目中
- 在 Startup.cs 中声明以下命名空间
using VMD.RESTApiResponseWrapper.Core.Extensions;
- 在 Startup.cs 的
Configure()
方法中注册以下中间件app.UseAPIResponseWrapperMiddleware();
注意:请确保在 MVC 中间件“之前”注册它。
- 完成。
以下是一篇演示如何将此库集成到 ASP.NET Core REST API 项目中的文章: ASP.NET Core 2.1:将 VMD.RESTApiResponseWrapper.Core 集成到你的 REST API 应用程序
ASP.NET Web API 集成
对于标准的 ASP.NET Web API 应用程序,你可以执行以下操作
PM> Install-Package VMD.RESTApiResponseWrapper.Net -Version 1.0.3
安装完成后,你可以按照以下步骤开始将包装器集成到你的 ASP.NET Web API 项目中
- 在 WebApiConfig.cs 中声明以下命名空间
using VMD.RESTApiResponseWrapper.Net; using VMD.RESTApiResponseWrapper.Net.Filters;
- 在 WebApiConfig.cs 中注册以下内容
config.Filters.Add(new ApiExceptionFilter()); config.MessageHandlers.Add(new WrappingHandler());
- 完成。
引用注意:截至本文撰写时,两个包的最新版本均为 v1.0.3
示例响应输出
以下是响应输出的示例
带有数据的成功响应格式
{
"Version": "1.0.0.0",
"StatusCode": 200,
"Message": "Request successful.",
"Result": [
"value1",
"value2"
]
}
不带数据的成功响应格式
{
"Version": "1.0.0.0",
"StatusCode": 201,
"Message": "Student with ID 6 has been created."
}
验证错误的响应格式
{
"Version": "1.0.0.0",
"StatusCode": 400,
"Message": "Request responded with exceptions.",
"ResponseException": {
"IsError": true,
"ExceptionMessage": "Validation Field Error.",
"Details": null,
"ReferenceErrorCode": null,
"ReferenceDocumentLink": null,
"ValidationErrors": [
{
"Field": "LastName",
"Message": "'Last Name' should not be empty."
},
{
"Field": "FirstName",
"Message": "'First Name' should not be empty."
}
]
}
}
错误的响应格式
{
"Version": "1.0.0.0",
"StatusCode": 404,
"Message": "Unable to process the request.",
"ResponseException": {
"IsError": true,
"ExceptionMessage": "The specified URI does not exist. Please verify and try again.",
"Details": null,
"ReferenceErrorCode": null,
"ReferenceDocumentLink": null,
"ValidationErrors": null
}
}
定义自定义异常
这个库不仅仅是中间件或包装器,它还提供了一个方法,你可以用它来定义自己的异常。例如,如果你想抛出自己的异常消息,你可以简单地这样做
throw new ApiException("Your Message",401, ModelStateExtension.AllErrors(ModelState));
ApiException
具有以下可以设置的参数
ApiException(string message,
int statusCode = 500,
IEnumerable<ValidationError> errors = null,
string errorCode = "",
string refLink = "")
定义你自己的响应对象
除了抛出自定义异常外,你还可以通过在 API 控制器中使用 ApiResponse
对象来返回你自己定义的自定义 JSON
响应。例如
return new APIResponse(201,"Created");
APIResponse
具有以下可以设置的参数
APIResponse(int statusCode,
string message = "",
object result = null,
ApiError apiError = null,
string apiVersion = "1.0.0.0")
包源代码
这些包装器的代码是开源的,可在 github 上找到
- https://github.com/proudmonkey/RESTApiResponseWrapper.Core
- https://github.com/proudmonkey/RESTApiResponseWrapper.Net
欢迎查看。
实现
让我们看看如何为 ASP.NET Core 和标准 Web API 实现自定义包装器。让我们从两个项目都使用的通用类开始。
包装器类
ASP.NET Core 和标准 Web API 项目都使用了以下类
ValidationError
ApiError
ApiException
ApiResponse
ResponseMessageEnum
上面的每个类都将用于实现自定义包装器,以管理异常和响应一致性。请记住,本文中演示的代码只是库的基本基础。你可以自由地修改和添加自己的属性,甚至根据你的业务需求自定义实现。
以下是每个类的实际代码
ValidationError 类
public class ValidationError
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string Field { get; }
public string Message { get; }
public ValidationError(string field, string message)
{
Field = field != string.Empty ? field : null;
Message = message;
}
}
ValidationError
类包含一些属性,用于存储字段及其相应的消息。请注意 Field
属性上的 [JsonProperty(NullValueHandling=NullValueHandling.Ignore)]
属性。这是为了确保在值为 null
的情况下,字段不会被序列化。
ApiError 类
public class ApiError
{
public bool IsError { get; set; }
public string ExceptionMessage { get; set; }
public string Details { get; set; }
public string ReferenceErrorCode { get; set; }
public string ReferenceDocumentLink { get; set; }
public IEnumerable<ValidationError> ValidationErrors { get; set; }
public ApiError(string message)
{
this.ExceptionMessage = message;
this.IsError = true;
}
public ApiError(ModelStateDictionary modelState)
{
this.IsError = true;
if (modelState != null && modelState.Any(m => m.Value.Errors.Count > 0))
{
this.ExceptionMessage = "Please correct the specified validation errors and try again.";
this.ValidationErrors = modelState.Keys
.SelectMany(key => modelState[key].Errors.Select
(x => new ValidationError(key, x.ErrorMessage)))
.ToList();
}
}
}
ApiError
类是一种自定义序列化类型,用于通过 JSON
向消费者返回错误信息。此类包含几个重要属性,可为消费者提供有意义的信息,如 ExceptionMessage
、Details
、ReferenceErrorCode
、ReferenceDocumentLink
和 ValidationErrors
。此类还具有一个重载的构造函数,用于传入 ModelStateDictionary
以向消费者返回验证错误列表。
ApiException 类
public class ApiException : System.Exception
{
public int StatusCode { get; set; }
public IEnumerable<ValidationError> Errors { get; set; }
public string ReferenceErrorCode { get; set; }
public string ReferenceDocumentLink { get; set; }
public ApiException(string message,
int statusCode = 500,
IEnumerable<ValidationError> errors = null,
string errorCode = "",
string refLink = "") :
base(message)
{
this.StatusCode = statusCode;
this.Errors = errors;
this.ReferenceErrorCode = errorCode;
this.ReferenceDocumentLink = refLink;
}
public ApiException(System.Exception ex, int statusCode = 500) : base(ex.Message)
{
StatusCode = statusCode;
}
}
ApiException
类是一种自定义异常,用于抛出显式和应用程序生成的错误。这些通常用于验证错误或可能具有已知负面响应的常见操作,例如登录失败。目标是返回一个定义良好且可安全供消费者使用的错误消息。
ApiResponse 类
[DataContract]
public class APIResponse
{
[DataMember]
public string Version { get; set; }
[DataMember]
public int StatusCode { get; set; }
[DataMember]
public string Message { get; set; }
[DataMember(EmitDefaultValue = false)]
public ApiError ResponseException { get; set; }
[DataMember(EmitDefaultValue = false)]
public object Result { get; set; }
public APIResponse(int statusCode, string message = "", object result = null,
ApiError apiError = null, string apiVersion = "1.0.0.0")
{
this.StatusCode = statusCode;
this.Message = message;
this.Result = result;
this.ResponseException = apiError;
this.Version = apiVersion;
}
}
APIResponse
类是一种自定义包装器响应对象,用于为所有 API 响应提供一致的数据结构。它包含一些基本属性,如 Version
、StatusCode
、Message
、ResponseException
和 Result
。我们使用 DataContract
属性来定义要返回的属性,例如,如果 ResponseException
和 Result
属性的值为 null
,则不会返回它们。
ResponseMessage Enum
public enum ResponseMessageEnum
{
[Description("Request successful.")]
Success,
[Description("Request responded with exceptions.")]
Exception,
[Description("Request denied.")]
UnAuthorized,
[Description("Request responded with validation error(s).")]
ValidationError,
[Description("Unable to process the request.")]
Failure
}
ResponseMessageEnum
为响应描述提供了一个枚举,如 Success
、Exception
、UnAuthorize
和 ValidationError
。
ASP.NET Core 实现
现在我们已经准备好了包装器类,是时候进行实际实现了。
对于 ASP.NET Core 实现,我们将使用 中间件 来实现上述自定义包装器功能。中间件是构成处理应用程序请求和响应的管道的组件。调用的每个中间件都可以选择在调用管道中的下一个中间件之前对请求进行一些处理。在从调用下一个中间件返回执行后,有机会对响应进行处理。有关更多详细信息,请参阅 ASP.NET Core 中间件。
我们需要在中间件类中做一些工作,因为我们想吐出我们自己预定义的 Response
对象,并且我们想捕获或过滤掉显式和未处理的 API 异常。
这是自定义 ASP.NET Core 中间件的代码。
public class APIResponseMiddleware
{
private readonly RequestDelegate _next;
public APIResponseMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (IsSwagger(context))
await this._next(context);
else
{
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
try
{
await _next.Invoke(context);
if (context.Response.StatusCode == (int)HttpStatusCode.OK)
{
var body = await FormatResponse(context.Response);
await HandleSuccessRequestAsync(context, body, context.Response.StatusCode);
}
else
{
await HandleNotSuccessRequestAsync(context, context.Response.StatusCode);
}
}
catch (System.Exception ex)
{
await HandleExceptionAsync(context, ex);
}
finally
{
responseBody.Seek(0, SeekOrigin.Begin);
await responseBody.CopyToAsync(originalBodyStream);
}
}
}
}
private static Task HandleExceptionAsync(HttpContext context, System.Exception exception)
{
ApiError apiError = null;
APIResponse apiResponse = null;
int code = 0;
if (exception is ApiException)
{
var ex = exception as ApiException;
apiError = new ApiError(ex.Message);
apiError.ValidationErrors = ex.Errors;
apiError.ReferenceErrorCode = ex.ReferenceErrorCode;
apiError.ReferenceDocumentLink = ex.ReferenceDocumentLink;
code = ex.StatusCode;
context.Response.StatusCode = code;
}
else if (exception is UnauthorizedAccessException)
{
apiError = new ApiError("Unauthorized Access");
code = (int)HttpStatusCode.Unauthorized;
context.Response.StatusCode = code;
}
else
{
#if !DEBUG
var msg = "An unhandled error occurred.";
string stack = null;
#else
var msg = exception.GetBaseException().Message;
string stack = exception.StackTrace;
#endif
apiError = new ApiError(msg);
apiError.Details = stack;
code = (int)HttpStatusCode.InternalServerError;
context.Response.StatusCode = code;
}
context.Response.ContentType = "application/json";
apiResponse = new APIResponse
(code, ResponseMessageEnum.Exception.GetDescription(), null, apiError);
var json = JsonConvert.SerializeObject(apiResponse);
return context.Response.WriteAsync(json);
}
private static Task HandleNotSuccessRequestAsync(HttpContext context, int code)
{
context.Response.ContentType = "application/json";
ApiError apiError = null;
APIResponse apiResponse = null;
if (code == (int)HttpStatusCode.NotFound)
apiError = new ApiError
("The specified URI does not exist. Please verify and try again.");
else if (code == (int)HttpStatusCode.NoContent)
apiError = new ApiError("The specified URI does not contain any content.");
else
apiError = new ApiError("Your request cannot be processed. Please contact a support.");
apiResponse = new APIResponse
(code, ResponseMessageEnum.Failure.GetDescription(), null, apiError);
context.Response.StatusCode = code;
var json = JsonConvert.SerializeObject(apiResponse);
return context.Response.WriteAsync(json);
}
private static Task HandleSuccessRequestAsync(HttpContext context, object body, int code)
{
context.Response.ContentType = "application/json";
string jsonString, bodyText = string.Empty;
APIResponse apiResponse = null;
if (!body.ToString().IsValidJson())
bodyText = JsonConvert.SerializeObject(body);
else
bodyText = body.ToString();
dynamic bodyContent = JsonConvert.DeserializeObject<dynamic>(bodyText);
Type type;
type = bodyContent?.GetType();
if (type.Equals(typeof(Newtonsoft.Json.Linq.JObject)))
{
apiResponse = JsonConvert.DeserializeObject<APIResponse>(bodyText);
if (apiResponse.StatusCode != code)
jsonString = JsonConvert.SerializeObject(apiResponse);
else if (apiResponse.Result != null)
jsonString = JsonConvert.SerializeObject(apiResponse);
else
{
apiResponse = new APIResponse
(code, ResponseMessageEnum.Success.GetDescription(), bodyContent, null);
jsonString = JsonConvert.SerializeObject(apiResponse);
}
}
else
{
apiResponse = new APIResponse
(code, ResponseMessageEnum.Success.GetDescription(), bodyContent, null);
jsonString = JsonConvert.SerializeObject(apiResponse);
}
return context.Response.WriteAsync(jsonString);
}
private async Task<string> FormatResponse(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
var plainBodyText = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
return plainBodyText;
}
private bool IsSwagger(HttpContext context)
{
return context.Request.Path.StartsWithSegments("/swagger");
}
}
我们自定义中间件的主要方法是 Invoke()
。此方法接受 HttpContext
作为参数。该上下文包含管道中的当前 Request
和 Response
对象。这允许我们拦截上下文并进行一些自定义处理,在本例中是:(a) 处理异常 (b) 返回标准的自定义响应对象。
APIResponseMiddleware
还包含三个主要的 private
方法:
HandleExceptionAsync()
HandleNotSuccessRequestAsync()
HandleSuccessRequestAsync()
.
HandleExceptionAsync()
方法处理已抛出的异常,然后从中构建一个自定义响应对象并将其作为最终响应对象返回。HandleNotSuccessRequestAsync()
方法根据状态码处理特定响应。在此示例中,我们过滤了 NotFound
和 NoContent
StatusCodes
,然后构建了一个自定义响应。最后,HandleSuccessRequestAsync()
方法处理成功响应并构建一个将返回给消费者的自定义响应对象。
请注意,以上所有方法都使用 APIResponse
类作为最终响应对象。
现在我们已经实现了自定义中间件,我们可以创建一个 static
类来简化将中间件添加到应用程序管道的操作
public static class ApiResponseMiddlewareExtension
{
public static IApplicationBuilder UseAPIResponseWrapperMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<APIResponseMiddleware>();
}
}
使用自定义中间件的最后一步是在 Startup
类的 Configure()
方法中调用我们上面创建的扩展方法
app.UseAPIResponseMiddleware();
标准 ASP.NET Web API 实现
由于中间件是为 ASP.NET Core 应用程序设计的,在标准的 Web API 项目中,我们将使用 ExceptionFilterAttribute
来处理和管理异常,并使用 DelegatingHandler
来实现自定义响应包装器。
异常过滤器
让我们从异常过滤器实现开始。这是过滤器实现的 kode:
public class ApiExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
ApiError apiError = null;
APIResponse apiResponse = null;
int code = 0;
if (context.Exception is ApiException)
{
var ex = context.Exception as ApiException;
apiError = new ApiError(ex.Message);
apiError.ValidationErrors = ex.Errors;
apiError.ReferenceErrorCode = ex.ReferenceErrorCode;
apiError.ReferenceDocumentLink = ex.ReferenceDocumentLink;
code = ex.StatusCode;
}
else if (context.Exception is UnauthorizedAccessException)
{
apiError = new ApiError("Unauthorized Access");
code = (int)HttpStatusCode.Unauthorized;
}
else
{
#if !DEBUG
var msg = "An unhandled error occurred.";
string stack = null;
#else
var msg = context.Exception.GetBaseException().Message;
string stack = context.Exception.StackTrace;
#endif
apiError = new ApiError(msg);
apiError.Details = stack;
code = (int)HttpStatusCode.InternalServerError;
}
apiResponse = new APIResponse
(code, ResponseMessageEnum.Exception.GetDescription(), null, apiError);
HttpStatusCode c = (HttpStatusCode)code;
context.Response = context.Request.CreateResponse(c, apiResponse);
}
}
与 ASP.NET Core 的 HandleExceptionAsync()
方法一样,自定义异常过滤器方法会处理从应用程序中抛出的异常。过滤器的实现与 HandleExceptionAsync()
方法的 ASP.NET Core 实现几乎相同。
让我们稍微详细地说明一下过滤器实际在做什么。异常过滤器区分几种不同的异常类型。首先,它查看自定义 ApiException
类型,这是一个特殊的应用程序生成的 Exception
,可以用来向消费者显示有意义的响应。
接下来是 UnAuthorized
异常,它会得到特殊的处理,返回一个强制的 401
异常,客户端可以用来强制进行身份验证。
最后,还有 Unhandled
异常 - 这些是应用程序未明确知道的意外故障。这可能是一个硬件故障、一个 null
引用异常、一个意外的解析错误。基本上任何未处理的错误。这些错误在生产环境中会生成一个通用的错误消息,以避免返回敏感数据。
Delegating Handler
DelegatingHandlers
对于跨领域关注点非常有用。它们会挂钩到请求-响应管道的非常早和非常晚的阶段,这使得它们非常适合在响应发送回客户端之前对其进行操作。这是 Delegating Handler 的 kode:
public class WrappingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage>
SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (IsSwagger(request))
{
return await base.SendAsync(request, cancellationToken);
}
else
{
var response = await base.SendAsync(request, cancellationToken);
return BuildApiResponse(request, response);
}
}
private static HttpResponseMessage
BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
{
dynamic content = null;
object data = null;
string errorMessage = null;
ApiError apiError = null;
var code = (int)response.StatusCode;
if (response.TryGetContentValue(out content) && !response.IsSuccessStatusCode)
{
HttpError error = content as HttpError;
//handle exception
if (error != null)
{
content = null;
if (response.StatusCode == HttpStatusCode.NotFound)
apiError = new ApiError("The specified URI does not exist.
Please verify and try again.");
else if (response.StatusCode == HttpStatusCode.NoContent)
apiError = new ApiError("The specified URI does not contain any content.");
else
{
errorMessage = error.Message;
#if DEBUG
errorMessage = string.Concat
(errorMessage, error.ExceptionMessage, error.StackTrace);
#endif
apiError = new ApiError(errorMessage);
}
data = new APIResponse((int)code, ResponseMessageEnum.Failure.GetDescription(),
null, apiError);
}
else
data = content;
}
else
{
if (response.TryGetContentValue(out content))
{
Type type;
type = content?.GetType();
if (type.Name.Equals("APIResponse"))
{
response.StatusCode = Enum.Parse(typeof(HttpStatusCode),
content.StatusCode.ToString());
data = content;
}
else if (type.Name.Equals("SwaggerDocument"))
data = content;
else
data = new APIResponse(code, ResponseMessageEnum.Success.GetDescription(), content);
}
else
{
if (response.IsSuccessStatusCode)
data = new APIResponse((int)response.StatusCode,
ResponseMessageEnum.Success.GetDescription());
}
}
var newResponse = request.CreateResponse(response.StatusCode, data);
foreach (var header in response.Headers)
{
newResponse.Headers.Add(header.Key, header.Value);
}
return newResponse;
}
private bool IsSwagger(HttpRequestMessage request)
{
return request.RequestUri.PathAndQuery.StartsWith("/swagger");
}
}
上面的 kode 实现方式不同,但它实现了与 ASP.NET Core 中间件相同的功能。在这种情况下,我们使用一个 delegating handler 来拦截当前上下文并为消费者构建一个自定义响应对象。我们使用 Request.CreateResponse()
方法来使用适当的格式化程序创建一个新响应,然后在返回最终响应对象之前复制旧的未包装响应中的任何标头。
要使用过滤器和包装器,只需在 WebApiConfig.cs 文件中注册它们即可
config.Filters.Add(new ApiExceptionFilter());
config.MessageHandlers.Add(new WrappingHandler());
摘要
在本文中,我们学习了如何为 ASP.NET Core 和标准 Web API 项目创建简单的自定义包装器来管理 API 异常和一致的响应。我们还学习了如何轻松地将 VMS.RESTApiResponseWrapper
库集成到你的 ASP.NET Core 和标准 Web API 项目中,而无需进行本文中演示的所有代码实现。
欢迎下载源代码或查看 github 存储库。谢谢!:)