.NET实现统一接口数据返回格式

.NET实现统一接口数据返回格式

前言

在学习了博客系统后,发现画星星博主写的统一接口返回格式类非常滴好用,这里就讲解一下如何使用他写的规范。

合法的接口返回值应该包括状态码、提示信息、和数据

{
  "statusCode": 200,
  "successful": true,
  "message": null,
  "data": {}
}

但是默认的AspNetCore的WebApi是没有特定的返回格式的,所以需要根据业务要求自己去开发定义。

原理的话直接去原文链接,我这里只讲用法

https://www.cnblogs.com/deali/p/16995384.html

用法

首先定义一个IApiResonse接口

public interface IApiResponse {
    public int StatusCode { get; set; }
    public bool Successful { get; set; }
    public string? Message { get; set; }
}

public interface IApiResponse<T> : IApiResponse {
    public T? Data { get; set; }
}
public interface IApiErrorResponse {
    public Dictionary<string,object> ErrorData { get; set; }
}

接口定义完成后着手创建ApiResponse类实现该接口的方法。

public class ApiResponse<T> : IApiResponse<T> {
    public ApiResponse() {
    }

    public ApiResponse(T? data) {
        Data = data;
    }

    public int StatusCode { get; set; } = 200;
    public bool Successful { get; set; } = true;
    public string? Message { get; set; }

    public T? Data { get; set; }

    /// <summary>
    /// 实现将 <see cref="ApiResponse"/> 隐式转换为 <see cref="ApiResponse{T}"/>
    /// </summary>
    /// <param name="apiResponse"><see cref="ApiResponse"/></param>
    public static implicit operator ApiResponse<T>(ApiResponse apiResponse) {
        return new ApiResponse<T> {
            StatusCode = apiResponse.StatusCode,
            Successful = apiResponse.Successful,
            Message = apiResponse.Message
        };
    }
}

public class ApiResponse : IApiResponse, IApiErrorResponse {
    public int StatusCode { get; set; } = 200;
    public bool Successful { get; set; } = true;
    public string? Message { get; set; }
    public object? Data { get; set; }

    /// <summary>
    /// 可序列化的错误
    /// <para>用于保存模型验证失败的错误信息</para>
    /// </summary>
    public Dictionary<string,object>? ErrorData { get; set; }

    public ApiResponse() {
    }

    public ApiResponse(object data) {
        Data = data;
    }

    public static ApiResponse NoContent(string message = "NoContent") {
        return new ApiResponse {
            StatusCode = StatusCodes.Status204NoContent,
            Successful = true, Message = message
        };
    }

    public static ApiResponse Ok(string message = "Ok") {
        return new ApiResponse {
            StatusCode = StatusCodes.Status200OK,
            Successful = true, Message = message
        };
    }

    public static ApiResponse Ok(object data, string message = "Ok") {
        return new ApiResponse {
            StatusCode = StatusCodes.Status200OK,
            Successful = true, Message = message,
            Data = data
        };
    }

    public static ApiResponse Unauthorized(string message = "Unauthorized") {
        return new ApiResponse {
            StatusCode = StatusCodes.Status401Unauthorized,
            Successful = false, Message = message
        };
    }

    public static ApiResponse NotFound(string message = "NotFound") {
        return new ApiResponse {
            StatusCode = StatusCodes.Status404NotFound,
            Successful = false, Message = message
        };
    }

    public static ApiResponse BadRequest(string message = "BadRequest") {
        return new ApiResponse {
            StatusCode = StatusCodes.Status400BadRequest,
            Successful = false, Message = message
        };
    }

    public static ApiResponse BadRequest(ModelStateDictionary modelState, string message = "ModelState is not valid.") {
        return new ApiResponse {
            StatusCode = StatusCodes.Status400BadRequest,
            Successful = false, Message = message,
            ErrorData = new SerializableError(modelState)
        };
    }

    public static ApiResponse Error(string message = "Error", Exception? exception = null) {
        object? data = null;
        if (exception != null) {
            data = new {
                exception.Message,
                exception.Data
            };
        }

        return new ApiResponse {
            StatusCode = StatusCodes.Status500InternalServerError,
            Successful = false,
            Message = message,
            Data = data
        };
    }
}

定义完成后就能使用了

列子:

public ApiResponse<Post> Get(string id) {
    var post = _postService.GetById(id);
    return post == null ? ApiResponse.NotFound() : new ApiResponse<Post>(post);
}

根据上面的代码,可以发现 ApiResponse.NotFound() 返回的是一个 ApiResponse 对象

但这接口的返回值明明是 ApiResponse<Post> 类型呀,这不是类型不一致吗?

不过在 ApiResponse<T> 中,我们定义了一个运算符重载,实现了 ApiResponse 类型到 ApiResponse<T> 的隐式转换,所以就完美解决这个问题,大大减少了代码量。

注意

除了这些以 ApiResponseApiResponse<T> 作为返回类型的接口,还有很多其他返回类型的接口,比如

public List<ConfigItem> GetAll() {
    return _service.GetAll();
}

这些接口在 AspNetCore 生成响应的时候,会把这些返回值归类为 ObjectResult ,如果不做处理,就会直接序列化成不符合我们返回值规范的格式。

这个不行,必须对这部分接口的返回格式也统一起来。

因为种种原因,最终我选择使用过滤器来实现这个功能。

关于过滤器的详细用法,可以参考官方文档,本文就不展开了,直接上代码。

创建ResponseWrapperFilter.cs类

public class ResponseWrapperFilter : IAsyncResultFilter {
    public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) {
        if (context.Result is ObjectResult objectResult) {
            if (objectResult.Value is IApiResponse apiResponse) {
                objectResult.StatusCode = apiResponse.StatusCode;
                context.HttpContext.Response.StatusCode = apiResponse.StatusCode;
            }
            else {
                var statusCode = objectResult.StatusCode ?? context.HttpContext.Response.StatusCode;

                var wrapperResp = new ApiResponse<object> {
                    StatusCode = statusCode,
                    Successful = statusCode is >= 200 and < 400,
                    Data = objectResult.Value,
                };

                objectResult.Value = wrapperResp;
                objectResult.DeclaredType = wrapperResp.GetType();
            }
        }
        await next();
    }
}

之后在 Program.cs 里注册一下这个过滤器。

var mvcBuilder = builder.Services.AddControllersWithViews(
    options => { options.Filters.Add<ResponseWrapperFilter>(); }
);

完成

最后所有接口(可序列化的),返回格式就都变成了这样

{
  "statusCode": 200,
  "successful": true,
  "message": null,
  "data": {}
}

如果出现报错按Alt+Enter 快速下载nuget包就行了

如Microsoft.AspNetCore.Mvc.Core

posted @ 2023-02-11 19:07  妙妙屋(zy)  阅读(484)  评论(0)    收藏  举报