IT俱乐部 ASP.NET .Net MinimalApis响应返回值的详细过程

.Net MinimalApis响应返回值的详细过程

前言

本文主要讲 MinimalApis 中的使用自定义IResultModel和系统自带IResult做响应返回值。
MinimalApis支持以下类型的返回值:

  • string – 这包括 Task 和 ValueTask

  • T(任何其他类型)- 这包括 Task 和 ValueTask

  • 基于 IResult – 这包括 Task 和 ValueTask

    本文的完整源代码在文末

string 返回值

行为 Content-Type
框架将字符串直接写入响应。 text/plain

200 状态代码与 text/plain Content-Type 标头和以下内容一起返回

1
Hello World

T(任何其他类型)返回值

我们上面说的自定义 IResultModel就是用这种模式处理的

行为 Content-Type
框架 JSON 序列化响应。 application/json

MinimalApis 框架 Json 序列化全局配置如下

1
2
3
4
5
6
7
8
//通过调用 ConfigureHttpJsonOptions 来全局配置应用的选项
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;//忽略循环引用
    options.SerializerOptions.WriteIndented = true;
    options.SerializerOptions.IncludeFields = true;
    options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});

返回 T

1
2
3
app.MapGet("/TestT", User () => new User() { Name = "Ruipeng", Email = "xxx@163.com", Age = 18 })
   .WithSummary("测试类")
   .Produces();

返回值

1
2
3
4
5
{
  "name": "Ruipeng",
  "email": "xxx@163.com",
  "age": 18
}

200 状态代码与 application/json Content-Type 标头和以下内容一起返回

这个 HttpCode状态码只能返回 200,且不支持多种返回形式,比较局限

统一响应格式代码

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
public interface IResultModel
{
    /// <summary>
    ///     是否成功
    /// </summary>
    bool? IsSuccess { get; }
    /// <summary>
    ///     错误信息
    /// </summary>
    string? Message { get; }
    /// <summary>
    ///     业务码,用于业务中自定义
    /// </summary>
    int? StatusCode { get; set; }
    /// <summary>
    ///     时间戳
    /// </summary>
    long? Timestamp { get; }
}
/// <summary>
///     返回结果模型泛型接口
/// </summary>
///
public interface IResultModel : IResultModel
{
    /// <summary>
    ///     返回数据
    /// </summary>
    T? Data { get; }
}

实现

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
public class ResultModel : IResultModel
{
    public ResultModel()
    {
        Timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
    }
    /// <summary>
    ///     处理是否成功
    /// </summary>
    public bool? IsSuccess { get; set; }
    /// <summary>
    ///     错误信息
    /// </summary>
    public string? Message { get; set; }
    /// <summary>
    ///     业务码
    /// </summary>
    public int? StatusCode { get; set; }
    /// <summary>
    ///     时间戳
    /// </summary>
    public long? Timestamp { get; }
    /// <summary>
    ///     返回数据
    /// </summary>
    public T? Data { get; set; }
    /// <summary>
    ///     成功
    /// </summary>
    ///
    public ResultModel Success(T? data = default)
    {
        this.IsSuccess = true;
        StatusCode = 200;
        Data = data;
        return this;
    }
    /// <summary>
    ///     失败
    /// </summary>
    /// 说明
    ///
    public ResultModel Failed(string? msg = "failed", int? code = 500)
    {
        IsSuccess = false;
        Message = msg;
        StatusCode = code;
        return this;
    }
}
/// <summary>
///     返回结果
/// </summary>
public static class ResultModel
{
    /// <summary>
    ///     数据已存在
    /// </summary>
    ///
    public static IResultModel HasExists => Failed("data already exists");
    /// <summary>
    ///     数据不存在
    /// </summary>
    public static IResultModel NotExists => Failed("data doesn't exist");
    /// <summary>
    ///     成功
    /// </summary>
    /// 返回数据
    ///
    public static IResultModel Success(T? data = default)
    {
        return new ResultModel().Success(data);
    }
    /// <summary>
    ///     成功
    /// </summary>
    /// 任务
    ///
    public static async Task> SuccessAsync(Task? task = default)
    {
        return task is not null && task != default ? new ResultModel().Success(await task) : new ResultModel();
    }
    /// <summary>
    ///     成功
    /// </summary>
    ///
    public static IResultModel Success()
    {
        return Success();
    }
    /// <summary>
    ///     失败
    /// </summary>
    /// 错误信息
    ///
    public static IResultModel Failed(string? error = null)
    {
        return new ResultModel().Failed(error ?? "failed");
    }
    /// <summary>
    ///     失败
    /// </summary>
    ///
    public static IResultModel Failed(string? error = null)
    {
        return Failed(error);
    }
    /// <summary>
    ///     根据布尔值返回结果
    /// </summary>
    ///
    ///
    public static IResultModel Result(bool success)
    {
        return success ? Success() : Failed();
    }
    /// <summary>
    ///     根据布尔值返回结果
    /// </summary>
    ///
    ///
    public static async Task Result(Task success)
    {
        return await success ? Success() : Failed();
    }
    /// <summary>
    ///     根据布尔值返回结果
    /// </summary>
    ///
    ///
    public static IResultModel Result(bool success)
    {
        return success ? Success() : Failed();
    }
    /// <summary>
    /// 时间戳起始日期
    /// </summary>
    public static readonly DateTime TimestampStart = new(1970, 1, 1, 0, 0, 0, 0);
}

