本文转自:http://www.cnblogs.com/catcher1994/p/6021046.html

Web API,是一个能让前后端分离、解放前后端生产力的好东西。不过大部分公司应该都没能做到完全的前后端分离。API的实现方式有很

多,可以用ASP.NET Core、也可以用ASP.NET Web API、ASP.NET MVC、NancyFx等。说到Web API,不同的人有不同的做法,可能前台、

中台和后台各一个api站点,也有可能一个模块一个api站点,也有可能各个系统共用一个api站点,当然这和业务有必然的联系。

  安全顺其自然的成为Web API关注的重点之一。现在流行的OAuth 2.0是个很不错的东西,不过本文是暂时没有涉及到的,只是按照最最最

原始的思路做的一个授权验证。在之前的MVC中,我们可能是通过过滤器来处理这个身份的验证,在Core中,我自然就是选择Middleware来处

理这个验证。

  下面开始本文的正题:

  先编写一个能正常运行的api,不进行任何的权限过滤。

 1 using Dapper;
2 using Microsoft.AspNetCore.Mvc;
3 using System.Data;
4 using System.Linq;
5 using System.Threading.Tasks;
6 using WebApi.CommandText;
7 using WebApi.Common;
8 using Common;
9
10 namespace WebApi.Controllers
11 {
12 [Route("api/[controller]")]
13 public class BookController : Controller
14 {
15
16 private DapperHelper _helper;
17 public BookController(DapperHelper helper)
18 {
19 this._helper = helper;
20 }
21
22 // GET: api/book
23 [HttpGet]
24 public async Task<IActionResult> Get()
25 {
26 var res = await _helper.QueryAsync(BookCommandText.GetBooks);
27 CommonResult<Book> json = new CommonResult<Book>
28 {
29 Code = "000",
30 Message = "ok",
31 Data = res
32 };
33 return Ok(json);
34 }
35
36 // GET api/book/5
37 [HttpGet("{id}")]
38 public IActionResult Get(int id)
39 {
40 DynamicParameters dp = new DynamicParameters();
41 dp.Add("@Id", id, DbType.Int32, ParameterDirection.Input);
42 var res = _helper.Query<Book>(BookCommandText.GetBookById, dp, null, true, null, CommandType.StoredProcedure).FirstOrDefault();
43 CommonResult<Book> json = new CommonResult<Book>
44 {
45 Code = "000",
46 Message = "ok",
47 Data = res
48 };
49 return Ok(json);
50 }
51
52 // POST api/book
53 [HttpPost]
54 public IActionResult Post([FromForm]PostForm form)
55 {
56 DynamicParameters dp = new DynamicParameters();
57 dp.Add("@Id", form.Id, DbType.Int32, ParameterDirection.Input);
58 var res = _helper.Query<Book>(BookCommandText.GetBookById, dp, null, true, null, CommandType.StoredProcedure).FirstOrDefault();
59 CommonResult<Book> json = new CommonResult<Book>
60 {
61 Code = "000",
62 Message = "ok",
63 Data = res
64 };
65 return Ok(json);
66 }
67
68 }
69
70 public class PostForm
71 {
72 public string Id { get; set; }
73 }
74
75 }
  api这边应该没什么好说的,都是一些常规的操作,会MVC的应该都可以懂。主要是根据id获取图书信息的方法(GET和POST)。这是我们后

面进行单元测试的两个主要方法。这样部署得到的一个API站点,是任何一个人都可以访问http://yourapidomain.com/api/book 来得到相关

的数据。现在我们要对这个api进行一定的处理,让只有权限的站点才能访问它。

  下面就是编写自定义的授权验证中间件了。

  Middleware这个东西大家应该都不会陌生了,OWIN出来的时候就有中间件这样的概念了,这里就不展开说明,在ASP.NET Core中是如何

实现这个中间件的可以参考官方文档 Middleware

  我们先定义一个我们要用到的option,ApiAuthorizedOptions

 1 namespace WebApi.Middlewares
2 {
3 public class ApiAuthorizedOptions
4 {
5 //public string Name { get; set; }
6
7 public string EncryptKey { get; set; }
8
9 public int ExpiredSecond { get; set; }
10 }
11 }

  option内容比较简单,一个是EncryptKey ,用于对我们的请求参数进行签名,另一个是ExpiredSecond ,用于检验我们的请求是否超时。

与之对应的是在appsettings.json中设置的ApiKey节点

1   "ApiKey": {
2 //"username": "123",
3 //"password": "123",
4 "EncryptKey": "@*api#%^@",
5 "ExpiredSecond": "300"
6 }

  有了option,下面就可以编写middleware的内容了

  我们的api中就实现了get和post的方法,所以这里也就对get和post做了处理,其他http method,有需要的可以自己补充。

  这里的验证主要是下面的几个方面:

  1.参数是否被篡改

  2.请求是否已经过期

  3.请求的应用是否合法

  主检查方法:Check
 1          /// <summary>
2 /// the main check method
3 /// </summary>
4 /// <param name="context"></param>
5 /// <param name="requestInfo"></param>
6 /// <returns></returns>
7 private async Task Check(HttpContext context, RequestInfo requestInfo)
8 {
9 string computeSinature = HMACMD5Helper.GetEncryptResult($"{requestInfo.ApplicationId}-{requestInfo.Timestamp}-{requestInfo.Nonce}", _options.EncryptKey);
10 double tmpTimestamp;
11 if (computeSinature.Equals(requestInfo.Sinature) &&
12 double.TryParse(requestInfo.Timestamp, out tmpTimestamp))
13 {
14 if (CheckExpiredTime(tmpTimestamp, _options.ExpiredSecond))
15 {
16 await ReturnTimeOut(context);
17 }
18 else
19 {
20 await CheckApplication(context, requestInfo.ApplicationId, requestInfo.ApplicationPassword);
21 }
22 }
23 else
24 {
25 await ReturnNoAuthorized(context);
26 }
27 }

  Check方法带了2个参数,一个是当前的httpcontext对象和请求的内容信息,当签名一致,并且时间戳能转化成double时才去校验是否超时

和Applicatioin的相关信息。这里的签名用了比较简单的HMACMD5加密,同样是可以换成SHA等加密来进行这一步的处理,加密的参数和规则是

随便定的,要有一个约定的过程,缺少灵活性(就像跟银行对接那样,银行说你就要这样传参数给我,不这样就不行,只好乖乖从命)。

  Check方法还用到了下面的4个处理

  1.子检查方法--超时判断CheckExpiredTime

 1          /// <summary>
