ASP.NET Core学习之路02

本文章是我听B站杨中科的所做的笔记

杨中科B站视频链接:.NET 6教程,.Net Core 2022视频教程,杨中科主讲_哔哩哔哩_bilibili

程序员的性能优化万金油:缓存

什么是缓存

缓存(Caching)是系统优化中简单又有效的工具,投入小收效大。数据库中的索引等简单有效的优化功能本质上都是缓存

ASP.NET Core学习之路02_第1张图片

缓存的概念

1、缓存命中

2、缓存命中率

3、缓存数据不一致

多级缓存

ASP.NET Core学习之路02_第2张图片

ASP.NET Core客户端响应缓存

cache-control

1、RFC7324是HTTP协议中对缓存进行控制的规范,其中重要的是cache-control这个响应报文头。服务器如果cache-control:max-age=60,则表示服务器指示浏览器端"可以缓存这个响应内容60秒"

2、我们只要给需要进行缓存控制的控制器的操作方法添加ResponseCacheAttribute这个Attribute,ASP.NET Core会自动添加cache-control报文头

3、验证:编写一个返回当前时间的Action方法。分别加和不加ResponseCacheAttribute看区别。也F12看看Network

4、缺点:各个浏览器缓存是相互独立的,每个浏览器都得请求一遍服务器端,再进行缓存,缓存不共享

ASP.NET Core服务端响应缓存

Response Caching Middleware

1、如果ASP.NET Core中安装了“响应缓存中间件”,那么ASP.NET Core不仅会继续根据[ResponseCache]设置来生成cache-control响应报文头来设置客户端缓存,而且服务器端也会按照[ResponseCache]的设置来对响应进行服务器端缓存。和客户端缓存的区别?来自多个不同客户端的相同请求。

2、“响应缓存中间件”的好处:对于来自不同客户端的相同请求或者不支持客户端缓存的客户端,能降低服务器端的压力。

3、用法:app.MapControllers()之前加上app.UseResponseCaching()。请确保app.UseCors()写到app.UseResponseCacheing()之前

演示效果

1、大部分浏览器都是支持RFC7324规范的,所以不方便用来测试服务端响应缓存。用默认忽略RFC7324规范的PostMan测试。试一下请求服务器端

2、可以在浏览器的“开发人员工具”中禁用缓存,但是和PostMan中不一致,为什么?“cache-control:no-cache”

3、也可以让PostMan在请求报文头中加如“cache-control:no-cache”,只要在Postman的设置中开启【Send no-cache headers】

服务器端响应缓存很鸡肋

1、无法解决恶意请求给服务器带来的压力

2、服务器端响应缓存还有很多限制,包括但不限于:响应状态码为200的Get或者HEAD请求才可能被缓存;报文头中不能含有Authorization、Set-Cookie等。

Authorization为什么?:把一个浏览器响应的具体用户数据的缓存下来,给到另外一个用户浏览器不现实

3、不怪他,honor RFC7234.It's a feature,not a bug

4、怎么办呢?采用内存缓存、分布式缓存等

ASP.NET Core中的内存缓存

内存缓存

1、把缓存数据放到应用程序的内存。内存缓存中保存的是一系列的键值对,就像Dictionaty类型一样

2、内存缓存的数据保存再当前运行的网站程序的内存中,是和进程相关的。因为再Web服务器中,多个不同的网站时运行在不同的进程中的,因此不同网站的内存缓存是不会互相干扰的,而且网站重启后,内存缓存中所有数据也就都被清空了。

内存缓存的用法

1、启用:builder.Service.AddMemoryCache()

2、注入IMemoryCache接口,查看接口的方法:TryGetValue、Remove、Set、GetOrCreate、GetOrCreateAsync

3、用GetOrCreateAsync讲解

public async Task GetBooks()
{
    logger.LogInformation("开始执行GetBooks");
    var items = await memCache.GetOrCreateAsync("AllBooks", async (e) =>
    {
        logger.LogInformation("从数据库中读取数据");
        return await dbCtx.Books.ToArrayAsync();
    });
    logger.LogInformation("把数据返回给调用者");
    return items;
}

缓存的过期时间策略

缓存的过期时间

1、上面的例子中的缓存不会过期,除非重启服务器。

2、解决方法:在数据改变的时候调用Remove或者Set来删除或者修改缓存(优点:及时);过期时间(只要过期时间比较短,缓存数据不一致的情况也不会持续很长时间)

