本文版權歸博客園和做者吳雙本人共同全部,轉載和爬蟲請註明原文地址: 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補充感謝 春華秋實 的以下建議: