本文版權歸博客園和作者吳雙本人共同所有,轉載和爬蟲請注明原文地址: 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深度定制
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() ?合並Model層的XML文件
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中。
如果我的點滴分享對你有點滴幫助,歡迎點下方紅色按鈕關注,我將持續分享。也歡迎為我,也為你自己點贊。