3、两种过期时间策略:绝对过期时间、滑动过期时间。它们分别是什么?

缓存的绝对过期时间

1、GetOrCreaeteAsync()方法的回调方法中有一个ICacheEntry类型的参数,通过ICacheEntry对当前的缓存项做设置

2、AbsoluteExpirationRelativeToNow用来设定缓存项的绝对过期时间

缓存的滑动过期时间

1、ICacheEntry的SlidingExpiration属性用来设定缓存项的滑动过期时间

两种过期时间混用

使用滑动过期时间策略,如果一个缓存项一直被频繁访问,那么这个缓存项就会一直被续期而不过期。可以对一个缓存项同时设定滑动过期时间和绝对过期时间,并且把绝对过期时间设定的比滑动过期时间长,这样缓存项的内容会在绝对过期时间内随着访问被滑动续期,但是一旦超过了绝对过期时间,缓存项就会被删除

内存缓存的是与非

1、无论用哪种过期时间策略,程序中都会存在缓存数据不一致的情况。部分系统(博客等)无所谓,部分系统不能忍受(比如金融)

2、可以通过其他机制获取数据源改变的信息,再通过代码调用IMemoryCache的Set方法更新缓存

缓存穿透问题

什么是缓存穿透

string cacheKey = "Book" + id;//缓存键
Book? b = memCache.Get(cacheKey);
if(b==null)//如果缓存中没有数据
{
    //查询数据库,然后写入缓存
    b = await dbCtx.Books.FindAsync(id);
    memCache.Set(cacheKey, b);
}

缓存穿透的解决方案

1、解决方法:把"查不到"也当成一个数据放入缓存

2、我们用GetOrCreateAsync方法即可,因为它会把null值也当成合法的缓存值

string cacheKey = "Book" + id;
var book = await memCache.GetOrCreateAsync(cacheKey, async (e) => {
    var b = await dbCtx.Books.FindAsync(id);
    logger.LogInformation("数据库查询:{0}",b==null?"为空":"不为空");
    return b;
});
logger.LogInformation("Demo5执行结束:{0}", book == null ? "为空" : "不为空");

缓存雪崩和数据混乱问题

缓存雪崩

1、缓存项集中过期引起缓存雪崩

2、解决方法:再基础过期时间之上,再加一个随机的过期时间

缓存数据混乱

public User GetUserInfo()
{
    Guid userId=...;//获取当前用户Id
    return memCache.GetOrCreate("UserInfo", (e) => {
        return ctx.User.Find(userId);
    });
}

解决方法:合理设置key

封装内存缓存操作的帮助类

需求

1、IQueryable、IEnumerable等类型可能存在着延迟加载的问题,如果把这两种类型的变量指向的对象保存到缓存中,在我们把它们取出来再去执行的时候,如果它们延迟加载时候需要的对象已经被释放的话,就会执行失败。因此缓存禁止这两种类型

2、实现随机缓存过期时间

接口

public interface IMemoryCacheHelper
{
    TResult? GetOrCreate(string cacheKey, 
    Func valueFactory, int expireSeconds);
    Task GetOrCreateAsync(string cacheKey, 
    Func> valueFactory, int expireSeconds);
    void Remove(string cacheKey);
}

查看实现代码

NETBookMaterials/MemoryCacheHelper.cs at main · yangzhongke/NETBookMaterials · GitHub

分布式缓存

分布式系统中的内存缓存

ASP.NET Core学习之路02_第3张图片

如果集群节点的数量非常多的话,这样的重复查询也同样可能会把数据库压垮

分布式缓存服务器

ASP.NET Core学习之路02_第4张图片  

用户什么做缓存服务器

1、用SQLServer做缓存性能并不好

2、Memcached是缓存专用,性能非常高,但是集群、高可用等方面比较弱,而且有“缓存键的最大长度为250字节”等限制。可以安装EnyimMemcachedCore这个第三方NuGet包

3、Redis不局限于缓存,Redis做缓存服务器比Memcached性能稍差,但是Redis的高可用、集群等方便非常强大,合适在数据量大、高可用性等场合使用

分布式缓存用法

1、NuGet安装:Microsoft.Extensions.Caching.StackExchangeRedis

2、

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost";
    options.InstanceName = “yzk_”;//避免混乱
});

3、用时间显示测试用法

封装分布式缓存操作的帮助类

需求

1、解决缓存穿透、缓存雪崩等问题

2、自动地进行其他类型的转换

public interface IDistributedCacheHelper
{
    TResult? GetOrCreate(string cacheKey, Func valueFactory, int expireSeconds);
    Task GetOrCreateAsync(string cacheKey, 
        Func> valueFactory, int expireSeconds);
    void Remove(string cacheKey);
    Task RemoveAsync(string cacheKey);
}

3、实现类 NETBookMaterials/DistributedCacheHelper.cs at main · yangzhongke/NETBookMaterials · GitHub

ASP.NET Core与配置系统的集成

ASP.NET Core默认添加的配置提供者

1)加载现有的IConfiguration

2)加载项目根目录下的appsettings.json

3)加载项目根目录下的appsettings.{Environment}.json

4)当程序运行在开发环境下,程序会加载“用户机密”配置

5)加载环境变量中的配置

6)加载命令行中的配置

配置的环境问题

1、Why?开发环境、测试环境、生产环境需要进行不同的配置

2、运行环境:ASP.NET Core会从环境变量中读取名字为ASPNETCORE_ENVIRONMENT的值。推荐值:Developmen(开发环境)、Staging(测试环境)、Production(生产环境)

3、读取方法:app.Environment.EnvironmentName、app.Environment.IsDevelopment()

4、在WinDows和VS(推荐开发时用)中设置环境变量的方法

.NET Core防止机密配置外泄

ASP.NET Core学习之路02_第5张图片

 ASP.NET Core学习之路02_第6张图片

 ASP.NET Core学习之路02_第7张图片

User Secrets

1、把不方便放到appsetting.json中的机密信息放到一个不在项目中的json文件中

2、在ASP.NET Core项目上单击右键,选择【管理用户机密】

3、secrets.json文件到底保存在哪里?如何和项目建立关系?csproj文件中的

注意事项

1、供开发人员使用的,不适合在生产环境中使用

2、仍然是明文存储。不想别人看到怎么办?Azure Key Vault、Zack.AnyDBConfigProvider等。无法完全避免。加强安全防控更重要

3、如果因为重装、新员工等原因导致secrets.json重建,就要重新配置,麻烦。如果影响大的话,还是用集中式配置服务器

ASP.NET Core配置系统综合案例

功能需求

1)系统的主要配置(Redis、Smtp)放到配置专用的数据库中。Zack.AnyDBConfigProvider

2)连接配置数据库的连接字符串配置在“用户机密”中。“Data Sourse=.;Initial Catalog=demo1;Integrated Security=SSPI;”

3)把Smtp的配置显示到界面上

4)程序启动的时候就连接Redis,并且把Redis连接对象注册到依赖注入系统中

builder.Services.AddSingleton(sp => {
    string connStr = builder.Configuration.GetValue("Redis:ConnStr");
    return ConnectionMultiplexer.Connect(connStr);
});

ASP.NET Core分层项目中EF Core的用法

分层项目的建立

1、为什么要项目分层?带来什么问题?

2、创建一个.NET类库项目BooksEFCore,放实体等类。NuGet:Microsoft.EntityFrameworkCore.Relational

3、BooksEFCore中增加实体类Book和配置类

数据库的配置1

1、上下文类MyDbContext:为什么正式项目中最好不要在MyDbContext写数据库配置(连接不同的DB甚至不同类型的DB)。尽量数据库配置的代码写到ASP.NET Core项目中。不重写OnConfiguring方法,而是为MyDbContext类的构造方法增加DbContextOptions参数。在ASP.NET Core项目对DbContextOptions的配置

2、创建ASP.NET Core项目,添加对“BookEFCore”项目的引用。NuGet安装Microsoft.EntityFrameworkCore.SqlServer

3、配置文件、配置代码等放到ASP.NET Core项目中。

builder.Services.AddDbContext(opt => {
    string connStr = builder.Configuration.GetConnectionString("Default");
    opt.UseSqlServer(connStr); 
});

数据库的配置2

4、在Controller中注入MyDbContext,编写测试代码

5、生成实体类的迁移脚本。多项目的环境执行Add-Migration的时候可能会出现这个错误,原理是什么?

ASP.NET Core学习之路02_第8张图片

数据库的配置3

6、不用研究项目中Add-Migration的细节。实用的方案:编写实现IDesignTimeDbContextFactory接口的类,把配置放到里面反正是开发环境而已

