技术 思绪 摘录 旅行
Swagger可以根据xml文件生成api接口文档,前后端节约沟通时间,减少对接成本,是一个非常好的解决方案;本文主要记录一下,Vs2019从0开始,搭建一个WebApi最终利用Swagger看到相关接口,并解决Swagger汉化的问题,解决Swagger在.net Webapi中控制器名称标注问题。

1、打开VS2019,选择新建项目,选择【ASP.NET Web 应用程序(.NET Framework)】类型。

image.png


2、点击下一步,输入api项目名称,选择.NET Framework 4.5。

image.png


3、点击创建,选择WebAPI,https看情况,有需要可以勾选上,开发阶段影响不大。

image.png


4、点击创建,我们得到一个默认结构的WebApi项目。

image.png

其中有MVC的结构,不是我们需要的,比如fonts文件夹这些,删除一下。

image.png

编译下,发现报错,是因为我们删除了APP_Start文件夹下的一个cs文件,需要把Global.asax里面最后一句去掉。

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    GlobalConfiguration.Configure(WebApiConfig.Register);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    //BundleConfig.RegisterBundles(BundleTable.Bundles);
}

现在编译成功,得到最简洁的一个webapi站点。


5、项目上单击右键,选择【管理NuGet程序包】。搜索【Swashbuckle】,本项目安装的Swashbuckle 5.6

image.png


6、此时我们发现APP_Start中多了一个SwagerConfig.cs,此时编译并F5运行一下。

   访问的是:https://localhost:44333/

image.png

无资源的原因是我们地址不对,正确的应该是:https://localhost:44333/swagger

重新回车一下,看到下面这个界面,说明没问题。


image.png

观察里面的接口内容,是控制器文件夹下ValuesController.cs这个里面的内容。

接口思路是一个控制器只干一件事,并且提供不同的协议类型的接口。

思路很好,但是不是我想要的,我要的是一个控制器里面多个接口,接口协议自定义。


7、修改一下api路由,打开APP_Start文件夹下WebApiConfig.cs文件。修改如下

public static class WebApiConfig{
    public static void Register(HttpConfiguration config)
    {
        // Web API 配置和服务
         // Web API 路由       
        config.MapHttpAttributeRoutes();
 
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{action}/{id}",//修改了这里
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

再次编译刷新刚刚的页面。

image.png

现在接口路径中,已经多了Action名称,我们可以在控制器下开多个Action,并对外开放。


8、接下来可以创建一个新的控制器【LoginController.cs】,Controller文件夹名称上,单击右键-->选择添加-->控制器-->选择WebApi2控制器 空的-->确认-->输入LoginController名称-->确定

image.png

现在里面什么代码都没得,添加如下代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace WebApiToSwagger.Controllers
{
    /// <summary>
    /// 登录相关
    /// </summary>
    /// <seealso cref="System.Web.Http.ApiController" />
    public class LoginController : ApiController
    {
        /// <summary>
        /// 获取列表数据
        /// </summary>
        /// <param name="value">The value.</param>
        public string GetListData([FromBody]string value)
        {
            return "数据";
        }
    }
}

再次编译,刷新页面看效果。

image.png

看到我们新增的Login控制器,其中的GetListData接口也能看到,已经可以使用了。


但是我们看到这个界面上有两个问题,一个是界面是英文的,还有就是Login控制器是有注释的,但是没显示出来,一旦折叠,很难分辨这个控制器是干啥的。

接下来就是汉化和新增这个注释。


9、项目右键-->属性-->生成-->勾选XML文档文件选项-->关闭保存。后续我们将基于api的xml文件来生成接口。


10、修改APP_Start下SwaggerConfig.cs如下

using System.Web.Http;
using WebActivatorEx;
using WebApiToSwagger;
using Swashbuckle.Application;

[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]

namespace WebApiToSwagger
{
    public class SwaggerConfig
    {
        public static void Register()
        {
            var thisAssembly = typeof(SwaggerConfig).Assembly;

            GlobalConfiguration.Configuration
                .EnableSwagger(c =>
                    {
                        c.SingleApiVersion("v1", "WebApiToSwagger");
                        c.IncludeXmlComments(GetXmlCommentsPath());//让swagger根据xml文档来解析
                        c.CustomProvider((defaultProvider) => new SwaggerControllerDescProvider(defaultProvider, GetXmlCommentsPath()));//获取控制器的注释方法类
                    })
                .EnableSwaggerUi(c =>
                {
                    c.DocumentTitle("WebApiToSwagger");
                    c.InjectJavaScript(thisAssembly, "WebApiToSwagger.Models.SwaggerCustom.js");//汉化js
                });
        }
        /// <summary>
        /// Gets the XML comments path.
        /// </summary>
        /// <returns>System.String.</returns>
        private static string GetXmlCommentsPath()
        {
            return $"{System.AppDomain.CurrentDomain.BaseDirectory}/bin/WebApiToSwagger.XML";
        }
    }
}

目前还缺少2个文件。


11、先添加SwaggerControllerDescProvider.cs,新建文件夹App_Filter-->新增类SwaggerControllerDescProvider.cs,添加代码如下:

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Swashbuckle.Swagger;

namespace WebApiToSwagger.App_Filter
{
    /// <summary>
    /// swagger显示控制器的描述
    /// </summary>
    public class SwaggerControllerDescProvider : ISwaggerProvider
    {
        private readonly ISwaggerProvider _swaggerProvider;
        private static ConcurrentDictionary<string, SwaggerDocument> _cache = new ConcurrentDictionary<string, SwaggerDocument>();
        private readonly string _xml;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="swaggerProvider"></param>
        /// <param name="xml">xml文档路径</param>
        public SwaggerControllerDescProvider(ISwaggerProvider swaggerProvider, string xml)
        {
            _swaggerProvider = swaggerProvider;
            _xml = xml;
        }

        /// <summary>
        /// Gets the swagger.
        /// </summary>
        /// <param name="rootUrl">The root URL.</param>
        /// <param name="apiVersion">The API version.</param>
        /// <returns>SwaggerDocument.</returns>
        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 ConcurrentDictionary<string, string> GetControllerDesc()
        {
            string xmlpath = _xml;
            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;
        }
    }
}


12、在Model文件夹中添加SwaggerCustom.js,并且把这个js文件的属性,改一下,改成【嵌入的资源】

image.png

代码如下:

'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": "值",
    "Example 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();
});

