.net6学习
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
一、异步方法
- 异步方法的返回值一般是Task< T >,T是真正的返回值类型.。惯例:异步方法名字一般以Async结尾
- 即使方法没有返回值,也最好把返回值的类型声明为非泛型的Task
- 调用异步方法时,一般在方法前加上await关,这样拿到的返回值就是泛型指定的T类型
- 异步方法的传染性:一个方法中如果有await调用,则这个方法必须修饰为Async
async Task<T> T1Async(){
}
async Task<string> T2Async(){
await T1Async();
}
如果同样的功能,既有同步方法,也有异步方法,那么首先使用异步方法。
对于不支持的异步方法怎么办? Wait()(无返回值);Result(有返回值)。风险:死锁
static void main(string[] args){
Task<string> t=File.ReadAllTextAsync(@"e:\temp\a\1.txt");
string s= t.Result;
File.WriteAllTextAsync(@"e:\temp\a\1.txt","aaaaaaaaaaaaaaaaaaa").Wait();
}
lambda表达式调用异步方法
static void main(string[] args){
ThreadPool.QueueUserWorkItem(async (obj)=>{
while(true){
await File.WriteAllTextAsync(@"e:\temp\a\1.txt","aaaaaaaaaaaaaaaaaaa");
}
})
}
不写Async、await
public static Task<double> Calc2Async(int n)
{
return Task.Run(()=>{
Console.WriteLine("CalcAsync:"+Thread.CurrentThread.ManagedThreadId());
double result=0;
Random radom=new Random();
for(var i=0;i<n*n;i++)
{
result+=rand.NextDouble();
}
return Task.FromResult(result);
})
}
为什么有的异步方法没有Async
static Task<string> ReadAsync(int num)
{
if(num==0)
{
return File.ReadAllTextAsync(@"e:\temp\a\1.txt");
}
else if(numm==2)
{
return File.ReadAllTextAsync(@"e:\temp\a\2.txt");
}
else
{
throw new ArgumentException();
}
}
优点:只是普通的方法调用,运行效率更高
Async方法缺点
- 异步方法会生成一个类,运行效率没有普通方法高
- 可能会占用非常多的线程
不要使用Thread.Sleep()
如果想在异步方法中暂停一段时间,不要用Thread.Sleep(),因为它会阻塞调用线程,而要用await Task.Delay();
CancellationToken:用于获得提前终止执行的信号
三种情况使用方法
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(1000);
//await DownloadAsync2("https://www.baidu.com", 100,cts.Token);
await DownloadAsync3("https://www.baidu.com", 100,cts.Token);
}
static async Task DownloadAsync2(string url,int n,CancellationToken cancellationToken)
{
using (HttpClient httpClient = new HttpClient())
{
for (int i = 0; i < n; i++)
{
string html = await httpClient.GetStringAsync(url);
Console.WriteLine(html);
//抛出异常方式一
/*手动处理
if (cancellationToken.IsCancellationRequested)
{
//自己决定怎么处理
Console.WriteLine("cancel request");
break;
}
*/
//抛出异常方式二:只是发现异常抛出
cancellationToken.ThrowIfCancellationRequested();
}
}
}
static async Task DownloadAsync3(string url, int n, CancellationToken cancellationToken)
{
using (HttpClient httpClient = new HttpClient())
{
for (int i = 0; i < n; i++)
{ //由GetAsync()方法来处理异常请求
var resp=await httpClient.GetAsync(url,cancellationToken);
string html=await resp.Content.ReadAsStringAsync();
Console.WriteLine(html);
}
}
}
WhenAll
Task类的重要方法:
- Task WhenAny(IEnumerable tasks)等,任何一个Task完成,Task就完成
- Task<TResult[]> WhenAll(paramasTask[] tasks)等,所有的Task完成,Task才完成。用于等待多个任务执行结束,但是不在乎它们的执行顺序。
- FromResult()创建普通数值的Task对象
Task<string> t1=File.ReadAllTextAsync("d:\1.txt");
Task<string> t2=File.ReadAllTextAsync("d:\1.txt");
Task<string> t3=File.ReadAllTextAsync("d:\1.txt");
string[] strs=await Task.WhenAll(t1,t2,t3);
异步与yield
yield return:在迭代中一个一个返回待处理的值
//并非一起返回,而是先返回第一个return的数据,处理完之后再返回下一个return
static IEnumerable<string> Test()
{
yield return "hello";
yield return "Rao";
yield return "Ao";
}
注意事项:在旧版C#中,async方法中不能用yield。从C#8.0开始,把返回值声明为IAsyncEnumerable(不要带Task),然后遍历的时候用await foreach()即可
//并非一起返回,而是先返回第一个return的数据,处理完之后再返回下一个return
static async Task Main(string[] args)
{
await foreach(var s in Test())
{
}
}
static async IEnumerable<string> Test()
{
yield return "hello";
yield return "Rao";
yield return "Ao";
}
二、LINQ
Linq的简单使用
static void Main(string[] args)
{
int[] nums = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
IEnumerable<int> result = nums.Where(b => b > 6);
}
手写where方法
static void Main(string[] args)
{
int[] nums = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//IEnumerable<int> result = nums.Where(b => b > 6);
IEnumerable<int> result = MyWhere2(nums, b => b > 6);
foreach (int i in result)
{
Console.WriteLine(i);
}
}
//手写where方法
static IEnumerable<int> MyWhere1(IEnumerable<int> items, Func<int, bool> f)
{
List<int> result=new List<int>();
foreach (int item in items)
{
if (f(item) == true)
{
result.Add(item);
}
}
return result;
}
//使用yield
static IEnumerable<int> MyWhere2(IEnumerable<int> items, Func<int, bool> f)
{
List<int> result = new List<int>();
foreach (int item in items)
{
if (f(item) == true)
{
yield return item;
}
}
return result;
}
常用扩展方法
static void Main(string[] args)
{
List<Employee> list = new List<Employee>();
list.Add(new Employee { Id=1,Name="jerry",Age=28,Gender=true,Salary=5000});
list.Add(new Employee { Id=2,Name="jerry",Age=28,Gender=true,Salary=5000});
list.Add(new Employee { Id=3,Name="jerry",Age=28,Gender=true,Salary=5000});
list.Add(new Employee { Id=4,Name="jerry",Age=28,Gender=true,Salary=5000});
list.Add(new Employee { Id=5,Name="jerry",Age=28,Gender=true,Salary=5000});
list.Add(new Employee { Id=6,Name="jerry",Age=28,Gender=true,Salary=5000});
list.Add(new Employee { Id=7,Name="jerry",Age=28,Gender=true,Salary=5000});
list.Add(new Employee { Id=8,Name="jerry",Age=28,Gender=true,Salary=5000});
list.Add(new Employee { Id=9,Name="jerry",Age=28,Gender=true,Salary=5000});
list.Add(new Employee { Id=10,Name="jerry",Age=28,Gender=true,Salary=5000});
}
Where()方法
//Where()方法
IEnumerable<Employee> items=list.Where(e => e.Age > 20);
foreach(var i in items)
{
Console.WriteLine(i.Id);
}
list()方法
//获取数据条数
Console.WriteLine(list.Count());
//判断条件的Count方法
Console.WriteLine(list.Count(e => e.Age > 20));
Console.WriteLine(list.Count(e => e.Salary > 8000 && e.Age > 20));
//是否有一条数据
Console.WriteLine(list.Any());
//是否有一条数据的Salary大于30000
Console.WriteLine(list.Any(e=>e.Salary>30000));
获取一条数据(是否带参数的两种写法)
Single:有且只有一条满足要求的数据
SingleOrDefault:最多只有一条满足要求的数据
First:至少有一条,返回第一条
FirstOrDefault:返回第一条或者默认值
//Single:有且只有一条满足要求的数据
IEnumerable<Employee> item1 = list.Where(e => e.Name == "jack");
Employee e1 = item1.Single();
//另一种方法
//Employee e2 = list.Where(e => e.Name == "jack").Single();
Employee e2 = list.Single(e => e.Name == "jack");
//SingleOrDefault:最多只有一条满足要求的数据
Employee e3 = list.SingleOrDefault(e => e.Name == "jack");
//First:至少有一条,返回第一条
Employee e4 = list.First(e => e.Name == "jerry");
//FirstOrDefault:返回第一条或者默认值
Employee e4 = list.FirstOrDefault(e => e.Name == "jerry");
排序:
Order():对数据正序排序
OrderByDescending():倒序排序
//Order():对数据正序排序
IEnumerable<Employee> item2 = list.OrderBy(e => e.Id);
//OrderByDescending():倒序排序
IEnumerable<Employee> item3 = list.OrderByDescending(e => e.Id);
//随机排序
var item4 = list.OrderByDescending(e => Guid.NewGuid());
Random rand = new Random();
var item5 = list.OrderByDescending(e => rand.Next());
//先按照年龄排序,相同年龄的再按照工资排序
var item6 = list.OrderBy(e => e.Age).ThenBy(e => e.Salary);
//限制结果集,获取部分数据
//跳过三条数据,再取两条数据
var item7=list.Skip(3).Take(2);
分组:
GroupBy()方法参数是分组条件表达式,返回值为IGrouping<TKey,TSource>类型的泛型IEnumerable,也就是 每一组以一个IGrouping对象的形式返回
//GroupBy
//按照年龄分组
IEnumerable<IGrouping<int, Employee>> item8 = list.GroupBy(e => e.Age);
foreach(IGrouping<int, Employee> group in item8)
{
Console.WriteLine(group.Key);
foreach(Employee e in group)
{
Console.WriteLine(e);
}
}
投影:把集合中的每一项转换为另一个类型
IEnumerable<int> ages=list.Select(e => e.Age);
IEnumerable<string> names=list.Select(e => e.Gender?"男":"女");
集合转换
var item9 = list.Where(e => e.Salary > 6000);
List<Employee> list1=item9.ToList();
Employee[] array1 = item9.ToArray();
查询语法
//查询语法
var item10 = list.Where(e=>e.Salary>3000).OrderBy(e=>e.Age)
.Select(e=>new { e.Age, e.Name, XB = e.Gender ? "男" : "女" });
var item11 = from e in list
where e.Salary > 5000
select new { e.Age, e.Name, XB = e.Gender ? "男" : "女" };
Linq解决面试问题
//计算平均值
string s = "52,52,657,9,826,252,65";
string[] strs=s.Split(',');
IEnumerable<int> nums=strs.Select(e => Convert.ToInt32(e));
//double avg=nums.Average();
double avg=s.Split(',').Select(e=> Convert.ToInt32(e)).Average();
//统计一个字符串中每个字母出现的频率(忽略大小写),然后按照从高到低的顺序输出频率高于2次的单词和其出现频率
string s1 = "hello world,soamvpawmdmvalmkempw,c,pc,peq";
s.Where(c => char.IsLetter(c)).Select(c => char.ToLower(c))
.GroupBy(c => c).Select(g => new { g.Key, Count = g.Count() })
.OrderByDescending(g => g.Count).Where(g => g.Count > 2);
三、依赖注入
依赖注入的几个概念
服务:对象
注册服务
服务容器:负责管理注册的服务
查询服务:创建对象及关联对象
对象生命周期:Transient(瞬态)、Scoped(范围)、Singleton(单例)
internal class Program
{
static void Main(string[] args)
{
//传统方法
TestServiceImpl1 service = new TestServiceImpl1();
service.Name = "Test";
//service.SayHi();
//依赖注入
//创建服务集合
ServiceCollection service2 = new ServiceCollection();
//添加服务
service2.AddTransient<TestServiceImpl1>();
service2.AddTransient<TestServiceImpl2>();
//ServiceProvider==服务定位器
using(ServiceProvider sp = service2.BuildServiceProvider())
{
//向服务定位器Get一个服务
TestServiceImpl1 t1 = sp.GetService<TestServiceImpl1>();
TestServiceImpl2 t2 = sp.GetService<TestServiceImpl2>();
t1.Name = "Test1";
t1.SayHi();
t2.Name = "Test2";
t2.SayHi();
}
Console.ReadLine();
}
}
public interface ITestService
{
public string Name { get; set; }
public void SayHi();
}
public class TestServiceImpl1 : ITestService
{
public string Name { get; set; }
public void SayHi()
{
Console.WriteLine("TestServiceImpl1");
}
}
public class TestServiceImpl2 : ITestService
{
public string Name { get; set; }
public void SayHi()
{
Console.WriteLine("TestServiceImpl2");
}
}
服务的生命周期
Transient(瞬态)
通过GetService出来的是两个对象
service2.AddTransient<TestServiceImpl1>();
service2.AddTransient<TestServiceImpl2>();
using(ServiceProvider sp = service2.BuildServiceProvider())
{
//向服务定位器Get一个服务
TestServiceImpl1 t1 = sp.GetService<TestServiceImpl1>();
TestServiceImpl1 t2 = sp.GetService<TestServiceImpl1>();
Console.WriteLine(object.ReferenceEquals(t1, t2));//False
}
Singleton(单例)
通过GetService出来的是同一个对象
service2.AddSingleton<TestServiceImpl1>();
service2.AddSingleton<TestServiceImpl2>();
using(ServiceProvider sp = service2.BuildServiceProvider())
{
//向服务定位器Get一个服务
TestServiceImpl1 t1 = sp.GetService<TestServiceImpl1>();
TestServiceImpl1 t2 = sp.GetService<TestServiceImpl1>();
Console.WriteLine(object.ReferenceEquals(t1, t2));//true
}
Scoped(范围)
service2.AddScoped<TestServiceImpl1>();
service2.AddScoped<TestServiceImpl2>();
//ServiceProvider==服务定位器
using(ServiceProvider sp = service2.BuildServiceProvider())
{
TestServiceImpl1 tt1;
//using的范围就是scope1的范围
using (IServiceScope scope1 = sp.CreateScope())
{
//在scope中获取Scope相关的对象,scope1.ServiceProvider
TestServiceImpl1 t1 = scope1.ServiceProvider.GetService<TestServiceImpl1>();
TestServiceImpl1 t2 = scope1.ServiceProvider.GetService<TestServiceImpl1>();
Console.WriteLine(object.ReferenceEquals(t1, t2));//true,两个创建的是同一个scope
tt1 = t1;
}
using (IServiceScope scope2 = sp.CreateScope())
{
//在scope中获取Scope相关的对象,scope1.ServiceProvider
TestServiceImpl1 t3 = scope2.ServiceProvider.GetService<TestServiceImpl1>();
TestServiceImpl1 t4 = scope2.ServiceProvider.GetService<TestServiceImpl1>();
Console.WriteLine(object.ReferenceEquals(t3, t4));//true,两个创建的是同一个scope
Console.WriteLine(object.ReferenceEquals(tt1, t4));//False,两个创建的不是同一个scope
}
}
服务类型与实现类型不一致的方法
ServiceCollection services = new ServiceCollection();
//<TService,TServiceImpl>
//TService:服务的类型 接口
//TServiceImpl:实现的类型 实现类
services.AddScoped<ITestService,TestServiceImpl1>();
using (ServiceProvider sp = services.BuildServiceProvider())
{
ITestService ts1 = sp.GetService<ITestService>();
ts1.Name = "Test";
ts1.SayHi();
}
IServiceProvider的服务定位器方法
T GetService():如果获取不到对象,则返回null
object GetService(Type serviceType)
T GetRequiredService():如果获取不到对象,则抛异常
object GetRequiredService(Type ServiceType)
IEnumerable GetService():适用于可能有很多满足条件的服务
IEnumerable GetService(Type ServiceType)
四、EF Core
五、Web API
ASP.NET Core客户端响应缓存
1.RFC7324是HTTP协议中对缓存进行控制的规范,其中重要的是cache-control这个响应报文头。服务器如果返回contro:max-age-60,则表示服务器指示浏览器端“可以缓存这个响应内容60秒”
2.我们只要给需要进行缓存控制的控制器的操作方法添加ResponseCacheAttribute,ASP.NET Core会自动添加cache-control报文头
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
[ResponseCache(Duration = 20)]
[HttpGet]
public DateTime Now()
{
return DateTime.Now;
}
}
ASP.NET Core服务器响应缓存
1.客户端
当有客户端响应缓存后,再次向服务器请求相同的地址,会直接从缓存中读取,但不同的浏览器间的缓存并不共享。
2.服务端
服务端的缓存是由服务器进行缓存,所有浏览器访问同一地址,公用同一个缓存
Response Caching Middleware
1、如果ASP.NETCore中安装了“响应缓存中间件”,那么ASP.NET
Core不仅会继续根据[Responsecache]设置来生成cache-control响应报文头来设置客户端缓存,而且服务器端也会按照[Responsecache]的设置来对响应进行服务器端缓存。
2、“响应缓存中间件”的好处:对于来自不同客户端的相同请求或者不支持客户端缓存的客户端,能降低服务器端的压力。
3、用法:app.MapControllers()之前加上app.UseResponsecaching().请确保app.UseCors()写到app.UseResponsecaching()之前
app.UseAuthorization();
//启用服务器端响应缓存
app.UseResponseCaching();
app.MapControllers();
服务器端响应缓存的缺点
1.恶意请求会造成服务器压力过大当浏览器设置禁用缓存,客户端和服务端缓存都不会缓存,这时如果频繁执行耗时操作,就会服务器压力过大
2.服务端缓存限制
1.只有状态码为 200的Get和Head才会被缓存
2.报文头不能有Authorization、Set-Cookie
3.解决办法
使用内存缓存和分布式缓存
ASP.NET Core中的内存缓存
一、缓存数据类型
缓存放到应用程序的内存中。内存缓存中保存的是一系列的键值对。就像Dictionary类型一样。
二、缓存特性
内存缓存保存在当前运行的网站程序的内存中是和进程相关的。因为在web服务器中,多个网站是运行在不同的进程中的,因此不同进程的缓存不会相互影响。而且网站重启后,内存中的缓存会全部清除。
三、用法
1.启用AddMemoryCache()
builder.Services.AddMemoryCache();
2.注入IMemoryCache接口:方法TryGetValue,Remove,Set,GetOrCreat,GetOrCreatAsync
private readonly IMemoryCache memoryCache;
public TestController(IMemoryCache memoryCache)
{
this.memoryCache = memoryCache;
}
[HttpGet]
public async ActionResult<Book?> GetBookById(long id)
{
/*
Book? result = MyDbContext.GetById(id);
if(result = null){
return NotFound($"找不到id={id}的书")
}
else{
return result;
}*/
//查询缓存中的数据,如果没有数据,则回调,进行数据库查询,返回给调用者并保存到缓存
Book? b= await memoryCache.GetOrCreateAsync("Book" + id,async(e)=>{
return awaitMyDbContext.GetByIdAsync(id);
})
if(b==null){
return NotFound($"找不到id={id}的书");
}
else{
return b;
}
}
缓存的过期时间策略
缓存的绝对过期时间
1.从缓存保存的那一刻开始多长时间内缓存有效,超过这个时间,缓存失效
2.GetOrCreateAsync()方法的回调方法中有一个ICacheEntry类型的参数,通过对ICacheEntry对当前的缓存项做设置
3.AbsoluteExpirationRelativeToNow 用来设定缓存项的绝对过期时间
[HttpGet]
public async ActionResult<Book?> GetBookById(long id)
{
/*
Book? result = MyDbContext.GetById(id);
if(result = null){
return NotFound($"找不到id={id}的书")
}
else{
return result;
}*/
//查询缓存中的数据,如果没有数据,则回调,进行数据库查询,返回给调用者并保存到缓存
Book? b= await memoryCache.GetOrCreateAsync("Book" + id,async(e)=>{
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);//缓存有效期十秒
return await MyDbContext.GetByIdAsync(id);
})
if(b==null){
return NotFound($"找不到id={id}的书");
}
else{
return b;
}
}
缓存的滑动过期时间
1.只要在缓存没过期的时候请求一次,缓存自动续约一段时间
2.ICacheEntry的SlidingExpiration属性用来设定缓存项的滑动过期时间
[HttpGet]
public async ActionResult<Book?> GetBookById(long id)
{
/*
Book? result = MyDbContext.GetById(id);
if(result = null){
return NotFound($"找不到id={id}的书")
}
else{
return result;
}*/
//查询缓存中的数据,如果没有数据,则回调,进行数据库查询,返回给调用者并保存到缓存
Book? b= await memoryCache.GetOrCreateAsync("Book" + id,async(e)=>{
e.SlidingExpiration = TimeSpan.FromSeconds(10);//缓存有效期十秒
return awaitMyDbContext.GetByIdAsync(id);
})
if(b==null){
return NotFound($"找不到id={id}的书");
}
else{
return b;
}
}
内存缓存的是与非
1.无论用哪种过期时间策略,程序中都会存在缓存数据不一致的情况。
2.可以通过其他机制获取数据源改变的消息,再通过代码调用IMemoryCache的Set方法更新缓存
缓存穿透
分布式缓存
ASP.NET Core默认添加的配置提供者
1.加载现有的IConfiguration
2.加载项目根目录下的appsetting.json
3.加载项目根目录下的appsetting.{Environment}.json
4.当程序运行在开发环境下,程序会加载“用户机密”配置
5.加载环境变量中的配置
6.加载命令行中的配置
多层项目中EFCore的使用
Filter
1.Exception Filter
public class MyExceptionFilter : IAsyncExceptionFilter
{
private readonly IWebHostEnvironment webHostEnvironment;
public MyExceptionFilter(IWebHostEnvironment webHostEnvironment)
{
this.webHostEnvironment = webHostEnvironment;
}
public Task OnExceptionAsync(ExceptionContext context)
{
//context.Exception代表异常信息对象
//如果context.ExceptionHandled赋值为true,则其他ExceptionFilter不会被执行
//context.Result的值会被输出给客户端
string msg;
if (webHostEnvironment.IsDevelopment())
{
msg = context.Exception.ToString();
}
else
{
msg = "error";
}
ObjectResult result = new ObjectResult(new {code = 500,message = msg});
context.Result = result;
context.ExceptionHandled = true;
context.Result = result;
return Task.CompletedTask;
}
}
2.Action Filter
IAsyncActionFilter接口
多个ActionFilter的链式执行
public class MyActionFilter1 : IAsyncActionFilter
{
/**
* context:
* next:一个参数为空,返回值为Task<ActionExecutedContext>类型的委托,代表要执行的下一个ActionFilter
**/
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
Console.WriteLine("MyActionFilter1 before");
ActionExecutedContext result = await next();
if (result.Exception != null)
{
Console.WriteLine("MyActionFilter1 after ERROR");
}
else
{
Console.WriteLine("MyActionFilter1 after success");
}
}
}
自动启用事务的ActionFilter
1.数据库事务:要么全部成功、要么全部失败
2.自动化:启动、提交以及回滚事务
3.当一段使用EFCore进行数据库操作的代码放到TransactionScope声明的范围中的时候,这段代码就会自动被标记为“支持事务”
4.TransactionScope实现了IDisposable接口,如果一个TransactionScope的对象没有调用Complete()就执行了Dispose()方法,则事务会被回滚,否则事务就会被提交
5.TransactionScope还支持嵌套式事务
6 .NET Core中的TransactionScope不像.NET FX一样由MSDTCL()分布式事务提升的问题。请使用最终一致性事务
public class DemoController : ControllerBase
{
private MyDbContext ctx;
[HttpPost]
public string Test1()
{
using (TransactionScope tx = new TransactionScope())
{
ctx.Books.Add(new Book { Name = "sss", Price = 1 });
ctx.SaveChanges();//一个事务
ctx.Persons.Add(new Book { Name = "sss", Age = 1 });
ctx.SaveChanges();//一个事务
tx.Complete();
return "ok";
}
}
[HttpPost]
public async Task<string> Test1()
{
using (TransactionScope tx = new TransactionScope(
TransactionScopeAsyncFlowOption.Enabled))
{
ctx.Books.Add(new Book { Name = "sss", Price = 1 });
await ctx.SaveChangesAsync();//一个事务
ctx.Persons.Add(new Book { Name = "sss", Age = 1 });
await ctx.SaveChangesAsync();//一个事务
tx.Complete();
return "ok";
}
}
}
asp.net core ActionFilter 进行 所有接口 访问限速
中间件
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//app.MapGet("/", () => "Hello World!");
app.Map("/test", async (pipeBuilder) =>
{
pipeBuilder.Use(async (context, next) => {
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("1 start<br/>");
await next.Invoke();
await context.Response.WriteAsync("1 end<br/>");
});
pipeBuilder.Use(async (context, next) => {
await context.Response.WriteAsync("2 start<br/>");
await next.Invoke();
await context.Response.WriteAsync("2 end<br/>");
});
pipeBuilder.Run(async context =>
{
await context.Response.WriteAsync("Run<br/>");
});
});
app.Run();
}
简单的自定义中间件
1.如果中间件的代码比较复杂,或者我们需要复用使用一个中间件的话,我们最好把中间件的代码放到单独的中间件类中
2.中间件类是一个普通的.NET类,它不需要继承任何父类或者实现接口,但是这个类需要有一个构造方法,构造方法中至少要有一个RequestDelegate类型的参数,这个参数用来指向下一个中间件。这个类还需要定义一个名字为Invoke的或者InvokeAsync的方法,方法至少有一个HttpContext类型的参数,方法的返回值必须是Task类型。中间件类的构造方法和Invoke方法还可以定义其他参数,其他参数的值会通过依赖注入自动赋值
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//app.MapGet("/", () => "Hello World!");
app.Map("/test", async (pipeBuilder) =>
{
pipeBuilder.UseMiddleware<CheckMiddleware>();
pipeBuilder.Use(async (context, next) => {
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("1 start<br/>");
await next.Invoke();
await context.Response.WriteAsync("1 end<br/>");
});
pipeBuilder.Use(async (context, next) => {
await context.Response.WriteAsync("2 start<br/>");
await next.Invoke();
await context.Response.WriteAsync("2 end<br/>");
});
pipeBuilder.UseMiddleware<Test1Meddleware>();
pipeBuilder.Run(async context =>
{
await context.Response.WriteAsync("Run<br/>");
dynamic? ojb = context.Items["BodyJson"];
if (ojb!= null)
{
await context.Response.WriteAsync($"{ojb}<br/>");
}
});
});
app.Run();
}
编写MarkDown渲染中间件
public class MarkDownMiddleware
{
private readonly RequestDelegate next;
private readonly IWebHostEnvironment hotEnv;
public MarkDownMiddleware(RequestDelegate next,IWebHostEnvironment hotEnv) {
this.next = next;
this.hotEnv = hotEnv;
}
public async Task InvokeAsync(HttpContext context)
{
string path = context.Request.Path.ToString();
if (path.EndsWith(".md", true, null))//只处理.md文件请求
{
await next.Invoke(context);
return;
}
var file = hotEnv.WebRootFileProvider.GetFileInfo(path);
if(!file.Exists)//是否存在
{
await next.Invoke(context);
return;
}
using var stream = file.CreateReadStream();
//探测文本的的编码类型
Ude.CharsetDetector detector = new Ude.CharsetDetector();
detector.Feed(stream);
detector.DataEnd();//从头读到尾
string charset = detector.Charset??"UTF-8";
stream.Position = 0;//复位
//读取md文件
using StreamReader reader = new StreamReader(stream,Encoding.GetEncoding(charset));
string mdText = await reader.ReadToEndAsync();
//markdown文本转换html
Markdown markdown = new Markdown();
string html = markdown.Transform(mdText);
context.Response.ContentType = "text/html;charset=UTF-8";
await context.Response.WriteAsync(html);
}
}