7、可以把连接字符串配置到环境变量中,不过MyDbDesignTimeDbContextFactory中来读取配置系统,可以直接用Environment.GetEnvironmentVariable()读取环境变量

8、数据库迁移脚本要生成到BooksEFCore中,因此为这个项目安装Microsoft.EntityFrameworkCore.Tools、Microsoft.EntityFrameworkCore.SqlServer。然后把BooksEFCore设置为启动项目,并且在【程序包管理控制台】中也选中BookEFCore项目后,执行Add-Migration和Update-Database

步骤汇总:

1、建类库项目,方式实体类、DbContext、配置类等DbContext中不配置数据库连接,而是为DbContext增加一个DbContextOptions类型的构造函数

2、EFCore项目安装对应数据库的EFCore Provider

3、asp.net core项目引用EFCore项目,并且通过AddDbContext来注入DbContext及对DbContext进行配置

4、Controller中就可以注入DbContext类使用了

5、让开发环境的Add-Migration知道连接哪个数据库,在EFCore项目中创建一个实现了IDesignTimeDbContextFactory的类。并且在CreateDbContext返回一个连接开发数据库的DbContext

    public MyDbContext CreateDbContext(string[] args)
        {
            DbContextOptionsBuilder builder = new DbContextOptionsBuilder();
            string connStr = "Data Source=.;Initial Catalog=demo666;Integrated Security=SSPI;";
            builder.UseSqlServer(connStr);
            MyDbContext ctx = new MyDbContext(builder.Options);
            return ctx;
        }

如果不在乎连接字符串被上传到Git,就可以把连接字符串直接写死到CreateDbContext;如果在乎,那么CreateDbContext里面很难读取到VS中通过简单的方法设置的环境变量,所以必须把连接字符串配置到Windows的正式的环境变量中,然后再Environment.GetEnvironmentVariable读取 6、正常执行Add-Migration、Update-Database迁就就行了。需要把EFCore项目设置为启动项目,并且子啊【程序包管理器控制台】中也要选中EFCore项目,并且安装Microsoft.EntityFrameworkCore SqlServer、Microsoft.EntityFrameworkCore.Tools

填用AddDbContextPool

1、用AddDbContextPool代替AddDbContext可以实现“DbContext池”

2、AddDbContextPool的问题:

1)用AddDbContextPool注册的DbContext无法注入其他服务?而AddDbContext可以。为啥要为DbContext注册服务?为什么AddDbContextPool不行?

2)很多数据库的ADO.NET提供都实现了数据库连接池机制,可能会有冲突,实用的时候需要自己调节。

3、AddDbContextPool意义不大:

1)“小上下文”策略

2)有数据库连接池

ASP.NET Core学习之路02_第9张图片

4、DbContextPool中的每一个DbContext都对应一个数据库连接,DbContextPool中每多一个DbContext,数据库连接池中就会少一个数据库连接。当这两个池的大小不一样且DbContextPool大于数据库连接池,问题来了,DbContextPool根据自家池(假设128)子的大小畅快地向池中填DbContext,浑然不顾数据库连接池的大小(假设是100),当填到第101个DbContext时就会出现上面的错误

案例:批量注册上下文的案例

案例:复杂.NET Core项目中批量注册上下文

1、项目采用“小上下文”策略,在项目中可能存在多个上下文类,如果手动AddDbContext就太麻烦了

2、反射扫描程序集中所有的上下文类,然后逐个调用AddDbContext注册,Install-Package Zack.Infrastructure GitHub - yangzhongke/NETBookMaterials

3、使用演示

ASP.NET Core学习之路02_第10张图片

ASP.NET Core异常Filter

什么是Filter

1、切面编程机制,在ASP.NET Core特定的位置执行我们自定义的代码

2、ASP.NET Core中的Filter的五种类型: Authorization Filter、Resource Filter、Action Filter、Exception Filter、Result Filter。本书中重点讲解Exception Filter和Action Filter

3、所有筛选器一般有同步和异步两个版本,比如IActionFilter、IAsyncActionFilter接口

Exception Filter

1、当系统中出现未经处理的异常的时候,异常筛选器就会执行

ASP.NET Core学习之路02_第11张图片

实现

1、当系统中出现未处理异常的时候,我们需要统一给客户端返回如下格式的响应报文:{“code”:"500","message":"异常信息"}。对于开发环境中message是异常堆栈,对于其他环境message用一个general的报错信息

