本文版权归博客园和做者吴双本人共同全部,转载和爬虫请注明原文地址: www.cnblogs.com/tdwscss
在后台接口开发中,接口文档是必不可少的。在复杂的业务当中和多人对接的状况下,简单的接口文档又不能知足需求,试想你的单应用后台有几十个模块,几百甚至更多的接口,又有上百个ViewModel。怎么能让人用起来更顺手更明了?本篇介绍第一步的中度优化,下一篇将分享下一阶段的深度优化。html
第一篇:ASP.NET WebApi 文档Swagger中度优化node
1.上手使用git
2.Controller注释读取和汉化github
3.Actionf group by 分组web
4.经过exe整合xxxModel.xml和xxxAPI.XMLjson
5.经过批处理命令在生成后调用exe后端
第二篇:ASP.NET WebApi 文档Swashbuckle.Core与SwaggerUI深度定制 http://www.cnblogs.com/tdws/p/6103289.htmlapi
第三篇 :ASP.NET WebApi 文档增长登陆 http://www.cnblogs.com/tdws/p/7395556.htmlapp
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注释是没有读取的。那么你须要经过以下配置来达到目的,增长这样一个类,不用问为何。下面这段代码也是参考了一位园友的。汉化我也不必讲了,他已经说的很详细了。
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读取完成。
下面这个功能经过咱们自定义的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层的描述信息(若是你的实体没有在其余层是会正常显示的)。可是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补充感谢 春華秋實 的以下建议: