依赖注入
范围
Singleton 单例
Scoped 范围 每次TCP请求会创建一个新的实例
Transient 临时 每次向容器请求实例都会创建新的
获取Bean方式
- 构造函数注入
- 通过HttpContext.RequestServices 属性来获取当前请求的服务容器 IServiceProvider
- [FromServices] 方式从参数注入
配置系统
配置读取优先级(从高到低)
- 使用命令行配置提供程序通过命令行参数提供。
- 使用非前缀环境变量配置提供程序通过非前缀环境变量提供。
- 应用在 环境中运行时的用户机密。
- 使用 JSON 配置提供程序通过 appsettings.{Environment}.json 提供。 例如,appsettings.Production.json 和 appsettings.Development.json。
- 使用 JSON 配置提供程序通过 appsettings.json 提供。
- 回退到下一部分所述的主机配置。
基本使用
learn.microsoft.com/zh-cn/aspne…
- 依赖注入 IConfiguration 或 builder.Configuration
- 读取值 configuration["key:key2"] 、GetSection("Test")["MyConfig"]或 GetSection("key").Value等方式读取配置文件中最新值
选项方式
learn.microsoft.com/zh-cn/aspne…
选项模式使用类来提供对相关设置组的强类型访问,简单来说是把配置用 Configuration.GetSection(key).GetSection(key2).Get<Type>()或 bind(insatance) 方法绑定到对应类型
日志
.NET Core 和 ASP.NET Core 中的日志记录 | Microsoft Learn
ASP.NET Core 不包括用于将日志写入文件的日志记录提供程序。 若要将日志从 ASP.NET Core 应用写入文件,请考虑使用第三方日志记录提供程序。
EF CORE
配置
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore
Pomelo.EntityFrameworkCore.MySql
"ConnectionStrings": {
"DefaultConnection": "Server=127.0.0.1; Port=3306; Database=venue_book; Uid=root; Pwd=root;"
}
定义DBContext
public class MySQLConfig : DbContext
{
public MySQLConfig() { }
public MySQLConfig(DbContextOptions<MySQLConfig> options) : base(options) { }
//自定义实体类映射
public DbSet<AdminUser> AdminUsers { get; set; }
//z实体类映射
public DbSet<AdminOperLog> adminOperLogs { get; set; }
}
// MySQL服务,默认是scope
builder.Services.AddDbContext<MySQLConfig>(opt => {
string connectionString = builder.Configuration.GetSection("ConnectionStrings:DefaultConnection").Value;
var serverVersion = ServerVersion.AutoDetect(connectionString);
opt.UseMySql(connectionString, serverVersion);
});
使用
实体类配置
class BookConfig : IEntityTypeConfiguration<Book> {
public void Configure(EntityTypeBuilder<Book> builder) { // 映射表名
builder.ToTable("Test_Book"); // 映射属性名
builder.Property(x=>x.AuthorId).HasColumnName("Test_Author_Id"); // 作者对书籍:一对多
builder.HasOne<Author>(b=>b.Author).WithMany(a=>a.Books) .HasForeignKey(b=>b.AuthorId); // 书籍对读者:多对多
builder.HasMany<Reader>(b => b.Readers).WithMany(a => a.Books) .UsingEntity(j => j.ToTable("Book_Reader_Realation"));
}
}
单表配置
实体类型 - EF Core | Microsoft Learn
实体属性 - EF Core | Microsoft Learn
class TestDbContext : DbContext
{
public DbSet<Author> Authors { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connStr = "Server=.;Database=demo1;Trusted_Connection=True";
optionsBuilder.UseSqlServer(connStr);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}
多表配置
//一个文章对应多个评论
public class Comment{
public Article Article{get;set;}
}
public class Article{
public List<Comment> Comments{get;set;}
}
public void Configure(EntityTypeBuilder<Comment> builder){
builder.HasOne<Article>(c=>c.Article).WithMany(a=> a.Comments).IsRequired();
}
1.)HasOne 是该Coment(自己)关联一个 Article(对方)
WithMany 是Article(对方) 关联多个 Comment(自己)
相应的还有 hasMany 、 withone
两个对象的配置文件中有一个设定就行,两个文件通用
如果在has或with语句中不指定对象,则为单向导航,人话就是只设定自己与对方的关系,不设定对方与自己的关系
2.)IsRequired 表示外键字段可不可为空,主要在生成定义表的sql语句时使用,如果同时自定义了一套外键和导航,只要导航不标记为可空,外键就不能标记为可空,因为要通过外键填充导航
3.)不显式指定主键字段则默认主键字段名为 定义的表名+Id ,显式指定可以通过 指定对应关系.HasForeignKey(指定列)实现
builder.HasOne<Article>(c=>c.Article).WithMany(a=> a.Comments).HasForeignKey(c=>ArticleId);
关系导航 - EF Core | Microsoft Learn
关系中的外键和主键 - EF Core | Microsoft Learn
一对一
一对一关系 - EF Core | Microsoft Learn
一对多(重点)
一对多关系 - EF Core | Microsoft Learn
多对多
多对多关系 - EF Core | Microsoft Learn
查询筛选器
全局查询筛选器 - EF Core | Microsoft Learn
自动填充
官方的有bug,使用时会为对象填充DBNull,原因不明 生成的值 - EF Core | Microsoft Learn
自定义属性填充需要重写DBContext的SaveChanges()方法 参考资料
public override int SaveChanges()
{
//获取实体中属于添加状态的实体
var addedEntities = ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added);
foreach (var entityEntry in addedEntities)
{
//将实体中的
entityEntry.Property("Content").CurrentValue = "hhhhhhh";
//判断实体是否创建时间属性,如果有则在插入时赋初值
var createTime = entityEntry.Entity.GetType().GetProperty("CreateTime");
if (createTime != null && createTime.CanWrite)
{
entityEntry.Property("CreateTime").CurrentValue = DateTime.Now;
}
//以上代码是具有通用性的
}
return base.SaveChanges();
}
乐观锁
处理并发冲突 - EF Core | Microsoft Learn
查询
状态跟踪
EF 会保存查询出的实体的快照,在context.SaveChanges();时自动检测并保存对上下文跟踪的现有实体所做的更改。
更改跟踪 - EF Core | Microsoft Learn
断开连接的实体 - EF Core | Microsoft Learn
单表查询
查询数据 - EF Core | Microsoft Learn
连表查询
相关数据的预先加载 - EF Core | Microsoft Learn
相关数据的预先加载 - EF Core | Microsoft Learn
拆分查询
单个查询与拆分查询 - EF Core | Microsoft Learn
分页
分页 - EF Core | Microsoft Learn 推荐手写sql延迟关联
延迟执行
查询的工作原理 - EF Core | Microsoft Learn
只有使用终结方法或foreach时才会执行SQL,延迟加载特别容易产生不必要的额外往返,这可能会导致性能问题
有必要的话避免延迟加载,将用的数据提前取出
增删改
增和旧有删改,配置了外键后注意级联删除,主表通过旧有方法删数据后从表也会对应删除数据
执行原生sql
SQL 查询 - EF Core | Microsoft Learn
防止sql注入
执行原生sql时用的string是FormattableString,在使用$"sad{value}af"时不会在客户端拼接成一个完整的字符串,而是把{value}变成占位符,并把value一同传给 sql服务器,从而避免sql注入
事务
事务 - EF Core | Microsoft Learn
缓存
配置
StackExchange.Redis
"Redis": {
"Default": {
"Connection": "127.0.0.1:6379", //redis连接地址,端口号,密码
"InstanceName": "local", //实例名
"DefaultDB": "8" //Db8数据库
}
}
public class RedisHelper
{
//连接字符串
private string _connectionString;
//实例名称
private string _instanceName;
//默认数据库
private int _defaultDB;
private ConcurrentDictionary<string, ConnectionMultiplexer> _connections;
/// 初始化
public RedisHelper(string connectionString, string instanceName, int defaultDB = 0)
{
_connectionString = connectionString;
_instanceName = instanceName;
_defaultDB = defaultDB;
_connections = new ConcurrentDictionary<string, ConnectionMultiplexer>();
}
/// 获取ConnectionMultiplexer
private ConnectionMultiplexer GetConnect()
{
return _connections.GetOrAdd(_instanceName, p => ConnectionMultiplexer.Connect(_connectionString));
}
/// 获取redis数据库
public IDatabase GetDatabase()
{
return GetConnect().GetDatabase(_defaultDB);
}
public IServer GetServer(string? configName = null, int endPointsIndex = 0)
{
var confOption = ConfigurationOptions.Parse(_connectionString);
return GetConnect().GetServer(confOption.EndPoints[endPointsIndex]);
}
public ISubscriber GetSubscriber(string? configName = null)
{
return GetConnect().GetSubscriber();
}
public void Dispose()
{
if (_connections != null && _connections.Count > 0)
{
foreach (var item in _connections.Values)
{
item.Close();
}
}
}
}
// 配置Redis 注入类似于配置数据库连接字符串
var section = builder.Configuration.GetSection("Redis:Default");
// 连接字符串
string _connectionString = section.GetSection("Connection").Value;
// 实例名称
string _instanceName = section.GetSection("InstanceName").Value;
// 默认数据库
int _defaultDB = int.Parse(section.GetSection("DefaultDB").Value ?? "0");
builder.Services.AddSingleton(new RedisHelper(_connectionString, _instanceName, _defaultDB));
#endregion
使用
redishelper.GetDatabase()
RabbitMQ
配置
RabbitMQ.Client
"RabbitMQ": {
"UserName": "guest",
"Password": "guest",
"VirHost": "/",
"HostName":"127.0.0.1",
"Port": 5672
}
IConfiguration configuration = builder.Configuration.GetSection("RabbitMQ");
builder.Services.AddSingleton(sp =>
{
var factory = new ConnectionFactory()
{
HostName = configuration.GetSection("HostName").Value,
UserName = configuration.GetSection("UserName").Value,
Password = configuration.GetSection("Password").Value,
Port = int.Parse(configuration.GetSection("Port").Value),
VirtualHost = configuration.GetSection("VirHost").Value,
};
return factory.CreateConnection();
});
使用
public class RabbitmqConfig
{
public static IConnection Connection { get; set; }
public RabbitmqConfig(IConnection connection)
{
Connection = connection;
DeclareComponent(connection);
ConsumMessage(null);
Console.WriteLine("init");
}
public static void DeclareComponent(IConnection connection)
{
using (IModel channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "my_exchange",
type: ExchangeType.Direct,
durable:true);
channel.QueueDeclare(queue: "my_queue",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null
);
channel.QueueBind(queue: "my_queue",
exchange: "my_exchange",
routingKey: "my_routing_key");
/* var body = Encoding.UTF8.GetBytes(message);
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2;
var options = new BasicPublishOptions()
{
Mandatory = true
};
channel.BasicPublish(exchange: "my_exchange",
routingKey: "my_routing_key",
basicProperties: properties,
body: body);*/
}
}
public bool SendMesssage(String msg)
{
using IModel channel = Connection.CreateModel();
// 启用发布确认模式
channel.ConfirmSelect();
// 发送消息
var body = Encoding.UTF8.GetBytes(msg);
channel.BasicPublish(exchange: "my_exchange", routingKey: "my_routing_key", basicProperties: null, body: body);
// 等待确认
if (channel.WaitForConfirms())
{
Console.WriteLine("消息发送成功!");
return true;
}
else
{
Console.WriteLine("消息发送失败!");
return false;
}
}
public void ConsumMessage(Action<String> action)
{
using var channel = Connection.CreateModel();
// 创建消费者
var consumer = new EventingBasicConsumer(channel);
// 启用手动确认
channel.BasicConsume(queue: "test", autoAck: false, consumer: consumer);
// 注册消息处理程序
consumer.Received += (sender, eventArgs) =>
{
try
{
action(Encoding.UTF8.GetString(eventArgs.Body.ToArray()));
channel.BasicAck(deliveryTag: eventArgs.DeliveryTag, multiple: false);
}
catch (Exception ex)
{
// 发送拒绝消息
channel.BasicNack(deliveryTag: eventArgs.DeliveryTag, multiple: false, requeue: true);
}
};
}
}
消费者后台监听
builder.Services.AddHostedService<RabbitListener>();
public class RabbitListener : IHostedService
{
private readonly IConnection connection;
private readonly IModel channel;
public RabbitListener(IConnection connection)
{
this.connection = connection;
channel = connection.CreateModel();
}
public/* async Task<String>*/ Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("异步任务开始");
Register();
//return "saf";
return Task.CompletedTask;
}
protected string RouteKey;
protected string QueueName= "my_queue";
// 处理消息的方法
public virtual bool Process(string message)
{
Console.WriteLine(message);
return true;
}
// 注册消费者监听在这里
public void Register()
{
// 创建消费者
var consumer = new EventingBasicConsumer(channel);
// 启用手动确认
channel.BasicConsume(queue: "test", autoAck: false, consumer: consumer);
// 注册消息处理程序
consumer.Received += (sender, eventArgs) =>
{
try
{
Process(Encoding.UTF8.GetString(eventArgs.Body.ToArray()));
channel.BasicAck(deliveryTag: eventArgs.DeliveryTag, multiple: false);
}
catch (Exception ex)
{
// 发送拒绝消息
channel.BasicNack(deliveryTag: eventArgs.DeliveryTag, multiple: false, requeue: true);
}
//为什么方法结束后 consumer 没有消失 connect和channel没有关闭0怕。
Console.WriteLine("注册完成");
};
}
public void DeRegister()
{
this.connection.Close();
}
public Task StopAsync(CancellationToken cancellationToken)
{
this.connection.Close();
Console.WriteLine("连接关闭");
return Task.CompletedTask;
}
}
Controller
配置
// Add services to the container.
builder.Services.AddControllers();
app.UseHttpsRedirection();
app.MapControllers();
在controller类上标注 [ApiController]即可将其注入到容器中
控制器基类的选用
有两个控制器父类可用,
ControllerBase: 其中内置了很多属性和方法,如HttpContext、Request、Response
Controller: 继承自ControllerBase,自定义了和视图相关的方法
api项目继承ControllerBase即可
路由
在 ASP.NET Core 中路由到控制器操作 | Microsoft Learn
参数绑定
ASP.NET Core 中的模型绑定 | Microsoft Learn
解析日期
builder.Services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new DateTimeConverter("yyyy-MM-dd", "HH:mm:ss", "yyyy-MM-dd HH:mm:ss"));
});
public class DateTimeConverter : JsonConverter<DateTime>
{
private readonly string[] _formats;
public DateTimeConverter(params string[] formats)
{
_formats = formats;
}
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var dateString = reader.GetString();
foreach (var format in _formats)
{
if (DateTime.TryParseExact(dateString, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
{
return result;
}
}
if (DateTime.TryParse(dateString, CultureInfo.InvariantCulture, DateTimeStyles.None, out var fallbackResult))
{
return fallbackResult;
}
throw new JsonException($"Unable to parse date '{dateString}' using formats '{string.Join(",", _formats)}'.");
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_formats[0]));
}
}
数据校验
learn.microsoft.com/zh-cn/aspne…
learn.microsoft.com/zh-cn/dotne…
自定义返回格式
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using System.Net;
using System.Xml.Linq;
using Newtonsoft.Json;
namespace VenueBookAdmin.Common
{
public class AjaxResult : IActionResult
{
public int Code { get; set ; }
public String Msg { get ; set ; }
public Object Data { get; set; }
public AjaxResult()
{
}
public AjaxResult(int code, String msg, Object data)
{
this.Code = code;
this.Msg = msg;
this.Data= data;
}
public static AjaxResult Success()
{
return new AjaxResult { Data = null, Code = (int)HttpStatusCode.OK, Msg = "操作成功" };
}
public static AjaxResult Success(Object data)
{
return new AjaxResult { Data = data, Code = (int)HttpStatusCode.OK, Msg = "操作成功" };
}
public static AjaxResult Success(Object data,String message)
{
return new AjaxResult { Data = data, Code = (int)HttpStatusCode.OK, Msg = message };
}
public static AjaxResult Fail(int code , String message)
{
return new AjaxResult { Data = null, Code = code, Msg = message };
}
public static AjaxResult ServicesException()
{
return new AjaxResult { Data = null, Code = (int)HttpStatusCode.InternalServerError, Msg = "服务器异常" };
}
public Task ExecuteResultAsync(ActionContext context)
{
HttpResponse response = context.HttpContext.Response;
response.ContentType = $"{context.HttpContext.Request.ContentType}; charset=utf-8";
string json = string.Empty;
var settings = new JsonSerializerSettings
{
//避免重复序列化循环引用的对象导致出错
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
//指定日期格式
DateFormatString = "yyyy-MM-dd HH:mm:ss",
};
json = JsonConvert.SerializeObject(this, Formatting.Indented, settings);
return Task.FromResult(response.WriteAsync(json));
}
}
}
CORS
//add cors policy "AllowAll"
webApplicationBuilder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
// CORS 中间件添加到请求处理管道中。
app.UseCors("AllowAll");
中间件
ASP.NET Core 中间件 | Microsoft Learn
todo : 测试app.MapControllers(); 有没有定义MVC管道,filter在不在里面面
答案Asp.net core mapcontrollers 背后干了些啥 - 果小天 - 博客园 (cnblogs.com)
中间件有三个概念:Map、Use和Run。Map用来定义一个新的管道,Use定义管道中的中间件,Run定义管道终点。
每个管道可以定义很多中间件和子管道,依据定义的顺序指定,到达Run后"沿原路返回",例如下面代码
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//外层中间件1
app.Use(async (context, next) =>
{
Console.WriteLine("1");
await next.Invoke();
Console.WriteLine("2");
});
//分支管道1
app.Map("/test", async appbuilder => {
appbuilder.Use(async (context, next) => {
Console.WriteLine("in 1");
await next.Invoke();
Console.WriteLine("in 5");
});
appbuilder.Use(async (context, next) => {
Console.WriteLine("in 2");
await next.Invoke();
Console.WriteLine("in 4");
});
appbuilder.Run(async ctx => {
Console.WriteLine("in 3");
});
});
//分支管道2
app.Map("/test", async appbuilder => {
appbuilder.Use(async (context, next) => {
Console.WriteLine("inini 1");
await next.Invoke();
Console.WriteLine("inini 5");
});
appbuilder.Use(async (context, next) => {
Console.WriteLine("inini 2");
await next.Invoke();
Console.WriteLine("inini 4");
});
appbuilder.Run(async ctx => {
Console.WriteLine("inini 3");
});
});
//外层中间件2
app.Use(async (context, next) =>
{
Console.WriteLine("3");
await next.Invoke();
Console.WriteLine("4");
});
app.Run(
//内部不能写方法,会无法启动,原因未知
/*async ctx =>
{
Console.WriteLine(" 5");
}*/
);
}
}
发送/test路径的请求,会依次执行外层中间件1和分支管道1中的所有中间件和终结点Run,分支管道2不会执行,即使他也是对应test路径的管道,后面其他管道、中间件和终结点Run也不会执行,因为分支管道1的Run方法已经执行,"请求原路返回"
如果到达管道尽头,也会“原路返回”,结果相当于执行了空的Run() 方法
/test访问结果为
1
in 1
in 2
in 3
in 4
in 5
2
筛选器
基本原理、类型及交互方式
learn.microsoft.com/zh-cn/aspne…
特定属性
- TypeFilterAttribute TypeFilterAttribute 是 ASP.NET Core 中的一个特性,它可以用于标注授权过滤器、异常过滤器、结果过滤器等等。TypeFilterAttribute 允许我们指定一个类型,当请求到达标注了 TypeFilterAttribute 的 Controller 或 Action 方法时,ASP.NET Core 将自动创建 Filter 实例,并将其作为过滤器应用于请求管道中。这样,我们就可以使用依赖注入等高级特性来管理过滤器的生命周期和依赖关系。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class MyAuthorizeAttribute : TypeFilterAttribute
{
public MyAuthorizeAttribute(String? name = null,params string[]? allowedRoles) : base(typeof(CustomAuthorizationFilter))
{
Arguments = new object[] {allowedRoles, name};
}
}
public class CustomAuthorizationFilter : IAuthorizationFilter
{
private readonly string[] _allowedRoles;
private int len;
private String name;
public CustomAuthorizationFilter(string[] allowedRoles, String name)
{
_allowedRoles = allowedRoles;
this.name = name;
}
}
拦截结果、自定义结果
只要在过滤器中设置了 context.Result,后续的过滤器和控制器操作都会被中止,不会继续执行。这是因为在 ASP.NET Core 中,如果在过滤器中设置了结果,或者在控制器操作中返回了结果,则不会继续执行后续的过滤器和控制器操作。这是因为过滤器和控制器操作都可以返回结果,例如视图、JSON 对象、文件等,而 ASP.NET Core 会将这些结果写入 HTTP 响应并返回给客户端。因此,如果在过滤器中设置了结果,则不需要执行后续的过滤器和控制器操作,因为它们不会影响最终的响应结果。 自定filter中context.Resule可用的响应结果,这里的context是filter自带的参数,不是HttpContext
public class AjaxResult : IActionResult
{
public int Code { get; set ; }
public String Msg { get ; set ; }
public Object Data { get; set; }
public AjaxResult()
{
}
public AjaxResult(int code, String msg, Object data)
{
this.Code = code;
this.Msg = msg;
this.Data= data;
}
public static AjaxResult Success()
{
return new AjaxResult { Data = null, Code = (int)HttpStatusCode.OK, Msg = "操作成功" };
}
public static AjaxResult Success(Object data)
{
return new AjaxResult { Data = data, Code = (int)HttpStatusCode.OK, Msg = "操作成功" };
}
public static AjaxResult Success(Object data,String message)
{
return new AjaxResult { Data = data, Code = (int)HttpStatusCode.OK, Msg = message };
}
public static AjaxResult Fail(int code , String message)
{
return new AjaxResult { Data = null, Code = code, Msg = message };
}
public static AjaxResult ServicesException()
{
return new AjaxResult { Data = null, Code = (int)HttpStatusCode.InternalServerError, Msg = "服务器异常" };
}
public Task ExecuteResultAsync(ActionContext context)
{
HttpResponse response = context.HttpContext.Response;
response.ContentType = $"{context.HttpContext.Request.ContentType}; charset=utf-8";
string json = string.Empty;
json = JsonConvert.SerializeObject(this);
return Task.FromResult(response.WriteAsync(json));
}
}
执行顺序
答案来自GPT 没试过 1.同种处理器执行的顺序按照注册顺序来排序 2.使用TypeFilterAttribute 和 ActionFilterAttribute等特殊注解实现的注解,执行顺序为 类上标注的先执行、方法上标注的后执行,同一类或方法标注了多个注解,按标注先后执行 2. 不同种类的filter执行顺序是严格按照官方文档的顺序执行,与注册顺序无关 3. 使用TypeFilterAttribute 和 ActionFilterAttribute等特殊注解实现的注解式filter和使用service注册的filter混合使用情况下 ,先使用注解式filter,后使用service注册的filter
配置为只对某些路径生效
{
"ExcludeFilterPaths": [ "/api/health", "/api/version", "/api/users/*" ]
}
services.Configure<List<string>>(Configuration.GetSection("ExcludeFilterPaths"));
services.AddControllers(options =>
{
options.Filters.Add(new MyFilter(Configuration.GetSection("ExcludeFilterPaths").Get<List<string>>()));
options.Filters.Add(new MyOtherFilter());
});
using Microsoft.Extensions.FileSystemGlobbing;
public class MyFilter : IActionFilter
{
private readonly Matcher _matcher;
public MyFilter(List<string> excludePaths)
{
_matcher = new Matcher();
foreach (var path in excludePaths)
{
_matcher.AddInclude(path);
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (_matcher.Match(context.HttpContext.Request.Path.Value).HasMatches)
{
return;
}
// 在这里实现过滤器逻辑
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (_matcher.Match(context.HttpContext.Request.Path.Value).HasMatches)
{
return;
}
// 在这里实现过滤器逻辑
}
}
自定义授权筛选器示例
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class MyAuthorizeAttribute : TypeFilterAttribute
{
public MyAuthorizeAttribute(String? name = null,params string[]? allowedRoles) : base(typeof(CustomAuthorizationFilter))
{
Arguments = new object[] { name ,allowedRoles };
}
}
public class CustomAuthorizationFilter : IAuthorizationFilter
{
private readonly string[] _allowedRoles;
private int len;
private String name;
public CustomAuthorizationFilter(String name, params string[] allowedRoles)
{
_allowedRoles = allowedRoles;
this.name = name;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
//获取配置类
ConfigurationUtil. SetConfiguration(context.HttpContext.RequestServices.GetService<IConfiguration>() ) ;
// 检查用户是否已通过身份验证
CustomAssert.isTrue(context.HttpContext.User.Identity.IsAuthenticated, "未携带token,请重新登录", typeof(UserException));
//解析用户
AdminUser tokenUser = TokenUtil.ParseUserFromToken(context.HttpContext.User);
//查看redis中是否缓存了该用户登录信息
RedisHelper redisHelper = context.HttpContext.RequestServices.GetService<RedisHelper>();
AdminUser cacheUser = redisHelper.getObject<AdminUser>(TokenUtil.GetTokenKey(tokenUser.Account));
//redis中没缓存或该用户用户的uuid不对
CustomAssert.isNotNull(cacheUser,"令牌失效,请重新登录",typeof(UserException));
CustomAssert.isFalse(cacheUser.UUID!=tokenUser.UUID,"异地登陆",typeof (UserException));
//刷新缓存存活时间
redisHelper.SetExpireTime(TokenUtil.GetTokenKey(cacheUser.Account), TokenUtil.GetTokenExpireTime());
//要求超级管理员
CustomAssert.isFalse(cacheUser.Auth != 0 && _allowedRoles.Contains("超级管理员"), "权限不足", typeof(UserException));
//保存到ThreadLocal
LoginUserUtil.SetLoginUser(cacheUser);
}
}
自定义异常过滤器示例
注意无法处理结果过滤器、鉴权过滤器和其他管道抛出的异常
services.Configure<MvcOptions>(options =>
{
options.Filters.Add<ExceptionHandler>();
options.Filters.Add<CustomActionLogFilter>();
options.Filters.Add<TransactionActionFilter>();
});
public class ExceptionHandler : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
Exception e = context.Exception;
IActionResult contentResult = null;
//如果是自定义异常
if (e.GetType().IsSubclassOf(typeof(CustomException)))
{
CustomException customException = (CustomException)e;
contentResult = AjaxResult.Fail(customException.Code, customException.Message);
}
else
{
contentResult = AjaxResult.ServicesException();
}
context.Result = contentResult;
//标记为异常已经处理完成
context.ExceptionHandled = true;
}
自定义行为过滤器(事务)
services.Configure<MvcOptions>(options =>
{
options.Filters.Add<ExceptionHandler>();
options.Filters.Add<CustomActionLogFilter>();
options.Filters.Add<TransactionActionFilter>();
});
[AttributeUsage(AttributeTargets.Method)]
public class TransactionAttribute : Attribute
{
}
public class TransactionActionFilter : IActionFilter
{
private readonly DbContext _dbContext;
public TransactionActionFilter(DbContext dbContext)
{
_dbContext = dbContext;
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ActionDescriptor.EndpointMetadata.OfType<TransactionAttribute>().Any())
{
_dbContext.Database.BeginTransaction();
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception == null && context.ActionDescriptor.EndpointMetadata.OfType<TransactionAttribute>().Any())
{
_dbContext.Database.CommitTransaction();
}
else if (context.Exception != null && context.ActionDescriptor.EndpointMetadata.OfType<TransactionAttribute>().Any())
{
_dbContext.Database.RollbackTransaction();
}
}
}
JWT
1.)添加依赖
Microsoft.AspNetCore.Authentication.JwtBearer
2.)添加配置文件
"Jwt": {
"SecretKey": "safjkhkjl158151saf",
"KeepTime": 30,
"Issuer": "WebAppIssuer",
"Audience": "WebAppAudience"
}
3.)添加JWT配置
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}
).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false, //是否验证Issuer
ValidIssuer = builder.Configuration["Jwt:Issuer"], //发行人Issuer
ValidateAudience = false, //是否验证Audience
ValidAudience = builder.Configuration["Jwt:Audience"], //订阅人Audience
ValidateIssuerSigningKey = true, //是否验证SecurityKey1
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"])), //SecurityKey
ValidateLifetime = false, //是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(30), //过期时间容错值,解决服务器端时间不同步问题(秒)
RequireExpirationTime = false,
};
}
);
下面这段代码用于开启JWT检验
app.UseAuthentication();
4.)创建Token
List<Claim> claims = new List<Claim>();
claims.Add(new Claim("account", user.Account));
claims.Add(new Claim("uuid", user.UUID));
// claims.Add(new Claim("role", user.Auth.ToString()));
// claims.Add(new Claim("venueIds", JsonSerializer.Serialize(user.VenueIds)));
// 2. 从 appsettings.json 中读取SecretKey
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ConfigurationUtil.GetValue("Jwt:SecretKey")));
// 3. 选择加密算法
var algorithm = SecurityAlgorithms.HmacSha256;
// 4. 生成Credentials
var signingCredentials = new SigningCredentials(secretKey, algorithm);
// 5. 根据以上,生成token
// 5. 根据以上,生成token
var jwtSecurityToken = new JwtSecurityToken(
ConfigurationUtil.GetValue("Jwt:Issuer"), //Issuer
ConfigurationUtil.GetValue("Jwt:Audience"), //Audience
claims, //Claims,
DateTime.Now, //notBefore
DateTime.Now.AddSeconds(30), //expires
signingCredentials //Credentials
);
// 6. 将token变为string
var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
5.) 检验token
第一点,配置app.UseAuthentication();后 会启用ASP.NET Core 认证中间件来验证身份验证令牌,并将解码后的负载信息添加到 HTTP 请求上下文的 User 属性中,与Authorize属性无关。
第二点 ,请求的令牌前要携带Bearer 前缀 ,空格不可省略
是否携带有效token
HttpContext.User.Identity.IsAuthenticated
token携带的Claims
HttpContext.User
后台托管
继承IHostedService
实现方法
public/* async Task<String>*/ Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("异步任务开始");
DoSomeThing();
//return "saf";
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("连接关闭");
return Task.CompletedTask;
}
注册到Service中services.AddHostedService<子类>();