在.Net Core 2.0中使用MySQL

在之前,我简单的介绍过在.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;
        }


        /// <summary>
        /// 取前top条新闻
        /// </summary>
        /// <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;
        }

        /// <summary>
        /// 取单条
        /// </summary>
        /// <param name="id"></param>
        /// <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;
        }


        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<Models.ResultModel<object>> Delete(int id)
        {
            Models.ResultModel<object> result = new ResultModel<object>();
            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;
        }


        /// <summary>
        /// 使用事务
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<Models.ResultModel<object>> DeleteCategory(int id)
        {
            Models.ResultModel<object> result = new ResultModel<object>();

            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的一些不大一样的地方(二)——服务器环境

我在《.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.Signature

Header
在header中通常包含了两部分:token类型和采用的加密算法。{ "alg": "HS256", "typ": "JWT"} 接下来对这部分内容使用 Base64Url 编码组成了JWT结构的第一部分。

Payload
Token的第二部分是负载,它包含了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的合法性,进行下一步操作。

这里就涉及到了签名和验签两部分。先是签名:
在自己的项目中,找一个适合的地方,添加以下几个类:

  1. RSAKeyHelper.cs

  2. TokenAuthOption.cs

  3. RsaParameterStorage.cs

RSAKeyHelper.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;
        }

        /// <summary>
        /// 从本地文件中读取用来签发 Token 的 RSA Key
        /// </summary>
        /// <param name="filePath">存放密钥的文件夹路径</param>
        /// <param name="withPrivate"></param>
        /// <param name="keyParameters"></param>
        /// <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;
        }

        /// <summary>
        /// 生成并保存 RSA 公钥与私钥
        /// </summary>
        /// <param name="filePath">存放密钥的文件夹路径</param>
        /// <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.读取保存在本地的公钥私钥对并生成Key

