.net swagger知识点汇总 自定义swagger返回值

注入

services.AddSwaggerGen(options =>
    {
        //使用options注入服务
    });   

SwaggerDoc

SwaggerDoc主要用来声明一个文档,上面的例子中声明了一个名称为v1的接口文档,可声明多个接口文档,比如按开发版本进行声明:  

options.SwaggerDoc("v1", new OpenApiInfo()
    {
        Version = "v0.0.1",
        Title = "项目v0.0.1",
        Description = $"接口文档说明v0.0.1",
        Contact = new OpenApiContact()
        {
            Name = "zhangsan",
            Email = "xxx@qq.com",
            Url = null
        }
    });

    options.SwaggerDoc("v2", new OpenApiInfo()
    {
        Version = "v0.0.2",
        Title = "项目v0.0.2",
        Description = $"接口文档说明v0.0.2",
        Contact = new OpenApiContact()
        {
            Name = "lisi",
            Email = "xxxx@qq.com",
            Url = null
        }
    });

IncludeXmlComments

IncludeXmlComments是用于加载注释文件,Swashbuckle会从注释文件中去获取接口的注解,接口参数说明以及接口返回的参数说明等信息,这个在上面的一般用法中已经介绍了,这里不再重复说明

IgnoreObsoleteActions

IgnoreObsoleteActions表示过滤掉ObsoleteAttribute属性声明的接口,也就是说不会在SwaggerUI中显示接口了,ObsoleteAttribute修饰的接口表示接口已过期,尽可能不要再使用。

方法调用等价于:options.SwaggerGeneratorOptions.IgnoreObsoleteActions = true;

IgnoreObsoleteProperties

IgnoreObsoleteProperties的作用类似于IgnoreObsoleteActions,只不过IgnoreObsoleteActions是作用于接口,而IgnoreObsoleteProperties作用于接口的请求实体和响应实体参数中的属性。

方法调用等价于:  

options.SchemaGeneratorOptions.IgnoreObsoleteProperties = true;

OrderActionsBy

OrderActionsBy用于同一组接口(可以理解为同一控制器下的接口)的排序,默认情况下,一般都是按接口所在类的位置进行排序(源码中是按控制器名称排序,但是同一个控制器中的接口是一样的)。

  比如上面的例子中,我们可以修改成按接口路由长度排序:  

options.OrderActionsBy(apiDescription => apiDescription.RelativePath.Length.ToString());

CustomSchemaIds

CustomSchemaIds方法用于自定义SchemaId,Swashbuckle中的每个Schema都有唯一的Id,框架会使用这个Id匹配引用类型,因此这个Id不能重复。

  默认情况下,这个Id是根据类名得到的(不包含命名空间),因此,当我们有两个相同名称的类时,Swashbuckle就会报错:  

System.InvalidOperationException: Can't use schemaId "$XXXXX" for type "$XXXX.XXXX". The same schemaId is already used for type "$XXXX.XXXX.XXXX"

  就是类似上面的异常,一般时候我们都得去改类名,有点不爽,这时就可以使用这个方法自己自定义实现SchemaId的获取,比如,我们自定义实现使用类名的全限定名(包含命名空间)来生成SchemaId,上面的异常就没有了:   

options.CustomSchemaIds(CustomSchemaIdSelector);

string CustomSchemaIdSelector(Type modelType)
{
    if (!modelType.IsConstructedGenericType) return modelType.FullName.Replace("[]", "Array");

    var prefix = modelType.GetGenericArguments()
        .Select(genericArg => CustomSchemaIdSelector(genericArg))
        .Aggregate((previous, current) => previous + current);

    return prefix + modelType.FullName.Split('`').First();
}

TagActionsBy

Tag是标签组,也就是将接口做分类的一个概念。

  TagActionsBy用于获取一个接口所在的标签分组,默认的接口标签分组是控制器名,也就是接口被分在它所属的控制器下面,我们可以改成按请求方法进行分组  

options.TagActionsBy(apiDescription => new string[] { apiDescription.HttpMethod});

AddSecurityDefinition

AddSecurityDefinition用于声明一个安全认证,注意,只是声明,并未指定接口必须要使用认证,比如声明JwtBearer认证方式:  

 //定义JwtBearer认证方式一
    options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
    {
        Description = "这是方式一(直接在输入框中输入认证信息,不需要在开头添加Bearer)",
        Name = "Authorization",//jwt默认的参数名称
        In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
        Type = SecuritySchemeType.Http,
        Scheme = "bearer"
    });

AddSecurityRequirement

AddSecurityDefinition仅仅是声明已一个认证,不一定要对接口用,而AddSecurityRequirement是将声明的认证作用于所有接口(AddSecurityRequirement好像可以声明和引用一起实现),比如将上面的JwtBearer认证作用于所有接口:  