定义接口

1
2
3
4
5
6
7
app.MapGet("/TestResultModel", IResultModel (int age) =>
{
    List users = [new User() { Name = "Ruipeng", Email = "xxx@163.com", Age = 18 }];
    return users.FirstOrDefault(_ => _.Age > age) is User user ? ResultModel.Success(user) : ResultModel.Failed();
})
   .WithSummary("测试自定义IResultModel")
   .Produces>();

封装了一个静态类来简化自定义类的创建,支持多个返回类型

返回值

1
2
3
4
5
6
7
8
9
{
  "isSuccess": true,
  "statusCode": 200,
  "timestamp": 1711001093,
  "data": {
    "name": "Ruipeng",
    "email": "xxx@163.com",
    "age": 18
  }

自定义类的自动包装实现

创建一个Attribute

1
2
[AttributeUsage(AttributeTargets.Method)]
public class EnableResponseWrapperAttribute : Attribute { }

创建中间件自动包装

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
public class ResponseWrapperMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext context)
    {
        if (context.GetEndpoint()?.Metadata.GetMetadata() is not null)
        {
            // 存储原始响应体流
            var originalResponseBodyStream = context.Response.Body;
            try
            {
                // 创建内存流以捕获响应
                using var memoryStream = new MemoryStream();
                context.Response.Body = memoryStream;
                // 调用管道中的下一个中间件
                await next(context);
                // 恢复原始响应体流并写入格式化结果
                context.Response.Body = originalResponseBodyStream;
                // 重置内存流位置并读取响应内容
                memoryStream.Seek(0, SeekOrigin.Begin);
                var readToEnd = await new StreamReader(memoryStream).ReadToEndAsync();
                var objResult = JsonSerializer.Deserialize(readToEnd);
                var result = new ResultModel<object data-origwidth="" data-origheight="" style="width: 1264px;">
                {
                    Data = objResult,
                    IsSuccess = true,
                    StatusCode = context.Response.StatusCode
                };
                await context.Response.WriteAsJsonAsync(result as object);
            }
            finally
            {
                // 确保在出现异常时恢复原始响应体流
                context.Response.Body = originalResponseBodyStream;
            }
        }
        else
        {
            await next(context);
        }
    }
}</object>

应用中间件

1
app.UseMiddleware();

创建测试接口

1
2
app.MapGet("/TestTestAutoWarpper", [EnableResponseWrapper] User () => new User() { Name = "Ruipeng", Email = "xxx@163.com", Age = 18 }).WithSummary("测试类")
   .Produces();

返回值

1
2
3
4
5
6
7
8
9
10
{
  "isSuccess": true,
  "statusCode": 200,
  "timestamp": 1711005201,
  "data": {
    "name": "Ruipeng",
    "email": "xxx@163.com",
    "age": 18
  }
}

