--- title: ASP.Net date: 2021-03-23 10:30:31 author: 文永达 top_img: https://gcore.jsdelivr.net/gh/volantis-x/cdn-wallpaper/abstract/67239FBB-E15D-4F4F-8EE8-0F1C9F3C4E7C.jpeg --- # ASP.NET Core ## Web API 项目初始化搭建 首先打开Visual Studio 2022,然后选择创建新项目 之后筛选下拉框选择如红框标注 ![image-20230301104542343](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301104542343.png) 起一个项目名称及选择项目位置,下一步 ![image-20230301104644030](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301104644030.png) 框架选择.Net 6.0(长期支持) 选择启用Docker,为了之后可以部署到Docker容器 启用OpenAPI支持是为了可以输出Swagger接口文档,但如果使用Furion框架的话,需要勾掉 顶级语句是无需在Program.cs中显式包含Main方法,可以使用顶级语句功能最大程度地减少必须编写的代码 ![image-20230301104855227](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301104855227.png) 点击创建即可 ![image-20230301105745182](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301105745182.png) ## 集成Furion框架 在NuGet包管理器中搜索`Furion` ![image-20230301110105535](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301110105535.png) 选择安装的项目,然后安装即可 ### 可能遇到的问题 #### 包降级 ![image-20230301110232202](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20230301110232202.png) 将提示的NuGet包升级到 前者的版本即可,比如图内的 Swashbuckle.AspNetCore 原有的版本是 6.2.3 那么升级到 6.5.0即可 ## 部署到Docker ### 安装.Net SDK 6.0环境 ```shell sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm sudo yum install dotnet-sdk-6.0 dotnet --info ``` ### Visual Studio添加Docker支持 ![image-20221121144928205](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221121144928205.png) ### Linux下构建Docker镜像 ```shell docker image build -f ./XiaodaERP/Dockerfile -t aspnetcore . docker images ``` ### 运行Docker镜像 ```shell docker run --name=aspnetcore -p 9001:80 -d aspnetcore docker ps ``` ```shell cd /usr/local/jenkins_home/workspace/XiaodaERP_NetCore echo $PWD docker image build -f ./XiaodaERP/Dockerfile -t xiaodaerp/netcore . docker images docker run --name xiaodaerp/netcore -p 7274:80 -d xiaodaerp/netcore ``` ## 顶级语句配置`Program.cs` ### 取消默认JSON首字母小写命名 ```c# builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; }); ``` ### Json序列化时忽略属性为null的值 ```c# builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; }); ``` ### Json序列化时日期类型格式化输出 ```c# builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new SystemTextJsonDateTimeJsonConverter("yyyy-MM-dd HH:mm:ss")); }); ``` ### 集成Furion框架 ```c# var builder = WebApplication.CreateBuilder(args).Inject(); builder.Services.AddControllers().AddInject(); app.UseInject(); ``` ### 使用Autofac自动注入Service 通过NuGet包管理器 安装NuGet包 ![image-20221130161234399](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221130161234399.png) ![image-20221130161319595](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221130161319595.png) Autofac Autofac.Extensions.DependencyInjection Autofac.Extras.DynamicProxy 新建`ServiceAutofac.cs`类 ```c# using System.Reflection; namespace XiaodaERP { public class ServiceAutofac { /// /// 获取程序集名称 /// /// public static string GetAssemblyName() { return Assembly.GetExecutingAssembly().GetName().Name; } } } ``` `Program.cs`配置 ```c# builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer(builder => { Assembly assembly = Assembly.Load(ServiceAutofac.GetAssemblyName());//注入Service程序集 可以是其他程序集 builder.RegisterAssemblyTypes(assembly) .AsImplementedInterfaces() .InstancePerDependency(); }); ``` ### 注入Entity Framework Core 6 DbContext上下文 ```c# builder.Services.AddDbContext(options => options.UseOracle(builder.Configuration.GetConnectionString("OracleDbContext"))); builder.Services.AddDbContext(options => options.UseSqlServer(builder.Configuration.GetConnectionString("SqlServerDbContext"))); ``` ### 使用JWT进行授权与认证 安装NuGet包 `Microsoft.AspNetCore.Authentication.JwtBearer` ![image-20221206130808039](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206130808039.png) `appsettings.json`配置文件中配置 ```json "Authentication": { "SecretKey": "nadjhfgkadshgoihfkajhkjdhsfaidkuahfhdksjaghidshyaukfhdjks", "Issuer": "www.xiaoda", "Audience": "www.xiaoda" } ``` `Program.cs`顶级语句配置 ```c# // 使用Autofac自动注入Service builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer(builder => { Assembly assembly = Assembly.Load(ServiceAutofac.GetAssemblyName());//注入Service程序集 可以是其他程序集 builder.RegisterAssemblyTypes(assembly) .AsImplementedInterfaces() .InstancePerDependency(); // 在IOC容器中注入 // 用于Jwt的各种操作 builder.RegisterType().InstancePerLifetimeScope(); // 支持泛型存入Jwt builder.RegisterType().InstancePerLifetimeScope(); }); //JWT认证 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => { //取出私钥 var secretByte = Encoding.UTF8.GetBytes(builder.Configuration["Authentication:SecretKey"]); options.TokenValidationParameters = new TokenValidationParameters() { //验证发布者 ValidateIssuer = true, ValidIssuer = builder.Configuration["Authentication:Issuer"], //验证接受者 ValidateAudience = true, ValidAudience = builder.Configuration["Authentication:Audience"], //验证是否过期 ValidateLifetime = true, //验证私钥 IssuerSigningKey = new SymmetricSecurityKey(secretByte) }; }); // 顺序不能颠倒 // 你是谁 授权 app.UseAuthentication(); // 你可以干什么 验证 app.UseAuthorization(); ``` 新建`TokenHelper.cs`工具类 ```c# using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Reflection; using System.Security.Claims; using System.Text; using XiaodaERP.Models; namespace XiaodaERP.Utils { public class TokenHelper { private readonly IConfiguration _configuration; private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler; public TokenHelper(IConfiguration configuration, JwtSecurityTokenHandler jwtSecurityTokenHandler) { this._configuration = configuration; this._jwtSecurityTokenHandler = jwtSecurityTokenHandler; } public static string? Token { get; set; } // 生成Token public string CreateJwtToken(T user) { // 生成JWT // Header,选择签名算法 var signingAlogorithm = SecurityAlgorithms.HmacSha256; // Payload,存放用户信息,放用户ID,用户名 var claimList = this.CreateClaimList(user); //Signature //取出私钥并以utf8编码字节输出 var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]); //使用非对称算法对私钥进行加密 var signingKey = new SymmetricSecurityKey(secretByte); //使用HmacSha256来验证加密后的私钥生成数字签名 var signingCredentials = new SigningCredentials(signingKey, signingAlogorithm); //生成Token var Token = new JwtSecurityToken( issuer: _configuration["Authentication:Issuer"], //发布者 audience: _configuration["Authentication:Audience"], //接收者 claims: claimList, //存放的用户信息 notBefore: DateTime.UtcNow, //发布时间 expires: DateTime.UtcNow.AddMinutes(30), //有效期设置为1天 signingCredentials //数字签名 ); //生成字符串token var TokenStr = new JwtSecurityTokenHandler().WriteToken(Token); return TokenStr; } // 获取Token Payload信息 public T GetToken(string token) { Type t = typeof(T); object obj = Activator.CreateInstance(t); var b = _jwtSecurityTokenHandler.ReadJwtToken(token); foreach (var item in b.Claims) { PropertyInfo propertyInfo = t.GetProperty(item.Type); if (propertyInfo != null && propertyInfo.CanRead) { propertyInfo.SetValue(obj, item.Value, null); } } return (T)obj; } // 根据类生成Token 断言列表 private List CreateClaimList(T authUser) { var Class = typeof(T); List claimList = new(); foreach (var item in Class.GetProperties()) { // 不将PassWord放入Token中 if (item.Name == "PassWord") { continue; } // 将UserName属性名重命名为username存入Token中 if (item.Name == "UserName") { claimList.Add(new Claim("username", Convert.ToString(item.GetValue(authUser)))); continue; } claimList.Add(new Claim(item.Name, Convert.ToString(item.GetValue(authUser)))); } return claimList; } } } ``` 在登录方法中加入 ```c# public ViewUser Login(string UserName, string PassWord) { var res = _sqlServerDbContext.Users.Include(user => user.Role).FirstOrDefault(x => x.UserName == UserName); if (res != null) { if (res.PassWord == Md5Encoding(PassWord)) { // 生成JWT var TokenStr = _tokenHelper.CreateJwtToken(res); var config = new MapperConfiguration(cfg => cfg.CreateMap() .ForMember(d => d.username, opt => opt.MapFrom(src => src.UserName)) .AfterMap((src, des) => des.Roles = new Role[1] { src.Role }) .AfterMap((src, des) => des.Token = "bearer " + TokenStr) // 需要加上bearer .AfterMap((src, des) => des.HomePath = "/dashboard/analysis") .AfterMap((src, des) => des.password = null)); var mapper = config.CreateMapper(); return mapper.Map(res); } } return null; } ``` WebAPI 需要认证的加上`[Authorize]`注解,注意登录不能加 ```c# [AuthFilter] [HttpPost(Name = "login")] public ResultUtil Login(ViewUser viewUser) => ResultUtil.ok(_userService.Login(viewUser.username, viewUser.password)); // 需要认证的API [Authorize] [AuthFilter] [HttpGet(Name = "getUserInfo")] public ResultUtil GetUserInfo() { Token = HttpContext.Request.Headers["Authorization"]; Token = Token.Split(" ")[1]; TokenHelper.Token = Token; ViewUser us = _tokenHelper.GetToken(Token); return ResultUtil.ok(us); } ``` 访问登录接口 ![image-20221206131847333](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206131847333.png) 访问需要认证的接口,需要把Token放在请求头中,如果不携带Token,访问则报401 ![image-20221206132042533](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206132042533.png) 请求头Key 为 Authorization 访问成功 ![image-20221206132124913](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206132124913.png) ## 面向切面编程(AOP) 三大拦截器 `AuthorizeAttribute` 认证拦截器 `ActionFilterAttribute` 方法拦截器 ```c# using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.IdentityModel.Tokens.Jwt; using Castle.Core.Internal; using XiaodaERP.Models; using XiaodaERP.Utils; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; namespace XiaodaERP.Attributes { public class AuthFilter : ActionFilterAttribute { //private readonly TokenHelper _tokenHelper; //public AuthFilter(TokenHelper tokenHelper) //{ // this._tokenHelper = tokenHelper; //} private readonly SqlServerDbContext _sqlServerDbContext; public AuthFilter(SqlServerDbContext sqlServerDbContext) { this._sqlServerDbContext = sqlServerDbContext; } private SysActionLog sysActionLog = new() { ActionId = Guid.NewGuid().ToString().Replace("-", "").ToUpper() }; public override void OnActionExecuting(ActionExecutingContext context) { var descriptor = context.ActionDescriptor as ControllerActionDescriptor; string param = string.Empty; string globalParam = string.Empty; var jsonSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; foreach (var arg in context.ActionArguments) { string value = Newtonsoft.Json.JsonConvert.SerializeObject(arg.Value, Formatting.None, jsonSetting); param += $"{arg.Key} : {value} \r\n"; globalParam += value; } // 方法名 Console.WriteLine(descriptor.ActionName); // 参数值拼接 Console.WriteLine(globalParam); // 参数名 与 值 Console.WriteLine(param); sysActionLog.ActionName = descriptor.ActionName; sysActionLog.RequestParams = param; } public override void OnActionExecuted(ActionExecutedContext context) { // 获取请求Host Console.WriteLine(context.HttpContext.Request.Host); sysActionLog.RequestHost = context.HttpContext.Request.Host.ToString(); // 获取请求方法 Console.WriteLine(context.HttpContext.Request.Method); sysActionLog.RequestMethod = context.HttpContext.Request.Method; // 获取请求Url Console.WriteLine(context.HttpContext.Request.Path); sysActionLog.RequestPath = context.HttpContext.Request.Path.ToString(); // 获取应答返回状态码 Console.WriteLine(context.HttpContext.Response.StatusCode); if (context.HttpContext.Request.Path.Equals("/api/User/login")) { sysActionLog.ActionTime = DateTime.Now; } else { string Token = context.HttpContext.Request.Headers["Authorization"]; // Token失效 if (Token.IsNullOrEmpty()) { } else { Token = Token.Split(" ")[1]; TokenHelper.Token = Token; ViewUser us = new TokenHelper(new JwtSecurityTokenHandler()).GetToken(Token); Console.WriteLine(us.UserId); sysActionLog.ActionUserId = us.UserId; Console.WriteLine(us.username); sysActionLog.ActionUserName = us.username; } sysActionLog.ActionTime = DateTime.Now; } _sqlServerDbContext.SysActionLogs.Add(sysActionLog); _sqlServerDbContext.SaveChanges(); } } } ``` 接口上使用 ```c# //[AuthFilter] [TypeFilter(typeof(AuthFilter))] [HttpPost(Name = "login")] public ResultUtil Login(ViewUser viewUser) => ResultUtil.ok(_userService.Login(viewUser.username, viewUser.password)); [Authorize] //[AuthFilter] // 注解为拦截器类名 [TypeFilter(typeof(AuthFilter))] // 因为主键中使用了构造器依赖注入,所以需要使用TypeFilter,并需要在顶级语句中注入 AuthFilter [HttpGet(Name = "getUserInfo")] public ResultUtil GetUserInfo() { Token = HttpContext.Request.Headers["Authorization"]; Token = Token.Split(" ")[1]; TokenHelper.Token = Token; ViewUser us = _tokenHelper.GetToken(Token); return ResultUtil.ok(us); } ``` 顶级语句中注入 ```c# builder.Services.AddScoped(); ``` `ExceptionFilterAttribute` 异常拦截器