2 /// check the expired time
3 /// </summary>
4 /// <param name="timestamp"></param>
5 /// <param name="expiredSecond"></param>
6 /// <returns></returns>
7 private bool CheckExpiredTime(double timestamp, double expiredSecond)
8 {
9 double now_timestamp = (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
10 return (now_timestamp - timestamp) > expiredSecond;
11 }

  这里取了当前时间与1970年1月1日的间隔与请求参数中传过来的时间戳进行比较,是否超过我们在appsettings中设置的那个值,超过就是

超时了,没超过就可以继续下一个步骤。

  2.子检查方法--应用程序判断CheckApplication

  应用程序要验证什么呢?我们会给每个应用程序创建一个ID和一个访问api的密码,所以我们要验证这个应用程序的真实身份,是否是那些

有权限的应用程序。

 1         /// <summary>
2 /// check the application
3 /// </summary>
4 /// <param name="context"></param>
5 /// <param name="applicationId"></param>
6 /// <param name="applicationPassword"></param>
7 /// <returns></returns>
8 private async Task CheckApplication(HttpContext context, string applicationId, string applicationPassword)
9 {
10 var application = GetAllApplications().Where(x => x.ApplicationId == applicationId).FirstOrDefault();
11 if (application != null)
12 {
13 if (application.ApplicationPassword != applicationPassword)
14 {
15 await ReturnNoAuthorized(context);
16 }
17 }
18 else
19 {
20 await ReturnNoAuthorized(context);
21 }
22 }

  先根据请求参数中的应用程序id去找到相应的应用程序,不能找到就说明不是合法的应用程序,能找到再去验证其密码是否正确,最后才确

定其能否取得api中的数据。

  下面两方法是处理没有授权和超时处理的实现:

  没有授权的返回方法ReturnNoAuthorized

 1         /// <summary>
2 /// not authorized request
3 /// </summary>
4 /// <param name="context"></param>
5 /// <returns></returns>
6 private async Task ReturnNoAuthorized(HttpContext context)
7 {
8 BaseResponseResult response = new BaseResponseResult
9 {
10 Code = "401",
11 Message = "You are not authorized!"
12 };
13 context.Response.StatusCode = 401;
14 await context.Response.WriteAsync(JsonConvert.SerializeObject(response));
15 }

  这里做的处理是将响应的状态码设置成401(Unauthorized)。

  超时的返回方法ReturnTimeOut

 1         /// <summary>
2 /// timeout request
3 /// </summary>
4 /// <param name="context"></param>
5 /// <returns></returns>
6 private async Task ReturnTimeOut(HttpContext context)
7 {
8 BaseResponseResult response = new BaseResponseResult
9 {
10 Code = "408",
11 Message = "Time Out!"
12 };
13 context.Response.StatusCode = 408;
14 await context.Response.WriteAsync(JsonConvert.SerializeObject(response));
15 }

  这里做的处理是将响应的状态码设置成408(Time Out)。

  下面就要处理Http的GET请求和POST请求了。

  HTTP GET请求的处理方法GetInvoke

 1         /// <summary>
2 /// http get invoke
3 /// </summary>
4 /// <param name="context"></param>
5 /// <returns></returns>
6 private async Task GetInvoke(HttpContext context)
7 {
8 var queryStrings = context.Request.Query;
9 RequestInfo requestInfo = new RequestInfo
10 {
11 ApplicationId = queryStrings["applicationId"].ToString(),
12 ApplicationPassword = queryStrings["applicationPassword"].ToString(),
13 Timestamp = queryStrings["timestamp"].ToString(),
14 Nonce = queryStrings["nonce"].ToString(),
15 Sinature = queryStrings["signature"].ToString()
16 };
17 await Check(context, requestInfo);
18 }

  处理比较简单,将请求的参数赋值给RequestInfo,然后将当前的httpcontext和这个requestinfo交由我们的主检查方法Check去校验

这个请求的合法性。

  同理,HTTP POST请求的处理方法PostInvoke,也是同样的处理。

 1         /// <summary>
2 /// http post invoke
3 /// </summary>
4 /// <param name="context"></param>
5 /// <returns></returns>
6 private async Task PostInvoke(HttpContext context)
7 {
8 var formCollection = context.Request.Form;
9 RequestInfo requestInfo = new RequestInfo
10 {
11 ApplicationId = formCollection["applicationId"].ToString(),
12 ApplicationPassword = formCollection["applicationPassword"].ToString(),
13 Timestamp = formCollection["timestamp"].ToString(),
14 Nonce = formCollection["nonce"].ToString(),
15 Sinature = formCollection["signature"].ToString()
16 };
17 await Check(context, requestInfo);
18 }

  最后是Middleware的构造函数和Invoke方法。

 1        public ApiAuthorizedMiddleware(RequestDelegate next, IOptions<ApiAuthorizedOptions> options)
2 {
3 this._next = next;
4 this._options = options.Value;
5 }
6
7 public async Task Invoke(HttpContext context)
8 {
9 switch (context.Request.Method.ToUpper())
10 {
11 case "POST":
12 if (context.Request.HasFormContentType)
13 {
14 await PostInvoke(context);
15 }
16 else
17 {
18 await ReturnNoAuthorized(context);
19 }
20 break;
21 case "GET":
22 await GetInvoke(context);
23 break;
24 default:
25 await GetInvoke(context);
26 break;
27 }
28 await _next.Invoke(context);
29 }

  到这里,Middleware是已经编写好了,要在Startup中使用,还要添加一个拓展方法ApiAuthorizedExtensions

 1 using Microsoft.AspNetCore.Builder;
2 using Microsoft.Extensions.Options;
3 using System;
4
5 namespace WebApi.Middlewares
6 {
7 public static class ApiAuthorizedExtensions
8 {
9 public static IApplicationBuilder UseApiAuthorized(this IApplicationBuilder builder)
10 {
11 if (builder == null)
12 {
13 throw new ArgumentNullException(nameof(builder));
14 }
15
16 return builder.UseMiddleware<ApiAuthorizedMiddleware>();
17 }
18
19 public static IApplicationBuilder UseApiAuthorized(this IApplicationBuilder builder, ApiAuthorizedOptions options)
20 {
21 if (builder == null)
22 {
23 throw new ArgumentNullException(nameof(builder));
24 }
25
26 if (options == null)
27 {
28 throw new ArgumentNullException(nameof(options));
29 }
30
31 return builder.UseMiddleware<ApiAuthorizedMiddleware>(Options.Create(options));
32 }
33 }
34 }

  到这里我们已经可以在Startup的Configure和ConfigureServices方法中配置这个中间件了

  这里还有一个不一定非要实现的拓展方法ApiAuthorizedServicesExtensions,但我个人还是倾向于实现这个ServicesExtensions。

 1 using Microsoft.Extensions.DependencyInjection;
2 using System;
3
4 namespace WebApi.Middlewares
5 {
6 public static class ApiAuthorizedServicesExtensions
7 {
8
9 /// <summary>
10 /// Add response compression services.
11 /// </summary>
12 /// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
13 /// <returns></returns>
14 public static IServiceCollection AddApiAuthorized(this IServiceCollection services)
15 {
16 if (services == null)
17 {
18 throw new ArgumentNullException(nameof(services));
19 }
20
21 return services;
22 }
23
24 /// <summary>
25 /// Add response compression services and configure the related options.
26 /// </summary>
27 /// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
28 /// <param name="configureOptions">A delegate to configure the <see cref="ResponseCompressionOptions"/>.</param>
29 /// <returns></returns>
30 public static IServiceCollection AddApiAuthorized(this IServiceCollection services, Action<ApiAuthorizedOptions> configureOptions)
31 {
32 if (services == null)
33 {
34 throw new ArgumentNullException(nameof(services));
35 }
36 if (configureOptions == null)
37 {
38 throw new ArgumentNullException(nameof(configureOptions));
39 }
40
41 services.Configure(configureOptions);
42 return services;
43 }
44 }
45 }

ApiAuthorizedServicesExtensions

  为什么要实现这个拓展方法呢?个人认为

  Options、Middleware、Extensions、ServicesExtensions这四个是实现一个中间件的标配(除去简单到不行的那些中间件)

  Options给我们的中间件提供了一些可选的处理,提高了中间件的灵活性;

  Middleware是我们中间件最最重要的实现;

  Extensions是我们要在Startup的Configure去表明我们要使用这个中间件;

  ServicesExtensions是我们要在Startup的ConfigureServices去表明我们把这个中间件添加到容器中。

  下面是完整的Startup

 1 using Microsoft.AspNetCore.Builder;
2 using Microsoft.AspNetCore.Hosting;
3 using Microsoft.Extensions.Configuration;
4 using Microsoft.Extensions.DependencyInjection;
5 using Microsoft.Extensions.Logging;
6 using System;
7 using WebApi.Common;
8 using WebApi.Middlewares;
9
10 namespace WebApi
11 {
12 public class Startup
13 {
14 public Startup(IHostingEnvironment env)
15 {
16 var builder = new ConfigurationBuilder()
17 .SetBasePath(env.ContentRootPath)
18 .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
19 .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
20
21 if (env.IsEnvironment("Development"))
22 {
23 // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
24 builder.AddApplicationInsightsSettings(developerMode: true);
25 }
26
27 builder.AddEnvironmentVariables();
28 Configuration = builder.Build();
29 }
30
31 public IConfigurationRoot Configuration { get; }
32
33 // This method gets called by the runtime. Use this method to add services to the container
34 public void ConfigureServices(IServiceCollection services)
35 {
36 // Add framework services.
37 services.AddApplicationInsightsTelemetry(Configuration);
38 services.Configure<IISOptions>(options =>
39 {
40
41 });
42
43 services.Configure<DapperOptions>(options =>
44 {
45 options.ConnectionString = Configuration.GetConnectionString("DapperConnection");
46 });
47
48 //api authorized middleware
49 services.AddApiAuthorized(options =>
50 {
51 options.EncryptKey = Configuration.GetSection("ApiKey")["EncryptKey"];
52 options.ExpiredSecond = Convert.ToInt32(Configuration.GetSection("ApiKey")["ExpiredSecond"]);
53 });
54
55
56 services.AddMvc();
57
58 services.AddSingleton<DapperHelper>();
59 }
60
61 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline
62 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
63 {
64
65 loggerFactory.AddConsole(Configuration.GetSection("Logging"));
66 loggerFactory.AddDebug();
67
68 app.UseDapper();
69
70 //api authorized middleware
71 app.UseApiAuthorized();
72
73 app.UseApplicationInsightsRequestTelemetry();
74
75 app.UseApplicationInsightsExceptionTelemetry();
76
77 app.UseMvc();
78 }
79 }
80 }

  万事具备,只欠测试!!

  建个类库项目,写个单元测试看看。

 1 using Common;
2 using Newtonsoft.Json;
3 using System;
4 using System.Collections.Generic;
5 using System.Net.Http;
6 using System.Threading.Tasks;
7 using Xunit;
8
9 namespace WebApiTest
10 {
11 public class BookApiTest
12 {
13 private HttpClient _client;
14 private string applicationId = "1";
15 private string applicationPassword = "123";
16 private string timestamp = (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds.ToString();
17 private string nonce = new Random().Next(1000, 9999).ToString();
18 private string signature = string.Empty;
19
20 public BookApiTest()
21 {
22 _client = new HttpClient();
23 _client.BaseAddress = new Uri("http://localhost:8091/");
24 _client.DefaultRequestHeaders.Clear();
25 signature = HMACMD5Helper.GetEncryptResult($"{applicationId}-{timestamp}-{nonce}", "@*api#%^@");
26 }
27
28 [Fact]
29 public async Task book_api_get_by_id_should_success()
30 {
31 string queryString = $"applicationId={applicationId}&timestamp={timestamp}&nonce={nonce}&signature={signature}&applicationPassword={applicationPassword}";
32
33 HttpResponseMessage message = await _client.GetAsync($"api/book/4939?{queryString}");
34 var result = JsonConvert.DeserializeObject<CommonResult<Book>>(message.Content.ReadAsStringAsync().Result);
35
36 Assert.Equal("000", result.Code);
37 Assert.Equal(4939, result.Data.Id);
38 Assert.True(message.IsSuccessStatusCode);
39 }
40
41 [Fact]
42 public async Task book_api_get_by_id_should_failure()
43 {
44 string inValidSignature = Guid.NewGuid().ToString();
45 string queryString = $"applicationId={applicationId}&timestamp={timestamp}&nonce={nonce}&signature={inValidSignature}&applicationPassword={applicationPassword}";
46
47 HttpResponseMessage message = await _client.GetAsync($"api/book/4939?{queryString}");
48 var result = JsonConvert.DeserializeObject<CommonResult<Book>>(message.Content.ReadAsStringAsync().Result);
49
50 Assert.Equal("401", result.Code);
51 Assert.Equal(System.Net.HttpStatusCode.Unauthorized, message.StatusCode);
52 }
53
54 [Fact]
55 public async Task book_api_post_by_id_should_success()
56 {
57 var data = new Dictionary<string, string>();
58 data.Add("applicationId", applicationId);
59 data.Add("applicationPassword", applicationPassword);
60 data.Add("timestamp", timestamp);
61 data.Add("nonce", nonce);
62 data.Add("signature", signature);
63 data.Add("Id", "4939");
64 HttpContent ct = new FormUrlEncodedContent(data);
65
66 HttpResponseMessage message = await _client.PostAsync("api/book", ct);
67 var result = JsonConvert.DeserializeObject<CommonResult<Book>>(message.Content.ReadAsStringAsync().Result);
68
69 Assert.Equal("000", result.Code);
70 Assert.Equal(4939, result.Data.Id);
71 Assert.True(message.IsSuccessStatusCode);
72
73 }
74
75 [Fact]
76 public async Task book_api_post_by_id_should_failure()
77 {
78 string inValidSignature = Guid.NewGuid().ToString();
79 var data = new Dictionary<string, string>();
80 data.Add("applicationId", applicationId);
81 data.Add("applicationPassword", applicationPassword);
82 data.Add("timestamp", timestamp);
83 data.Add("nonce", nonce);
84 data.Add("signature", inValidSignature);
85 data.Add("Id", "4939");
86 HttpContent ct = new FormUrlEncodedContent(data);
87
88 HttpResponseMessage message = await _client.PostAsync("api/book", ct);
89 var result = JsonConvert.DeserializeObject<CommonResult<Book>>(message.Content.ReadAsStringAsync().Result);
90
91 Assert.Equal("401", result.Code);
92 Assert.Equal(System.Net.HttpStatusCode.Unauthorized, message.StatusCode);
93 }
94 }
95 }

  测试用的是XUnit。这里写了get和post的测试用例。

  下面来看看测试的效果。

  测试通过。这里是直接用VS自带的测试窗口来运行测试,比较直观。

  当然也可以通过我们的dotnet test命令来运行测试。

  本文的Demo已经上传到Github:

  https://github.com/hwqdt/Demos/tree/master/src/ASPNETCoreAPIAuthorizedDemo

  Thanks for your reading!

如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!    
声明: 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果您发现博客中出现了错误,或者有更好的建议、想法,请及时与我联系!!如果想找我私下交流,可以私信或者加我QQ。
 

[转]用Middleware给ASP.NET Core Web API添加自己的授权验证的更多相关文章

  1. 用Middleware给ASP.NET Core Web API添加自己的授权验证

    Web API,是一个能让前后端分离.解放前后端生产力的好东西.不过大部分公司应该都没能做到完全的前后端分离.API的实现方式有很 多,可以用ASP.NET Core.也可以用ASP.NET Web ...

  2. 使用JWT创建安全的ASP.NET Core Web API

    在本文中,你将学习如何在ASP.NET Core Web API中使用JWT身份验证.我将在编写代码时逐步简化.我们将构建两个终结点,一个用于客户登录,另一个用于获取客户订单.这些api将连接到在本地 ...

  3. List多个字段标识过滤 IIS发布.net core mvc web站点 ASP.NET Core 实战:构建带有版本控制的 API 接口 ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js 搭建前后端分离项目 Using AutoFac

    List多个字段标识过滤 class Program{  public static void Main(string[] args) { List<T> list = new List& ...

  4. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

  5. ASP.NET Core Web API 最佳实践指南

    原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide 介绍 当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求. 但是,你难 ...

  6. 如何在ASP.NET Core Web API中使用Mini Profiler

    原文如何在ASP.NET Core Web API中使用Mini Profiler 由Anuraj发表于2019年11月25日星期一阅读时间:1分钟 ASPNETCoreMiniProfiler 这篇 ...

  7. 使用 Swagger 自动生成 ASP.NET Core Web API 的文档、在线帮助测试文档(ASP.NET Core Web API 自动生成文档)

    对于开发人员来说,构建一个消费应用程序时去了解各种各样的 API 是一个巨大的挑战.在你的 Web API 项目中使用 Swagger 的 .NET Core 封装 Swashbuckle 可以帮助你 ...

  8. 在ASP.NET Core Web API上使用Swagger提供API文档

    我在开发自己的博客系统(http://daxnet.me)时,给自己的RESTful服务增加了基于Swagger的API文档功能.当设置IISExpress的默认启动路由到Swagger的API文档页 ...

  9. Docker容器环境下ASP.NET Core Web API应用程序的调试

    本文主要介绍通过Visual Studio 2015 Tools for Docker – Preview插件,在Docker容器环境下,对ASP.NET Core Web API应用程序进行调试.在 ...

随机推荐

  1. 前端学HTTP之缓存

    前面的话 Web缓存是可以自动保存常见文档副本的HTTP设备.当Web请求抵达缓存时,如果本地有“已缓存的”副本,就可以从本地存储设备而不是原始服务器中提取这个文档.本文将详细介绍缓存的相关内容 功能 ...

  2. ASP.NET MVC5+EF6+EasyUI 后台管理系统(6)-Unity 依赖注入

    系列目录 前言 为了符合后面更新后的重构系统,文章于2016-11-1日重写 本节重构一下代码,采用IOC控制反转,也就是依赖注入 您可以访问http://unity.codeplex.com/rel ...

  3. Node.js简单操作

    在node中是不支持BOM和DOM操作的,所以像alert().document.write...都是不支持的,可以是console.log() 首先我们来输出"hello world&qu ...

  4. IOS 封装功能和逻辑思想

    在ios开发中,难免会用到helper的思想.这篇就简单讲解下关于helper的简单实用方法. 假设我们要做一个这样的界面: 会议分为四种情况: 未召开 正在召开 已结束 已取消 再看看逻辑关系: 编 ...

  5. C#基础回顾(二)—页面值传递、重载与重写、类与结构体、装箱与拆箱

    一.前言 -孤独的路上有梦想作伴,乘风破浪- 二.页面值传递 (1)C#各页面之间可以进行数据的交换和传递,页面之间可根据获取的数据,进行各自的操作(跳转.计算等操作).为了实现多种方式的数据传递,C ...

  6. 从零开始学 Java - Spring 集成 Memcached 缓存配置(二)

    Memcached 客户端选择 上一篇文章 从零开始学 Java - Spring 集成 Memcached 缓存配置(一)中我们讲到这篇要谈客户端的选择,在 Java 中一般常用的有三个: Memc ...

  7. 阿里巴巴最新开源项目 - [HandyJSON] 在Swift中优雅地处理JSON

    项目名称:HandyJSON 项目地址:https://github.com/alibaba/handyjson 背景 JSON是移动端开发常用的应用层数据交换协议.最常见的场景便是,客户端向服务端发 ...

  8. how to use panda

    0.Introduce pandas.read_csv(filepath_or_buffer,sep=', ', dialect=None, compression='infer', doublequ ...

  9. autofac 组件的实例范围

    实例范围决定如何在请求之间共享服务. 原文地址:http://docs.autofac.org/en/latest/lifetime/instance-scope.html 每个依赖一个实例 使用这个 ...

  10. 20个不可思议的 WebGL 示例和演示

    WebGL 是一项在网页浏览器呈现3D画面的技术,有别于过去需要安装浏览器插件,通过 WebGL 的技术,只需要编写网页代码即可实现3D图像的展示.WebGL 可以为 Canvas 提供硬件3D加速渲 ...