2、实现IAsyncExceptionFilter接口。注入IHostEnvironment得知运行环境

ObjectResult result = new ObjectResult(new { code = 500, message = message });
result.StatusCode = 500;
context.Result = result;
context.ExceptionHandled = true;

3、

builder.Services.Configure(options =>
{
    options.Filters.Add();
});

ActionFilter基础

Action Filter

1、IAsyncAction接口

2、多个ActionFilter的链式执行,栈(先进后出)

ASP.NET Core学习之路02_第12张图片

测试代码

public class MyActionFilter1 : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, 
        ActionExecutionDelegate next)
    {
        Console.WriteLine("MyActionFilter 1:开始执行");
        ActionExecutedContext r = await next();
        if (r.Exception != null)
            Console.WriteLine("MyActionFilter 1:执行失败");
        else
            Console.WriteLine("MyActionFilter 1:执行成功");
    }
}

再编写MyActionFilter2

builder.Services.Configure(options =>
{
    options.Filters.Add();
    options.Filters.Add();
});

无论是同步还是异步Action方法,都能用IAsyncActionFilter处理

自动启用事务的筛选器

需求

1、数据库事务:要么全部成功、要么全部失败

2、自动化:启动、提交以及回滚事务

3、当一般使用EF Core进行数据库操作的代码放到TransactionScope声明的范围中的时候,这段代码就会自动被标记为“支持事务”

4、TransactionScope实现了IDisposable接口,如果一个TransactionScope的对象没有调用Complete()就执行了Dispose()方法,则事务会被回滚,否则事务就会被提交。

5、TransactionScope还支持嵌套式事务

6、.NET Core中的TransactionScope不像.NET FX一样有MSDTC分布式事务提升的问题。请使用最终一致性事务

用法

ASP.NET Core学习之路02_第13张图片

1、对于强制不进行事务控制的Action方法,请标注NotTransactionalAttribute

2、开发筛选器TransactionScopeFilter;把TransactionScopeFilter注册到Program.cs中

3、编写两个插入数据的代码,测试

案例:开发请求限流器

需求

1、Action Filter可以在满足条件的时候终止操作方法的执行

2、在Action Filter中,如果我们不调用await next(),就可以终止Action方法的执行了

3、为了避免恶意客户端频繁发送大量请求消耗服务器资源,我们要实现“一秒钟内只允许最多有一个来自同一个IP地址的请求”

ASP.NET Core学习之路02_第14张图片

中间件是什么

why 中间件

中间件是ASP.NET Core的核心组件,MVC框架、响应缓存、身份验证、CORS、Swagger等都是内置中间件

ASP.NET Core学习之路02_第15张图片

什么是中间件

1、广义上来讲:Tomcat、WebLogic、Redis、IIS;狭义上来讲,ASP.NET Core中的中间件指ASP.NET Core中的一个组件。

2、中间件有前逻辑、next、后逻辑3部分组成,前逻辑为第一段要执行的逻辑代码、next为指向下一个中间件的调用、后逻辑为从下一个中间件执行返回所执行的逻辑代码。每个HTTP请求都要经历一系列中间件的处理,每个中间件对于请求进行特定的处理后,在转到下一个中间件,最终的业务逻辑代码执行完成后,响应的内容也会按照处理的相反顺序进行处理,然后形成HTTP响应报文返回给客户端。

3、中间件组成一个管道,整个ASP.NET Core的执行过程就是HTTP请求和响应按照中间件组装的顺序在中间件之间流转的过程。开发人员可以对组成管道的中间件按照需要进行自由组合

中间件的三个概念

Map、Use和Run。Map用来定义一个管道可以处理哪些请求。Use和Run用来定义管道,一个管道由若干个Use和一个Run组成,每个Use引入一个中间件,而Run是用来执行最终的核心应用逻辑

ASP.NET Core学习之路02_第16张图片

ASP.NET Core基本的中间件

简单的自定义中间件

