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的效果变成了这样:

直接修改路由模板的效果.png

  显然,这太不美观了,不仅每个控制器的名字都带上了controller,而且因为webapi对属性路由的支持,路由模板中的{namespace}被swagger当作了参数:

namespace被识别为参数.png

  于是开始对swagger进行改造。

  对了,还有一件事,上述自定义路由以后Controllers根目录下的接口同样会在url中带有命名空间Controllers,建议不要在Controllers根目录下直接建接口:

controllers根目录下的接口.png

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的显示效果正常了,请求也正常。

去掉namespace参数.jpg

请求效果:

请求成功效果.png

  只需要满足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的接口多版本管理,我在部分接口上指定了版本号,看一下实现效果:

swagger版本管理演示效果.gif

有写得不好的地方还请指教,有讨论的也可以留言。

标签: webapi, swagger, route, namespace

添加新评论