【问题标题】:How to autoformat API response in .NET core 5 Web API project?如何在 .NET core 5 Web API 项目中自动格式化 API 响应?
【发布时间】:2021-08-19 15:48:19
【问题描述】:

我有一个带有格式化响应的 API,如下所示:

{
    "statusCode": 200,
    "totalRecord": 2,
    "message": "Succesfully get merchants",
    "data": [
        {
            "id": 1,
            "name": "Leo Shop",
            "address": "Flower Street 9A",
            "isComplete": false,
            "createdAt": "2021-05-30T14:16:27.654233",
            "updatedAt": "2021-05-30T14:16:28.515476"
        },
        {
            "id": 3,
            "name": "Test Shop",
            "address": "Playing Street 12A",
            "isComplete": false,
            "createdAt": "2021-05-30T14:16:27.654233",
            "updatedAt": "2021-05-30T14:16:28.515476"
        }
    ]
}

这些响应背后的代码如下所示:

[HttpGet]
public async Task<ActionResult<IEnumerable<Merchant>>> GetMerchants()
{
    var data = await _context.Merchants.ToListAsync();
    ApiResponse res = new ApiResponse{StatusCode = 200, Message = "Succesfully get merchants", TotalRecord = data.Count, Data = data}; // Focus on this line
    return Ok(res);
}

我的问题,如何自动将默认响应转换为 ApiResponse 模型,而无需在每个控制器内的每个操作返回时重复写入 new ApiResponse() 模型?

希望任何人都可以帮助我.. 在此先谢谢大家

【问题讨论】:

  • 您的返回值看起来不正确:您没有返回IEnumerable&lt;Merchant&gt; 的列表,您返回的是响应对象的单个实例,不是吗?
  • 您可以创建中间件来拦截响应并修改响应主体以合并您的 ApiResponse 类。
  • @PanagiotisKanavos...什么序列化选项将消除在每个端点中新建 ApiResponse 的需要?这个问题似乎更多是关于消除重复代码。也许我误解了它?
  • 你真的需要这样的信息吗?当您从 WebAPI 获得成功响应时,它将具有 HTTP 代码 200。在任何情况下,接收到的记录数都是已知的。 “成功获取商家”消息重复了已知信息:“成功” - 代码 200,“获取商家” - 您向其发出请求的 WebAPI 地址。因此,根据 Web API 指南的建议,我将只返回一组数据。

标签: c# asp.net-core entity-framework-core asp.net-core-webapi .net-5


【解决方案1】:

您可以在 MVC 过滤器管道中使用结果过滤器

MVC Filter pipeline

所以改变你的控制器看起来像这样

[HttpGet]
public Task<List<Merchant>> GetMerchants()
{
   return _context.Merchants.ToListAsync();
}

添加新类,名为 ResultManipulator.cs

  public class ResultManipulator : IResultFilter
        {
            public void OnResultExecuted(ResultExecutedContext context)
            {
                // do nothing
            }
    
            public void OnResultExecuting(ResultExecutingContext context)
            {
                //run code immediately before and after the execution of action results. They run only when the action method has executed successfully. They are useful for logic that must surround view or formatter execution.
                var result = context.Result as ObjectResult;
                var resultObj = result.Value;

    //change this ResultApi with your ApiResponse class
                var resp = new ResultApi
                {
                    Path = context.HttpContext.Request.Path.HasValue ? context.HttpContext.Request.Path.Value : "",
                    Method = context.HttpContext.Request.Method
                };
    
                if (resultObj is not null && resultObj is not Unit)
                    resp.Payload = resultObj;
    
//you can also change this from System.Text.Json to newtonsoft if you use newtonsoft
                context.Result = new JsonResult(resp, new JsonSerializerOptions()
                {
                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
                    Converters = { new JsonStringEnumConverter() }
                });
            }
        }

别忘了在 Startup 中添加 ResultManipulator 类

services.AddControllers(options =>
            {
                options.Filters.Add(new ResultManipulator());
            })

希望能解决你的问题

【讨论】:

  • 这可以在 .NET core 5 Web API 项目中使用吗?因为我想,我没有在这个项目中使用 MVC。谢谢文迪
  • 可能在netcore 5 web api项目中,关于.net核心框架内置的管道请求中间件
【解决方案2】:

试试这个作为中间件解决方案。它将改变响应。

public class YourResponseMiddleware
    {
        private readonly RequestDelegate _next;    
        public YourResponseMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            var existingBody = context.Response.Body;
            using (var newBody = new MemoryStream())
            {
                context.Response.Body = newBody;
                await _next(context);
                var newResponse = await FormatResponse(context.Response);
                context.Response.Body = new MemoryStream();
                newBody.Seek(0, SeekOrigin.Begin);
                context.Response.Body = existingBody;
                var newContent = new StreamReader(newBody).ReadToEnd();
             
                // Send modified content to the response body.
                //
                await context.Response.WriteAsync(newResponse);
            }
        }
    

        private async Task<string> FormatResponse(HttpResponse response)
        {
            //We need to read the response stream from the beginning...and copy it into a string...I'D LIKE TO SEE A BETTER WAY TO DO THIS
            //
            response.Body.Seek(0, SeekOrigin.Begin);
            var content= await new StreamReader(response.Body).ReadToEndAsync();    
            var yourResponse = new YourResponse (); // CREATE THIS CLASS
            yourResponse.StatusCode = response.StatusCode;
            if(!IsResponseValid(response))
            {
                yourResponse.ErrorMessage = content;
            }
            else
            {
                yourResponse.Content = content;
            }
            yourResponse.Size = response.ToString().Length;
            var json = JsonConvert.SerializeObject(yourResponse );

            //We need to reset the reader for the response so that the client an read it
            response.Body.Seek(0, SeekOrigin.Begin);    
            return $"{json}";
        }

        private bool IsResponseValid(HttpResponse response)
        {
            if ((response != null)
                && (response.StatusCode == 200
                || response.StatusCode == 201
                || response.StatusCode == 202))
            {
                return true;
            }
            return false;
        }
    }

 public static class ResponseMiddleware
    {
        public static void UseYourResponseMiddleware(this IApplicationBuilder app)
        {
            app.UseMiddleware<YourResponseMiddleware>();
        }
    }

在 Startup.cs (Configure()) 中

 app.UseYourResponseMiddleware();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-21
    • 2021-08-04
    • 2022-10-06
    • 2021-05-22
    • 2019-07-23
    • 1970-01-01
    相关资源
    最近更新 更多