XiaodaBlogSource/source/_posts/ASP.NET Core.md
2023-09-14 10:28:46 +08:00

548 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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
{
/// <summary>
/// 获取程序集名称
/// </summary>
/// <returns></returns>
public static string GetAssemblyName()
{
return Assembly.GetExecutingAssembly().GetName().Name;
}
}
}
```
`Program.cs`配置
```c#
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
Assembly assembly = Assembly.Load(ServiceAutofac.GetAssemblyName());//注入Service程序集 可以是其他程序集
builder.RegisterAssemblyTypes(assembly)
.AsImplementedInterfaces()
.InstancePerDependency();
});
```
### 注入Entity Framework Core 6 DbContext上下文
```c#
builder.Services.AddDbContext<OracleDbContext>(options =>
options.UseOracle(builder.Configuration.GetConnectionString("OracleDbContext")));
builder.Services.AddDbContext<SqlServerDbContext>(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<ContainerBuilder>(builder =>
{
Assembly assembly = Assembly.Load(ServiceAutofac.GetAssemblyName());//注入Service程序集 可以是其他程序集
builder.RegisterAssemblyTypes(assembly)
.AsImplementedInterfaces()
.InstancePerDependency();
// 在IOC容器中注入
// 用于Jwt的各种操作
builder.RegisterType<JwtSecurityTokenHandler>().InstancePerLifetimeScope();
// 支持泛型存入Jwt
builder.RegisterType<TokenHelper>().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>(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<T>(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<Claim> CreateClaimList<T>(T authUser)
{
var Class = typeof(T);
List<Claim> 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<User, ViewUser>()
.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<ViewUser>(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<ViewUser>(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<ViewUser>(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<ViewUser>(Token);
return ResultUtil.ok(us);
}
```
顶级语句中注入
```c#
builder.Services.AddScoped<AuthFilter>();
```
异常拦截器 `ExceptionFilterAttribute`