案例：Windows Server 2008 R2系统，安装好IIS之后，再依次安装.net 3.5和.net 4.6.2。安装完成后发现，.net 3.5的站点可以正常访问，.net 4的站点无法访问，显示403或404，网站的应用程序池中的设置无异常。检查站点的IIS配置，发现IIS中.Net编译无法打开，报类似“无法识别的元素 folderLevelBuildProviders Web.config 第xx行”错误；打开IIS的应用程序映射，发现里面缺少与.net 4.0相关的所有的映射信息。解决方法：重新安装了一次.net 4.6.2的SDK故障依旧，重启了IIS也是毫无作用。这个映射信息太多了，难道需要挨个手动添加吗？使用下面的方法可以完美解决这个问题：打开.net 4.0的安装路径C:\Windows\Microsoft.NET\Framework64\v4.0.30319\，按住键盘的Shift键，同时在该文件夹的空白处右键，打开命令行（CMD），输入下面的命令：aspnet_regiis.exe /iru之后回车，执行完成后，问题成功排除。如果提示执行时出现错误，按提示信息打开日志，按日志里把对应的错误处理掉，再重新执行就可以了。
一、背景公司的一个asp.net项目，需要请求Http接口，并发比较高，会经常出现接口服务无响应同时IIS连接数非常高的现象，但是在服务端观察请求只有几十个，而IIS上的总连接数达到了几万，影响了业务的正常使用。二、排查因为服务端是用C++自己写的一个Web服务，先是怀疑服务端的承载能力不够，用压测工具直接压服务端接口，吞吐量可以达到600QPS，可以满足目前的业务需求，说明瓶颈不在服务端，而在客户端。分别尝试了HttpWebRequest的同步方法和异步方法，以及HttpClient的异步方法，压测的结果显示这三种方法的性能从高到低顺序是：HttpWebRequest同步方法> HttpWebRequest异步方法>HttpClient异步方法。使用4000并发压测，HttpWebRequest同步方法吞吐量在120QPS左右，HttpWebRequest异步方法在100左右，HttpClient异步方法只有40个左右，相差非常大。三、解决1.分析要提高Http请求的效率，一般有两种方法，一是维护一个连接池，二是连接复用。HttpWebRequest自带连接池，所以在前面的测试结果中，HttpWebRequest的吞吐量比较高；而HttpWebRequest的异步方法很可能是伪异步，所以效果不是特别明显；HttpClient也只有异步方法，但是却是表现最差的，原因就在每次请求都会重新实例化，请求结束后释放资源，在实例化和释放资源上损耗大量性能。2.方案有两种方案，一是使用HttpWebRequest的同步方法，维护好连接池并调优参数，但是这对耐力是一个很大的考验，而且没有把握可以做到让人满意的结果；第二个方案就是复用HttpClient，但是对原有项目的改动比较大，但是更有把握成功。我这里选择的是复用HttpClient。3.过程1) 静态化HttpClient首先尝试静态化HttpClient来实现复用，测试效果，代码如下： public class DemoController : ApiController { private static readonly HttpClient httpClient = new HttpClient() { BaseAddress = new Uri("http://localhost") }; public async Task<string> Post() { using (var responseData = await httpClient.PostAsync("/", new StringContent(""))) { return await responseData.Content.ReadAsStringAsync(); } } } 测试发现返回的HashCode是一致的，说明实现了复用；并发1000压测之后会发现吞吐量可以达到700左右，但是因为本机性能和没有调优的原因响应会比较慢，但是没有请求失败的情况，这说明复用之后可以有效的提高性能。图1.1000并发压测结果2) 解决不同请求时复用的问题静态化可以解决HttpClient的复用问题，但是如果这个项目中需要请求多个不同的接口地址，那么只是一个静态是解决不了的，因为HttpClient静态实例化之后，参数不能再次修改，怎么解决这个问题呢？如果是在.Net Core 2.1及以上的项目中，可以直接使用 HttpClientFactory 来实现，具体 HttpClientFactory 的使用方法后面再讲。但是在.Net Framework项目中不支持 HttpClientFactory ，而且不直接支持注入，这里我用到另一个方法来实现类似的功能。3) 解决依赖注入如果我们把每个不同的请求都定义一个静态实例，那就完全没了写代码的乐趣。要解决这个问题，就需要依赖注入来实现。在.Net Framework中微软自身没有提供依赖注入的方法，我们可以借用第三方库来完成，我这里使用Autofac来实现，前提是项目为.Net Framework 4.6.1及以上，如果低于该版本，需要先对项目进行升级。使用nuget分别安装 Autofac 、 Autofac.Integration.Mvc 和 Autofac.Integration.WebApi 这三个扩展，个别情况还需要安装 System.ValueTuple ，因环境而异。创建类 IocConfig ，代码如下： public class IocConfig { public static void RegisterDependencies() { ContainerBuilder builder = new ContainerBuilder(); HttpConfiguration config = GlobalConfiguration.Configuration; builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); HttpClient httpClient = new HttpClient { BaseAddress = new Uri("http://localhost") }; builder.Register(x=>httpClient).As<HttpClient>().SingleInstance(); var container = builder.Build(); config.DependencyResolver = new AutofacWebApiDependencyResolver(container); } } 在Global.asax中的 Application_Start 方法内，增加： //依赖注入 IocConfig.RegisterDependencies();我们去看一下注入有没有成功，在控制器的构造函数中增加要注入的内容： private static HttpClient _httpClient; public DemoController(HttpClient httpClient) { _httpClient = httpClient; }之后调用： public async Task<string> Post() { using (var responseData = await _httpClient.PostAsync("/", new StringContent(""))) { return await responseData.Content.ReadAsStringAsync(); } }之后我们测试一下，可以发现httpClient实例的HashCode保持一致，说明实现了复用，同样使用1000并发2分钟进行压测，压测结果与1) 的相差不大甚至略好于第一次，吞吐量稳定在700+，同样没有请求失败的情况。图2.依赖注入后1000并发压测结果这里成功的通过依赖注入实现了HttpClient的复用，下面我们要继续改造，对HttpClient进行封装。4) 进一步优化，对HttpClient进行封装新建类库项目 HttpService ，添加接口 IHttpService ，接口中增加一个异步Post方法： public interface IHttpService { /// /// 异步POST方法 /// /// 请求路径 /// 参数 /// 编码 /// 类型 /// <returns></returns> Task<string> PostAsync(string path, string paramsData, string charset, string contentType); }创建 HttpService 类，用来实现 IHttpService 接口： public class HttpService:IHttpService { private readonly HttpClient _client; public HttpService(HttpClient httpClient) { _client = httpClient; } /// /// 异步Post提交 /// /// /// /// /// /// <returns></returns> public async Task<string> PostAsync(string path,string paramsData,string charset,string contentType) { try { using (var responseData = await _client.PostAsync(path, new StringContent(paramsData, Encoding.GetEncoding(charset), contentType))) { return await responseData.Content.ReadAsStringAsync(); } } catch (System.Threading.ThreadAbortException e) { System.Threading.Thread.ResetAbort(); } catch (WebException e) { } catch (Exception e) { return ""; } finally { } } }修改Autofac的依赖注入配置文件中的代码： public class IocConfig { public static void RegisterDependencies() { ContainerBuilder builder = new ContainerBuilder(); HttpConfiguration config = GlobalConfiguration.Configuration; builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); HttpClient httpClient = new HttpClient { BaseAddress = new Uri("http://localhost") }; builder.Register(x => new HttpService.HttpService(httpClient)).As<HttpService.IHttpService>().SingleInstance(); var container = builder.Build(); config.DependencyResolver = new AutofacWebApiDependencyResolver(container); } }修改注入处的构造函数和调用方法，这里是直接把我们封装好的HttpService注入了进来： private readonly HttpService.IHttpService _httpService; public DemoController(HttpService.IHttpService httpService) { _httpService = httpService; } public async Task<string> Post() { return await _httpService.PostAsync("/", "", "utf-8", "text/html"); } 同样1000并发2分钟压测，吞吐量比前面的两个少了一点，稳定在680~690左右，不过同样稳定，没有请求失败的情况，性能同样很好。图3.对HttpClient进行简单封装后依赖注入，1000并发压测结果这样我们就可以复用我们封装好了的Http请求的方法了，但是如果要提前不同的接口，这里就不行了，下面继续优化。5) 多个HttpClient实例注入如果我们的项目需要请求多个不同的第三方接口，只需要改造一下 IocConfig 和注入的参数就可以了，IocConfig的修改： public class IocConfig { public static void RegisterDependencies() { ContainerBuilder builder = new ContainerBuilder(); HttpConfiguration config = GlobalConfiguration.Configuration; builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); var httpServices = new Dictionary<string, HttpService.IHttpService> { { "demo1", new HttpService.HttpService(new HttpClient(){ BaseAddress=new Uri("http://localhost")}) }, { "demo2", new HttpService.HttpService(new HttpClient(){ BaseAddress=new Uri("http://localhost:8001")}) } }; builder.RegisterInstance(httpServices).As<Dictionary<string, HttpService.IHttpService>>().SingleInstance(); var container = builder.Build(); config.DependencyResolver = new AutofacWebApiDependencyResolver(container); } }这里是分别实例化多个 HttpService.HttpService 方法，放到 Dictionary<string,HttpService.IHttpService> 中，之后把 Dictionary<string,HttpService.IHttpService> 通过控制器的构造函数注入进来，相应的我们的控制器的构造函数作如下的改动： private readonly Dictionary<string, HttpService.IHttpService> _httpServices; public DemoController(Dictionary<string, HttpService.IHttpService> httpService) { _httpServices = httpService; } public async Task<string> Post([FromUri]string key="demo1") { return await _httpServices[key].PostAsync("/", "", "utf-8", "text/html"); }测试一下，参数key分别传demo1和demo2 可以分别返回对应的接口的数据，说明我们这个方法成功了。继续压测一下，效果没有什么差别。图4.多HttpClient实例注入压测结果6) 进一步优化如果每增加一个需要调用的第三方API就改一次IocConfig，那就太不方便了，我们可以再进一步优化，把这些放到配置文件中，只需要维护配置文件就可以了。继续在第5)步的基础上进行封装，HttpService 项目中增加一个Config实体类，包含这些属性： /// /// 配置 /// public class Config { /// /// 标识 /// public string Key { get; set; } /// /// 中文名称 /// public string Name { get; set; } /// /// Http请求服务器 /// public string BaseAddress { get; set; } /// /// 超时时间 /// public int Timeout { get; set; } = 0; /// /// Http请求头 /// public Dictionary<string, string> Headers { get; set; } }创建配置文件，我这里使用json文件，放到了 App_Data 文件夹中，命名为 httpConfig.json，内容如下：[ { "Key": "demo1", "Name": "演示1接口", "BaseAddress": "http://localhost", "Headers": { "Accept": "application/json", "User-Agent": "Biz126-HttpClient", "KeepAlive": "true" }, "Timeout": 10 }, { "Key": "demo2", "Name": "演示2接口", "BaseAddress": "http://localhost:8001", "Headers": { "Accept": "application/json", "User-Agent": "Biz126-HttpClient", "KeepAlive": "true" }, "Timeout": 10 } ]新建类 HttpConfig 用来处理配置文件，代码如下： /// /// 处理配置 /// public class HttpConfig { string configPath; public HttpConfig() { configPath = string.Format("{0}{1}", HttpRuntime.AppDomainAppPath, System.Web.Configuration.WebConfigurationManager.AppSettings["httpConfig"]); } public HttpConfig(string path) { configPath = string.Format("{0}{1}", HttpRuntime.AppDomainAppPath, path); } /// /// 取链接配置 /// /// <returns></returns> public List<Config> GetConfig() { if (File.Exists(configPath)) { using (StreamReader f2 = new StreamReader(configPath, System.Text.Encoding.GetEncoding("gb2312"))) { return JsonConvert.DeserializeObject<List<Config>>(f2.ReadToEnd()); } } return new List<Config>(); } /// /// 指定链接的配置 /// /// /// <returns></returns> public Config GetConfig(string key) { var list = GetConfig(); if (list.Count > 0) { return list.Where(x => (x.Key.Equals(key, StringComparison.OrdinalIgnoreCase))).FirstOrDefault(); } return new Config(); } /// /// 生成HttpService列表 /// /// <returns></returns> public Dictionary<string, IHttpService> HttpServiceList() { var dict=new Dictionary<string, IHttpService>(); GetConfig().ForEach(x => { var httpClient = new HttpClient(); dict.Add(x.Key, new HttpService(httpClient, x)); }); return dict; } }修改 HttpService 类，在构造函数中增加配置相关的设置： /// /// 构建Http方法 /// /// HttpClient实例 /// 配置 public HttpService(HttpClient httpClient,Config config) { httpClient.BaseAddress = new Uri(config.BaseAddress); foreach (var header in config.Headers) { httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); } httpClient.Timeout = new TimeSpan(config.Timeout*10000000); httpClient.DefaultRequestHeaders.ExpectContinue = false; //关闭Expect:[100-continue]，默认为开启状态 很多旧的HTTP/1.0和HTTP/1.1应用不支持Expect头部 _client = httpClient; //忽略https证书有效性检查 if (_client.BaseAddress.Scheme.StartsWith("https", StringComparison.OrdinalIgnoreCase)) { ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult); } } /// /// 忽略证书 /// /// /// /// /// /// <returns></returns> public static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { //直接确认，否则打不开 return true; }修改 IocConfig 中的注入相关方法:builder.RegisterInstance(new HttpService.HttpConfig().HttpServiceList()).As<Dictionary<string, HttpService.IHttpService>>().SingleInstance();注入点的构造函数和调用方法不变，如下： private readonly Dictionary<string, HttpService.IHttpService> _httpServices; public DemoController(Dictionary<string, HttpService.IHttpService> httpService) { _httpServices = httpService; } public async Task<string> Post([FromUri]string key="demo1") { return await _httpServices[key].PostAsync("/", "", "utf-8", "text/html"); }最后在Web.config中的AppSettings节点下，增加一项配置：<add key="httpConfig" value="/App_Data/httpConfig.json" />我们运行项目，可以看到实现了注入，两项设置都成功加载了图5.加载配置文件后进行注入以上我们就完成了整个的优化，代码我放到了github上，其中 Biz126.HttpService 是封装后的HttpClient方法， Biz126.WebDemo 是前面的每一步的演示代码， Biz126.WebUI 是最终的调用代码。Github:https://github.com/hongbai/HttpClientFactory
ASP.Net Core 2.0 在Linux下连接SQL Server数据库问题
在ASP.Net Core 2.0下，通过Dapper来使用SQL Server数据库，在Windows系统下完全正常，而部署到Linux服务器上会出现连不上数据库的情况，从日志里看，报下面的错误：Connection Timeout Expired. The timeout period elapsed during the post-login phase. The connection could have timed out while waiting for server to complete the login process and respond; Or it could have timed out while attempting to create multiple active connections. The duration spent while attempting to connect to this server was - [Pre-Login] initialization=23; handshake=365; [Login] initialization=0; authentication=2; [Post-Login] complete=28022;连接超时。开始的时候怀疑是防火墙的原因，检查了一下防火墙正常，而且通过telnet命令检查数据库的1433端口是通的，看来问题是出在.net core上。通过百度和Google搜索相关的关键字，找到了一篇帖子：《Timeout Connecting to SQL Server instance from Linux》，说的是只有SQL Server 2008 及之前的版本会有这问题，SQL Server 2012及之后修复了这个问题。检查了下自己的SQL数据库版本，是SQL Server 2008 R2 版的，正在此列。开了腾讯云的SQL Server云数据库连接测试，完全正常。以上。
表单验证(Forms验证)是一个基于票据（ticket-based）[也称为基于令牌(token-based)]的系统。这意味着当用户登录系统以后，他们得到一个包含基于用户信息的票据（ticket）。这些信息被存放在加密过的cookie里面，这些cookie和响应绑定在一起，因此每一次后续请求都会被自动提交到服务器。当用户请求匿名用户无法访问的ASP.NET页面时，ASP.NET运行时验证这个表单验证票据是否有效。如果无效，ASP.NET自动将用户转到登录页面。这时就该由你来操作了。你必须创建这个登录页面并且验证由登录页面提交的凭证。如果用户验证成功，你只需要告诉ASP.NET架构验证成功（通过调用FormsAuthentication类的一个方法），运行库会自动设置验证cookie（实际上包含了票据）并将用户转到原先请求的页面。通过这个请求，运行库检测到验证cookie中包含一个有效票据，然后赋给用户对这个页面的访问权限。下面，就是具体的实现。我们先写一个生成用户票据的方法：//生成Token并保存到Cookies中 private void CreateToken(Models.User user) { string json = JsonConvert.SerializeObject(user); FormsAuthentication.SetAuthCookie(user.UserName.Trim(), true, FormsAuthentication.FormsCookiePath); FormsAuthenticationTicket Ticket = new FormsAuthenticationTicket(1, user.UserName, DateTime.Now, DateTime.Now.AddHours(12), false, json); Biz126.Cache.SetCookies.AddCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(Ticket)); }其中Biz126.Cache.SetCookies.AddCookie是我自己封装的保存Cookie方法。生成Token并保存到Cookie中的方法我们已经写好了，只要在登录成功之后直接调用这个方法就可以了。下面是验证票据，验证票据，我们放在过滤器中进行验证。新建AuthenticationAttribute类，继承ActionFilterAttribute，重写OnActionExecuting方法： /// /// 检查用户是否有该Action执行的操作权限 /// /// public override void OnActionExecuting(HttpActionContext filterContext) { Models.ResultModel result = new Models.ResultModel (); result.code = -401; result.message = "请登录后进行操作"; result.status = false; //如果存在身份信息 if (!HttpContext.Current.User.Identity.IsAuthenticated) { filterContext.Response = filterContext.Request.CreateResponse(HttpStatusCode.Unauthorized, result, "application/json"); } else { BLL.IManager manager = new BLL.Manage.Manager(); result = manager.Detail(); //取当前登录的用户信息，进行其他的操作，比如判断有没有权限等，这里省略 //。。。省略掉其他的操作 if (!result.status) { filterContext.Response = filterContext.Request.CreateResponse(HttpStatusCode.Unauthorized, result, "application/json"); //filterContext.Result = redirect; } } }之后，在要在需要用户登录的Action上面，加上[Authentication]就可以了，也可以新加一个控制器，如BaseWebApiController，在这个控制器上加上[Authentication]，其他的控制器都继承BaseWebApiController，在不需要验证的Action上加上[AllowAnonymous]就可以了。下面是通过票据取得用户信息： if (HttpContext.Current.Request.IsAuthenticated)//是否通过身份验证 { HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];//获取cookie FormsAuthenticationTicket Ticket = FormsAuthentication.Decrypt(authCookie.Value);//解密 string json = Ticket.UserData;//反序列化 Models.Models.User user = new Models.User(); try { user = JsonConvert.DeserializeObject<Models.User>(json); result.data = user; result.message = "success"; result.status = true; } catch { } }还需要在web.config中增加配置： <system.web> <authentication mode="Forms"> <forms loginUrl="~/Users/Login" timeout="2880" /> </authentication> </system.web>到这里，其本上都已经完成了，但是你会发现，在IE上，会有很多用户登录的时候验证不通过的情况，如果记录日志，会发现HttpContext.Current.Request.IsAuthenticated==falseHttpContext.Current.Request.User为空而且这种情况只有在IE下才会出现，其他的现代浏览器比如Chrome、Firefox等都很正常。通过网络搜索，大部分都没给出答案来。其他把配置按下面的方法修改，就可以解决这个问题： <system.web> <authentication mode="Forms"> <forms cookieless="UseCookies" loginUrl="~/Users/Login" timeout="2880" /> </authentication> <authorization> <allow users="*" /> </authorization> </system.web>或者是打开服务器的IIS，按下面的步骤修改：1.打开IIS，选择自己的站点，之后双击IIS中的“身份验证”功能2.选中Forms身份验证，点击右侧操作区的“编辑”菜单，如果没有启用请先点击“启用”3.这是Forms身份验证的默认设置，我们需要改动一下4.按这里修改一下，就可以了。以上。
我们知道log4net的日志功能非常强大，而使用方法也比较复杂；在ASP.NET Core 2.0下，可以通过一个第三方的扩展方法来降低我们的使用难度，具体使用方法如下：我们先新建一个自己的静态类Log4Net，用于之后调用记录日志： public static class Log4Net { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(Log4Net)); static void SetConfig() { XmlDocument log4netConfig = new XmlDocument(); log4netConfig.Load(File.OpenRead("log4net.config")); var repo = log4net.LogManager.CreateRepository( Assembly.GetEntryAssembly(), typeof(log4net.Repository.Hierarchy.Hierarchy)); log4net.Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]); } /// /// 信息 /// /// public static void LogInfo(string Message) { if (!log.IsInfoEnabled) SetConfig(); log.Info(Message); } /// /// 信息 /// /// /// public static void LogInfo(string Message, Exception ex) { if (!log.IsInfoEnabled) SetConfig(); log.Info(Message, ex); } /// /// 错误日志 /// /// public static void ErrorInfo(string Message) { if (!log.IsErrorEnabled) SetConfig(); log.Error(Message); } /// /// 错误日志 /// /// /// public static void Error(Exception ex) { if (!log.IsInfoEnabled) SetConfig(); log.Error("异常", ex); } /// /// 错误日志 /// /// /// public static void ErrorInfo(string Message, Exception ex) { if (!log.IsErrorEnabled) SetConfig(); log.Error(Message, ex); } /// /// Debug日志 /// /// public static void DebugInfo(string Message) { if (!log.IsDebugEnabled) SetConfig(); log.Debug(Message); } /// /// Debug日志 /// /// /// public static void DebugInfo(string Message, Exception ex) { if (!log.IsDebugEnabled) SetConfig(); log.Debug(Message, ex); } /// /// 警告 /// /// public static void WarnInfo(string Message) { if (!log.IsWarnEnabled) SetConfig(); log.Warn(Message); } /// /// 警告 /// /// /// public static void WarnInfo(string Message,Exception ex) { if (!log.IsWarnEnabled) SetConfig(); log.Warn(Message,ex); } /// /// 致命错误 /// /// public static void FataInfo(string Message) { if (!log.IsFatalEnabled) SetConfig(); log.Fatal(Message); } /// /// 致命错误 /// /// /// public static void FataInfo(string Message,Exception ex) { if (!log.IsFatalEnabled) SetConfig(); log.Fatal(Message, ex); } 该类下需要通过NuGet安装下面几个扩展：Microsoft.Extensions.LoggingMicrosoft.Extensions.Logging.Log4Net.AspNetCorelog4net之后log4net.config配置文件内容：<log4net> <appender name="Console" type="log4net.Appender.ConsoleAppender"> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%5level [%thread] - STACK: %exception{stacktrace} - MESSAGE: %message%newline" /> </layout> </appender> <appender name="RollingFile" type="log4net.Appender.RollingFileAppender"> <!--<file value="Log4Net" /> <appendToFile value="true" /> <rollingStyle value="Date" /> <datePattern value="yyyyMMdd".log"" /> <maximumFileSize value="100KB" /> <maxSizeRollBackups value="2" />--> <layout type="log4net.Layout.PatternLayout"> <!--<conversionPattern value="%level %thread - STACK: %exception{stacktrace} - MESSAGE: %message%newline" />--> </layout> </appender> <root> <level value="ALL" /> <appender-ref ref="Console" /> <appender-ref ref="RollingFile" /> </root> </log4net>最后，修改Startup.cs文件的Configure方法，增加新参数如下：public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)在Configure方法中，添加：loggerFactory.AddLog4Net();完整如下： // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } loggerFactory.AddLog4Net(); app.UseStaticFiles(); app.UseMvc(); }测试：我们写个测试的来看看，在Program类的静态方法中，添加段代码测试一下： public static void Main(string[] args) { Logger.Log4Net.LogInfo("test测试"); try { int a = 5; int b = 1 / (a - 5); } catch (Exception e) { Logger.Log4Net.ErrorInfo("异常信息", e); } BuildWebHost(args).Run(); }运行之后，我们会在根目录下的logfile文件夹中，看到出现了一个日志文件：20180513.log打开，内容如下：2018-05-13 22:43:12,039 [1] INFO Biz126.Logger.Log4Net - test测试 2018-05-13 22:43:12,930 [1] ERROR Biz126.Logger.Log4Net - 异常信息 System.DivideByZeroException: Attempted to divide by zero. at Biz126.WebAPI.Program.Main(String[] args) in D:\Items\Log测试\Biz126.WebAPI\Program.cs:line 23 2018-05-13 22:43:18,274 [5] INFO Microsoft.AspNetCore.Hosting.Internal.WebHost - Request starting HTTP/1.1 GET http://localhost:65176/api/values 2018-05-13 22:43:18,753 [5] INFO Microsoft.AspNetCore.Hosting.Internal.WebHost - Request finished in 479.9045ms 404 符合预期，以上。
关于 asp.net core 2.0 webapi的跨域，我们这里使用CORS来实现，不使用旧的JSONP，可以这样配置：打开 Startup.cs文件，转到ConfigureServices(IServiceCollection services) 中，增加：services.AddCors(options => { options.AddPolicy("AnyOrigin", builder => { builder .AllowAnyOrigin() .AllowAnyMethod(); }); });再转到Configure(IApplicationBuilder app, IHostingEnvironment env)中，增加：app.UseCors("AnyOrigin");就可以开启CORS跨域了。关于ASP.Net Core的CORS跨域问题详细的使用方法，请参考《Enable Cross-Origin Requests (CORS) in ASP.NET Core》一文。下面测试一下： 直接双击通过浏览器打开这个页面，可以看到，已经读到远程接口的数据了：以上。
在.Net网站下使用Oracle，需要网站服务器安装Oracle数据库的客户端，这是目前必须要安装的。以下是在Windows Server 2008 R2服务器环境下的设置方法及注意事项，其他系统可能会略有不同。1.服务器需要安装Oracle数据库的客户端，版本为Oracle Database 11g Release 1，而且只能使用这个版本，其他版本不可行，另外需要与你的网站应用池的位数（32位或64位）一致；2.安装时，安装类型选择“运行时”；3.安装完成后，查看环境变量中，有没有把安装目录下的BIN文件夹添加到环境变量中，如：D:\app\Administrator\product\11.2.0\client_1\bin;；4.打开安装目录右键属性，切换到“安全”选项卡，编辑，选中“Authenticated Users“，把”读取和执行“权限去掉，之后再重新勾选，之后”应用“；5.最后一步，也是重要的一步，那就是重启服务器，不重启服务器不会生效。这就可以了。
在之前，我简单的介绍过在.net core中使用Mongodb（见文章《.Net Core系列教程（三）——使用Mongodb》）,也使用过PostgreSQL（但是没有写文章介绍怎么使用，只是在文章《.Net Core系列教程（一）——环境搭建》中简单介绍过如何安装）。当然，我的文章质量都不高，只是把自己平时遇到的问题记录下来，很多问题是自己经历过之后在网上苦苦的寻找答案但都不适用或者不明了的情况下，自己摸索出来的解决方案，这也算是防止自己再次误入坑，也给遇到同样问题的朋友一点帮助吧。下面说下怎样在.net core中使用MySQL，这个问题网上随便一搜有很多，我的当然也是从网上搜索来的，只是用自己的语言再次整理下而已。在使用MySQL时，需要使用到MySQL的驱动，之前MySQL官方没有出驱动的时候，需要使用第三方的，不过现在有官方的驱动，还是尽量使用官方的吧，我这里也以官方的为准。另外还用到了Dapper这个小型ORM，这两个都可以通过Nuget来安装。需要注意的是，MySQL.Data需要安装最新版的（现在是6.10.3-rc版），旧版本不支持.net core 2.0先在appsettings.json文件中，添加数据库的配置： "ConnectionStrings": { "MySqlConnection": "server=127.0.0.1;userid=root;pwd=root's password;port=3306;database=database's name;sslmode=none;Charset=utf8;" }之后，增加数据库连接的Model类： public class ConnectionStrings { public string MySqlConnection { get; set; } }在Startup.cs文件中的ConfigureServices方法里，在services.AddMvc();之前增加添加调用：services.Configure<Models.ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));这样就会把appsettings.json中的数据库连接配置注入到Models.ConnectionStrings实体类中。在控制器中，添加：private readonly IOptions<Models.ConnectionStrings> _settings;之后控制器的构造函数： public NewsController(IOptions<Models.ConnectionStrings> settings) { _settings = settings; }这里我是直接把数据库连接的实体类传给了DAL层，这样写： public NewsController(IOptions<Models.ConnectionStrings> settings) { _settings = new BLL.ServiceImp.News(settings.Value); }其中的settings.Value就是数据库连接实体类了之后在DAL层中使用，同时也附带了几个数据库操作的实例： private string ConnString; public News(Models.ConnectionStrings Connection) { ConnString = Connection.MySqlConnection; } /// /// 取前top条新闻 /// /// <returns></returns> public async Task<Models.ResultModel<List<Models.News>>> Newslatest(int top) { Models.ResultModel<List<Models.News>> result = new ResultModel<List<Models.News>>(); using (var Conn = new MySqlConnection(ConnString)) { string sql = "select Id,title,datetime,writer,remark,body,cid,status,isimage,thumb,category from news where status=1 order by datetime desc limit 0,@top"; var data = await Conn.QueryAsync<Models.News>(sql, new { top = top }); if(data.Count()>0) { result.code = 0; result.status = true; result.message = "OK"; result.data = data.AsList(); } } return result; } /// /// 取单条 /// /// /// <returns></returns> public async Task<Models.ResultModel<Models.News>> Detail(int id) { Models.ResultModel<Models.News> result = new ResultModel<Models.News>(); using (var Conn = new MySqlConnection(ConnString)) { string sql = "select n.Id,title,datetime,writer,remark,body,cid,status,isimage,thumb,c.category from news n left join category c on n.cid=c.id where n.Id=@id"; var res = await Conn.QueryAsync<Models.News>(sql, new { id = id }); var data = res.SingleOrDefault(); //注意这里 result.code = -404; result.message = "没有找到该条新闻"; if (data != null) { result.status = true; result.message = "OK"; result.code = 0; result.data = data; } else { result.status = false; result.code = -404; result.message = "没有找到该条新闻"; } } return result; } /// /// 删除 /// /// /// <returns></returns> public async Task<Models.ResultModel > Delete(int id) { Models.ResultModel result = new ResultModel (); using (var Conn = new MySqlConnection(ConnString)) { string sql = "delete from news where Id=@id"; var res = await Conn.ExecuteAsync(sql, new { id = id }); result.status = res > 0; if (result.status) { result.code = 0; result.message = "success"; } } return result; } /// /// 使用事务 /// /// /// <returns></returns> public async Task<Models.ResultModel > DeleteCategory(int id) { Models.ResultModel result = new ResultModel (); using (var Conn = new MySqlConnection(ConnString)) { Conn.Open(); //注意这里，使用事务时，需要先open using (IDbTransaction transaction = Conn.BeginTransaction()) { try { string sql = "update news set cid=0 where cid=@id;delete from category where Id=@id;"; //删除分类，同时把该分类下的新闻移动到默认分类下 var res = await Conn.ExecuteAsync(sql, new { id = id }); transaction.Commit(); //提交事务 result.status = res > 0; if (result.status) { result.code = 0; result.message = "success"; } else { transaction.Rollback(); //删除失败，回滚 } } catch { transaction.Rollback(); //出现异常，回滚 } } Conn.Close(); } return result; }以上。
我在《.Net Core 2.0的一些不大一样的地方（一）——基础身份认证》中，说了下关于Cookies的不同之处，在这篇文章中，我说下关于服务器的相关问题。服务器的环境安装，在官网中介绍的.Net Core 2.0环境的安装方法，是通过源的方法来安装，我尝试了下，失败了，可能是和我的服务器中有之前的.Net Core 1.0环境有关。把原来的环境删掉，先删除/usr/local/bin/dotnet软链接，之后再删除掉.net core的文件夹/opt/dotnet。不过我后面再没有试通过源的方式安装是否没问题，这里我还是使用.net core 1.0的时候安装的方法，下载.net core 2.0 sdk，之后解压缩设置软链接的方式安装。微软官方给出的通过源安装的方法，参见：《.NET and C# - Get Started in 10 Minutes》一文，自行选择自己使用的操作系统，来按步骤安装。如果在程序中，使用了身份认证（如Cookies等，见《.Net Core 2.0的一些不大一样的地方（一）——基础身份认证》），那么还需要在Startup.cs文件的Configure方法中，在调用UseAuthentication之前调用UseForwardedHeaders，如下：app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); app.UseAuthentication();这样，在做身份验证的时候，能够通过反代服务器取到用户访问的URL，不然的话，会因为URL不一致（用户访问的是Nginx服务器上绑定的域名，而.net core服务器使用的是localhost）导致验证失败。nginx服务器的安装及反代服务配置与之前的版本没有什么区别，有区别的是守护程序的配置，使用的还是systemd(systemctl)，配置中，需要多设置一项：WorkingDirectory=站点的根目录如：[Unit] Description=Example .NET Web API Application running on Ubuntu [Service] WorkingDirectory=/var/aspnetcore/hellomvc ExecStart=/usr/local/bin/dotnet /var/aspnetcore/hellomvc/hellomvc.dll Restart=always RestartSec=10 # Restart service after 10 seconds if dotnet service crashes SyslogIdentifier=dotnet-example User=www-data Environment=ASPNETCORE_ENVIRONMENT=Production [Install] WantedBy=multi-user.target如果不设置这项的话，所有的静态资源文件都请求不到，返回502或404错误。好了，基本就这些了吧，遇到其他的再继续补充。
.Net Core 2.0的一些不大一样的地方（一）——基础身份认证
近日，把之前使用.net core 1.0写的网站，使用.net core 2.0改写了一下，发现一些不大一样的地方，如果不注意的话，会出现些问题。一、先说下关于使用Cookie来验证用户登录的地方：在.net core 1.x时代，具体作法如我前面的文章《.Net Core系列教程（四）—— 基础身份认证》所说，这里我就不重新写了而在.net core 2.0中，需要做以下调整：1）在Startup.cs文件中，ConfigureServices方法下添加： services.AddAuthentication(options=> { options.DefaultChallengeScheme = "Cookie"; options.DefaultSignInScheme = "Cookie"; options.DefaultAuthenticateScheme = "Cookie"; }) .AddCookie("Cookie", m => { m.LoginPath = new PathString("/Manage/CPanel/Login"); m.AccessDeniedPath = new PathString("/Manage/CPanel/Forbidden"); m.LogoutPath = new PathString("/Manage/CPanel/Logout"); m.Cookie.Path = "/"; });完整的代码如下： public void ConfigureServices(IServiceCollection services) { services.AddApplicationInsightsTelemetry(Configuration); services.AddAuthentication(options=> { options.DefaultChallengeScheme = "Cookie"; options.DefaultSignInScheme = "Cookie"; options.DefaultAuthenticateScheme = "Cookie"; }) .AddCookie("Cookie", m => { m.LoginPath = new PathString("/Manage/CPanel/Login"); m.AccessDeniedPath = new PathString("/Manage/CPanel/Forbidden"); m.LogoutPath = new PathString("/Manage/CPanel/Logout"); m.Cookie.Path = "/"; }); services.AddOptions(); services.Configure<Models.ConnectionStrings>(Configuration.GetSection("ConnectionStrings")); services.AddAuthorization(); //Form基础验证 services.AddMvc(); }之后在Configure方法下添加：app.UseAuthentication();完整代码： public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); app.UseAuthentication(); //app.UseApplicationInsightsRequestTelemetry(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } //app.UseApplicationInsightsExceptionTelemetry(); app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "area", template: "{area:exists}/{controller=CPanel}/{action=Index}/{id?}"); routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }之后在控制器中使用：登录时： result = _manage.Login(login); if (result.status) {//登录成功 string token = result.data.ToString(); //登录成功后生成的token，用于验证登录有效性 var claims = new List<Claim>() { new Claim(ClaimTypes.Name,login.username) }; var userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, token)); await HttpContext.SignInAsync("Cookie", userPrincipal, new Microsoft.AspNetCore.Authentication.AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddHours(12), IsPersistent = true, AllowRefresh = false }); }验证有效性： var auth = await HttpContext.AuthenticateAsync("Cookie"); if(auth.Succeeded) { //验证有效 string username = auth.Principal.Identity.Name; //用户名 //通过验证后的其他处理代码 }在其他需要验证是否登录状态，可以在对应的Action或Controller上增加特性：[Authorize]，就可以了。-==以下部分为2018年5月13日新增==-二、使用Jwt方式Jwt是Json Web Token的缩写，下面是关于Jwt介绍的一些搬运：JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准（RFC 7519），定义了一种简洁的，自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在，这些信息是可信的，JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。简洁(Compact): 可以通过URL，POST参数或者在HTTP header发送，因为数据量小，传输速度也很快 自包含(Self-contained)：负载中包含了所有用户所需要的信息，避免了多次查询数据库。JWT包含了使用.分隔的三部分： Header 头部 Payload 负载 Signature 签名其结构看起来是这样的Header.Payload.SignatureHeader在header中通常包含了两部分：token类型和采用的加密算法。{ "alg": "HS256", "typ": "JWT"} 接下来对这部分内容使用 Base64Url 编码组成了JWT结构的第一部分。PayloadToken的第二部分是负载，它包含了claim， Claim是一些实体（通常指的用户）的状态和额外的元数据，有三种类型的claim：reserved, public 和 private.Reserved claims: 这些claim是JWT预先定义的，在JWT中并不会强制使用它们，而是推荐使用，常用的有 iss（签发者）,exp（过期时间戳）, sub（面向的用户）, aud（接收方）, iat（签发时间）。 Public claims：根据需要定义自己的字段，注意应该避免冲突 Private claims：这些是自定义的字段，可以用来在双方之间交换信息 负载使用的例子：{ "sub": "1234567890", "name": "John Doe", "admin": true} 上述的负载需要经过Base64Url编码后作为JWT结构的第二部分。Signature创建签名需要使用编码后的header和payload以及一个秘钥，使用header中指定签名算法进行签名。例如如果希望使用HMAC SHA256算法，那么签名应该使用下列方式创建： HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) 签名用于验证消息的发送者以及消息是没有经过篡改的。 完整的JWT 完整的JWT格式的输出是以. 分隔的三段Base64编码，与SAML等基于XML的标准相比，JWT在HTTP和HTML环境中更容易传递。 下列的JWT展示了一个完整的JWT格式，它拼接了之前的Header， Payload以及秘钥签名。关于概念性的介绍就写到这里，下面是使用方法。先说流程：客户端提交用户名和密码，发起登录请求；服务器接收到请求后，验证用户名和密码的合法性，验证通过，给生成token返回给客户端；客户端得到token之后自行保存；客户端再次发起其他需要用户登录身份的请求时，在HTTP头中带上前面申请到的token；服务器接受到请求后，验证token的合法性，进行下一步操作。这里就涉及到了签名和验签两部分。先是签名：在自己的项目中，找一个适合的地方，添加以下几个类：RSAKeyHelper.csTokenAuthOption.csRsaParameterStorage.csRSAKeyHelper.cs类内容： public class RSAKeyHelper { public static RSAParameters GenerateKey() { string keyDir = $"{PlatformServices.Default.Application.ApplicationBasePath}key"; if (TryGetKeyParameters(keyDir, true, out RSAParameters keyParams) == false) { keyParams = GenerateAndSaveKey(keyDir); } return keyParams; } /// /// 从本地文件中读取用来签发 Token 的 RSA Key /// /// 存放密钥的文件夹路径 /// /// /// <returns></returns> public static bool TryGetKeyParameters(string filePath, bool withPrivate, out RSAParameters keyParameters) { string filename = withPrivate ? "key.json" : "key.public.json"; keyParameters = default(RSAParameters); string path = Path.Combine($"{filePath}/", filename); //Console.WriteLine($"filePath:{path}"); if (File.Exists(path) == false) return false; string json = File.ReadAllText(path); var parameterStorage = JsonConvert.DeserializeObject<RsaParameterStorage>(json); keyParameters.D = parameterStorage.D; keyParameters.DP = parameterStorage.DP; keyParameters.DQ = parameterStorage.DQ; keyParameters.Exponent = parameterStorage.Exponent; keyParameters.InverseQ = parameterStorage.InverseQ; keyParameters.Modulus = parameterStorage.Modulus; keyParameters.P = parameterStorage.P; keyParameters.Q = parameterStorage.Q; return true; } /// /// 生成并保存 RSA 公钥与私钥 /// /// 存放密钥的文件夹路径 /// <returns></returns> public static RSAParameters GenerateAndSaveKey(string filePath) { RSAParameters publicKeys, privateKeys; using (var rsa = new RSACryptoServiceProvider(2048)) { try { privateKeys = rsa.ExportParameters(true); publicKeys = rsa.ExportParameters(false); } finally { rsa.PersistKeyInCsp = false; } } if (Directory.Exists(filePath) == false) Directory.CreateDirectory(filePath); File.WriteAllText(Path.Combine(filePath, "key.json"), ToJsonString(privateKeys)); File.WriteAllText(Path.Combine(filePath, "key.public.json"), ToJsonString(publicKeys)); return privateKeys; } // 转换成 json 字符串 static string ToJsonString(RSAParameters parameters) { var parameterStorage = new RsaParameterStorage { D = parameters.D, DP = parameters.DP, P = parameters.P, DQ = parameters.DQ, Q = parameters.Q, Exponent = parameters.Exponent, InverseQ = parameters.InverseQ, Modulus = parameters.Modulus }; return JsonConvert.SerializeObject(parameterStorage); } }这个类主要干了这些事：1.生成公钥私钥对并转成JSON保存到本地；2.读取保存在本地的公钥私钥对并生成KeyTokenAuthOption.cs类内容： public class TokenAuthOption { /// /// 受众,代表将要使用这些token的实体 /// public static string Audience { get; }; /// /// 发行者,表示生成token的实体 /// public static string Issuer { get; }; public static RsaSecurityKey Key { get; } = new RsaSecurityKey(RSAKeyHelper.GenerateKey()); /// /// 安全密钥和创建签名算法 /// public static SigningCredentials SigningCredentials { get; } = new SigningCredentials(Key, SecurityAlgorithms.RsaSha256Signature); /// /// 有效期 /// public static TimeSpan ExpiresSpan { get; } = TimeSpan.FromDays(30); }RsaParameterStorage.cs类，是用于保存和读取生成的公私钥对的转换，因为ASP.NET Core 2.0之后，私密信息在转成JSON时不允许保存了，必须要转一下，为了省事，这里就用了个最笨的办法： public class RsaParameterStorage { public byte[] D { get; set; } public byte[] DP { get; set; } public byte[] DQ { get; set; } public byte[] Exponent { get; set; } public byte[] InverseQ { get; set; } public byte[] Modulus { get; set; } public byte[] P { get; set; } public byte[] Q { get; set; } }上面这些都是准备工作，下面开始配置：打开Startup.cs文件，在ConfigureServices方法中添加： //Jwt验证 //http://www.cnblogs.com/rocketRobin/p/8058760.html services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "JwtBearer"; options.DefaultChallengeScheme = "JwtBearer"; }) .AddJwtBearer("JwtBearer", jwtBearerOptions => { jwtBearerOptions.RequireHttpsMetadata = false; jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = TokenAuthOption.Key, ValidateIssuer = true, ValidIssuer = TokenAuthOption.Issuer,//The name of the issuer, ValidateAudience = true, ValidAudience = TokenAuthOption.Audience,//The name of the audience, ValidateLifetime = true, //validate the expiration and not before values in the token ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date }; jwtBearerOptions.Events = new JwtBearerEvents() { OnAuthenticationFailed = c => { c.NoResult(); c.Response.StatusCode = 200; c.Response.ContentType = "application/json"; return c.Response.WriteAsync( JsonConvert.SerializeObject( new Biz126.Models.ReturnModel () { Status = false, Message = "登录信息已失效", Code = 403, ResultData = new { tokenExpired = true } } ) ); } }; }); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); 这里需要注意：如果有开启CORS跨域的话，一定要放在CORS的配置之后。还是在Startup.cs文件中，在Configure方法内，如果有CORS配置的话，也是在CORS配置之后，添加：app.UseAuthentication();我的完整的Startup.cs文件如下，包含了Redis、数据库连接、CORS跨域、log4net和Jwt验证： public class Startup { public static ILoggerRepository repository { get; set; } public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); if (env.IsDevelopment()) { // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately. builder.AddApplicationInsightsSettings(developerMode: true); } Configuration = builder.Build(); RedisHelper.InitializeConfiguration(Configuration); //Redis repository = LogManager.CreateRepository("NETCoreRepository"); XmlConfigurator.Configure(repository, new FileInfo("log4net.config")); } public IConfigurationRoot Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddOptions(); services.Configure<Dictionary<string, string>>(Configuration.GetSection("ConnectionStrings")); //数据库连接 //CORS跨域 services.AddCors(options => { options.AddPolicy("AnyOrigin", builder => { builder .WithOrigins("http://localhost:65176") .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); }); }); services.Configure<MvcOptions>(options => { options.Filters.Add(new CorsAuthorizationFilterFactory("AnyOrigin")); }); //Jwt验证 //http://www.cnblogs.com/rocketRobin/p/8058760.html services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "JwtBearer"; options.DefaultChallengeScheme = "JwtBearer"; }) .AddJwtBearer("JwtBearer", jwtBearerOptions => { jwtBearerOptions.RequireHttpsMetadata = false; jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = TokenAuthOption.Key, ValidateIssuer = true, ValidIssuer = TokenAuthOption.Issuer,//The name of the issuer, ValidateAudience = true, ValidAudience = TokenAuthOption.Audience,//The name of the audience, ValidateLifetime = true, //validate the expiration and not before values in the token ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date }; jwtBearerOptions.Events = new JwtBearerEvents() { OnAuthenticationFailed = c => { c.NoResult(); c.Response.StatusCode = 200; c.Response.ContentType = "application/json"; return c.Response.WriteAsync( JsonConvert.SerializeObject( new Biz126.Models.ReturnModel () { Status = false, Message = "登录信息已失效", Code = 403, ResultData = new { tokenExpired = true } } ) ); } }; }); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } loggerFactory.AddLog4Net(); //CORS跨域 app.UseCors("AnyOrigin"); app.UseAuthentication(); app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "area", template: "{area:exists}/{controller=Product}/{action=List}/{id?}"); routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } }这里配置结束，下面是开始使用了：先增加一个生成token的方法，我这里是单独拿的一个类来做的：Authorize.cs类： public static class Authorize { /// /// 生成Token /// /// /// <returns></returns> public static string GenerateToken(Biz126.User.Models.UserToken userToken) { var handler = new JwtSecurityTokenHandler(); string jti = TokenAuthOption.Audience + userToken.User_Name + DateTime.Now.Add(TokenAuthOption.ExpiresSpan).Millisecond; jti = WebTools.Tools.MD5Cryptog(jti); // Jwt 的一个参数，用来标识 Token ClaimsIdentity identity = new ClaimsIdentity( new GenericIdentity(userToken.User_Name, "TokenAuth"), new[] { new Claim("user_id", userToken.User_Id.ToString()), //用户ID new Claim("user_name",userToken.User_Name), //用户名 new Claim("user_type",userToken.User_Type.ToString()), //身份 new Claim("jti",jti,ClaimValueTypes.String) // jti，用来标识 token } ); var securityToken = handler.CreateToken(new SecurityTokenDescriptor { Issuer = TokenAuthOption.Issuer, Audience = TokenAuthOption.Audience, SigningCredentials = TokenAuthOption.SigningCredentials, Subject = identity, NotBefore = DateTime.Now, Expires = DateTime.Now.Add(TokenAuthOption.ExpiresSpan) }); return handler.WriteToken(securityToken); } }重写IHttpContextAccessorExtension.cs类，得到当前用户信息： public static class IHttpContextAccessorExtension { public static User.Models.UserToken CurrentUser(this IHttpContextAccessor httpContextAccessor) { var claimsIdentity = httpContextAccessor?.HttpContext?.User?.Identity as ClaimsIdentity; //var stringUserId = claimsIdentity?.Claims?.FirstOrDefault(x => x.Type.Equals("user_id", StringComparison.CurrentCultureIgnoreCase))?.Value; //int.TryParse(stringUserId ?? "0", out int userId); var userToken = new User.Models.UserToken(); var claims = claimsIdentity?.Claims?.ToList(); var propertys = userToken.GetType().GetProperties(); foreach (PropertyInfo property in propertys) { string name = property.Name.ToLower(); var value = claims?.FirstOrDefault(x => x.Type.Equals(name, StringComparison.CurrentCultureIgnoreCase))?.Value; property.SetValue(userToken, string.IsNullOrEmpty(value) ? null : Convert.ChangeType(value, property.PropertyType), null); } return userToken; } }用户登录时，验证账号密码通过，调用Authorize.GenerateToken(userinfo)方法生成token返回给客户端；客户端在请求头中增加"Authorization"，值为"Bearer"+空格+Token，如“Bearer Header.Payload.Signature”服务器WebAPI接口在控制器的构造函数中，这样写： private Biz126.Models.UserToken current_userToken = new Biz126.Models.UserToken(); //当前用户基本信息 public MemberController(IOptions<Dictionary<string, string>> settings, IHttpContextAccessor httpContextAccessor) { current_userToken = httpContextAccessor.CurrentUser(); }就可以拿到保存在token中的用户信息了。当然，记得要在需要使用验证的控制器或者方法上，增加[Authorize]特性，如果不需要验证的话，要增加[AllowAnonymous]。以上。