Swagger for Asp.net 应用笔记

简介

一直听说Swagger是做Web API文档的好工具,正好手头一个项目使用WebApi,决定使用Swagger来文档化接口,作为和前端交流的基础。下面是使用Swashbuckle.net 给ASP.net web API添加文档的简要步骤。

使用中如果碰到任何问题欢迎评论,一起讨论解决

项目引入Swagger

Swashbuckle是Swagger在dotnet环境中的实现,在ASP.net项目中加入后即可支持Swagger/UI。5.X版本支持ASP.net, 6.X(beta)版本支持ASP.net Core. 目前项目使用ASP.net for IIS,所以使用了5.4的版本。 关于selfhost和Owin Swashbuckle 的readme有很清楚的描述,可以自行查看。

使用nuget加入Swashbuckle的引用。
Paste_Image.png

安装好以后,在App_Start目录下,会有一个SwaggerConfig.cs文件,SwaggerConfig类通过[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]启动时运行。

nuget添加完引用,无须任何配置,编译后,访问 http://yoururl/swagger 即可看到所有API的文档说明,当然说明都很简单,没有太大价值。

SwaggerConfig简单介绍

SwaggerConfig.cs文件会自动添加到项目的App_Start目录下,代码本身包含大量注释掉的代码,清除后,代码如下:

 public class SwaggerConfig
   {
       public static void Register()
       {
           var thisAssembly = typeof(SwaggerConfig).Assembly;
            GlobalConfiguration.Configuration
               .EnableSwagger(c =>//用于启用和设置Swagger的配置信息。
                   {
                       c.SingleApiVersion("v1", "Cxx.xxx.Web");       
                      //a#                
                   })
               .EnableSwaggerUi(c =>
                   {//用于启用UI界面上的东西。
                      //b#
                  });
       }
   }
支持XML注释

