ASP.NET?Core?WebApi返回結(jié)果統(tǒng)一包裝實(shí)踐記錄
前言
近期在重新搭建一套基于ASP.NET Core WebAPI的框架,這其中確實(shí)帶來了不少的收獲,畢竟當(dāng)你想搭建一套框架的時候,你總會不自覺的去想,如何讓這套框架變得更完善一點(diǎn)更好用一點(diǎn)。其中在關(guān)于WebApi統(tǒng)一結(jié)果返回的時候,讓我也有了更一步的思考,首先是如何能更好的限制返回統(tǒng)一的格式,其次是關(guān)于結(jié)果的包裝一定是更簡單更強(qiáng)大。在不斷的思考和完善中,終于有了初步的成果,便分享出來,學(xué)無止境思考便無止境,希望以此能與君共勉。
統(tǒng)一結(jié)果類封裝
首先如果讓返回的結(jié)果格式統(tǒng)一,就得有一個統(tǒng)一的包裝類去包裝所有的返回結(jié)果,因?yàn)榉祷氐木唧w數(shù)據(jù)雖然格式一致,但是具體的值的類型是不確定的,因此我們這里需要定義個泛型類。當(dāng)然如果你不選擇泛型類的話用dynamic或者object類型也是可以的,但是這樣的話可能會帶來兩點(diǎn)不足
- 一是可能會存在裝箱拆箱的操作。
- 二是如果引入swagger的話是沒辦法生成返回的類型的,因?yàn)閐ynamic或object類型都是執(zhí)行具體的action時才能確定返回類型的,但是swagger的結(jié)構(gòu)是首次運(yùn)行的時候就獲取到的,因此無法感知具體類型。
定義包裝類
上面我們也說了關(guān)于定義泛型類的優(yōu)勢,這里就話不多說來直接封裝一個結(jié)果返回的包裝類
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
{
// 如果沒有自定義的結(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是一個枚舉類型,用于定義具體的返回狀態(tài)碼,用于判斷返回的結(jié)果是正常還是異常或者其他,我這里只是簡單的定義了一個最簡單的示例,有需要的話也可以自行擴(kuò)展
public enum ResultStatus
{
[Description("請求成功")]
Success = 1,
[Description("請求失敗")]
Fail = 0,
[Description("請求異常")]
Error = -1
}
這種情況下定義枚舉類型并且結(jié)合它的DescriptionAttribute的特性去描述枚舉的含義是一個不錯的選擇,首先它可以統(tǒng)一管理每個狀態(tài)的含義,其次是更方便的獲取每個狀態(tài)對應(yīng)的描述。這樣的話如果沒有自定義的結(jié)果描述,則可以獲取當(dāng)前狀態(tài)的描述來充當(dāng)默認(rèn)值的情況。這個時候在寫具體action的時候會是以下的效果
[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 };
}
這樣的話每次編寫action的時候都可以返回一個ResponseResult<T>的結(jié)果了,這里就體現(xiàn)出了使用枚舉定義狀態(tài)碼的優(yōu)勢了,相當(dāng)一部分場景我們可以省略了狀態(tài)碼甚至是消息的編寫,畢竟很多時候在保障功能的情況下,代碼還是越簡介越好的,更何況是一些高頻操作呢。
升級一下操作
上面雖然我們定義了ResponseResult<T>來統(tǒng)一包裝返回結(jié)果,但是每次還得new一下,在無疑是不太方便的,而且還要每次都還得給屬性賦值啥的,也是夠麻煩的,這個時候就想,如果能有相關(guān)的輔助方法去簡化操作就好了,方法不用太多能滿足場景就好,也就是夠用就好,最主要的是能支持?jǐn)U展就可以。因此,進(jìn)一步升級一下結(jié)果包裝類,來簡化一下操作
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)一步封裝了幾個方法,至于具體封裝幾個這種方法,還是那句話夠用就好,這里我封裝了幾個常用的操作,成功狀態(tài)、失敗狀態(tài)、異常狀態(tài)、最完全狀態(tài),這幾種狀態(tài)基本上可以滿足大多數(shù)的場景,不夠的話可以自行進(jìn)行進(jìn)一步的多封裝幾個方法。這樣的話在action使用的時候就會簡化很多,省去了手動屬性賦值
[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)一步完善
上面我們通過完善ResponseResult<T>類的封裝,確實(shí)在某些程度上節(jié)省了一部分操作,但是還是有點(diǎn)美中不足,那就是每次返回結(jié)果的時候,雖然定義了幾個常用的靜態(tài)方法去操作返回結(jié)果,但是每次還得通過手動去把ResponseResult<T>類給請出來才能使用,現(xiàn)在呢想在操作返回結(jié)果的時候不想看到它了。這個呢也很簡單,我們可以借助微軟針對MVC的Controller的封裝進(jìn)一步得到一個思路,那就是定義一個基類的Controller,我們在Controller基類中,把常用的返回結(jié)果封裝一些方法,這樣在Controller的子類中呢就可以直接調(diào)用這些方法,而不需要關(guān)注如何去編寫方法的返回類型了,話不多說動手把Controller基類封裝起來
[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);
}
}
有了這么一個大神的輔助,一切似乎又向著美好進(jìn)發(fā)了一步,這樣的話每次我們自定義的Controller可以繼承ApiControllerBase類,從而使用里面的簡化操作。所以再寫起來代碼,大概是這么一個效果
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);
}
}
這個時候確實(shí)變得很美好了,但是還是沒有逃脫一點(diǎn),那就是我還是得通過特定的方法來得到一個ResponseResult<T>類型的返回結(jié)果,包括我們給ResponseResult<T>類封裝靜態(tài)方法,或者甚至是定義ApiControllerBase基類,都是為了進(jìn)一步簡化這個操作?,F(xiàn)在呢我想告別這個限制,我能不能把返回的結(jié)果直接就默認(rèn)的轉(zhuǎn)化成ResponseResult<T>類型的結(jié)果呢?當(dāng)然可以,這也是通過ASP.NET Core的封裝思路中得到的啟發(fā),借助implicit自動完成隱式轉(zhuǎn)換,這個在ASP.NET Core的ActionResult<T>類中也有體現(xiàn)
public static implicit operator ActionResult<TValue>(TValue value)
{
return new ActionResult<TValue>(value);
}
通過這個思路我們可以進(jìn)一步完善ResponseResult<T>類的實(shí)現(xiàn)方式,給它添加一個隱式轉(zhuǎn)換的操作,僅僅定義一個方法即可,在ResponseResult<T>類中繼續(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 };
}
這種對于絕大部分返回成功結(jié)果的時候提供了非常簡化的操作,這個時候如果你再去使用action的時候就可以進(jì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)換,所以這個時候我們就可以直接返回結(jié)果了,而不需要手動對結(jié)果返回值進(jìn)行包裝。
漏網(wǎng)之魚處理
在上面我們?yōu)榱吮M量簡化action返回ResponseResult<T>的統(tǒng)一返回結(jié)構(gòu)的封裝,已經(jīng)對ResponseResult<T>類進(jìn)行了許多的封裝,并且還通過封裝ApiControllerBase基類進(jìn)一步簡化這一操作,但是終究還是避免不了一點(diǎn),那就是很多時候可能想不起來對action的返回值去加ResponseResult<T>類型的返回值,但是我們之前的所有封裝都得建立在必須要聲明ResponseResult<T>類型的返回值的基礎(chǔ)上才行,否則就不存在統(tǒng)一返回格式這一說法了。所以針對這些漏網(wǎng)之魚,我們必須要有統(tǒng)一的攔截機(jī)制,這樣才能更完整的針對返回結(jié)果進(jìn)行處理,針對這種對action返回值的操作,我們首先想到的就是定義過濾器進(jìn)行處理,因此筆者針對這一現(xiàn)象封裝了一個統(tǒng)一包裝結(jié)果的過濾器,實(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則說明不需要對返回結(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>類型的則不需要進(jìn)行再次包裝了
if (objectResult.DeclaredType.IsGenericType && objectResult.DeclaredType?.GetGenericTypeDefinition() == typeof(ResponseResult<>))
{
return;
}
rspResult.Data = objectResult.Value;
context.Result = new ObjectResult(rspResult);
}
return;
}
}
}
在使用WebAPI的過程中,我們的action絕大部分是直接返回ViewModel或Dto而并沒有返回ActionResult類型相關(guān),但是無妨,這個時候MVC的底層操作會為我們將這些自定義的類型包裝成ObjectResult類型的,因此我們的ResultWrapperFilter過濾器也是通過這一機(jī)制進(jìn)行操作的。這里有兩點(diǎn)需要考慮的
- 首先是,我們必須要允許并非所有的返回結(jié)果都要進(jìn)行
ResponseResult<T>的包裝,為了滿足這一需求我們還定義了NoWrapperAttribute來實(shí)現(xiàn)這一效果,只要Controller或Action有NoWrapperAttribute的修飾則不對返回結(jié)果進(jìn)行任何處理。 - 其次是,如果我們的Action上的返回類型已經(jīng)是
ResponseResult<T>類型的,則也不需要對返回結(jié)果進(jìn)行再次的包裝。
關(guān)于ResultWrapperFilter的定義其實(shí)很簡單,因?yàn)樵谶@里它只是起到了一個標(biāo)記的作用
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class NoWrapperAttribute:Attribute
{
}
到了這里,還有一種特殊的情況需要注意,那就是當(dāng)程序發(fā)生異常的時候,我們上面的這些機(jī)制也是沒有辦法生效的,因此我們還需要定義一個針對全局異常處理的攔截機(jī)制,同樣是可以使用統(tǒng)一異常處理過濾器進(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;
}
}
}
寫完過濾器了,千萬不能忘了全局注冊一下,否則它也就只能看看了,不會起到任何效果
builder.Services.AddControllers(options =>
{
options.Filters.Add<ResultWrapperFilter>();
options.Filters.Add<GlobalExceptionFilter>();
});
漏網(wǎng)之魚另一種處理
當(dāng)然針對上面兩種針對漏網(wǎng)之魚的處理,在ASP.NET Core上還可以通過中間件的方式進(jìn)行處理,至于過濾器和中間件有何不同,相信大家已經(jīng)非常清楚了,核心不同總結(jié)起來就一句話二者的處理階段不同,即針對管道的生命周期處理是不一樣的,中間件可以處理任何生命周期在它之后的場景,但是過濾器只管理Controller這一塊的一畝三分地但是針對結(jié)果包裝這一場景,筆者覺得使用過濾器的方式更容易處理一點(diǎn),因?yàn)楫吘刮覀兪且僮鰽ction的返回結(jié)果,通過過濾器中我們可以直接拿到返回結(jié)果的值。但是這個操作如果在中間件里進(jìn)行操作的話,只能通過讀取Response.Body進(jìn)行操作了,筆者這里也封裝了一個操作,如下所示
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沒辦法進(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)
{
//只針對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的返回類型
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);
//對原始結(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歸還回來
context.Response.Body = originalResponseBody;
}
});
}
}
相信通過上面的處理,我們就可以更容易的看出來,誰更容易的對統(tǒng)一結(jié)果進(jìn)行包裝處理了,畢竟我們是針對Action的返回結(jié)果進(jìn)行處理,而過濾器顯然就是為針對Controller和Action的處理而生的。但是通過中間件的方式能更完整的針對結(jié)果進(jìn)行處理,因?yàn)樵S多時候我們可能是在自定義的中間件里直接攔截請求并返回,但是根據(jù)二八原則這種情況相對于Action的返回值畢竟是少數(shù),有這種情況我們可以通過直接ResponseResult<T>封裝的方法進(jìn)行返回操作,也很方便。但是這個時候呢,關(guān)于異常處理我們通過全局異常處理中間件,則能更多的處理更多的場景,且沒有副作用,看一下它的定義
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());
}
});
});
}
使用全局異常梳理中間件是沒有副作用的,主要因?yàn)樵诋惓L幚淼臅r候我們不需要讀取Response.Body進(jìn)行讀取操作的,所以至于是選擇異常處理中間件還是過濾器,大家可以針對自己的實(shí)際場景進(jìn)行選擇,兩種方式都是可以的。
總結(jié)
本文主要是展示了針對ASP.NET Core WeApi結(jié)果統(tǒng)一返回格式的相關(guān)操作,通過示例我們一步一步的展示了完成這一目標(biāo)的不斷升級的實(shí)現(xiàn),雖然整體看起來比較簡單,但是卻承載著筆者一次又一次的思考升級。每次實(shí)現(xiàn)完一個階段,都會去想有沒有更好的方式去完善它。這其中還有一些思路來自微軟源碼為我們提供的思路,所以很多時候還是建議大家去看一看源碼的,可以在很多時候?yàn)槲覀兲峁┮环N解決問題的思路。正如我看到的一句話,讀源碼也是一種圍城,外面的人不想進(jìn)去,里面的人不想出來。如果大家有更好的實(shí)現(xiàn)方式,歡迎一起討論。曾經(jīng)的時候我會為自己學(xué)到了一個新的技能而感到高興,到了后來我會對有一個好的思路,或者好的解決問題的方法而感到高興。讀萬卷書很重要,行萬里路同樣重要,讀書是沉淀,行路是實(shí)踐,結(jié)合到一起才能更好的促進(jìn),而不是只選擇一種。
到此這篇關(guān)于ASP.NET Core WebApi返回結(jié)果統(tǒng)一包裝實(shí)踐的文章就介紹到這了,更多相關(guān)ASP.NET Core WebApi返回結(jié)果內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Asp.net下用JQuery找出哪一個元素引起PostBack
在Asp.net webform中,如何找出哪一個按鈕觸發(fā)Button PostBack事件。2010-06-06
C#中efcore-ShardingCore呈現(xiàn)“完美”分表
本文簡單的介紹了efcore的分表,著重介紹了efcore下最完美的分表組件ShardingCore,可以幫助大家更好的學(xué)習(xí),感興趣的小伙伴可以參考一下2021-08-08
asp.net正則表達(dá)式刪除指定的HTML標(biāo)簽的代碼
抓取某網(wǎng)頁的數(shù)據(jù)后(比如描述),如果照原樣顯示的話,可能會因?yàn)樗锩姘瑳]有閉合的HTML標(biāo)簽而打亂了格式,也可能它里面用了比較讓人 費(fèi)解 的HTML標(biāo)簽,把預(yù)訂的格式攪亂.2010-09-09
FileUpload1 上傳文件類型驗(yàn)證正則表達(dá)式
FileUpload1 上傳文件類型驗(yàn)證正則表達(dá)式...2006-10-10
ASP.NET中將聲音文件添加到資源中并進(jìn)行播放的方法
這篇文章主要介紹了ASP.NET中將聲音文件添加到資源中并進(jìn)行播放的方法,實(shí)例分析了聲音文件的添加及播放實(shí)現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-07-07
AspNetCore&MassTransit?Courier實(shí)現(xiàn)分布式事務(wù)的詳細(xì)過程
MassTransit?Courier是一種用于創(chuàng)建和執(zhí)行帶有故障補(bǔ)償?shù)姆植际绞聞?wù)的機(jī)制,它可以用于滿足本地事務(wù)的需求,也可以在分布式系統(tǒng)中實(shí)現(xiàn)分布式事務(wù),這篇文章主要介紹了AspNetCore&MassTransit?Courier實(shí)現(xiàn)分布式事務(wù),需要的朋友可以參考下2022-10-10
HttpWebRequest的常見錯誤使用TcpClient可避免
有時使用HttpWebRequest對象會出現(xiàn)錯誤有三種服務(wù)器提交了協(xié)議沖突/基礎(chǔ)連接已經(jīng)關(guān)閉:連接被意外關(guān)閉/無法發(fā)送具有此謂詞類型的內(nèi)容正文,感興趣的朋友可以參考下本文2013-02-02
SignalR Self Host+MVC等多端消息推送服務(wù)(二)
這篇文章主要為大家詳細(xì)介紹了SignalR Self Host+MVC等多端消息推送服務(wù)的第二篇,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06

