[转]用Middleware给ASP.NET Core Web API添加自己的授权验证
本文转自:http://www.cnblogs.com/catcher1994/p/6021046.html
Web API,是一个能让前后端分离、解放前后端生产力的好东西。不过大部分公司应该都没能做到完全的前后端分离。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站点,是任何一个人都可以访问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.参数是否被篡改
3.请求的应用是否合法

- 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是我们中间件最最重要的实现;
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}×tamp={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}×tamp={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!
[转]用Middleware给ASP.NET Core Web API添加自己的授权验证的更多相关文章
- 用Middleware给ASP.NET Core Web API添加自己的授权验证
Web API,是一个能让前后端分离.解放前后端生产力的好东西.不过大部分公司应该都没能做到完全的前后端分离.API的实现方式有很 多,可以用ASP.NET Core.也可以用ASP.NET Web ...
- 使用JWT创建安全的ASP.NET Core Web API
在本文中,你将学习如何在ASP.NET Core Web API中使用JWT身份验证.我将在编写代码时逐步简化.我们将构建两个终结点,一个用于客户登录,另一个用于获取客户订单.这些api将连接到在本地 ...
- 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& ...
- 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 重点: 实现多级子目录的压缩, ...
- ASP.NET Core Web API 最佳实践指南
原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide 介绍 当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求. 但是,你难 ...
- 如何在ASP.NET Core Web API中使用Mini Profiler
原文如何在ASP.NET Core Web API中使用Mini Profiler 由Anuraj发表于2019年11月25日星期一阅读时间:1分钟 ASPNETCoreMiniProfiler 这篇 ...
- 使用 Swagger 自动生成 ASP.NET Core Web API 的文档、在线帮助测试文档(ASP.NET Core Web API 自动生成文档)
对于开发人员来说,构建一个消费应用程序时去了解各种各样的 API 是一个巨大的挑战.在你的 Web API 项目中使用 Swagger 的 .NET Core 封装 Swashbuckle 可以帮助你 ...
- 在ASP.NET Core Web API上使用Swagger提供API文档
我在开发自己的博客系统(http://daxnet.me)时,给自己的RESTful服务增加了基于Swagger的API文档功能.当设置IISExpress的默认启动路由到Swagger的API文档页 ...
- Docker容器环境下ASP.NET Core Web API应用程序的调试
本文主要介绍通过Visual Studio 2015 Tools for Docker – Preview插件,在Docker容器环境下,对ASP.NET Core Web API应用程序进行调试.在 ...
随机推荐
- .NET 程序集单元测试工具 SmokeTest 应用指南
Smoke Test(冒烟测试),也称Regression Test(回归测试),是对软件的安装和基本功能的测试.一般地我们使用脚本来实现Smoke Test的自动化,可借用虚拟机的snapshot机 ...
- MongoDB安装与故障
下载完毕后 bin为官方代码 data为自行创建的文件夹 db存在数据 log存在日志 启动MongoDB 通过cmd到db的文件目录 通过mongod.exe代码执行data下的log文 ...
- socket编程为什么需要htons(), ntohl(), ntohs(),htons() 函数
在C/C++写网络程序的时候,往往会遇到字节的网络顺序和主机顺序的问题.这是就可能用到htons(), ntohl(), ntohs(),htons()这4个函数. 网络字节顺序与本地字节顺序之间的转 ...
- Php基础知识测试题
一:选择题 1. LAMP具体结构不包含下面哪种(A ) A:Windows系统 如果是这个就是WMP B:Apache服务器 C:MySQL数据库 D:PHP语 ...
- Bootstrap人民币玩家攻略
用bootstrap及其它基于它的框架,做了多次网站大改版~对bootstrap的特点有了越来越深的了解~从一开始接触时觉得超级鸡肋,到后来觉得方便,再到后来觉得还是能不用就别用了~为什么这么说?我们 ...
- H5实现本地预览图片
我们使用H5可以很容易的实现图片上传前对其进行预览的功能 Html代码如下: <!DOCTYPE html> <html lang="en"> <he ...
- Nginx反向代理部署指南
一.反向代理 我们都知道,80端口是web服务的默认端口,其他主机访问web服务器也是默认和80端口进行web交互,而一台服务器也只有一个80端口,这是约定俗成的标准. 我们来看下面两个场景: 1.服 ...
- sqlserver 游标的使用
declare @temp_temp uniqueidentifier--临时变量 DECLARE aaa CURSOR for select Id from A ------------------ ...
- JDBC——Java代码与数据库链接的桥梁
常用数据库的驱动程序及JDBC URL: Oracle数据库: 驱动程序包名:ojdbc14.jar 驱动类的名字:oracle.jdbc.driver.OracleDriver JDBC URL:j ...
- Xshell显示中文乱码问题
[文件]–>[打开]–>在打开的session中选择连接的那个,点击[属性] -> [终端], 编码选择为:Unicode(UTF-8),然后重新连接服务器即可.也可以在Xshell ...