ASP.NET?Core?WebApi返回結(jié)果統(tǒng)一包裝實(shí)踐記錄
前言
近期在重新搭建一套基于ASP.NET Core WebAPI的框架,這其中確實(shí)帶來(lái)了不少的收獲,畢竟當(dāng)你想搭建一套框架的時(shí)候,你總會(huì)不自覺(jué)的去想,如何讓這套框架變得更完善一點(diǎn)更好用一點(diǎn)。其中在關(guān)于WebApi統(tǒng)一結(jié)果返回的時(shí)候,讓我也有了更一步的思考,首先是如何能更好的限制返回統(tǒng)一的格式,其次是關(guān)于結(jié)果的包裝一定是更簡(jiǎn)單更強(qiáng)大。在不斷的思考和完善中,終于有了初步的成果,便分享出來(lái),學(xué)無(wú)止境思考便無(wú)止境,希望以此能與君共勉。
統(tǒng)一結(jié)果類(lèi)封裝
首先如果讓返回的結(jié)果格式統(tǒng)一,就得有一個(gè)統(tǒng)一的包裝類(lèi)去包裝所有的返回結(jié)果,因?yàn)榉祷氐木唧w數(shù)據(jù)雖然格式一致,但是具體的值的類(lèi)型是不確定的,因此我們這里需要定義個(gè)泛型類(lèi)。當(dāng)然如果你不選擇泛型類(lèi)的話用dynamic或者object類(lèi)型也是可以的,但是這樣的話可能會(huì)帶來(lái)兩點(diǎn)不足
- 一是可能會(huì)存在裝箱拆箱的操作。
- 二是如果引入swagger的話是沒(méi)辦法生成返回的類(lèi)型的,因?yàn)閐ynamic或object類(lèi)型都是執(zhí)行具體的action時(shí)才能確定返回類(lèi)型的,但是swagger的結(jié)構(gòu)是首次運(yùn)行的時(shí)候就獲取到的,因此無(wú)法感知具體類(lèi)型。
定義包裝類(lèi)
上面我們也說(shuō)了關(guān)于定義泛型類(lèi)的優(yōu)勢(shì),這里就話不多說(shuō)來(lái)直接封裝一個(gè)結(jié)果返回的包裝類(lèi)
public class ResponseResult<T> { /// <summary> /// 狀態(tài)結(jié)果 /// </summary> public ResultStatus Status { get; set; } = ResultStatus.Success; private string? _msg; /// <summary> /// 消息描述 /// </summary> public string? Message { get { // 如果沒(méi)有自定義的結(jié)果描述,則可以獲取當(dāng)前狀態(tài)的描述 return !string.IsNullOrEmpty(_msg) ? _msg : EnumHelper.GetDescription(Status); } set { _msg = value; } } /// <summary> /// 返回結(jié)果 /// </summary> public T Data { get; set; } }
其中這里的ResultStatus
是一個(gè)枚舉類(lèi)型,用于定義具體的返回狀態(tài)碼,用于判斷返回的結(jié)果是正常還是異?;蛘咂渌疫@里只是簡(jiǎn)單的定義了一個(gè)最簡(jiǎn)單的示例,有需要的話也可以自行擴(kuò)展
public enum ResultStatus { [Description("請(qǐng)求成功")] Success = 1, [Description("請(qǐng)求失敗")] Fail = 0, [Description("請(qǐng)求異常")] Error = -1 }
這種情況下定義枚舉類(lèi)型并且結(jié)合它的DescriptionAttribute
的特性去描述枚舉的含義是一個(gè)不錯(cuò)的選擇,首先它可以統(tǒng)一管理每個(gè)狀態(tài)的含義,其次是更方便的獲取每個(gè)狀態(tài)對(duì)應(yīng)的描述。這樣的話如果沒(méi)有自定義的結(jié)果描述,則可以獲取當(dāng)前狀態(tài)的描述來(lái)充當(dāng)默認(rèn)值的情況。這個(gè)時(shí)候在寫(xiě)具體action的時(shí)候會(huì)是以下的效果
[HttpGet("GetWeatherForecast")] public ResponseResult<IEnumerable<WeatherForecast>> GetAll() { var datas = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }); return new ResponseResult<IEnumerable<WeatherForecast>> { Data = datas }; }
這樣的話每次編寫(xiě)action的時(shí)候都可以返回一個(gè)ResponseResult<T>
的結(jié)果了,這里就體現(xiàn)出了使用枚舉定義狀態(tài)碼的優(yōu)勢(shì)了,相當(dāng)一部分場(chǎng)景我們可以省略了狀態(tài)碼甚至是消息的編寫(xiě),畢竟很多時(shí)候在保障功能的情況下,代碼還是越簡(jiǎn)介越好的,更何況是一些高頻操作呢。
升級(jí)一下操作
上面雖然我們定義了ResponseResult<T>
來(lái)統(tǒng)一包裝返回結(jié)果,但是每次還得new一下,在無(wú)疑是不太方便的,而且還要每次都還得給屬性賦值啥的,也是夠麻煩的,這個(gè)時(shí)候就想,如果能有相關(guān)的輔助方法去簡(jiǎn)化操作就好了,方法不用太多能滿足場(chǎng)景就好,也就是夠用就好,最主要的是能支持?jǐn)U展就可以。因此,進(jìn)一步升級(jí)一下結(jié)果包裝類(lèi),來(lái)簡(jiǎn)化一下操作
public class ResponseResult<T> { /// <summary> /// 狀態(tài)結(jié)果 /// </summary> public ResultStatus Status { get; set; } = ResultStatus.Success; private string? _msg; /// <summary> /// 消息描述 /// </summary> public string? Message { get { return !string.IsNullOrEmpty(_msg) ? _msg : EnumHelper.GetDescription(Status); } set { _msg = value; } } /// <summary> /// 返回結(jié)果 /// </summary> public T Data { get; set; } /// <summary> /// 成功狀態(tài)返回結(jié)果 /// </summary> /// <param name="result">返回的數(shù)據(jù)</param> /// <returns></returns> public static ResponseResult<T> SuccessResult(T data) { return new ResponseResult<T> { Status = ResultStatus.Success, Data = data }; } /// <summary> /// 失敗狀態(tài)返回結(jié)果 /// </summary> /// <param name="code">狀態(tài)碼</param> /// <param name="msg">失敗信息</param> /// <returns></returns> public static ResponseResult<T> FailResult(string? msg = null) { return new ResponseResult<T> { Status = ResultStatus.Fail, Message = msg }; } /// <summary> /// 異常狀態(tài)返回結(jié)果 /// </summary> /// <param name="code">狀態(tài)碼</param> /// <param name="msg">異常信息</param> /// <returns></returns> public static ResponseResult<T> ErrorResult(string? msg = null) { return new ResponseResult<T> { Status = ResultStatus.Error, Message = msg }; } /// <summary> /// 自定義狀態(tài)返回結(jié)果 /// </summary> /// <param name="status"></param> /// <param name="result"></param> /// <returns></returns> public static ResponseResult<T> Result(ResultStatus status, T data, string? msg = null) { return new ResponseResult<T> { Status = status, Data = data, Message = msg }; } }
這里進(jìn)一步封裝了幾個(gè)方法,至于具體封裝幾個(gè)這種方法,還是那句話夠用就好,這里我封裝了幾個(gè)常用的操作,成功狀態(tài)、失敗狀態(tài)、異常狀態(tài)、最完全狀態(tài),這幾種狀態(tài)基本上可以滿足大多數(shù)的場(chǎng)景,不夠的話可以自行進(jìn)行進(jìn)一步的多封裝幾個(gè)方法。這樣的話在action使用的時(shí)候就會(huì)簡(jiǎn)化很多,省去了手動(dòng)屬性賦值
[HttpGet("GetWeatherForecast")] public ResponseResult<IEnumerable<WeatherForecast>> GetAll() { var datas = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }); return ResponseResult<IEnumerable<WeatherForecast>>.SuccessResult(datas); }
進(jìn)一步完善
上面我們通過(guò)完善ResponseResult<T>
類(lèi)的封裝,確實(shí)在某些程度上節(jié)省了一部分操作,但是還是有點(diǎn)美中不足,那就是每次返回結(jié)果的時(shí)候,雖然定義了幾個(gè)常用的靜態(tài)方法去操作返回結(jié)果,但是每次還得通過(guò)手動(dòng)去把ResponseResult<T>
類(lèi)給請(qǐng)出來(lái)才能使用,現(xiàn)在呢想在操作返回結(jié)果的時(shí)候不想看到它了。這個(gè)呢也很簡(jiǎn)單,我們可以借助微軟針對(duì)MVC的Controller的封裝進(jìn)一步得到一個(gè)思路,那就是定義一個(gè)基類(lèi)的Controller,我們?cè)贑ontroller基類(lèi)中,把常用的返回結(jié)果封裝一些方法,這樣在Controller的子類(lèi)中呢就可以直接調(diào)用這些方法,而不需要關(guān)注如何去編寫(xiě)方法的返回類(lèi)型了,話不多說(shuō)動(dòng)手把Controller基類(lèi)封裝起來(lái)
[ApiController] [Route("api/[controller]")] public class ApiControllerBase : ControllerBase { /// <summary> /// 成功狀態(tài)返回結(jié)果 /// </summary> /// <param name="result">返回的數(shù)據(jù)</param> /// <returns></returns> protected ResponseResult<T> SuccessResult<T>(T result) { return ResponseResult<T>.SuccessResult(result); } /// <summary> /// 失敗狀態(tài)返回結(jié)果 /// </summary> /// <param name="code">狀態(tài)碼</param> /// <param name="msg">失敗信息</param> /// <returns></returns> protected ResponseResult<T> FailResult<T>(string? msg = null) { return ResponseResult<T>.FailResult(msg); } /// <summary> /// 異常狀態(tài)返回結(jié)果 /// </summary> /// <param name="code">狀態(tài)碼</param> /// <param name="msg">異常信息</param> /// <returns></returns> protected ResponseResult<T> ErrorResult<T>(string? msg = null) { return ResponseResult<T>.ErrorResult(msg); } /// <summary> /// 自定義狀態(tài)返回結(jié)果 /// </summary> /// <param name="status"></param> /// <param name="result"></param> /// <returns></returns> protected ResponseResult<T> Result<T>(ResultStatus status, T result, string? msg = null) { return ResponseResult<T>.Result(status, result, msg); } }
有了這么一個(gè)大神的輔助,一切似乎又向著美好進(jìn)發(fā)了一步,這樣的話每次我們自定義的Controller可以繼承ApiControllerBase
類(lèi),從而使用里面的簡(jiǎn)化操作。所以再寫(xiě)起來(lái)代碼,大概是這么一個(gè)效果
public class WeatherForecastController : ApiControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; [HttpGet("GetWeatherForecast")] public ResponseResult<IEnumerable<WeatherForecast>> GetAll() { var datas = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }); return SuccessResult(datas); } }
這個(gè)時(shí)候確實(shí)變得很美好了,但是還是沒(méi)有逃脫一點(diǎn),那就是我還是得通過(guò)特定的方法來(lái)得到一個(gè)ResponseResult<T>
類(lèi)型的返回結(jié)果,包括我們給ResponseResult<T>
類(lèi)封裝靜態(tài)方法,或者甚至是定義ApiControllerBase
基類(lèi),都是為了進(jìn)一步簡(jiǎn)化這個(gè)操作?,F(xiàn)在呢我想告別這個(gè)限制,我能不能把返回的結(jié)果直接就默認(rèn)的轉(zhuǎn)化成ResponseResult<T>
類(lèi)型的結(jié)果呢?當(dāng)然可以,這也是通過(guò)ASP.NET Core的封裝思路中得到的啟發(fā),借助implicit
自動(dòng)完成隱式轉(zhuǎn)換,這個(gè)在ASP.NET Core的ActionResult<T>
類(lèi)中也有體現(xiàn)
public static implicit operator ActionResult<TValue>(TValue value) { return new ActionResult<TValue>(value); }
通過(guò)這個(gè)思路我們可以進(jìn)一步完善ResponseResult<T>
類(lèi)的實(shí)現(xiàn)方式,給它添加一個(gè)隱式轉(zhuǎn)換的操作,僅僅定義一個(gè)方法即可,在ResponseResult<T>
類(lèi)中繼續(xù)完善
/// <summary> /// 隱式將T轉(zhuǎn)化為ResponseResult<T> /// </summary> /// <param name="value"></param> public static implicit operator ResponseResult<T>(T value) { return new ResponseResult<T> { Data = value }; }
這種對(duì)于絕大部分返回成功結(jié)果的時(shí)候提供了非常簡(jiǎn)化的操作,這個(gè)時(shí)候如果你再去使用action的時(shí)候就可以進(jìn)一步來(lái)簡(jiǎn)化返回值的操作了
[HttpGet("GetWeatherForecast")] public ResponseResult<IEnumerable<WeatherForecast>> GetAll() { var datas = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }); return datas.ToList(); }
因?yàn)槲覀兌x了T
到ResponseResult<T>
的隱式轉(zhuǎn)換,所以這個(gè)時(shí)候我們就可以直接返回結(jié)果了,而不需要手動(dòng)對(duì)結(jié)果返回值進(jìn)行包裝。
漏網(wǎng)之魚(yú)處理
在上面我們?yōu)榱吮M量簡(jiǎn)化action返回ResponseResult<T>
的統(tǒng)一返回結(jié)構(gòu)的封裝,已經(jīng)對(duì)ResponseResult<T>
類(lèi)進(jìn)行了許多的封裝,并且還通過(guò)封裝ApiControllerBase
基類(lèi)進(jìn)一步簡(jiǎn)化這一操作,但是終究還是避免不了一點(diǎn),那就是很多時(shí)候可能想不起來(lái)對(duì)action的返回值去加ResponseResult<T>
類(lèi)型的返回值,但是我們之前的所有封裝都得建立在必須要聲明ResponseResult<T>
類(lèi)型的返回值的基礎(chǔ)上才行,否則就不存在統(tǒng)一返回格式這一說(shuō)法了。所以針對(duì)這些漏網(wǎng)之魚(yú),我們必須要有統(tǒng)一的攔截機(jī)制,這樣才能更完整的針對(duì)返回結(jié)果進(jìn)行處理,針對(duì)這種對(duì)action返回值的操作,我們首先想到的就是定義過(guò)濾器
進(jìn)行處理,因此筆者針對(duì)這一現(xiàn)象封裝了一個(gè)統(tǒng)一包裝結(jié)果的過(guò)濾器,實(shí)現(xiàn)如下
public class ResultWrapperFilter : ActionFilterAttribute { public override void OnResultExecuting(ResultExecutingContext context) { var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; var actionWrapper = controllerActionDescriptor?.MethodInfo.GetCustomAttributes(typeof(NoWrapperAttribute), false).FirstOrDefault(); var controllerWrapper = controllerActionDescriptor?.ControllerTypeInfo.GetCustomAttributes(typeof(NoWrapperAttribute), false).FirstOrDefault(); //如果包含NoWrapperAttribute則說(shuō)明不需要對(duì)返回結(jié)果進(jìn)行包裝,直接返回原始值 if (actionWrapper != null || controllerWrapper != null) { return; } //根據(jù)實(shí)際需求進(jìn)行具體實(shí)現(xiàn) var rspResult = new ResponseResult<object>(); if (context.Result is ObjectResult) { var objectResult = context.Result as ObjectResult; if (objectResult?.Value == null) { rspResult.Status = ResultStatus.Fail; rspResult.Message = "未找到資源"; context.Result = new ObjectResult(rspResult); } else { //如果返回結(jié)果已經(jīng)是ResponseResult<T>類(lèi)型的則不需要進(jìn)行再次包裝了 if (objectResult.DeclaredType.IsGenericType && objectResult.DeclaredType?.GetGenericTypeDefinition() == typeof(ResponseResult<>)) { return; } rspResult.Data = objectResult.Value; context.Result = new ObjectResult(rspResult); } return; } } }
在使用WebAPI的過(guò)程中,我們的action絕大部分是直接返回ViewModel
或Dto
而并沒(méi)有返回ActionResult
類(lèi)型相關(guān),但是無(wú)妨,這個(gè)時(shí)候MVC的底層操作會(huì)為我們將這些自定義的類(lèi)型包裝成ObjectResult
類(lèi)型的,因此我們的ResultWrapperFilter
過(guò)濾器也是通過(guò)這一機(jī)制進(jìn)行操作的。這里有兩點(diǎn)需要考慮的
- 首先是,我們必須要允許并非所有的返回結(jié)果都要進(jìn)行
ResponseResult<T>
的包裝,為了滿足這一需求我們還定義了NoWrapperAttribute
來(lái)實(shí)現(xiàn)這一效果,只要Controller或Action有NoWrapperAttribute
的修飾則不對(duì)返回結(jié)果進(jìn)行任何處理。 - 其次是,如果我們的Action上的返回類(lèi)型已經(jīng)是
ResponseResult<T>
類(lèi)型的,則也不需要對(duì)返回結(jié)果進(jìn)行再次的包裝。
關(guān)于ResultWrapperFilter
的定義其實(shí)很簡(jiǎn)單,因?yàn)樵谶@里它只是起到了一個(gè)標(biāo)記的作用
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class NoWrapperAttribute:Attribute { }
到了這里,還有一種特殊的情況需要注意,那就是當(dāng)程序發(fā)生異常的時(shí)候,我們上面的這些機(jī)制也是沒(méi)有辦法生效的,因此我們還需要定義一個(gè)針對(duì)全局異常處理的攔截機(jī)制,同樣是可以使用統(tǒng)一異常處理過(guò)濾器進(jìn)行操作,實(shí)現(xiàn)如下
public class GlobalExceptionFilter : IExceptionFilter { private readonly ILogger<GlobalExceptionFilter> _logger; public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger) { _logger = logger; } public void OnException(ExceptionContext context) { //異常返回結(jié)果包裝 var rspResult = ResponseResult<object>.ErrorResult(context.Exception.Message); //日志記錄 _logger.LogError(context.Exception, context.Exception.Message); context.ExceptionHandled = true; context.Result = new InternalServerErrorObjectResult(rspResult); } public class InternalServerErrorObjectResult : ObjectResult { public InternalServerErrorObjectResult(object value) : base(value) { StatusCode = StatusCodes.Status500InternalServerError; } } }
寫(xiě)完過(guò)濾器了,千萬(wàn)不能忘了全局注冊(cè)一下,否則它也就只能看看了,不會(huì)起到任何效果
builder.Services.AddControllers(options => { options.Filters.Add<ResultWrapperFilter>(); options.Filters.Add<GlobalExceptionFilter>(); });
漏網(wǎng)之魚(yú)另一種處理
當(dāng)然針對(duì)上面兩種針對(duì)漏網(wǎng)之魚(yú)的處理,在ASP.NET Core上還可以通過(guò)中間件的方式進(jìn)行處理,至于過(guò)濾器和中間件有何不同,相信大家已經(jīng)非常清楚了,核心不同總結(jié)起來(lái)就一句話二者的處理階段不同,即針對(duì)管道的生命周期處理是不一樣的,中間件可以處理任何生命周期在它之后的場(chǎng)景,但是過(guò)濾器只管理Controller這一塊的一畝三分地
但是針對(duì)結(jié)果包裝這一場(chǎng)景,筆者覺(jué)得使用過(guò)濾器的方式更容易處理一點(diǎn),因?yàn)楫吘刮覀兪且僮鰽ction的返回結(jié)果,通過(guò)過(guò)濾器中我們可以直接拿到返回結(jié)果的值。但是這個(gè)操作如果在中間件里進(jìn)行操作的話,只能通過(guò)讀取Response.Body
進(jìn)行操作了,筆者這里也封裝了一個(gè)操作,如下所示
public static IApplicationBuilder UseResultWrapper(this IApplicationBuilder app) { var serializerOptions = app.ApplicationServices.GetRequiredService<IOptions<JsonOptions>>().Value.JsonSerializerOptions; serializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; return app.Use(async (context, next) => { var originalResponseBody = context.Response.Body; try { //因?yàn)镽esponse.Body沒(méi)辦法進(jìn)行直接讀取,所以需要特殊操作一下 using var swapStream = new MemoryStream(); context.Response.Body = swapStream; await next(); //判斷是否出現(xiàn)了異常狀態(tài)碼,需要特殊處理 if (context.Response.StatusCode == StatusCodes.Status500InternalServerError) { context.Response.Body.Seek(0, SeekOrigin.Begin); await swapStream.CopyToAsync(originalResponseBody); return; } var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint; if (endpoint != null) { //只針對(duì)application/json結(jié)果進(jìn)行處理 if (context.Response.ContentType.ToLower().Contains("application/json")) { //判斷終結(jié)點(diǎn)是否包含NoWrapperAttribute NoWrapperAttribute noWrapper = endpoint.Metadata.GetMetadata<NoWrapperAttribute>(); if (noWrapper != null) { context.Response.Body.Seek(0, SeekOrigin.Begin); await swapStream.CopyToAsync(originalResponseBody); return; } //獲取Action的返回類(lèi)型 var controllerActionDescriptor = context.GetEndpoint()?.Metadata.GetMetadata<ControllerActionDescriptor>(); if (controllerActionDescriptor != null) { //泛型的特殊處理 var returnType = controllerActionDescriptor.MethodInfo.ReturnType; if (returnType.IsGenericType && (returnType.GetGenericTypeDefinition() == typeof(Task<>) || returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))) { returnType = returnType.GetGenericArguments()[0]; } //如果終結(jié)點(diǎn)已經(jīng)是ResponseResult<T>則不進(jìn)行包裝處理 if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(ResponseResult<>)) { context.Response.Body.Seek(0, SeekOrigin.Begin); await swapStream.CopyToAsync(originalResponseBody); return; } context.Response.Body.Seek(0, SeekOrigin.Begin); //反序列化得到原始結(jié)果 var result = await JsonSerializer.DeserializeAsync(context.Response.Body, returnType, serializerOptions); //對(duì)原始結(jié)果進(jìn)行包裝 var bytes = JsonSerializer.SerializeToUtf8Bytes(ResponseResult<object>.SuccessResult(result), serializerOptions); new MemoryStream(bytes).CopyTo(originalResponseBody); return; } } } context.Response.Body.Seek(0, SeekOrigin.Begin); await swapStream.CopyToAsync(originalResponseBody); } finally { //將原始的Body歸還回來(lái) context.Response.Body = originalResponseBody; } }); } }
相信通過(guò)上面的處理,我們就可以更容易的看出來(lái),誰(shuí)更容易的對(duì)統(tǒng)一結(jié)果進(jìn)行包裝處理了,畢竟我們是針對(duì)Action的返回結(jié)果進(jìn)行處理,而過(guò)濾器顯然就是為針對(duì)Controller和Action的處理而生的。但是通過(guò)中間件的方式能更完整的針對(duì)結(jié)果進(jìn)行處理,因?yàn)樵S多時(shí)候我們可能是在自定義的中間件里直接攔截請(qǐng)求并返回,但是根據(jù)二八原則這種情況相對(duì)于Action的返回值畢竟是少數(shù),有這種情況我們可以通過(guò)直接ResponseResult<T>
封裝的方法進(jìn)行返回操作,也很方便。但是這個(gè)時(shí)候呢,關(guān)于異常處理我們通過(guò)全局異常處理中間件,則能更多的處理更多的場(chǎng)景,且沒(méi)有副作用,看一下它的定義
public static IApplicationBuilder UseException(this IApplicationBuilder app) { return app.UseExceptionHandler(configure => { configure.Run(async context => { var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>(); var ex = exceptionHandlerPathFeature?.Error; if (ex != null) { var _logger = context.RequestServices.GetService<ILogger<IExceptionHandlerPathFeature>>(); var rspResult = ResponseResult<object>.ErrorResult(ex.Message); _logger?.LogError(ex, message: ex.Message); context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.ContentType = "application/json;charset=utf-8"; await context.Response.WriteAsync(rspResult.SerializeObject()); } }); }); }
使用全局異常梳理中間件是沒(méi)有副作用的,主要因?yàn)樵诋惓L幚淼臅r(shí)候我們不需要讀取Response.Body
進(jìn)行讀取操作的,所以至于是選擇異常處理中間件還是過(guò)濾器,大家可以針對(duì)自己的實(shí)際場(chǎng)景進(jìn)行選擇,兩種方式都是可以的。
總結(jié)
本文主要是展示了針對(duì)ASP.NET Core WeApi結(jié)果統(tǒng)一返回格式的相關(guān)操作,通過(guò)示例我們一步一步的展示了完成這一目標(biāo)的不斷升級(jí)的實(shí)現(xiàn),雖然整體看起來(lái)比較簡(jiǎn)單,但是卻承載著筆者一次又一次的思考升級(jí)。每次實(shí)現(xiàn)完一個(gè)階段,都會(huì)去想有沒(méi)有更好的方式去完善它。這其中還有一些思路來(lái)自微軟源碼為我們提供的思路,所以很多時(shí)候還是建議大家去看一看源碼的,可以在很多時(shí)候?yàn)槲覀兲峁┮环N解決問(wèn)題的思路。正如我看到的一句話,讀源碼也是一種圍城,外面的人不想進(jìn)去,里面的人不想出來(lái)。如果大家有更好的實(shí)現(xiàn)方式,歡迎一起討論。曾經(jīng)的時(shí)候我會(huì)為自己學(xué)到了一個(gè)新的技能而感到高興,到了后來(lái)我會(huì)對(duì)有一個(gè)好的思路,或者好的解決問(wèn)題的方法而感到高興。讀萬(wàn)卷書(shū)很重要,行萬(wàn)里路同樣重要,讀書(shū)是沉淀,行路是實(shí)踐,結(jié)合到一起才能更好的促進(jìn),而不是只選擇一種。
到此這篇關(guān)于ASP.NET Core WebApi返回結(jié)果統(tǒng)一包裝實(shí)踐的文章就介紹到這了,更多相關(guān)ASP.NET Core WebApi返回結(jié)果內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
asp.net 無(wú)刷新附件上傳實(shí)現(xiàn)方法
一直以來(lái)附件上傳都是個(gè)很郁悶的問(wèn)題,剛開(kāi)始是利用js添加input file 然后一起提交來(lái)實(shí)現(xiàn)多文件上傳,在使用163郵箱的時(shí)候很是羨慕它的附件上傳部分(選擇完文件就提交,可以多個(gè)文件一起上傳,而且還可以獲取上傳進(jìn)度),這時(shí)就很想自己也寫(xiě)個(gè)那樣的東西出來(lái)。2010-01-01asp.net repeater手寫(xiě)分頁(yè)實(shí)例代碼
想用repeater實(shí)現(xiàn)手寫(xiě)分頁(yè)的代碼,想自己控制各種樣式的朋友,可以用用。2009-05-05Repeater的FooterTemplate顯示某列總計(jì)思路與代碼
在Repeater的FooterTemplate顯示某列總計(jì),接下來(lái)與大家分享詳細(xì)的實(shí)現(xiàn)方案,感興趣的各位可以參考下哈2013-03-03Community Server專(zhuān)題二:體系結(jié)構(gòu)
Community Server專(zhuān)題二:體系結(jié)構(gòu)...2007-03-03.NET 中Worker Service的使用入門(mén)
隨著 .NET Core 3.0 的發(fā)布,ASP.NET 團(tuán)隊(duì)引入了一個(gè)新的 Worker Service 項(xiàng)目模板,該模板作為 .NET SDK 的一部分發(fā)布。在本文中,我將向您介紹這個(gè)新模板,以及使用它開(kāi)發(fā)的一些實(shí)際的服務(wù)示例。2021-05-05ASP.NET入門(mén)之HTML服務(wù)器控件概述
這篇文章主要介紹了ASP.NET入門(mén)之HTML服務(wù)器控件,對(duì)于初學(xué)者來(lái)說(shuō)很有借鑒學(xué)習(xí)價(jià)值,需要的朋友可以參考下2014-07-07ajax添加數(shù)據(jù)后如何在網(wǎng)頁(yè)顯示
這篇文章主要介紹了ajax添加數(shù)據(jù)顯示在網(wǎng)頁(yè)上,此文是通過(guò)ajax獲取后臺(tái)json數(shù)據(jù)來(lái)實(shí)現(xiàn)此功能,需要的朋友可以參考下2015-07-07