In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
Most people do not understand the knowledge points of this article "the method of unified packaging of ASP.NET Core WebApi return results", so the editor summarizes the following contents to you, detailed contents, clear steps, and has a certain reference value. I hope you can get something after reading this article. Let's take a look at this "ASP.NET Core WebApi return results unified packaging method" article.
Unified result class encapsulation
First of all, if the format of the returned results is unified, there must be a unified wrapper class to wrap all the returned results, because the specific data returned is consistent in format, but the type of the specific value is uncertain, so we need to define a generic class here. Of course, it's OK to use dynamic or object types if you don't choose generic classes, but this may bring two disadvantages.
First, there may be the operation of packing and unpacking.
Second, if swagger is introduced, it is impossible to generate the return type, because both dynamic and object types can determine the return type only when a specific action is executed, but the structure of swagger is obtained when it is run for the first time, so it is impossible to perceive the specific type.
Define wrapper class
We also talked about the advantages of defining generic classes, so let's just wrap a wrapper class returned by the result.
Public class ResponseResult {/ status result / public ResultStatus Status {get; set;} = ResultStatus.Success; private string? _ msg; / message description / public string? Message {get {/ / if there is no custom result description, you can get the description of the current state return! string.IsNullOrEmpty (_ msg)? _ msg: EnumHelper.GetDescription (Status);} set {_ msg = value }} / return result / public T Data {get; set;}}
The ResultStatus here is an enumeration type, which is used to define the specific return status code, which is used to determine whether the returned result is normal, abnormal or otherwise. I simply define the simplest example here, and you can expand it if necessary.
Public enum ResultStatus {[Description ("request successful")] Success = 1, [Description ("request failed")] Fail = 0, [Description ("request exception")] Error =-1}
In this case, it is a good choice to define the enumeration type and combine its DescriptionAttribute characteristics to describe the meaning of enumeration. first, it can uniformly manage the meaning of each state, and secondly, it is more convenient to obtain the corresponding description of each state. In this way, if there is no custom result description, you can get a description of the current state to act as the default. At this time, when writing a specific action, it will have the following effect
[HttpGet ("GetWeatherForecast")] public ResponseResult GetAll () {var datas = Enumerable.Range (1,5) .Select (index = > new WeatherForecast {Date = DateTime.Now.AddDays (index), TemperatureC = Random.Shared.Next (- 20,55), Summary = Summares [Random.Shared.Next (Summaries.Length)]}); return new ResponseResult {Data = datas};}
In this way, you can return a ResponseResult result every time you write action. This shows the advantage of using enumerations to define status codes. In quite a number of scenarios, we can omit the status codes or even the writing of messages. After all, in many cases, the more brief the code, the better, not to mention some high-frequency operations.
Upgrade the operation
Although we define ResponseResult to uniformly package the returned results, it is undoubtedly not convenient to new every time, and it is troublesome enough to assign values to attributes every time. At this time, I think, if only there are relevant auxiliary methods to simplify the operation, the method does not need to meet the scene, that is, it is enough, and the most important thing is to support expansion. So, further upgrade the result wrapper class to simplify the operation
Public class ResponseResult {/ status result / public ResultStatus Status {get; set;} = ResultStatus.Success; private string? _ msg; / message description / public string? Message {get {return! string.IsNullOrEmpty (_ msg)? _ msg: EnumHelper.GetDescription (Status);} set {_ msg = value;}} / return result / public T Data {get; set } / data returned in successful status / public static ResponseResult SuccessResult (T data) {return new ResponseResult {Status = ResultStatus.Success, Data = data} } / failure status returns result / status code / failure message / public static ResponseResult FailResult (string? Msg = null) {return new ResponseResult {Status = ResultStatus.Fail, Message = msg};} / abnormal status returned result / status code / exception information / public static ResponseResult ErrorResult (string? Msg = null) {return new ResponseResult {Status = ResultStatus.Error, Message = msg};} / the custom status returns the result / public static ResponseResult Result (ResultStatus status, T data, string? Msg = null) {return new ResponseResult {Status = status, Data = data, Message = msg};}}
Several methods are further encapsulated here. As for the specific encapsulation of several of these methods, it would be better if that sentence is enough. Here I encapsulate several commonly used operations, such as success state, failure state, abnormal state, and the most complete state. These states can basically meet most scenarios, if not enough, you can further encapsulate several methods. In this way, it will be much simpler when using action, eliminating the need for manual attribute assignment.
[HttpGet ("GetWeatherForecast")] public ResponseResult GetAll () {var datas = Enumerable.Range (1,5) .Select (index = > new WeatherForecast {Date = DateTime.Now.AddDays (index), TemperatureC = Random.Shared.Next (- 20,55), Summary = Summares [Random.Shared.Next (Summaries.Length)]}); return ResponseResult.SuccessResult (datas);} further perfect
Above, we do save some operations to some extent by improving the encapsulation of the ResponseResult class, but there are still some imperfections, that is, every time the result is returned, although several commonly used static methods are defined to operate the returned result, each time we have to manually invite the ResponseResult class to come out to use it, now I don't want to see it when the operation returns the result. This is also very simple. We can further get an idea with the help of Microsoft's Controller encapsulation for MVC, that is, to define a base class Controller. We encapsulate some methods of the commonly used return results in the Controller base class, so that we can directly call these methods in the subclass of Controller, without paying attention to how to write the return type of the method, not to mention encapsulating the Controller base class.
[ApiController] [Route ("api/ [controller]")] public class ApiControllerBase: ControllerBase {/ data returned by successful status / protected ResponseResult SuccessResult (T result) {return ResponseResult.SuccessResult (result) } / failure status returns result / status code / failure message / protected ResponseResult FailResult (string? Msg = null) {return ResponseResult.FailResult (msg);} / abnormal status returns result / status code / exception information / protected ResponseResult ErrorResult (string? Msg = null) {return ResponseResult.ErrorResult (msg);} / Custom status returns the result / protected ResponseResult Result (ResultStatus status, T result, string? Msg = null) {return ResponseResult.Result (status, result, msg);}}
With the help of such a great god, everything seems to be another step towards the better, so that every time our custom Controller can inherit the ApiControllerBase class, thus using the simplified operation inside. So if you write the code again, it will probably have this effect.
Public class WeatherForecastController: ApiControllerBase {private static readonly string [] Summaries = new [] {"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"} [HttpGet ("GetWeatherForecast")] public ResponseResult GetAll () {var datas = Enumerable.Range (1,5) .Select (index = > new WeatherForecast {Date = DateTime.Now.AddDays (index), TemperatureC = Random.Shared.Next (- 20,55), Summary = Summares [Random.Shared.Next (Summaries.Length)]}); return SuccessResult (datas);}}
It's really nice at this time, but I still don't escape the fact that I still have to get a return result of a ResponseResult type through certain methods, including wrapping static methods for the ResponseResult class, or even defining the ApiControllerBase base class, in order to further simplify this operation. Now that I want to say goodbye to this restriction, can I directly convert the returned results to ResponseResult results by default? Of course, this is also inspired by the encapsulation idea of ASP.NET Core, which automatically completes implicit conversion with the help of implicit, which is also reflected in the ActionResult class of ASP.NET Core.
Public static implicit operator ActionResult (TValue value) {return new ActionResult (value);}
Through this idea, we can further improve the implementation of the ResponseResult class, add an implicit conversion operation to it, define only a method, and continue to improve in the ResponseResult class.
/ / implicitly convert T to ResponseResult/// public static implicit operator ResponseResult (T value) {return new ResponseResult {Data = value};}
This provides a very simplified operation when most successful results are returned. At this time, if you use action again, you can further simplify the operation of return values.
[HttpGet ("GetWeatherForecast")] public ResponseResult GetAll () {var datas = Enumerable.Range (1,5) .Select (index = > new WeatherForecast {Date = DateTime.Now.AddDays (index), TemperatureC = Random.Shared.Next (- 20,55), Summary = Summares [Random.Shared.Next (Summaries.Length)]}); return datas.ToList ();}
Because we have defined an implicit conversion from T to ResponseResult, we can return the result directly at this time without having to manually wrap the result return value.
Treatment of fish that missed the net
In order to simplify the encapsulation of the unified return structure of ResponseResult returned by action, we have encapsulated the ResponseResult class as much as possible, and further simplified this operation by encapsulating the ApiControllerBase base class, but we can't avoid one point after all, that is, we may not remember to add the return value of ResponseResult type to the return value of action in many cases. But all of our previous wrappers have to be based on the fact that the return value of the ResponseResult type must be declared, otherwise there will be no uniform return format. Therefore, for these missing fish, we must have a unified interception mechanism, so that we can deal with the returned results more completely. for this operation on the return value of action, the first thing we think of is to define a filter to deal with it, so the author encapsulates a filter with unified packaging results for this phenomenon, and the implementation is as follows
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 () / / if NoWrapperAttribute is included, the returned result does not need to be wrapped. Instead, the original value if (actionWrapper! = null | | controllerWrapper! = null) {return;} / / is implemented according to the actual needs: var rspResult = new ResponseResult (); if (context.Result is ObjectResult) {var objectResult = context.Result as ObjectResult If (objectResult?.Value = = null) {rspResult.Status = ResultStatus.Fail; rspResult.Message = "Resource not found"; context.Result = new ObjectResult (rspResult) } else {/ / if the returned result is already of type ResponseResult, there is no need to repackage if (objectResult.DeclaredType.IsGenericType & & objectResult.DeclaredType?.GetGenericTypeDefinition () = = typeof (ResponseResult)) {return;} rspResult.Data = objectResult.Value Context.Result = new ObjectResult (rspResult);} return;}}
In the process of using WebAPI, most of our action returns ViewModel or Dto directly without returning ActionResult type correlation, but it doesn't matter. At this time, the underlying operation of MVC will wrap these custom types into ObjectResult types for us, so our ResultWrapperFilter filter is also operated through this mechanism. There are two points to consider.
First of all, we must allow not all returned results to be wrapped in ResponseResult. In order to meet this requirement, we also define NoWrapperAttribute to achieve this effect, as long as Controller or Action is decorated with NoWrapperAttribute, there is no processing of the returned results.
Second, if the return type on our Action is already of type ResponseResult, there is no need to re-wrap the return result.
The definition of ResultWrapperFilter is actually very simple, because here it just acts as a tag.
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class NoWrapperAttribute:Attribute {}
At this point, there is another special situation to pay attention to, that is, when an exception occurs in the program, the above mechanisms do not work, so we also need to define an interception mechanism for global exception handling. you can also use a unified exception handling filter to implement the following
Public class GlobalExceptionFilter: IExceptionFilter {private readonly ILogger _ logger; public GlobalExceptionFilter (ILogger logger) {_ logger = logger;} public void OnException (ExceptionContext context) {/ / exception return result wrapper var rspResult = ResponseResult.ErrorResult (context.Exception.Message); / / logging _ 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;}
After writing the filter, you must not forget to register globally, otherwise it will only have a look at it and will not have any effect.
Builder.Services.AddControllers (options = > {options.Filters.Add (); options.Filters.Add ();})
Another way to deal with the fish that missed the net
Of course, for the above two kinds of processing for the missing fish, we can also deal with it through middleware on ASP.NET Core. As for the difference between filter and middleware, I believe we are already very clear. The core is different. To sum up, the processing stages of the two are different, that is, the life cycle processing for the pipeline is different, and the middleware can handle any scenario after it. But the filter only manages the one-mu / 3 land of Controller, but for the scenario of result packaging, the author thinks it is easier to deal with it by using the filter, because after all, we are going to manipulate the return result of Action, through which we can directly get the value of the returned result. However, if this operation is performed in the middleware, it can only be operated by reading Response.Body. The author also encapsulates an operation here, as shown below.
Public static IApplicationBuilder UseResultWrapper (this IApplicationBuilder app) {var serializerOptions = app.ApplicationServices.GetRequiredService (). Value.JsonSerializerOptions; serializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; return app.Use (async (context, next) = > {var originalResponseBody = context.Response.Body Try {/ / because Response.Body cannot read directly, a special operation using var swapStream = new MemoryStream (); context.Response.Body = swapStream; await next () is required. / / to determine whether there is an abnormal status code, special processing is required for if (context.Response.StatusCode = = StatusCodes.Status500InternalServerError) {context.Response.Body.Seek (0, SeekOrigin.Begin); await swapStream.CopyToAsync (originalResponseBody); return } var endpoint = context.Features.Get ()? .Endpoint If (endpoint! = null) {/ / process application/json results only if (context.Response.ContentType.ToLower () .Contains ("application/json")) {/ / determine whether the endpoint contains NoWrapperAttribute NoWrapperAttribute noWrapper = endpoint.Metadata.GetMetadata () If (noWrapper! = null) {context.Response.Body.Seek (0, SeekOrigin.Begin); await swapStream.CopyToAsync (originalResponseBody); return } / / get the return type of Action var controllerActionDescriptor = context.GetEndpoint ()? .Metadata.Getmetadata () If (controllerActionDescriptor! = null) {/ / Special handling of generics var returnType = controllerActionDescriptor.MethodInfo.ReturnType If (returnType.IsGenericType & & (returnType.GetGenericTypeDefinition ()) = = typeof (Task) | | returnType.GetGenericTypeDefinition () = = typeof (ValueTask) {returnType = returnType.GetGenericArguments () [0] } / / No wrapper is performed if the endpoint is already ResponseResult (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) / / deserialize the original result var result = await JsonSerializer.DeserializeAsync (context.Response.Body, returnType, serializerOptions); / / wrap the original result var bytes = JsonSerializer.SerializeToUtf8Bytes (ResponseResult.SuccessResult (result), serializerOptions) New MemoryStream (bytes) .CopyTo (originalResponseBody); return;} context.Response.Body.Seek (0, SeekOrigin.Begin); await swapStream.CopyToAsync (originalResponseBody) } finally {/ / return the original Body context.Response.Body = originalResponseBody;}});}}
I believe that through the above processing, we can more easily see who is easier to wrap the unified result. After all, we are dealing with the returned results of Action, and the filter is obviously created for the processing of Controller and Action. However, we can deal with the results more completely through middleware, because many times we may directly intercept the request and return it in the custom middleware, but according to the 2008 principle, this situation is after all a small number compared to the return value of Action. In this case, we can use the direct ResponseResult encapsulation method to return operation, which is also very convenient. But at this time, with regard to exception handling, through the global exception handling middleware, we can handle more scenarios without side effects. Take a look at its definition.
Public static IApplicationBuilder UseException (this IApplicationBuilder app) {return app.UseExceptionHandler (configure = > {configure.Run (async context = > {var exceptionHandlerPathFeature = context.Features.Get (); var ex = exceptionHandlerPathFeature?.Error; if (ex! = null) {var _ logger = context.RequestServices.GetService (); var rspResult = ResponseResult.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 ());}
There are no side effects when using global exception carding middleware, mainly because we do not need to read Response.Body for reading operation during exception handling, so as to choose exception handling middleware or filter, you can choose according to your actual scenario, both of which are possible.
The above is about the content of this article on "the method of unified packaging of ASP.NET Core WebApi return results". I believe we all have a certain understanding. I hope the content shared by the editor will be helpful to you. If you want to know more related knowledge, please pay attention to the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.