//声明一个Scheme,注意下面的Id要和上面AddSecurityDefinition中的参数name一致
var scheme = new OpenApiSecurityScheme()
{
    Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
};
//注册全局认证(所有的接口都可以使用认证)
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
    [scheme] = new string[0]
});

  运行后,发现所有接口后面多了一个锁,表明此接口需要认证信息:

DocumentFilter

DocumentFilter是文档过滤器,它是在获取swagger文档接口,返回结果前调用,也就是请求swagger.json时调用,它允许我们对即将返回的swagger文档信息做调整,比如上面的例子中添加的全局认证方式和AddSecurityRequirement添加的效果是一样的:

 public class MyDocumentFilter : IDocumentFilter
    {
        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            //声明一个Scheme,注意下面的Id要和上面AddSecurityDefinition中的参数name一致
            var scheme = new OpenApiSecurityScheme()
            {
                Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
            };
            //注册全局认证(所有的接口都可以使用认证)
            swaggerDoc.SecurityRequirements.Add(new OpenApiSecurityRequirement()
            {
                [scheme] = new string[0]
            });
        }
    }

然后使用DocumentFilter方法添加过滤器:  

options.DocumentFilter<MyDocumentFilter>();

  DocumentFilter方法需要提供一个实现了IDocumentFilter接口的Apply方法的类型和它实例化时所需要的的参数,而IDocumentFilter的Apply方法提供了OpenApiDocument和DocumentFilterContext两个参数,DocumentFilterContext参数则包含了当前文件接口方法的信息,比如调用的接口的Action方法和Action的描述(如路由等)。而OpenApiDocument即包含当前请求的接口文档信息,它包含的属性全部都是全局性的, 这样我们可以像上面添加认证一样去添加全局配置,比如,如果不使用AddServer方法,我们可以使用DocumentFilter去添加:  

 public class MyDocumentFilter : IDocumentFilter
    {
        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            swaggerDoc.Servers.Add(new OpenApiServer() { Url = "http://localhost:90", Description = "地址1" });
            swaggerDoc.Servers.Add(new OpenApiServer() { Url = "http://127.0.0.1:90", Description = "地址2" });
            //192.168.28.213是我本地IP
            swaggerDoc.Servers.Add(new OpenApiServer() { Url = "http://192.168.28.213:90", Description = "地址3" });
        }
    }

再比如,上面我们对接口进行了swagger文档分类使用的是ApiExplorerSettingsAttribute,如果不想对每个接口使用ApiExplorerSettingsAttribute,我们可以使用DocumentFilter来实现,先创建一个类实现IDocumentFilter接口:

 public class GroupNameDocumentFilter : IDocumentFilter
    {
        string documentName;
        string[] actions;

        public GroupNameDocumentFilter(string documentName, params string[] actions)
        {
            this.documentName = documentName;
            this.actions = actions;
        }

        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            foreach (var apiDescription in context.ApiDescriptions)
            {
                if (actions.Contains(apiDescription.ActionDescriptor.RouteValues["action"]))
                {
                    apiDescription.GroupName = documentName;
                }
            }
        }
    }
   
//然后使用DocumentFilter添加过滤器: 

    //All和Get接口属于文档v1
    options.DocumentFilter<GroupNameDocumentFilter>(new object[] { "v1", new string[] { nameof(HomeController.Get) } });
    //All和Post接口属于v2
    options.DocumentFilter<GroupNameDocumentFilter>(new object[] { "v2", new string[] { nameof(HomeController.Post) } });

OperationFilter

Operation可以简单的理解为一个操作,因为swagger是根据项目中的接口,自动生成接口文档,就自然需要对每个接口进行解析,接口路由是什么,接口需要什么参数,接口返回什么数据等等,而对每个接口的解析就可以视为一个Operation。

  OperationFilter是操作过滤器,这个方法需要一个实现类IOperationFilter接口的类型,而它的第二个参数arguments是这个类型实例化时传入的参数。

  OperationFilter允许我们对已经生成的接口进行修改,比如可以添加参数,修改参数类型等等。

  需要注意的是,OperationFilter在获取swagger文档接口时调用,也就是请求swagger.json时调用,而且只对属于当前请求接口文档的接口进行过滤调用。  

  比如我们有一个Operation过滤器:

 public class MyOperationFilter : IOperationFilter
    {
        string documentName;

        public MyOperationFilter(string documentName)
        {
            this.documentName = documentName;
        }

        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            //过滤处理
        }
    }

接着调用SwaggerGenOptions的OperationFilter方法添加  

options.OperationFilter<MyOperationFilter>(new object[] { "v1" });

