在前后端分离开发中,作为后端如何给前端提供有效直观的接口文档呢?
没错,就是使用swagger 俗称“丝袜哥”
教程:
一、首先我们建立一个webapi项目,然后引用需要包。swagger需引用下面这2个包
卸载重复包(提示:这里直接卸载是卸载不掉的,这个包是依赖于Swagger.Net.UI这个包的,所以我们要勾选中强制删除,然后再删):
swaggUI这个文件夹也没用,删掉
二、写一个测试用的接口:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; namespace webApiTest.Areas.Test.Controllers { ///<summary> ///测试接口 ///</summary public class TestController : ApiController { ///<summary> ///测试接口 ///</summary> ///<param name="name">姓名</param> ///<returns>返回问候语</returns> [Route("v1.0/SendVer"), HttpGet] public string SayHello(string name) { return "Hello," + name; } } }
默认的路由地址是:http://xxx/Swagger 访问一下已可以到接口描述:
三、现在已经有了接口描述,为了更加直观可视,我们还需重写下路由和进行汉化处理
1、首先加载汉化包:可以到我的百度网盘去下:
链接:https://pan.baidu.com/s/1nCBGIQ1_OWi6Ly8Ta84IzQ
提取码:epxz
下载完成后放到项目Scripts文件夹中去:
有了汉化文件,我们就来加载到项目中去,重写 swaggerConfig.cs文件,打开后发现一堆英文注释,看不球懂,自己翻译了整理下(
只翻译了部分我用到的,看实际情况食用):
using System.Web.Http; using WebActivatorEx; using webApiTest; using Swashbuckle.Application; using System; using Swashbuckle.Swagger; using System.Xml; using System.Collections.Concurrent; using System.IO; using System.Collections.Generic; [assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")] namespace webApiTest { public class SwaggerConfig { public static void Register() { var thisAssembly = typeof(SwaggerConfig).Assembly; GlobalConfiguration.Configuration .EnableSwagger(c => { //默认情况下,服务根url推断从请求用于访问文档。然而,可能存在的情况下(如代理和负载均衡环境),这确实notresolve正确。 //你可以解决这个通过提供您自己的代码来确定根URL。 //c.RootUrl(req => GetRootUrlFromAppConfig()); //如果Swagger2.0提供的文档不明确,然后计划用于访问文档作为默认值。 //如果您的API支持多个计划和你想要明确的,您可以使用“方案”选项如下所示。 //c.Schemes(new[] { "http", "https" }); //使用“SingleApiVersion”来描述一个版本的API。Swagger2.0包含一个“信息”对象持有额外的元数据API。 //版本和标题是必需的,但是您还可以提供额外的字段SingleApiVersion链接方法。 c.SingleApiVersion("v1", ""); //如果希望输出Swagger文档正确缩进,请启用“PrettyPrint”选项。 //c.PrettyPrint(); //如果您的API有多个版本,使用“MultipleApiVersions”而不是“SingleApiVersion”。 //在这种情况下,您必须提供一个告诉Swagger的行为应该被包括在给定API版本的文档。 //像“SingleApiVersion”,每次调用“版本”返回一个“信息”构建器可以提供额外的元数据API版本 //c.MultipleApiVersions( // (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion), // (vc) => // { // vc.Version("v2", "Swashbuckle Dummy API V2"); // vc.Version("v1", "Swashbuckle Dummy API V1"); // }); //您可以使用“BasicAuth”、“ApiKey”或“OAuth2”选项来描述API的安全方案。 //详细信息,请参阅https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md。 //注意:这些只定义相对应的方案,需要加上一个“安全”属性的文档或操作水平,表明该方案所需的操作。 //要做到这一点,你需要实现一个自定义IDocumentFilter和/或IOperationFilter根据您特定的授权设置这些属性实现 //c.BasicAuth("basic") // .Description("Basic HTTP Authentication"); // //注意:您还必须在SwaggerUI部分下面配置“EnableApiKeySupport” //c.ApiKey("apiKey") // .Description("API Key Authentication") // .Name("apiKey") // .In("header"); // //c.OAuth2("oauth2") // .Description("OAuth2 Implicit Grant") // .Flow("implicit") // .AuthorizationUrl("http://petstore.swagger.wordnik.com/api/oauth/dialog") // .TokenUrl("https://tempuri.org/token") // .Scopes(scopes => // { // scopes.Add("read", "Read access to protected resources"); // scopes.Add("write", "Write access to protected resources"); // }); //这个标志设置为省略过时的属性描述的任何行动 //c.IgnoreObsoleteActions(); //每个操作被分配一个或多个标签,然后用消费者出于各种原因。例如,swagger-ui组操作根据每个操作的第一个标记。 //默认情况下,这将是控制器的名字,但您可以使用“GroupActionsBy”选项与任何值覆盖。 //c.GroupActionsBy(apiDesc => apiDesc.HttpMethod.ToString()); //你也可以指定一个自定义的排序顺序组(定义为“GroupActionsBy”)规定的顺序列出操作。 //例如,如果默认分组(控制器名称)和您指定一个下行字母排序,然后从aProductsController行动将从CustomersController上市之前。 //这是通常用于定制swagger -ui分组的顺序。 //c.OrderActionGroupsBy(new DescendingAlphabeticComparer()); //如果您用它来注释控制器和API类型 //Xml注释(http://msdn.microsoft.com/en-us/library/b2s063f7(v=vs.110).aspx),可以合并 //将这些注释添加到生成的文档和UI中。您可以通过提供到one或更多的Xml注释文件。 //c.IncludeXmlComments(GetXmlCommentsPath()); //Swashbuckle尽力为各种类型生成与Swagger兼容的JSON模式在您的API中公开。然而,有时可能需要对输出进行更多的控制。 //这是通过“MapType”和“SchemaFilter”选项支持的:使用“MapType”选项覆盖特定类型的模式生成。 //需要注意的是,对于任何适用的操作,生成的模式都将放在“内联”位置。 //虽然Swagger 2.0支持“所有”模式类型的内联定义,但是Swagger -ui工具不支持。 //它期望“复杂”模式被单独定义和引用。出于这个原因,你只应该当得到的模式是基本类型或数组类型时, //使用“MapType”选项。如果你需要改变复杂模式,使用模式过滤器。 //c.MapType<ProductType>(() => new Schema { type = "integer", format = "int32" }); //这个是webApiTest接口这个项目的XML文档 c.IncludeXmlComments(string.Format("{0}/bin/webApiTest.XML", AppDomain.CurrentDomain.BaseDirectory)); //这个是实体类的XML文档,因为在实际中我们需要调用实体类库,我这里只是个简单的例子,所以没有使用到这个 //c.IncludeXmlComments(string.Format("{0}/bin/Model.XML", AppDomain.CurrentDomain.BaseDirectory)); c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider)); }) //重新定义路径及汉化文件 .EnableSwaggerUi("apis/{*assetPath}", c => { //这里代表你汉化文件的存放路径,这里有一个坑,当解决方案有'.'这种字符的时候会给识别为路径,且没办法转义, //这个坑的我好惨,比如解决方案改为webapi.test 就要出事了 //路径规则,项目命名空间.文件夹名称.js文件名称 c.InjectStylesheet(thisAssembly, "webApiTest.Scripts.custom.css"); c.InjectJavaScript(thisAssembly, "webApiTest.Scripts.swagger_lang.js"); //c.CustomAsset("index", thisAssembly, "NineTeam.API.Swagger.index.html"); }); } ///<summary> ///Swagger汉化 ///</summary> 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)) { 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\webApiTest.XML", System.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; } } } }
2、认真看了代码的童鞋可能已经发现,swagger的原理实际上就是读取你在编写代码时编写的注释,所以你的注释得详细,那么如何生成XML文件?
右键项目属性》生成》:
重新生成下项目,可以看到bin文件夹下已生成xml文件,打开看一下是什么东西:
可以到在末尾有我们刚才接口的注释。嗯~感觉是搭建的差不多了,跑一下:
汉化怎么没成???但是文档的注释是加载进来了。重新检查下路径发现也是对的?
怎么办呢,别急,只需要改下这两个文件的生产操作:
右键这两个文件》属性》生成操作》改为嵌入式:
OK!再试一下:
嗯~这就美观多了。
总结:实践是检验真理的唯一标准!