.Net Core API 全局修改响应

.Net Core API Modify Response Globally

我正在使用 Angular 客户端开发 .Net Core API 项目。而APIProgram.cs文件配置如下,

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("http://127.0.0.1", "http://localhost", "https://127.0.0.1", "https://localhost")
                                .AllowAnyHeader()
                                .AllowAnyMethod()
                                .AllowAnyOrigin();
        });
});


builder.Services.AddControllers();



var app = builder.Build();

app.UseHsts();
app.UseHttpsRedirection();


// non-api routes Spa
app.Use(async (context, next) =>
{
    await next();

    string path = context.Request.Path.Value == null ? "" : context.Request.Path.Value;
    bool isNotFound = context.Response.StatusCode == 404;
    bool isApiRequest = path.StartsWith("/api/");
    bool hasPathExtension = Path.HasExtension(path);

    if (isNotFound && !(isApiRequest || hasPathExtension))
    {
        context.Request.Path = "/index.html";
        await next();
    }
});


app.UseDefaultFiles();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRequestLocalization();

if (app.Environment.IsDevelopment())
{
    app.UseCors();
}

//app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

//app.MapRazorPages();
app.MapControllers();

app.Run();

我需要做的是将每个控制器的响应、异常包装到全局的通用响应结构中(使用中间件或任何其他方法),如下所示,

[Serializable]
public class SampleResponse
{
    public string? RequestUrlFormat { get; set; }
    public string? RequestUrl { get; set; }
    public bool Success { get; set; } = false;
    public object? Data { get; set; }
    public object? Error { get; set; }
}

所以每个响应都是 200OK 成功响应,如果有异常详细信息。

ex: if there are no errors, 'Success' = 'true', has 'Data' and 'Error' = null. If there are errors, 'Success' = 'false', has 'Error' and 'Data' = null.

我已经有了其他值的方法。我只需要修改 API 响应,SampleResponse

的例外情况

我怎样才能做到这一点?

您可以通过两种方式实现。

通用操作:有一个static方法来接受所需的输入,自定义和格式化响应。

  1. 响应包装器的中间件。
  2. 在返回响应之前在每个控制器方法中调用静态方法。

工作样本https://github.com/nayanbunny/dotnet-webapi-response-wrapper-sample


请求Url可以从HttpContext

获得
using Microsoft.AspNetCore.Http.Extensions;

// GetDisplayUrl(), GetEncodedUrl(), Path etc.
var requestUrl = HttpContext.Current.Request.GetDisplayUrl()

普通静态方法

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;

public static class ResponseWrapManager
{
    public static SampleResponse ResponseWrapper(object? result, HttpContext context, object? exception = null) {

        requestUrl = context.Request.GetDisplayUrl();
        requestUrlFormat = string.Empty;
        status = result != null;
        data = result;
        error = exception != null ? ExceptionWrapper(exception) : null;

        // NOTE: Add any further customizations if needed

        var response = new SampleResponse {
            RequestUrl = requestUrl,
            RequestUrlFormat = requestUrlFormat,
            Status = status,
            Data = data,
            Error = error
        };

        return response;
    }
}

选项 1:响应包装器中间件

ResponseWrapperMiddleware.cs

using Newtonsoft.Json;

public class ResponseWrapperMiddleware
{
    private readonly RequestDelegate _next;
    
    public ResponseWrapperMiddleware(RequestDelegate next) => _next = next;

    public async Task Invoke(HttpContext context)
    {
        // Storing Context Body Response
        var currentBody = context.Response.Body;

        // Using MemoryStream to hold Controller Response
        using var memoryStream = new MemoryStream();
        context.Response.Body = memoryStream;

        // Passing call to Controller
        await _next(context);

        // Resetting Context Body Response
        context.Response.Body = currentBody;

        // Setting Memory Stream Position to Beginning
        memoryStream.Seek(0, SeekOrigin.Begin);

        // Read Memory Stream data to the end
        var readToEnd = new StreamReader(memoryStream).ReadToEnd();

        // Deserializing Controller Response to an object
        var result = JsonConvert.DeserializeObject(readToEnd);
        var exception = ""; // Exception Caught
        
        // Invoking Customizations Method to handle Custom Formatted Response
        var response = ResponseWrapManager.ResponseWrapper(result, context, exception);

        // return response to caller
        await context.Response.WriteAsync(JsonConvert.SerializeObject(response));
    }
}

Startup.cs (.Net < 6.0)

public void Configure(...)
{
    ...

    app.UseMiddleware<ResponseWrapperMiddleware>();

    ...
}

Program.cs (.Net >= 6.0)

app.UseMiddleware<ResponseWrapperMiddleware>();

选项 2:控制器方法中的响应格式

TextController.cs

[HttpGet(Name="GetTestData")]
public IEnumerable<Test> Get() 
{
    result = new Enumerable.Range(1,5).Select(index => new Test {
        Id = index,
        Name = $"Test {index}"
    });

    return ResponseWrapManager.ResponseWrapper(result, HttpContext, exception)
}