上面的过滤器实例化需要一个参数documentName,所以在OperationFilter方法中有一个参数。

  这个接口只会对当前请求的接口文档进行调用,也就是说,如果我们请求的是swagger文档v1,也就是请求/swagger/v1/swagger.json时,这个过滤器会对All方法和Get方法执行,如果请求的是swagger文档v2,也就是请求/swagger/v2/swagger.json时,这个过滤器会对All方法和Post方法进行调用。自定义的OperationFilter需要实现IOperationFilter的Apply接口方法,而Apply方法有两个参数:OpenApiOperation和OperationFilterContext,同样的,OpenApiOperation包含了和当前接口相关的信息,比如认证情况,所属的标签,还可以自定义的自己的Servers。而OperationFilterContext则包换了接口方法的的相关引用。

  OperationFilter是用的比较多的方法了,比如上面的全局认证,因为直接调用AddSecurityRequirement添加的是全局认证,但是项目中可能部分接口不需要认证,这时我们就可以写一个OperationFilter对每一个接口进行判断了: 

 public class ResponsesOperationFilter : IOperationFilter
    {
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            var authAttributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
                .Union(context.MethodInfo.GetCustomAttributes(true))
                .OfType<AuthorizeAttribute>();

            var list = new List<OpenApiSecurityRequirement>();
            if (authAttributes.Any() && !context.MethodInfo.GetCustomAttributes(true).OfType<AllowAnonymousAttribute>().Any())
            {
                operation.Responses["401"] = new OpenApiResponse { Description = "Unauthorized" };
                //operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });

                //声明一个Scheme,注意下面的Id要和AddSecurityDefinition中的参数name一致
                var scheme = new OpenApiSecurityScheme()
                {
                    Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
                };
                //注册全局认证(所有的接口都可以使用认证)
                operation.Security = new List<OpenApiSecurityRequirement>(){new OpenApiSecurityRequirement()
                {
                    [scheme] = new string[0]
                }};
            }
        }
    }

然后使用OperationFilter添加这个过滤器:  

options.OperationFilter<ResponsesOperationFilter>();

swagger统一返回值

public class MyOperationFilter : IOperationFilter
    {
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            foreach (var key in operation.Responses.Keys)
            {
                var content = operation.Responses[key].Content;
                foreach (var mediaTypeKey in content.Keys)
                {
                    var mediaType = content[mediaTypeKey];
                    var schema = new OpenApiSchema();
                    schema.Type = "object";
                    schema.Properties = new Dictionary<string, OpenApiSchema>()
                    {
                        ["code"] = new OpenApiSchema() { Type = "integer" },
                        ["message"] = new OpenApiSchema() { Type = "string" },
                        ["error"] = new OpenApiSchema()
                        {
                            Type = "object",
                            Properties = new Dictionary<string, OpenApiSchema>()
                            {
                                ["message"] = new OpenApiSchema() { Type = "string" },
                                ["stackTrace"] = new OpenApiSchema() { Type = "string" }
                            }
                        },
                        ["result"] = mediaType.Schema
                    };
                    mediaType.Schema = schema;
                }
            }
        }
    }

注入
options.OperationFilter();

RequestBodyFilter

  RequestBody理所当然的就是请求体了,一般指的就是Post请求,RequestBodyFilter就是允许我们对请求体的信息作出调整,同样的,它是在获取Swagger.json文档时调用,而且只对那些有请求体的接口才会执行。

  RequestBodyFilter的用法类似DocumentFilter和OperationFilter,一般也不会去修改请求体的默认行为,因为它可能导致请求失败,所以一般不常用,这里就不介绍了

ParameterFilter

  Parameter指的是接口的参数,而ParameterFilter当然就是允许我们对参数的结构信息作出调整了,同样的,它是在获取Swagger.json文档时调用,而且只对那些参数的接口才会执行。

SchemaFilter

  Schema指的是结构,一般指的是接口请求参数和响应返回的参数结构,比如我们想将所有的int类型换成string类型,对特定类型进行处理 

//实现调整指定类型的描述信息
public class MySchemaFilter : ISchemaFilter
    {
        public void Apply(OpenApiSchema schema, SchemaFilterContext context)
        {
            if (context.Type == typeof(int))
            {
                schema.Type = "string";
              schema.Description += "test";
            }
        }
    }

注入
services.AddSwaggerGen(options =>
{
...

    options.SchemaFilter<MySchemaFilter>();
});

swagger默认值

[DefaultValue(1)]
public int PageIndex{get;set;};

参考

posted @   Hey,Coder!  阅读(719)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
历史上的今天:
2020-04-01 c# 获取文件信息
点击右上角即可分享
微信分享提示