分类:
C#
在前后端分离开发中,作为后端如何给前端提供有效直观的接口文档呢?
没错,就是使用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!再试一下:

嗯~这就美观多了。
总结:实践是检验真理的唯一标准!
50010702506256