Webapi路由添加namespace支持及Swagger的优化
webapi开发中通常会使用Swagger UI来生成接口文档,关于Swagger的安装与集成已经有大量博客进行了记录,这里不再赘述。随着业务扩展,接口越来越多,使用了一段时间Swagger以后,感觉基本功能不太满足业务要求,于是研究了一番webapi和Swagger。
webapi的默认路由模板最多支持:api/{controller}/{action}/{id},但我们想要在路由中加入命名空间,以此对接口进行业务划分,比如这样的:api/{namespace}/{action}/{id}。当接口较多时,文档一页会展示大量接口,眼睛都看花了,而且其中很多接口是早期项目中用到的,不需要频繁查看,这就需要用到swagger文档多版本管理功能。我查看了很多关于这两方面的博客,借鉴了部分前人的经验。由于部分博客存在随意复制粘贴的问题,内容不全且部分实现未经验证,花了我不少功夫。这里还是要感谢一下这些提供思路的博主。
在进行改造整合的时候,我发现swagger对改造后的路由不太兼容,而且版本控制大多都是以路由来控制的,不能实现我想达到的效果。下面将逐一介绍路由和文档的改造。
webapi路由模板改造
思路是自己实现一个IHttpControllerSelector,在路由中嵌入命名空间,这里参考了不是豆豆的博客。实现之后接口的请求路径就变成了api/{namespace}/{action}/{id},已经满足了使用需求,但swagger的支持效果不够好。不用swagger的小伙伴可以到此为止。为了让swagger显示的url更加美观,我稍作了修改,将路由模板的命名空间由namespace.controller的格式换成了namespace/controller的格式。实现之后,swagger的效果变成了这样:
显然,这太不美观了,不仅每个控制器的名字都带上了controller,而且因为webapi对属性路由的支持,路由模板中的{namespace}被swagger当作了参数:
于是开始对swagger进行改造。
对了,还有一件事,上述自定义路由以后Controllers根目录下的接口同样会在url中带有命名空间Controllers,建议不要在Controllers根目录下直接建接口:
Swagger改造
我用的是Swashbuckle 5.6.0版本,要改造swagger的action显示效果,需要开启swagger的action分组功能,在该功能中修改文档显示内容;
首先要新建一个ApiDescriptionExtension扩展方法,在该方法中新增GetControllerName方法,返回去除controller后缀的控制器名称,同时去掉url中那个烦人的{namespace},代码如下:
/// <summary>
/// 获取控制器名称
/// </summary>
/// <param name="description"></param>
/// <returns></returns>
public static string GetControllerName(this ApiDescription description)
{
string controllerFullName = description.ActionDescriptor.ControllerDescriptor.ControllerType.FullName;
// 匹配命名空间
var namespaceName = controllerFullName.Split('.');
// 命名空间位置
int postion = 0;
// 控制器名称
var controllerName = string.Empty;
// 命名空间名称
string spaceName = string.Empty;
for (var i = 0; i < namespaceName.Length; i++)
{
if (namespaceName[i].Contains("Controller"))
{
if (i == namespaceName.Length - 1)
{
postion = i;
controllerName = namespaceName[i].Remove(namespaceName[i].Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
}
}
}
if (postion != 0)
{
spaceName = namespaceName[postion - 1];
// 去掉文档中url路径的{namespace}
description.RelativePath = description.RelativePath.Replace("{namespace}/", "");
var findResult = description.ParameterDescriptions.Where(t => t.Name == "namespace");
if (findResult != null && findResult.Count() > 0)
{
description.ParameterDescriptions.Remove(findResult.First());
}
}
return controllerName;
}
接着在SwaggerConfig的EnableSwagger方法中开启分组功能:
c.GroupActionsBy(apiDesc => apiDesc.GetControllerName());
再次运行项目,可以看到swagger的显示效果正常了,请求也正常。
请求效果:
只需要满足url支持命名空间的话,上面的内容就够了,如果你也需要接口文档版本管理,那么可以继续往下看。
swagger开启多版本管理功能
查找了不少资料,发现基本实现方案都是在api的路由上做文章,而我们的路由已经定型了,肯定是不能再改动了,于是另寻方案。因为我已经给接口加了不少特性,我觉得利用特性来完成这个操作应该能满足当前的需求,而且也方便使用,不需要对路由做修改。
首先定义一个ApiVersion的特性:
/// <summary>
/// Api版本特性
/// </summary>
[AttributeUsage(AttributeTargets.All)]
public class ApiVersion : Attribute
{
/// <summary>
/// 设置版本号
/// </summary>
/// <param name="version"></param>
public ApiVersion(string version)
{
Version = version;
}
/// <summary>
/// 版本号
/// </summary>
public string Version { get; set; }
}
接着我们需要再SwaggerConfig.cs的EnableSwagger中开启版本管理功能,这里需要先确定项目需要的版本号。我这里演示使用了default、test、test2作为版本号,default是不对接口使用ApiVersion特性时的默认版本号,可以自定义,只要和ResolveVersionSupportByVersionConstraint中默认一致就好:
// 多版本文档管理,要注释掉原来的单版本功能
c.MultipleApiVersions(
(apiDesc, targetApiVersion) => ResolveVersionSupportByVersionConstraint(apiDesc, targetApiVersion),
(vc) =>
{
vc.Version("default", "默认版本文档");
vc.Version("test", "test版本文档");
vc.Version("test2", "test2 版本文档");
});
这里的关键在于ResolveVersionSupportByVersionConstraint方法,我们需要在这里对接口的版本号进行筛选:
/// <summary>
/// 多版本接口文档管理
/// 根据当前版本的版本号匹配接口,正确匹配的才会显示在文档中
/// </summary>
/// <param name="apiDesc"></param>
/// <param name="targetApiVersion"></param>
/// <returns></returns>
private static bool ResolveVersionSupportByVersionConstraint(ApiDescription apiDesc, string targetApiVersion)
{
var attributes = apiDesc.ActionDescriptor.GetCustomAttributes<ApiVersion>();
if (attributes.Count > 0)
{
return attributes[0].Version.ToLower().Contains(targetApiVersion);
}
else if (targetApiVersion == "default")
{
return true;
}
else
{
return false;
}
}
不要忘了在EnableSwaggerUi中开启多版本选择下拉框功能:
c.EnableDiscoveryUrlSelector();
到此为止就基本实现了swagger的接口多版本管理,我在部分接口上指定了版本号,看一下实现效果:
有写得不好的地方还请指教,有讨论的也可以留言。