介绍

现在大多工作中用到的后端开发都是基于RESTFUL的接口开发了,所以使用swagger进行接口管理也就成了比较默认的方式。最近相对比较有时间,所以觉得可以把在abp vnext中使用swagger的一些知识点进行一下总结。

先决条件

  • VS 2019
  • Abp VNext 的版本号:4.0.0
  • Swashbuckle.AspNetCore 版本号:5.5.0
  • Swashbuckle.AspNetCore.Filters 版本号:6.1.0

具体说明

因为abp vnext中对模块的概念使用比较多,所以这里我们也使用这种方式。将swagger的相关配置放到单独的类库中,在XXX.HttpApi.Host项目中添加相应的依赖模块来完成最终的swagger配置工作。

我们已类库:Hbw.Test.Swagger作为此文的说明案例。

模块类添加

添加一个TestSwaggerModule类,具体代码如下:

using Volo.Abp;
using Volo.Abp.Modularity;

namespace Hbw.Test.Swagger
{
    public class TestSwaggerModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            context.Services.AddSwagger();
        }

        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            context.GetApplicationBuilder().UseSwaggerUI();
        }
    }
}

Swagger配置类

添加一个扩展类来完成swagger的具体配置工作,类名为:TestSwaggerExtensions 。关于接口的配置,我主要介绍一下几个点:接口分组、接口注释添加、JWT认证的安全锁显示和枚举注释的显示等。

首先我们需要在此类中添加AddSwaggerUseSwaggerUI方法。具体形式如下:

public static IServiceCollection AddSwagger(this IServiceCollection services)
{
    ...
}

public static void UseSwaggerUI(this IApplicationBuilder app)
{
    ...
}

1、接口分组

接口的分组主要通过SwaggerDoc来完成,然后通过DocInclusionPredicate方法完成具体的接口在哪个组中进行显示,最后通过SwaggerEndpoint方法将配置好的分组内容放到对应的json文件中。

下面我们来具体的看看实现的过程。

  • SwaggerDoc
public static IServiceCollection AddSwagger(this IServiceCollection services)
{
    return services.AddSwaggerGen(
        options =>
        {
            options.SwaggerDoc("thridapi_v1", new OpenApiInfo
            {
                Title = "第三方数据API",
                Version = "v1",
                Description = "xxxxxxxxx系统第三方数据 v1.0 接口服务"
            });

            options.SwaggerDoc("system_v1", new OpenApiInfo
            {
                Title = "系统API",
                Version = "v1",
                Description = "xxxxxxxxx系统 v1.0 接口服务"
            });
        });
}
  • DocInclusionPredicate
public static IServiceCollection AddSwagger(this IServiceCollection services)
{
    return services.AddSwaggerGen(
        options =>
        {
            ...

            options.DocInclusionPredicate((docName, description) =>
            {
                if (!description.TryGetMethodInfo(out MethodInfo method))
                {
                    return false;
                }
                /*使用ApiExplorerSettingsAttribute里面的GroupName进行特性标识
                   * DeclaringType只能获取controller上的特性
                   * 我们这里是想以action的特性为主
                   * */
                var version = method.DeclaringType.GetCustomAttributes(true).OfType<ApiExplorerSettingsAttribute>().Select(m => m.GroupName);
                if (version.Any())
                {
                    return version.Any(v => v == docName);
                }
                //这里获取action的特性
                var actionVersion = method.GetCustomAttributes(true).OfType<ApiExplorerSettingsAttribute>().Select(m => m.GroupName);
                if (actionVersion.Any())
                {
                    return actionVersion.Any(v => v == docName);
                }

                return false;
            });
        });
}

说明:

1、如果DocInclusionPredicate方法中直接返回true,则显示所有的接口,分组也就无效了。

2、通过上面的方法,也可以把abp vnext中默认生成的接口进行隐藏。

  • SwaggerEndpoint
public static void UseSwaggerUI(this IApplicationBuilder app)
{
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/thridapi_v1/swagger.json", "第三方数据API");
        c.SwaggerEndpoint("/swagger/system_v1/swagger.json", "系统API");
        c.RoutePrefix = string.Empty;  // url 中不显示swagger
    });
}

在完成了上面所有配置后,还有一个地方需要处理才能实现最终的效果。那就是具体的api接口方法上,或者webapi的控制器上添加ApiExplorerSettings属性。

[ApiExplorerSettings(GroupName = "system_v1")]
public string Hello(string name)
{
    return $"{name}你好";
}

因为上面使用的分组是system_v1,所以Hello将会在这个分组中显示;如果使用的是thridapi_v1,则其将在此分组中显示。

效果图

在这里插入图片描述

2、接口注释

要实现接口的注释显示,首先应该把相应类库的xml文件生成出来。具体可以右击对应项目的【属性】,在【生成】选项卡中的【输出】部分,勾选“XML文档文件”即可。

在这里插入图片描述

然后在swagger中进行相应的配置,代码如下:

