免费资源网 – https://freexyz.cn/

目录
  • 前言
  • string 返回值
  • T(任何其他类型)返回值
    • 返回 T
    • 统一响应格式代码
    • 实现
    • 自定义类的自动包装实现
  • IResult 返回值
    • 返回多个 IResult 实现类型
    • IResult 自定义响应
    • 自定义 Json 格式
    • 返回 ProblemDetail
  • 最后

    前言

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

    • string – 这包括 Task<string> 和 ValueTask<string>

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

    • 基于 IResult – 这包括 Task<IResult> 和 ValueTask<IResult>

      本文的完整源代码在文末

    string 返回值

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

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

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

    Hello World

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

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

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

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

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

    返回 T

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

    返回值

    {
      "name": "Ruipeng",
      "email": "xxx@163.com",
      "age": 18
    }

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

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

    统一响应格式代码

    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>
    /// <typeparam name="T"></typeparam>
    public interface IResultModel<out T> : IResultModel
    {
        /// <summary>
        ///     返回数据
        /// </summary>
        T? Data { get; }
    }

    实现

    public class ResultModel<T> : IResultModel<T>
    {
        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>
        /// <param name="Data"></param>
        public ResultModel<T> Success(T? data = default)
        {
            this.IsSuccess = true;
            StatusCode = 200;
            Data = data;
            return this;
        }
        /// <summary>
        ///     失败
        /// </summary>
        /// <param name="msg">说明</param>
        /// <param name="code"></param>
        public ResultModel<T> Failed(string? msg = "failed", int? code = 500)
        {
            IsSuccess = false;
            Message = msg;
            StatusCode = code;
            return this;
        }
    }
    /// <summary>
    ///     返回结果
    /// </summary>
    public static class ResultModel
    {
        /// <summary>
        ///     数据已存在
        /// </summary>
        /// <returns></returns>
        public static IResultModel<string> HasExists => Failed("data already exists");
        /// <summary>
        ///     数据不存在
        /// </summary>
        public static IResultModel<string> NotExists => Failed("data doesn't exist");
        /// <summary>
        ///     成功
        /// </summary>
        /// <param name="data">返回数据</param>
        /// <returns></returns>
        public static IResultModel<T> Success<T>(T? data = default)
        {
            return new ResultModel<T>().Success(data);
        }
        /// <summary>
        ///     成功
        /// </summary>
        /// <param name="task">任务</param>
        /// <returns></returns>
        public static async Task<IResultModel<T>> SuccessAsync<T>(Task<T>? task = default)
        {
            return task is not null && task != default ? new ResultModel<T>().Success(await task) : new ResultModel<T>();
        }
        /// <summary>
        ///     成功
        /// </summary>
        /// <returns></returns>
        public static IResultModel<string> Success()
        {
            return Success<string>();
        }
        /// <summary>
        ///     失败
        /// </summary>
        /// <param name="error">错误信息</param>
        /// <returns></returns>
        public static IResultModel<T> Failed<T>(string? error = null)
        {
            return new ResultModel<T>().Failed(error ?? "failed");
        }
        /// <summary>
        ///     失败
        /// </summary>
        /// <returns></returns>
        public static IResultModel<string> Failed(string? error = null)
        {
            return Failed<string>(error);
        }
        /// <summary>
        ///     根据布尔值返回结果
        /// </summary>
        /// <param name="success"></param>
        /// <returns></returns>
        public static IResultModel<T> Result<T>(bool success)
        {
            return success ? Success<T>() : Failed<T>();
        }
        /// <summary>
        ///     根据布尔值返回结果
        /// </summary>
        /// <param name="success"></param>
        /// <returns></returns>
        public static async Task<IResultModel> Result(Task<bool> success)
        {
            return await success ? Success() : Failed();
        }
        /// <summary>
        ///     根据布尔值返回结果
        /// </summary>
        /// <param name="success"></param>
        /// <returns></returns>
        public static IResultModel<string> Result(bool success)
        {
            return success ? Success() : Failed();
        }
        /// <summary>
        /// 时间戳起始日期
        /// </summary>
        public static readonly DateTime TimestampStart = new(1970, 1, 1, 0, 0, 0, 0);
    }

    定义接口

    app.MapGet("/TestResultModel", IResultModel (int age) =>
    {
        List<User> 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<IResultModel<User>>();

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

    返回值

    {
      "isSuccess": true,
      "statusCode": 200,
      "timestamp": 1711001093,
      "data": {
        "name": "Ruipeng",
        "email": "xxx@163.com",
        "age": 18
      }

    自定义类的自动包装实现

    创建一个Attribute

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

    创建中间件自动包装

    public class ResponseWrapperMiddleware(RequestDelegate next)
    {
        public async Task InvokeAsync(HttpContext context)
        {
            if (context.GetEndpoint()?.Metadata.GetMetadata<EnableResponseWrapperAttribute>() 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<dynamic>(readToEnd);
                    var result = new ResultModel<object>
                    {
                        Data = objResult,
                        IsSuccess = true,
                        StatusCode = context.Response.StatusCode
                    };
                    await context.Response.WriteAsJsonAsync(result as object);
                }
                finally
                {
                    // 确保在出现异常时恢复原始响应体流
                    context.Response.Body = originalResponseBodyStream;
                }
            }
            else
            {
                await next(context);
            }
        }
    }

    应用中间件

    app.UseMiddleware<ResponseWrapperMiddleware>();

    创建测试接口

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

    返回值

    {
      "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
    //Return IResult
    app.MapGet("/IResult/TestResult", IResult () => Results.Ok(new User() { Name = "Ruipeng", Email = "xxx@163.com", Age = 18 }));

    没有调用扩展方法 Produces

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

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

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

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

    返回多个 IResult 实现类型

    app.MapGet("/IResult/ReturnMultipleTypes", Results<Ok<User>, NotFound> (int age) =>
    {
        List<User> 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<Ok<User>, NotFound> 联合类型

    IResult 自定义响应

    添加 Html 扩展

    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);
        }
    }
    app.MapGet("/IResult/Html", () => Results.Extensions.Html(@$"<!doctype html>
    <html>
        <head><title>miniHTML</title></head>
        <body>
            <h1>Hello World</h1>
            <p>The time on the server is {DateTime.Now:O}</p>
        </body>
    </html>"));

    返回结果

    <!DOCTYPE html>
    <html>
      <head>
        <title>miniHTML</title>
      </head>
      <body>
        <h1>Hello World</h1>
        <p>The time on the server is 2024-03-21T17:31:36.2931959+08:00</p>
      </body>
    </html>

    自定义 Json 格式

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

    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

    app.MapGet("/IResult/ProblemDetail", () =>
    {
        var problemDetail = new ProblemDetails()
        {
            Status = StatusCodes.Status500InternalServerError,
            Title = "内部错误"
        };
        return TypedResults.Problem(problemDetail);
    });

    返回值

    {
      "type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
      "title": "内部错误",
      "status": 500
    }

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

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

    最后

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

    免费资源网 – https://freexyz.cn/

    声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。