TokenAuthOption.cs类内容:

    public class TokenAuthOption
    {
        /// <summary>
        /// 受众,代表将要使用这些token的实体
        /// </summary>
        public static string Audience { get; };

        /// <summary>
        /// 发行者,表示生成token的实体
        /// </summary>
        public static string Issuer { get; };

        public static RsaSecurityKey Key { get; } = new RsaSecurityKey(RSAKeyHelper.GenerateKey());
        
        /// <summary>
        /// 安全密钥和创建签名算法
        /// </summary>
        public static SigningCredentials SigningCredentials { get; } = new SigningCredentials(Key, SecurityAlgorithms.RsaSha256Signature);

        /// <summary>
        /// 有效期
        /// </summary>
        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<object>()
                                {
                                    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<object>()
                                {
                                    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
    {
        /// <summary>
        /// 生成Token
        /// </summary>
        /// <param name="userToken"></param>
        /// <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]。

以上。

使用iText5来处理PDF

项目要求,通过pdf模板,把用户提交的数据保存到一个PDF文件中。其中有文字内容,也有图片。之前选了aspose.pdf,因为抠门,不能花钱买,就从网上找的的开心版,好不容易出来点模板,结果插入图片的时候,同一页只能插入一张图片,而官方的试用版是可以正常两张的,另外字段比较多,速度比较慢,几百个字段需要一分多钟,效率很低,放弃。之后尝试iText,发现要比aspose.pdf好用的多,下面就说下用法。
需要通过nuget安装iTextSharp,选第一个,版本号是5.5.12,也就是iText5版本,该版本是AGPL许可。
完整代码如下:

using iTextSharp.text;
using iTextSharp.text.pdf;
using System.IO;

namespace Tools
{
    public class iTextPdf
    {
        Document pdfDocument = new Document();
        private string _path = "";

        #region 内容属性
        /// <summary>
        /// 文字
        /// </summary>
        public string Content { get; set; }

        /// <summary>
        /// X坐标
        /// </summary>
        public float X { get; set; }

        /// <summary>
        /// Y坐标
        /// </summary>
        public float Y { get; set; }

        /// <summary>
        /// 页索引(从0开始)
        /// </summary>
        public int PageIndex { get; set; }

        /// <summary>
        /// 类型
        /// 1-文字
        /// 2-勾选(图片)
        /// 3-签名
        /// 4-身份证正面
        /// 5-身份证反而
        /// </summary>
        public int Type { get; set; }

        /// <summary>
        /// 字体大小
        /// </summary>
        public float textSize { get; set; }

        /// <summary>
        /// 宽度(图片使用)
        /// </summary>
        public float Width { get; set; }

        /// <summary>
        /// 高度(图片使用)
        /// </summary>
        public float Height { get; set; }
        #endregion


        public iTextPdf()
        {
            _path = string.Format("{0}/template.pdf", System.Web.HttpContext.Current.Request.MapPath(System.Web.Configuration.WebConfigurationManager.AppSettings["ConfigDir"]));
        }

        public iTextPdf(string path)
        {
            _path = path;
        }

        public void SavePdf(List<iTextPdf> pdflist,string savefile)
        {
            using (Stream inputPdfStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.Read))   //打开pdf模板
            using (Stream outputPdfStream = new FileStream(savefile, FileMode.Create, FileAccess.Write, FileShare.None))    //创建新的pdf对象用于保存
            {
                var reader = new PdfReader(inputPdfStream);
                var stamper = new PdfStamper(reader, outputPdfStream);
                var list = pdflist.OrderBy(x => x.PageIndex);   //对页号运行排序
                //FontFactory.RegisterDirectory("C:\\WINDOWS\\Fonts");  //字体目录
                list.Select(x => x.PageIndex).Distinct().ToList().ForEach(page =>
                {   //先按页进行遍历,可以减少实例化pdfContentByte的次数,减少资源占用
                    var pdfContentByte = stamper.GetOverContent(page);
                    Rectangle pagesize = reader.GetPageSizeWithRotation(page);
                    float height = pagesize.Height;
                    list.Where(x => x.PageIndex.Equals(page)).ToList().ForEach(item =>
                    { //针对每页进行要插入的内容进行遍历
                        switch (item.Type)
                        {
                            case 1:
                            case 2:
                                string content = item.Content;
                                float fontsize = 12;    //字号
                                if (!string.IsNullOrEmpty(content))
                                {   //只处理有文字内容的
                                    if ((2).Equals(item.Type))
                                    {   //对勾选的内容进行处理
                                        if ("1".Equals(content))
                                        {   //标记为1的,内容换成√,按插入文字处理
                                            content = "√"; 
                                            fontsize = 16;
                                        }
                                        else
                                        {
                                            content = "";
                                            break;
                                        }

                                    }
                                    BaseFont bfTimes = BaseFont.CreateFont(System.Web.HttpContext.Current.Request.MapPath("~/fonts/simhei.ttf"), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);   //注册字体,支持中文
                                    pdfContentByte.SetColorFill(BaseColor.BLACK);

                                    pdfContentByte.SetFontAndSize(bfTimes, fontsize);   //设置字体及字号
                                    pdfContentByte.BeginText(); //开始处理文字
                                    pdfContentByte.ShowTextAligned(Element.ALIGN_LEFT, content, item.X, height - item.Y, 0);
                                    pdfContentByte.EndText();   //文字处理达成
                                }

                                break;
                            case 4:
                            case 5:  //身份证图片
                                string imgpath = System.Web.HttpContext.Current.Request.MapPath(item.Content);  //图片物理路径
                                using (Stream inputImageStream = new FileStream(imgpath, FileMode.Open, FileAccess.Read, FileShare.Read))   //打开图片
                                {
                                    Image image = Image.GetInstance(inputImageStream);
                                    image.SetAbsolutePosition(item.X, item.Y);  //图片坐标
                                    image.ScaleAbsolute(item.Width, item.Height);   //设置图片宽度和高度
                                    pdfContentByte.AddImage(image);
                                }
                                break;
                        }
                    });
                });

                stamper.Close();    //关闭同时保存
            }
        }
    }
}

关于亿达普罗旺斯业主维权的一点看法

今天,有网友反映,大连亿达房产的普罗旺斯业主集体到大连高新区管委会门前维权,原因是买房时承诺的学区化为泡影。
据了解到的情况是这样的,开发商普罗旺斯小区一二三期打着12年理工教育的口号卖房,房子卖差不多了就偷换概念弄了个理工的老师当校长的普通学校。而在买房之前,也有人在民心网询问学校的事情,而且也得到了相关部门的确认,是理工附属学校。
当然,有人会说开发商无法确定学区,学区是教育局定的;也有人会说,这是开发商虚假宣传,应该去找开发商的,到管委会找什么事;还有的人会冷嘲热讽,嘲笑买学区房的业主,笑话他们孩子将来的好与坏只与孩子和家长有关,学区房无用。等等等等。
我想说的是,正是学区是教育局定的,而且在买房之前得到了教育局的肯定,说是理工附属学校,业主才会买的房,那么去找政府不找开发商,是站得住脚的;关于嘲笑别人买学区房的问题,我想说的是,要么是吃不到葡萄说葡萄酸,要么就和刘强东说的自己脸盲没觉得奶茶漂亮是一个道理,因为读的都是贵族学校,不需要考虑学区。
有的人强调重点在人,学区无用,关于这个问题,我再多聊两句。在孩子的教育这个问题上,我承认“人”是重点之一,但并不是唯一因素。影响孩子未来发展的因素有很多,除了孩子自身之外,家庭、学校在这其中都扮演着重要的角色。有其父必有其子、孟母三迁等说的就是家庭、学校环境对孩子的影响。
打一个比方,农村的最好的学校,放到城里也不及一个中等的学校。考上同一个大学成绩相当的两个孩子,来自农村的孩子在中学时的名次,要比城市孩子的名次要高,而其他的综合素质要比城市孩子的要差很多;同一个城市里的高中,重点中学的重本率都要比最差的高中本科率要高,差的学校打架斗殴拿刀捅人的要比重点学校的要多。当然,你又可能会说,是家长没教育好,这个理由我无法反驳,但是我想不明白的是,孩子在学校的时候,我还能跟着到学校看着他吗?现在能明白吧?
关于这件事,业主去政府门口固然不对,但是当弱势群体与强势群体抗衡时,又打不过他,除了找他爹之外,还有什么办法呢?
有人说法制不健全,就这件事来说,是法制不健全的原因吗?当然你也可以归咎到法制不健全上。以中国目前的法律法规,绝大多数的民事刑事纠纷都能找到处理方法。
有法不依,执法不严,办事不公,人情社会,这是根源,当然也需要更加完善的监督体系。
俗话说严以律己,宽以待人,希望机关人员都能严格的要求自己,宽容的对待百姓。

附相关资料:
相关文章:大连高新,学区房之殇。

防原文被删备份
防原文被删备份

2017.7.3 9:30 高新区管委会门前
2017.7.3 9:30 高新区管委会门前

网友提供的相关资料

经过网络搜索,普罗旺斯业主维权不止一次:
2016.2.21 大连亿达普罗旺斯业主维权纪实
2016.12.31 亿达普罗旺斯二期违规卖车位,亿达物业蛮横欺压普罗旺斯二期

亿达普罗旺斯小区简介:
(以下信息来源于网络)
亿达旅顺南路区域开发之首期低密度高端住宅社区,26年开发经验的巅峰代表作。项目独有大连稀缺的山谷地貌,空气质量绝佳、景观资源丰富。超大山体公园与低密建筑自然相生,幽静、私密、尊贵。依托软件园二期的迅猛发展,凌水湾、河口湾及大连天地的相继兴建,轻轨及地铁一号线的陆续投入使用,区域未来的商业和交通配套日益完善,发展潜力巨大。

详细信息

  • 楼盘名: 亿达普罗旺斯

  • 楼盘地址: 大连高新园区西段,小平岛沿旅顺南路西行1公里

  • 楼盘特色:近轻轨、品牌房企、山景房

  • 所属区域板块: 高新旅顺南路

  • 轨道交通: 大连轻轨8号线-蔡大岭站

  • 房屋现状: 期房

  • 售价: 18000元/平米备注: 顶跃18000元/平,底跃20000元/平

  • 开盘说明:

  • 预计交房时间: 2013-10-01

  • 交房说明: 一期2013年10月1日交房,二期2014年10月30日交房。

基本信息

  • 开发商: 大连软件园安博开发有限公司

  • 投资商: 美国安博基金

  • 售楼处地址: 大连高新园区西段,小平岛沿旅顺南路西行1公里

  • 物业类型: 住宅

  • 建筑形式: 多层、小高层、高层

  • 装修情况: 毛坯、精装修

  • 产权年限: 70年

  • 楼座信息: 一期:2栋30层高层、17栋4-6层多层、11栋8-11层小高层;二期:19栋(1-19#)11层小高层、6栋(20-25#)32层高层

  • 供热方式: 地热

  • 规划面积: 135000平方米

  • 建筑面积: 165000平方米

  • 规划户数: 1284户

  • 车位数: 1:1

  • 容积率: 1.26

  • 绿化率: 42%

  • 得房率: 高层80%;小高层88%

  • 物业公司:大连亿达物业管理有限公司

  • 物业费: 2.60元/平方米·月

配套信息

  • 公交: 和平广场-大龙王塘(蔡大岭站)、站北广场-旅顺(蔡大岭站)、802路(普罗旺斯站)

  • 轨道交通:大连轻轨8号线(202路延伸线)(蔡大岭站)

  • 教育: 中心小学、书香园小学、群英小学、辽宁师范大学附中、育明高中、大连第十七中学、大连理工大学附小

  • 医院: 医大二院

  • 银行: 浦发银行、建设银行、大连银行

  • 社区配套: 小区内部配套有超市、便利店等利民设施

  • 其他:小区内部配套有:便利店、洗衣房等

  • 所属小学: 大连理工大学附属学校小学部

  • 所属中学: 大连理工大学附属学校初中部

楼盘相关信息
来源:购房网

分类

最新文章

最近回复

  • 老徐: 已经加上了,抱歉才看到
  • 青山: 某种原因,暂停友链,抱歉。
  • 搬瓦工: 朋友 交换链接吗
  • 飞刀说: 名称:飞刀说 描述:...
  • 青山: 计划搬迁到腾讯云,正...
  • 河边的飞刀: 网站名称:飞刀说 网...
  • 老徐: 具体要哪个呢?
  • 老徐: 是不是有点老?
  • 青山: 哇,林志炫
  • 老白: 哇,这改的可以,能不...

归档

标签云

C# .net core asp.net 情感 SQL mongodb sql server EasyUI 安全 激活 linux 身份验证 https typecho .net sql注入 kms MVC IIS 高并发 IE 坑爹 服务器 mysql Oracle Combobox Datagrid 口语 数据抓取

其它