public static IServiceCollection AddSwagger(this IServiceCollection services)
{
    return services.AddSwaggerGen(
        options =>
        {
            ...

            //获取应用程序所在目录(绝对,不受工作目录影响,建议采用此方法获取路径)
            var basePath = Path.GetDirectoryName(typeof(TestSwaggerModule)Assembly.Location);
            var xmlPath = Path.Combine(basePath, "xxxxx.xml");//这个就是刚刚配置的xml文件名
            //默认的第二个参数是false,对方法的注释
            // 即swagger界面只有方法有注释,最上面的控制器没有注释
            // 而第二个参数为true, 则是controller的注释
            options.IncludeXmlComments(xmlPath, true);
        });
}

3、JWT认证的安全锁显示

直接上代码,如下:

public static IServiceCollection AddSwagger(this IServiceCollection services)
{
    return services.AddSwaggerGen(
        options =>
        {
            ...

            var security = new OpenApiSecurityScheme
            {
                Description = "JWT模式授权,请输入 Bearer {Token} 进行身份验证",
                Name = "Authorization",
                In = ParameterLocation.Header,
                Type = SecuritySchemeType.ApiKey
            };
            // 必须 oauth2 这个名称
            options.AddSecurityDefinition("oauth2", security);
            options.AddSecurityRequirement(new OpenApiSecurityRequirement { {security, new List<string>() } });
            options.OperationFilter<AddResponseHeadersFilter>();
            options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
            options.OperationFilter<SecurityRequirementsOperationFilter>();
        });
}

4、枚举注释的显示

如果没记错的话,swagger是从版本3.x开始使用了新的什么协议,网上也有写文章介绍了显示枚举注释的方法,但是都是基于2.x版本的介绍。最终找到一个类似的解决方法,稍加整理形成一下方法。

其实核心的思路都是通过自定义一个继承自IDocumentFilter接口的类来完成枚举的注释显示。

具体直接看代码吧。

public class EnumDocumentFilter : IDocumentFilter
{
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        Dictionary<string, Type> dict = GetAllEnum();
        foreach (var item in swaggerDoc.Components.Schemas)
        {
            var property = item.Value;
            var typeName = item.Key;
            Type itemType = null;
            if (property.Enum != null && property.Enum.Count > 0)
            {
                if (dict.ContainsKey(typeName))
                {
                    itemType = dict[typeName];
                    List<OpenApiInteger> list = new List<OpenApiInteger>();
                    foreach (var val in property.Enum)
                    {
                        list.Add((OpenApiInteger)val);
                    }
                    property.Description += DescribeEnum(itemType, list);
                }
            }
        }
        static Dictionary<string, Type> GetAllEnum()
        {
            Assembly ass = Assembly.Load("Hbw.Test.Domain.Shared");
            Type[] types = ass.GetTypes();
            Dictionary<string, Type> dict = new Dictionary<string, Type>();
            foreach (Type item in types)
            {
                if (item.IsEnum)
                {
                    dict.Add(item.Name, item);
                }
            }
            return dict;
        }
        static string DescribeEnum(Type type, List<OpenApiInteger> enums)
        {
            if (type == null)
            {
                return string.Empty;
            }
            var enumDescriptions = new List<string>();
            foreach (var item in enums)
            {
                var value = Enum.Parse(type, item.Value.ToString());
                var desc = GetDescription(type, value);
                if (string.IsNullOrEmpty(desc))
                {
                    enumDescriptions.Add($"{item.Value}:{Enum.GetName(type, value)}; ");
                }
                else
                {
                    enumDescriptions.Add($"{item.Value}:{Enum.GetName(type, value)}, {desc}; ");
                }
            }
            return $"<br/>{Environment.NewLine}{string.Join("<br/>" + Environment.NewLine, enumDescriptions)}";
        }
        static string GetDescription(Type t, object value)
        {
            foreach (MemberInfo mInfo in t.GetMembers())
            {
                if (mInfo.Name == t.GetEnumName(value))
                {
                    foreach (Attribute attr in Attribute.GetCustomAttributes(mInfo))
                    {
                        if (attr.GetType() == typeof(DescriptionAttribute))
                        {
                            return ((DescriptionAttribute)attr).Description;
                        }
                    }
                }
            }
            return string.Empty;
        }
    }
}

代码中的GetAllEnum方法中,第一行加载的程序集是定义枚举的类库文件,在abp vnext中也就是Hbw.Test.Domain.Shared这个库,所以这里把相应的项目类库名称放在此处即可。

完成这个类的定义后,我们需要在添加安全锁的代码的最后添加如下代码即可。

options.DocumentFilter<EnumDocumentFilter>();

最终的枚举注释显示效果如下:

在这里插入图片描述

以上基本完成了swagger的单独配置工作,而如果要使用它,我们还需要在Hbw.Test.HttpApi.Host项目中的模块类上添加依赖,具体如下:

[DependsOn(
        ...

        typeof(TestSwaggerModule)
    )]
public class TestHttpApiHostModule : AbpModule
{
    ...
}

总结

以上就是目前我在项目中使用到的swagger的相关配置内容,这样处理的好处是,如果以后有新的配置需要添加,那单独修改这个类库即可,如果你希望把它形成nuget包进行发布,也是很方便的。希望对你也有所帮助。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