zoukankan      html  css  js  c++  java
  • ASP.NET WebApi 文档Swagger中度优化

    本文版权归博客园和作者吴双本人共同所有,转载和爬虫请注明原文地址: www.cnblogs.com/tdws

      写在前面

    在后台接口开发中,接口文档是必不可少的。在复杂的业务当中和多人对接的情况下,简单的接口文档又不能满足需求,试想你的单应用后台有几十个模块,几百甚至更多的接口,又有上百个ViewModel。怎么能让人用起来更顺手更明了?本篇介绍第一步的中度优化,下一篇将分享下一阶段的深度优化。

     第一篇:ASP.NET WebApi 文档Swagger中度优化

    1.上手使用

    2.Controller注释读取和汉化

    3.Actionf group by 分组

    4.通过exe整合xxxModel.xml和xxxAPI.XML

    5.通过批处理命令在生成后调用exe

     第二篇:ASP.NET WebApi 文档Swashbuckle.Core与SwaggerUI深度定制  http://www.cnblogs.com/tdws/p/6103289.html

     第三篇  :ASP.NET WebApi 文档增加登录 http://www.cnblogs.com/tdws/p/7395556.html

    Swagger是一款完全开源的文档工具,其优点在于前后端的完整分离,他们之间的契约就是Json的数据格式。其后台项目就是github中的Swashbuckle。其前台项目就是github中的SwaggerUI。有一点需要注意的是,如果你直接从nuget安装Swashbuckle的话,你也并不想做更多的定制化,那么UI界面你完全不需要处理,因为所有的资源Resources都是嵌入到Swashbuckle.dll当中的,你可以在vs对象管理器中查看到Resources,如下图,是不是又复习了dll的作用了呢?其中还可以包含css,js,image等资源:

    看下本次分享的效果图吧,只选了四个Controller做展示,个人觉得还是比较明了的吧,如果模块和控制器多了起来,就会深刻体会到好处:

      先学会上手使用

      nuget中搜索Swashbuckle并安装到你的WebApi项目中,其他的不用安装哦。

      安装完成后你的App_Start中会多一个SwaggerConfig这样一个配置文件,这个文件是Swagger为我们留下的配置入口,我们可以根据其中的注释和介绍做很多事情。然后我把Swagger单独出来一个文件夹,直接将配置类拖进去,如下效果:

    下一步配置你的项目属性,在其生成选项当中,选择下图配置:

    配置文件中主要有两个入口:EnableSagger和EnableSwaggerUi,顾名思义,配置其后台项和前台项。

    找到下面这行代码,传入你所需的两个字符串

    下一步找到IncludeXmlComments方法,配置读取XML的路径:

     c.IncludeXmlComments(string.Format("{0}/bin/SwaggerDemo.XML", System.AppDomain.CurrentDomain.BaseDirectory));

    至此基本就可以显示你的接口了,请访问:localhost:xxxx/swagger   你可能会问为什么我没有添加页面也能展示,这就是因为页面和其路由设置都在dll中了!下面这段源码揭示了为什么可以直接通过路由访问到你的swagger主页,当然你也可以不必关心下面这段:

    public void EnableSwaggerUi(
                string routeTemplate,
                Action<SwaggerUiConfig> configure = null)
            {
                var config = new SwaggerUiConfig(_discoveryPaths, _rootUrlResolver);
                if (configure != null) configure(config);
    
                _httpConfig.Routes.MapHttpRoute(
                    name: "swagger_ui" + routeTemplate,
                    routeTemplate: routeTemplate,
                    defaults: null,
                    constraints: new { assetPath = @".+" },
                    handler: new SwaggerUiHandler(config)
                );
    
                if (routeTemplate == DefaultRouteTemplate)
                {
                    _httpConfig.Routes.MapHttpRoute(
                        name: "swagger_ui_shortcut",
                        routeTemplate: "swagger",
                        defaults: null,
                        constraints: new { uriResolution = new HttpRouteDirectionConstraint(HttpRouteDirection.UriResolution) },
                        handler: new RedirectHandler(_rootUrlResolver, "swagger/ui/index"));
                }
            }
     Controller注释读取

    默认情况下,Controller注释是没有读取的。那么你需要通过如下配置来达到目的,增加这样一个类,不用问为什么。下面这段代码也是参考了一位园友的。汉化我也没必要讲了,他已经说的很详细了。

     
    public class CachingSwaggerProvider : ISwaggerProvider
        {
            private static ConcurrentDictionary<string, SwaggerDocument> _cache =
                new ConcurrentDictionary<string, SwaggerDocument>();
    
            private readonly ISwaggerProvider _swaggerProvider;
    
            public CachingSwaggerProvider(ISwaggerProvider swaggerProvider)
            {
                _swaggerProvider = swaggerProvider;
            }
    
            public SwaggerDocument GetSwagger(string rootUrl, string apiVersion)
            {
                var cacheKey = String.Format("{0}_{1}", rootUrl, apiVersion);
                SwaggerDocument srcDoc = null;
                //只读取一次
                if (!_cache.TryGetValue(cacheKey, out srcDoc))
                {
    
                    //AppendModelToCurrentXml();
                    srcDoc = _swaggerProvider.GetSwagger(rootUrl, apiVersion);
                    srcDoc.vendorExtensions = new Dictionary<string, object> { { "ControllerDesc", GetControllerDesc() }, { "", "" } };
                    _cache.TryAdd(cacheKey, srcDoc);
                }
                return srcDoc;
            }
    
            /// <summary>
            /// 从API文档中读取控制器描述
            /// </summary>
            /// <returns>所有控制器描述</returns>
            public static ConcurrentDictionary<string, string> GetControllerDesc()
            {
                string xmlpath = String.Format("{0}/bin/SwaggerDemo.XML", AppDomain.CurrentDomain.BaseDirectory);
                ConcurrentDictionary<string, string> controllerDescDict = new ConcurrentDictionary<string, string>();
                if (File.Exists(xmlpath))
                {
                    XmlDocument xmldoc = new XmlDocument();
                    xmldoc.Load(xmlpath);
                    string type = String.Empty, path = String.Empty, controllerName = String.Empty;
    
                    string[] arrPath;
                    int length = -1, cCount = "Controller".Length;
                    XmlNode summaryNode = null;
                    foreach (XmlNode node in xmldoc.SelectNodes("//member"))
                    {
                        type = node.Attributes["name"].Value;
                        if (type.StartsWith("T:"))
                        {
                            //控制器
                            arrPath = type.Split('.');
                            length = arrPath.Length;
                            controllerName = arrPath[length - 1];
                            if (controllerName.EndsWith("Controller"))
                            {
                                //获取控制器注释
                                summaryNode = node.SelectSingleNode("summary");
                                string key = controllerName.Remove(controllerName.Length - cCount, cCount);
                                if (summaryNode != null && !String.IsNullOrEmpty(summaryNode.InnerText) && !controllerDescDict.ContainsKey(key))
                                {
                                    controllerDescDict.TryAdd(key, summaryNode.InnerText.Trim());
                                }
                            }
                        }
                    }
                }
                return controllerDescDict;
            }
        }

    并且在SwaggerConfig找到下面这行代码:

     c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider));

    至此XML读取完成。

      Action分组展示

    下面这个功能通过我们自定义的Attribute来实现。先往下看,不用问为什么,功能实现后自然明白啦:

     /// <summary>
        /// Controller描述信息
        /// </summary>
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
        public class ControllerGroupAttribute : Attribute
        {
            /// <summary>
            /// 当前Controller所属模块 请用中文
            /// </summary>
            public string GroupName { get; private set; }
    
            /// <summary>
            /// 当前controller用途    请用中文
            /// </summary>
            public string Useage { get; private set; }
    
            /// <summary>
            ///  Controller描述信息 构造
            /// </summary>
            /// <param name="groupName">模块名称</param>
            /// <param name="useage">当前controller用途</param>
            public ControllerGroupAttribute(string groupName, string useage)
            {
                if (string.IsNullOrEmpty(groupName) || string.IsNullOrEmpty(useage))
                {
                    throw new ArgumentNullException("groupName||useage");
                }
                GroupName = groupName;
                Useage = useage;
            }
        }

    给你的每个Controller加上这个Attribute:

     

    为了分模块,我做了这种描述,其他内容不一一描述:

    在Swagger找到GroupActionsBy方法:

    并且按照你所设定的分组Attribute来分组排序,最终达到我们想要的效果,请仔细阅读代码,就自然理解了:

     c.GroupActionsBy(apiDesc => apiDesc.GetControllerAndActionAttributes<ControllerGroupAttribute>().Any() ? 
    apiDesc.GetControllerAndActionAttributes<ControllerGroupAttribute>().First().GroupName + "_" +
    apiDesc.GetControllerAndActionAttributes<ControllerGroupAttribute>().First().Useage : "无模块归类");
       合并Model层的XML文件

    上面的效果,你可能看不到Model层的描述信息(如果你的实体没有在其他层是会正常显示的)。但是Model分层了,怎么能展示呢,我目前给出的解决方案是合并XML.写了一个ConsoleApp,当然你也可以手动来复制处理。

    通过相对路径,将model层的xml生成到与API层相同的路径下,方便我们来处理。ConsoleApp的代码如下

    static void Main(string[] args)
            {
                AppendModelToCurrentXml();
                Console.WriteLine(AppDomain.CurrentDomain.BaseDirectory);
                Console.WriteLine("XML整合成功");
                //Console.ReadLine();
            }
    
            public static void AppendModelToCurrentXml()
            {
                XmlDocument xmlDocument = new XmlDocument();
                xmlDocument.Load(AppDomain.CurrentDomain.BaseDirectory + @"/bin/Model.XML");
                var membersNode = xmlDocument.GetElementsByTagName("members")[0]; //Model层Members节点
                xmlDocument.Load(AppDomain.CurrentDomain.BaseDirectory + @"/bin/SwaggerDemo.XML");
                var currentMembersNode = xmlDocument.GetElementsByTagName("members")[0];  //API层Members节点
                for (int i = 0; i < membersNode.ChildNodes.Count; i++)
                {
                    var memberNode = membersNode.ChildNodes[i];
                    currentMembersNode.AppendChild(memberNode.Clone());
                    if (memberNode.HasChildNodes)
                    {
                        memberNode.AppendChild(memberNode.ChildNodes[0]);
                    }
                }
                xmlDocument.Save(AppDomain.CurrentDomain.BaseDirectory + @"/bin/SwaggerDemo.XML");
            }

    至于我为什么要取路径时选择appDomain,是因为我准备把该exe文件放置到webAPI项目的根目录下,这样取到的路径就是API项目的物理路径,如果把物理路径写死,很烦的,你懂的,因为发布或者到其他人电脑中,物理路径基本就变了。这段代码的主要功能就是把Model层中所生成的XML中的Members节点的所有内容,Copy到API项目的XML当中。

     并且,为了不用每次都手动调用exe文件,我使用批处理命令,在每次项目生成后,自动执行exe.

    Swagger的前后端分离,靠json格式的契约,你可以在network中查看json结果,下一篇深度定制分享将会用到它。

     

       写在最后

     本篇多数是配置化的内容,只不过在实际操作的时候,面对不同情况,我们希望能得到更好的结果。动手尝试一下吧,如果有问题和不足的地方,欢迎留言探讨,万一你以后用上了呢,不,应该说WebApi文档你是一定用得上。下一篇,将会更深一步的优化,让我们拿到源码后,无论有什么特别的需求,都能自己亲手修改,而不被蒙蔽在dll中。

    如果我的点滴分享对你有点滴帮助,欢迎点下方红色按钮关注,我将持续分享。也欢迎为我,也为你自己点赞。

    2016.11.27补充感谢  春華秋實 的如下建议:

  • 相关阅读:
    MySQL——事务,索引
    Python——多线程
    Python输入输出
    MySQL与Python的交互——增删改
    MySQL与Python的交互————查询
    Python的学习路
    MySQL数据库
    MySQL条件查询
    设计模式笔记 抽象工厂模式
    设计模式笔记 建造者模式
  • 原文地址:https://www.cnblogs.com/tdws/p/6100126.html
Copyright © 2011-2022 走看看