为了能够更清晰地了解中间件,我们创建一个空的ASP.NET Core的项目,然后手动添加中间件。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Map("/test", async appbuilder => {
    appbuilder.Use(async (context, next) => {
        context.Response.ContentType = "text/html";
        await context.Response.WriteAsync("1  Start
");        await next.Invoke();        await context.Response.WriteAsync("1 End
");   });    appbuilder.Use(async (context, next) => {        await context.Response.WriteAsync("2 Start
");        await next.Invoke();        await context.Response.WriteAsync("2 End
");   });        appbuilder.Run(async ctx => {        await ctx.Response.WriteAsync("hello middleware
");   }); }); app.Run();

封装简单的自定义中间件:

1、如果中间件的代码比较复杂,或者我们需要重复使用一个中间件的话,我们最好把中间件的代码放到一个单独的“中间件类”中

2、中间件类是一个普通的.NET类,它不需要集成任何父类或者实现任何接口,但是这个类需要有一个构造方法,构造方法至少要有一个RequestDelegate类型的参数,这个参数用来指向下一个中间件。这个类还需要定义一个名字为Invoke或InvokeAsync的方法,方法至少有一个HttpContext类型的参数,方法的返回值必须是Task类型。中间件类的构造方法和Invoke(或InvokeAsync)方法还可以定义其他的参数,其他参数的值会通过依赖注入自动赋值

检查请求中是否有password=123的查询字符串,而且会把请求报文体按照Json格式尝试解析为dynamic类型的对象,并且把dynamic对象放入context.items中供后续的中间件或者Run使用

public class CheckAndParsingMiddleware
{
    private readonly RequestDelegate next;
    public CheckAndParsingMiddleware(RequestDelegate next)
    {
        this.next = next;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        string pwd = context.Request.Query["password"];
        if (pwd == "123")
        {
            if (context.Request.HasJsonContentType())
            {
                var reqStream = context.Request.BodyReader.AsStream();
                dynamic? jsonObj = DJson.Parse(reqStream);
                context.Items["BodyJson"] = jsonObj;
            }
            await next(context);
        }
        else context.Response.StatusCode = 401;
    }
}
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Map("/test", async appbuilder => {
    appbuilder.UseMiddleware();
    appbuilder.Run(async ctx => {
        ctx.Response.ContentType = "text/html";
        ctx.Response.StatusCode = 200;
        dynamic? jsonObj = ctx.Items["BodyJson"];
        int i = jsonObj.i;
        int j = jsonObj.j;
        await ctx.Response.WriteAsync($"{i}+{j}={i+j}");
    });
});
app.Run();

自己动手模仿Web API中间件

代码解读

ASP.NET Core学习之路02_第17张图片

框架由MyStaticFilterMiddleware、MyWebAPIMiddleware、NotFoundMiddleware这3个中间件组成 运行查看效果,解读源代码

案例:Markdown转换器中间件

需求

1、什么是Markdown;不被浏览器支持;所以,编写一个在服务器端把Markdown装换为HTML的中间件

2、我们开发的中间件是构建在ASP.NET Core内置的StaticFiles中间件之上,并且在它之前运行,所有的*.md文件都被放到wwwroot文件夹下,当我们请求wwwroot下其他的静态文件的时候,StaticFiles中间件会把它们返回给浏览器,而当我们请求wwwroot下的*.md文件的时候,我们编写的中间件会读取对应的*.md文件并且把它们转换为HTML格式返回给浏览器

本文编码测试

调用Ude.NetStandard这个NuGet包的CharsetDetector类来探测文件的编码。

private static string DetectCharset(Stream stream)
{
    CharsetDetector charDetector = new();
    charDetector.Feed(stream);
    charDetector.DataEnd();
    string charset = charDetector.Charset ?? "UTF-8";
    stream.Position = 0;
    return charset;
}

MD-->HTML

NuGet包:MarkdownSharp

Markdown markdown = new Markdown();
string html= markdown.Transform(text);

整合

app.UseMiddleware();
app.UseStaticFiles();

Filter与Middleware的区别

关系

中间件是ASP.NET Core这个基础提供的功能,而Filter是ASP.NET Core MVC中提供的功能。ASP.NET Core MVC是由MVC中间件提供的框架,而Filter属于MVC中间件提供的功能

ASP.NET Core学习之路02_第18张图片

区别

1、中间件可以处理所有的请求,而Filter只能处理对控制器的请求;中间件运行在一个更底层、更抽象的级别,因此在中间件中无法处理MVC中间件特有的概念。

2、中间件和Filter可以完成很多相似的功能。“未处理异常中间件”和“未处理异常Filter”;“请求限流中间件”和“请求限流Filter”的区别

3、优先选择使用中间件;但是如果这个组件只针对MVC或者需要调用MVC相关的类的时候,我们就只能选择Filter

你可能感兴趣的:(学习,.netcore,后端)