使用Swagger的目的就是希望把代码中方法和类型的注释自动导出来。在Swashbuckle中,很简单。

  • 相关工程需要生成XML文档


    Paste_Image.png
  • 在Swagger.Config的Register方法的EnableSwagger匿名函数中加上对应的XML文件(可以添加在//a#代码后面。)

var baseDirectory = AppDomain.CurrentDomain.BaseDirectory +"\\bin\\"; var commentsFileName = Assembly.GetExecutingAssembly().GetName().Name + ".XML"; var commentsFile = Path.Combine(baseDirectory, commentsFileName); c.IncludeXmlComments(commentsFile);

上述代码把Web工程的XML注释加入到Swagger中。一般我们会把viewmodel或者其他类型定义在不同的工程中,通过下面的代码可以继续加入其它xml注释文件。(对应功能需要启用XML文档文件生成)
c.IncludeXmlComments(Path.Combine(baseDirectory, "CrXX.XX.xml"));

实现默认调用参数

Swagger本身支持直接调用,但是调用参数需要手动编写,一个比较简单的办法是自定义一个attribute,给viewmodel的参数加上默认参数。

  • 首先增加一个Swagger属性如下图:

     public class SwaggerDefaultValue : Attribute
     {
          public string Name { get; set; }
          public string Value { get; set; }
          public SwaggerDefaultValue(string value)
          {
              this.Value = value;
          }
    
          public SwaggerDefaultValue(string name, string value) : this(value)
          {
              this.Name = name;
          }
    }
    
  • 增加一个AddDefaultValues类,如下面代码

      public class AddDefaultValues : IOperationFilter
      {
        public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription     apiDescription)
        {
 
            IDictionary<string, object> parameterValuePairs =
            GetParameterValuePairs(apiDescription.ActionDescriptor);
            if (operation?.parameters != null)
            {
 
                foreach (var param in operation.parameters)
                {
                    if (param.schema != null && param.schema.@ref != null)
                    {
                        string schemaName = param.schema.@ref.Split('/').LastOrDefault();
                        if (schemaRegistry.Definitions.ContainsKey(schemaName))
                            foreach (var props in schemaRegistry.Definitions[schemaName].properties)
                            {
                                if (parameterValuePairs.ContainsKey(props.Key.ToLower()))//默认会转换为camelcase,所以强行转为小写
                                    props.Value.@default = parameterValuePairs[props.Key.ToLower()];//默认会转换为camelcase,所以强行转为小写
                            }
                    }
                    var parameterValuePair = parameterValuePairs.FirstOrDefault(p =>   p.Key.IndexOf(param.name, StringComparison.InvariantCultureIgnoreCase) >= 0);
                    param.@default = parameterValuePair.Value;
                }
            }
        }
 
      private IDictionary<string, object> GetParameterValuePairs(HttpActionDescriptor actionDescriptor)
      {
        IDictionary<string, object> parameterValuePairs = new Dictionary<string, object>();
 
        foreach (SwaggerDefaultValue defaultValue in actionDescriptor.GetCustomAttributes<SwaggerDefaultValue>())
        {
            parameterValuePairs.Add(defaultValue.Name, defaultValue.Value);
        }
 
        foreach (var parameter in actionDescriptor.GetParameters())
        {
            if (!parameter.ParameterType.IsPrimitive)
            {
                InitialType(parameterValuePairs, parameter.ParameterType);
            }
        }
 
        return parameterValuePairs;
      }
 
      void InitialType(IDictionary<string, object> parameterValuePairs, Type t)
      {
        foreach (PropertyInfo property in t.GetProperties())
        {
            var defaultValue = GetDefaultValue(property);
 
            if (defaultValue != null)
            {
 
                parameterValuePairs.Add(property.Name.ToLower(), defaultValue);//默认会转换为camelcase,所以强行转为小写
            }
        }
      }
 
      private static object GetDefaultValue(PropertyInfo property)
      {
        var customAttribute = property.GetCustomAttributes<SwaggerDefaultValue>().FirstOrDefault();
 
        if (customAttribute != null)
        {
            return customAttribute.Value;
        }
 
        return null;
      }  
}
  • 在Swagger.Config的Register方法的EnableSwagger匿名函数中对应的过滤器(可以添加在//a#代码后面。)
    c.OperationFilter<AddDefaultValues>();

  • 在具体的调用参数上加上默认参数,在swagger的界面上就会有默认参数啦。

 public class CheckMobile
 {
       [SwaggerDefaultValue("13482894351")]
       [Required]
       public string Mobile { get; set; }   
}
实现Oauth2 API的调用

我们的WebAPI基于OAuth2的验证,所以Swagger上并不能直接调用。如果希望Swagger能直接调用OAuth2保护的API。

  • 增加一个js到Web工程中, zhe


    Paste_Image.png

*js的内容如下

$('#input_apiKey').change(function () {
    var key = $('#input_apiKey')[0].value;
    var credentials = key.split(':'); //username:password expected
    $.ajax({
        url: "/Token",
        type: "post",
        contenttype: 'x-www-form-urlencoded',
        data: "grant_type=password&username=" + credentials[0] + "&password=" + credentials[1],
        success: function (response) {
            
            var bearerToken = 'Bearer ' + response.access_token;
            swaggerUi.api.clientAuthorizations.add('key', new SwaggerClient.ApiKeyAuthorization('Authorization', bearerToken, 'header'));
            
        },
        error: function (xhr, ajaxoptions, thrownerror) {
            alert("输入的用户名密码不对,不能登陆!");
        }
    });
});```
*在Swagger.config 的Register方法的EanbleSwaggerUI匿名函数中加上对应的JS,可以加在//b#后面
`c.InjectJavaScript(typeof(SwaggerConfig).Assembly, "CrXXX.XX.SwaggerLogin.js");`
>CrXXX.XX 需要改成web工程的名字

在Swagger界面上的API_Key输入框输入oauth2的用户名密码即可登录.

上面描述的是使用ASP.net 自带的OAuth认证库,不是真正的oauth2,如果采用IdenittyServer3的认证方法,需要参照[教程](http://knowyourtoolset.com/2015/08/secure-web-apis-with-swagger-swashbuckle-and-oauth2-part-2/) 来实现, 我会尽快翻译.
使用上面的方法要注意的是,在IdentityServer中的设置里面,Client需要
允许重定向到 "http://WebAPI地址/swagger/ui/o2c-html"

##### Swagger API文档的效果图

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1697920-057deb18522917ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容