在常见的bs前后分离开发中,我们一般会统一返回的格式,这样更方便前端进行处理。

创建统一响应类

我们先创建一个统一返回的类,为了方便我们会提供两个静态方法(成功和失败)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class UniformResponse<T>
{
/// <summary>
/// 自定义响应码
/// </summary>
public string? StatusCode { get; set; }
/// <summary>
/// 请求结果
/// </summary>
public bool Succeeded { get; set; }
/// <summary>
/// 请求正确时的数据
/// </summary>
public T? Data { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public string? Error { get; set; }
/// <summary>
/// 响应的时间
/// </summary>
public long Timestamp { get; set; } = DateTime.Now.Ticks;
/// <summary>
/// 额外的信息
/// </summary>
public dynamic? Extra { get; set; }

/// <summary>
/// 成功
/// </summary>
/// <param name="data">响应数据</param>
/// <param name="statusCode">自定义的正确响应码</param>
/// <returns></returns>
public static UniformResponse<T> Succeed(T? data, string statusCode = "200")
=> new() { StatusCode = statusCode, Succeeded = true, Data = data};

/// <summary>
/// 失败
/// </summary>
/// <param name="errors">错误信息</param>
/// <param name="statusCode">自定义的错误响应码</param>
/// <returns></returns>
public static UniformResponse<T> Failure(string errors, string statusCode = "500")
=> new() { Error = errors, StatusCode = statusCode };
}

实现控制器方法结果包装

对控制器方法结果包装我们需要实现IResultFilter或者IAsyncResultFilter,这两个筛选器可以很方便的操作控制器方法返回的结果,我们通过对不同结果的不同处理,然后实现结果包装。这里主要针对WebAPI返回的一个情况,如果需要判断其他IActionResult的派生类则需另加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class UniformResponseFilter : IAsyncResultFilter
{
//支持额外信息注入
private readonly UniformResponseExtraHelper _extraHelper;
private readonly ILogger<UniformResponseFilter> _logger;

public UniformResponseFilter(UniformResponseExtraHelper extraHelper, ILogger<UniformResponseFilter> logger)
{
_extraHelper = extraHelper;
_logger = logger;
}

public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
if (context.Result is FileResult)
{
await next();
return;
}
UniformResponse<object> targetResult;
if (context.Result is BadRequestObjectResult badResult)
{
string error = "异常";
if (badResult.Value is ValidationProblemDetails details)
{
error = details.Title + ";" + string.Join(";", details.Errors.Select(e => $"【{e.Key}:{string.Join(",", e.Value)}】"));
}
_logger.LogError(error);
targetResult = UniformResponse<object>.Failure(error);
}
else if (context.Result is EmptyResult)
{
targetResult = UniformResponse<object>.Succeed(null);
}
else if (context.Result is JsonResult jr)
{
targetResult = UniformResponse<object>.Succeed(jr.Value);
}
else
{
var objResult = (ObjectResult)context.Result;
if (objResult.StatusCode == 200 || objResult.StatusCode is null)
{
var value = objResult.Value;
object? result = context.Result == null ? true : value;
targetResult = UniformResponse<object>.Succeed(result);
}
else
{
if (objResult.Value is ProblemDetails details)
{
targetResult = UniformResponse<object>.Failure(details.Title!);
}
else
{
targetResult = UniformResponse<object>.Failure("请求异常");
}
}
}

//添加额外信息
targetResult.Extra = _extraHelper.GetExtraDatas();

context.Result = new ObjectResult(targetResult);
await next();
}
}

支持额外信息的返回

我们在UniformResponse中有一个Extra的属性,这个就是方便后面我们在不修改原本Data属性的格式下额外返回的一个东西,它将支持在整个请求中随时返回任意格式的数据,同时我们会提供一个添加额外信息的帮助类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class UniformResponseExtraHelper
{
private readonly Dictionary<string, object> extraDatas;

public UniformResponseExtraHelper()
{
extraDatas = new();
}

/// <summary>
/// 添加一个额外的数据
/// </summary>
/// <param name="name"></param>
/// <param name="data"></param>
public void Add(string name, object data) => extraDatas.TryAdd(name, data);

/// <summary>
/// 获取所有额外的数据
/// </summary>
/// <returns></returns>
public Dictionary<string, object>? GetExtraDatas() => extraDatas.Any() ? extraDatas : null;
}

在Program.cs添加

1
2
3
4
5
6
//UniformResponseExtraHelper注册为作用域服务
builder.Services.AddScoped<UniformResponseExtraHelper>();
builder.Services.AddControllers(options =>
{
options.Filters.Add<UniformResponseFilter>();
});

运行测试

新建测试控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[ApiController]
[Route("api/[controller]/[action]")]
public class TestController : ControllerBase
{
private readonly UniformResponseExtraHelper _extraHelper;
private readonly ILogger<TestController> _logger;

public TestController(UniformResponseExtraHelper extraHelper, ILogger<TestController> logger)
{
_extraHelper = extraHelper;
_logger = logger;
}

public async Task<string> UniformResponseTest()
{
_extraHelper.Add("info","额外信息");
return await Task.FromResult("统一响应体测试");
}
}

请求结果:

1
2
3
4
5
6
7
8
9
10
{
"statusCode": "200",
"succeeded": true,
"data": "统一响应体测试",
"error": null,
"timestamp": 638140879982390000,
"extra": {
"info": "额外信息"
}
}