再次编译看效果


image.png

汉化也成功了,注释也加上了。

最后要解决两个问题,一个是我们目前的接口,无法跨域访问,一个是如果控制器中写了一个私有方法也会被显示,我们需要加一个特性,来屏蔽指定的方法或者action。


13、跨域解决方案,引入Microsoft.AspNet.WebApi.Cors 5.2.7的包。

image.png

修改App_Start下的WebApiConfig文件,修改如下

using System.Web.Http;

namespace WebApiToSwagger
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {

            var allowOrigins = "*";//最好来自配置文件夹
            var allowHeaders = "*";//最好来自配置文件夹
            var allowMethods = "*";//最好来自配置文件夹
            var globalCors = new System.Web.Http.Cors.EnableCorsAttribute(allowOrigins, allowHeaders, allowMethods)
            {
                SupportsCredentials = true
            };
            config.EnableCors(globalCors);
            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

已经可以跨域访问接口了。


14、屏蔽某些接口不给界面展示,需要先修改一下App_Start文件夹下的SwaggerConfig文件。修改如下:

using System.Web.Http;
using WebActivatorEx;
using WebApiToSwagger;
using Swashbuckle.Application;
using WebApiToSwagger.App_Filter;

[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]

namespace WebApiToSwagger
{
    public class SwaggerConfig
    {
        public static void Register()
        {
            var thisAssembly = typeof(SwaggerConfig).Assembly;

            GlobalConfiguration.Configuration
                .EnableSwagger(c =>
                    {
                        c.SingleApiVersion("v1", "WebApiToSwagger");
                        c.IncludeXmlComments(GetXmlCommentsPath());
                        c.DocumentFilter<HiddenApiFilter>();
                        c.CustomProvider((defaultProvider) => new SwaggerControllerDescProvider(defaultProvider, GetXmlCommentsPath()));
                    })
                .EnableSwaggerUi(c =>
                {
                    c.DocumentTitle("WebApiToSwagger");
                    c.InjectJavaScript(thisAssembly, "WebApiToSwagger.Models.SwaggerCustom.js");

                });
        }
        /// <summary>
        /// Gets the XML comments path.
        /// </summary>
        /// <returns>System.String.</returns>
        private static string GetXmlCommentsPath()
        {
            return $"{System.AppDomain.CurrentDomain.BaseDirectory}/bin/WebApiToSwagger.XML";
        }
    }
}

在App_Filter文件夹中,添加HidddenApiFilter.cs,代码如下:

using System;
using System.Linq;
using System.Web.Http.Description;
using Swashbuckle.Swagger;

namespace WebApiToSwagger.App_Filter
{

    /// <summary>
    /// 隐藏接口,不生成到swagger文档展示
    /// </summary>
    /// <seealso cref="System.Attribute" />
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]

    public partial class HiddenApiAttribute : Attribute { }

    /// <summary>
    /// Class HiddenApiFilter.
    /// </summary>
    /// <seealso cref="Swashbuckle.Swagger.IDocumentFilter" />
    public class HiddenApiFilter : IDocumentFilter
    {
        /// <summary>
        /// 重写Apply方法,移除隐藏接口的生成
        /// </summary>
        /// <param name="swaggerDoc">swagger文档文件</param>
        /// <param name="schemaRegistry"></param>
        /// <param name="apiExplorer">api接口集合</param>
        public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
        {
            foreach (ApiDescription apiDescription in apiExplorer.ApiDescriptions)
            {
                if (Enumerable
                    .OfType<HiddenApiAttribute>(apiDescription.GetControllerAndActionAttributes<HiddenApiAttribute>())
                    .Any())
                {
                    string key = "/" + apiDescription.RelativePath;
                    if (key.Contains("?"))
                    {
                        int idx = key.IndexOf("?", StringComparison.Ordinal);
                        key = key.Substring(0, idx);
                    }

                    swaggerDoc.paths.Remove(key);
                }
            }
        }
    }
}


使用如下:

加上这个特性,这个接口或者这个控制器里面的所有接口,都不会显示了。

image.png


到这里就搭建完成了,最终项目结构如下:


image.png

波浪线是因为有些地方没写注释导致的。


运行发现还有个小错误。

image.png

最后禁用validator验证即可解决,SwaggerConfig.cs加上这句 c.DisableValidator();



CarsonIT 微信扫码关注公众号 策略、创意、技术

留下您的脚步

 

最近评论

查看更多>>

友情打赏

请打开您的微信,扫一扫