【swagger】C# 中 swagger 的使用及避坑
@
开发 web api 的时候,写文档是个痛苦的事情,而没有文档别人就不知道怎么调用,所以又不得不写。
swagger 可以自动生成接口文档,并测试接口,极大的解放了程序员的生产力。
1 安装
通过 nuget 安装 swashbuckle。
安装完成后,app_start 文件夹下会多出一个 swaggerconfig.cs 文件。
重新生成并发布 api,打开网页 http://localhost:7001/swagger(这里注意换成你的 host)
网页显示如下:
2 修改名称和版本号
上图中框出的名称和版本号是可以修改的,打开 swaggerconfig.cs 文件,找到如下代码:
c.singleapiversion("v1", "api.test");
修改其中的参数,重新发布即可。
3 显示说明
swagger 可以读取代码中的注释,并显示在网页上。如此一来,我们只需要在代码中将注释写好,就可以生成一份可供他人阅读的 api 文档了。
swagger 是通过编译时生成的 xml 文件来读取注释的。这个 xml 文件默认是不生成的,所以先要修改配置。
第一步: 右键项目 -> 属性 -> 生成,把 xml 文档文件勾上。
第二步: 添加配置
在 swaggerconfig.cs 文件中添加配置如下:
globalconfiguration.configuration .enableswagger(c => { c.singleapiversion("v2", "测试 api 接口文档"); // 配置 xml 文件路径 c.includexmlcomments($@"{system.appdomain.currentdomain.basedirectory}\bin\api.test.xml"); })
注意:发布的时候,xml 文件不会一起发布,需要手动拷到发布目录下。
4 显示控制器注释及汉化
默认是不会显示控制器注释的,需要自己写。
在 app_start 中新建类 swaggercontrollerdescprovider
,代码如下:
/// <summary> /// swagger 显示控制器的描述 /// </summary> public class swaggercacheprovider : iswaggerprovider { private readonly iswaggerprovider _swaggerprovider; private static concurrentdictionary<string, swaggerdocument> _cache = new concurrentdictionary<string, swaggerdocument>(); private readonly string _xmlpath; /// <summary> /// /// </summary> /// <param name="swaggerprovider"></param> /// <param name="xmlpath">xml文档路径</param> public swaggercacheprovider(iswaggerprovider swaggerprovider, string xmlpath) { _swaggerprovider = swaggerprovider; _xmlpath = xmlpath; } public swaggerdocument getswagger(string rooturl, string apiversion) { var cachekey = string.format("{0}_{1}", rooturl, apiversion); //只读取一次 if (!_cache.trygetvalue(cachekey, out swaggerdocument 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 concurrentdictionary<string, string> getcontrollerdesc() { concurrentdictionary<string, string> controllerdescdict = new concurrentdictionary<string, string>(); if (file.exists(_xmlpath)) { xmldocument xmldoc = new xmldocument(); xmldoc.load(_xmlpath); string[] arrpath; int ccount = "controller".length; foreach (xmlnode node in xmldoc.selectnodes("//member")) { string type = node.attributes["name"].value; if (type.startswith("t:")) { arrpath = type.split('.'); string controllername = arrpath[arrpath.length - 1]; if (controllername.endswith("controller")) //控制器 { //获取控制器注释 xmlnode 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; } }
另外,新建 swagger.js 文件并将其设置成 嵌入的资源,这个文件的作用就是显示控制器注释及汉化。js 代码如下:
'use strict'; window.swaggertranslator = { _words: [], translate: function () { var $this = this; $('[data-sw-translate]').each(function () { $(this).html($this._trytranslate($(this).html())); $(this).val($this._trytranslate($(this).val())); $(this).attr('title', $this._trytranslate($(this).attr('title'))); }); }, setcontrollersummary: function () { $.ajax({ type: "get", async: true, url: $("#input_baseurl").val(), datatype: "json", success: function (data) { var summarydict = data.controllerdesc; var id, controllername, strsummary; $("#resources_container .resource").each(function (i, item) { id = $(item).attr("id"); if (id) { controllername = id.substring(9); strsummary = summarydict[controllername]; if (strsummary) { $(item).children(".heading").children(".options").first().prepend('<li class="controller-summary" title="' + strsummary + '">' + strsummary + '</li>'); } } }); } }); }, _trytranslate: function (word) { return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] : word; }, learn: function (wordsmap) { this._words = wordsmap; } }; /* jshint quotmark: double */ window.swaggertranslator.learn({ "warning: deprecated": "警告:已过时", "implementation notes": "实现备注", "response class": "响应类", "status": "状态", "parameters": "参数", "parameter": "参数", "value": "值", "description": "描述", "parameter type": "参数类型", "data type": "数据类型", "response messages": "响应消息", "http status code": "http 状态码", "reason": "原因", "response model": "响应模型", "request url": "请求 url", "response body": "响应体", "response code": "响应码", "response headers": "响应头", "hide response": "隐藏响应", "headers": "头", "try it out!": "试一下!", "show/hide": "显示/隐藏", "list operations": "显示操作", "expand operations": "展开操作", "raw": "原始", "can't parse json. raw result": "无法解析 json。原始结果", "model schema": "模型架构", "model": "模型", "apply": "应用", "username": "用户名", "password": "密码", "terms of service": "服务条款", "created by": "创建者", "see more at": "查看更多:", "contact the developer": "联系开发者", "api version": "api 版本", "response content type": "响应 content type", "fetching resource": "正在获取资源", "fetching resource list": "正在获取资源列表", "explore": "浏览", "show swagger petstore example apis": "显示 swagger petstore 示例 apis", "can't read from server. it may not have the appropriate access-control-origin settings.": "无法从服务器读取。可能没有正确设置 access-control-origin。", "please specify the protocol for": "请指定协议:", "can't read swagger json from": "无法读取 swagger json于", "finished loading resource information. rendering swagger ui": "已加载资源信息。正在渲染 swagger ui", "unable to read api": "无法读取 api", "from path": "从路径", "server returned": "服务器返回" }); $(function () { window.swaggertranslator.translate(); window.swaggertranslator.setcontrollersummary(); });
在 swaggerconfig.cs 添加如下配置:
globalconfiguration.configuration .enableswagger(c => { c.customprovider((defaultprovider) => new swaggercacheprovider(defaultprovider, $@"{system.appdomain.currentdomain.basedirectory}\bin\api.test.xml")); }) .enableswaggerui(c => { c.injectjavascript(system.reflection.assembly.getexecutingassembly(), "api.test.swagger.js"); });
5 路由相同,查询参数不同的方法
在实际的 asp.net web api 中,是可以存在 路由相同,http 方法相同,查询参数不同 的方法的,但不好意思,swagger 中不支持,并且会直接报错。
如下代码,
[route("api/users")] public ienumerable<user> get() { return users; } [route("api/users")] public ienumerable<user> get(int sex) { return users; }
报错: multiple operations with path 'api/users' and method 'get'.
可以在 swaggerconfig.cs 添加如下配置:
globalconfiguration.configuration .enableswagger(c => { c.resolveconflictingactions(apidescriptions => apidescriptions.first()); })
此配置的意思是,当遇到此种情况时,取第一个方法展示。
这可以避免报错,但多个方法只会在 swagger 中展示一个。治标不治本,不推荐。所以唯一的解决方案就是设置成不同的路由。不知道这个问题在之后的版本中会不会修复。
6 忽略 model 中的某些字段
如下图,新建用户时,后台需要一个 user 类作为参数。点击右侧的 model
,可以显示 user 类的属性及注释。
但是,有些字段其实是无需调用者传递的。例如 state
,调用者无需知道这些字段的存在。
给这些属性标记上 [newtonsoft.json.jsonignore]
特性,swagger 中不再显示了。
当然这种做法也是有缺点的,因为 web api 在返回数据时,调用的默认序列化方法也是 newtonsoft.json 序列化。
7 传递 header
调用 api 时,有些信息是放在 http header 中的,例如 token
。这个 swagger 也是支持的。
新建一个特性:
public class apiauthorizeattribute : attribute { }
新建一个类代码如下:
public class authorityhttpheaderfilter : ioperationfilter { public void apply(operation operation, schemaregistry schemaregistry, apidescription apidescription) { if (operation.parameters == null) operation.parameters = new list<parameter>(); //判断是否添加权限过滤器 var isauthorized = apidescription.actiondescriptor.getcustomattributes<apiauthorizeattribute>().any(); if (isauthorized) { operation.parameters.add(new parameter { name = "token", @in = "header", description = "令牌", required = false, type = "string" }); } } }
这段代码就是告诉 swagger,如果遇到的方法上标记了 apiauthorizeattribute
特性,则添加一个名为 token
的参数在 header
中。
最后需要在 swaggerconfig.cs 中注册这个过滤器。
globalconfiguration.configuration .enableswagger(c => { c.operationfilter<authorityhttpheaderfilter>(); })
效果如下:
8 出错时的 http 状态码
我们在方法中返回一个 400
[route("api/users")] public httpresponsemessage post([frombody]user user) { return new httpresponsemessage() { content = new stringcontent("新建用户出错", encoding.utf8, "application/json"), statuscode = httpstatuscode.badrequest }; }
可是,swagger 中返回的状态码却是 0。
这是因为 content
指定了 json 格式,但传入的 content
又不是 json 格式的。
将 content
改为 json 格式,或者将 mediatype
改成 text/plain
就可以了。