为了方便测试在MinimalApis 的接口上如果添加了EnableResponseWrapperAttribute则通过中间件自动包装返回值

IResult 返回值

行为 Content-Type
框架调用 IResult.ExecuteAsync 由 IResult 实现决定

dotNet7 之后多了一个TypedResults类来替代 Results
IResult 接口定义一个表示 HTTP 终结点结果的协定。 静态 Results 类和静态 TypedResults 用于创建表示不同类型的响应的各种 IResult 对象。

返回 TypedResults(而不是 Results)有以下优点:

  • TypedResults 帮助程序返回强类型对象,这可以提高代码可读性、改进单元测试并减少运行时错误的可能性。
  • 实现类型会自动为 OpenAPI 提供响应类型元数据来描述终结点。
    实现在Microsoft.AspNetCore.Http.HttpResults
1
2
//Return IResult
app.MapGet("/IResult/TestResult", IResult () => Results.Ok(new User() { Name = "Ruipeng", Email = "xxx@163.com", Age = 18 }));

没有调用扩展方法 Produces

1
app.MapGet("/IResult/TestTypedResult", IResult () => TypedResults.Ok(new User() { Name = "Ruipeng", Email = "xxx@163.com", Age = 18 }));

可以看到 TypedResults 默认就会添加路由终结点的元数据描述

返回多个 IResult 实现类型

1
2
3
4
5
app.MapGet("/IResult/ReturnMultipleTypes", Results, NotFound> (int age) =>
{
    List users = [new User() { Name = "Ruipeng", Email = "xxx@163.com", Age = 18 }];
    return users.FirstOrDefault(_ => _.Age > age) is User user ? TypedResults.Ok(user) : TypedResults.NotFound();
});

图简单可以直接用 IResult 返回类型 但是,由于 TypedResults 帮助程序自动包含终结点的元数据,因此可以改为返回 Results, NotFound> 联合类型

IResult 自定义响应

添加 Html 扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static class ResultsExtensions
{
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions);
        return new HtmlResult(html);
    }
}
class HtmlResult(string html) : IResult
{
    private readonly string _html = html;
    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}
1
2
3
4
5
6
app.MapGet("/IResult/Html", () => Results.Extensions.Html(@$"
 
    <title>miniHTML</title><h1>Hello World</h1>
        <p>The time on the server is {DateTime.Now:O}</p>
     
"));

返回结果

1
2
3
4
<title>miniHTML</title><h1>Hello World</h1>
<p>The time on the server is 2024-03-21T17:31:36.2931959+08:00</p>

自定义 Json 格式

上面写了ConfigureHttpJsonOptions方法来配置全局请求的 Json 格式,下面则是针对单个路由终结点请求,方便一些个性化接口的处理

1
2
3
4
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{ WriteIndented = true };
app.MapGet("/IResult/CustomJsonConfig", () =>
    TypedResults.Json(new User() { Name = "Ruipeng", Email = "xxx@163.com", Age = 18 }, options));

返回 ProblemDetail

1
2
3
4
5
6
7
8
9
app.MapGet("/IResult/ProblemDetail", () =>
{
    var problemDetail = new ProblemDetails()
    {
        Status = StatusCodes.Status500InternalServerError,
        Title = "内部错误"
    };
    return TypedResults.Problem(problemDetail);
});

返回值

1
2
3
4
5
{
  "title": "内部错误",
  "status": 500
}

Microsoft.AspNetCore.Http.Results的扩展下,TypedResults 有非常多扩展的方法,比如处理文件,回调,流以及登录认证等,大家可以根据需求使用.

最后

用那种方式还是取决于项目的实际情况,如果你的系统是业务码和 httpStateCode要求分离的形式那建议用上面自定义统一响应的形式,要是没这方面的需求那dotNet自带的TypedResults使用起来就更合适。

到此这篇关于.Net MinimalApis响应返回值的文章就介绍到这了,更多相关.Net MinimalApis响应返回值内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!

本文收集自网络,不代表IT俱乐部立场,转载请注明出处。https://www.2it.club/code/asp-net/10738.html
上一篇
下一篇
联系我们

联系我们

在线咨询: QQ交谈

邮箱: 1120393934@qq.com

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部