来源: linux 服务器利用宝塔面板部署.net 6(.net core)服务端程序图文步骤_宝塔安装.net环境-CSDN博客
.net 温故知新:【2】 .Net Framework 、.Net 、 .NET Standard的概念与区别 - XSpringSun - 博客园
来源: .net 温故知新:【2】 .Net Framework 、.Net 、 .NET Standard的概念与区别 – XSpringSun – 博客园
作为了解历史和演进过程,我们需要将 .Net Framwork 、.Net、 .Net Stander几个概念进行下理解。
.net 代表跨平台框架,从.net 5开始就统一叫.net,废弃原来的.net core 叫法。由于太多名字防止混淆,我们就不管.net core了。
.NET Framework
在未来.NET Framework或许成为过去时,目前还是有很多地方在使用的。这一套体系对于做C#的老coder应该是再熟悉不过了,新入坑的也就用不着费力去学习。
.NET Framework 是一种技术,支持生成和运行 Windows 应用及 Web 服务。
.NET Framework 包括公共语言运行时 (CLR) 和 .NET Framework 类库。 公共语言运行时是 .NET Framework 的基础。
可将运行时看作一个在执行时管理代码的代理,它提供内存管理、线程管理和远程处理等核心服务,并且还强制实施严格的类型安全以及可提高安全性和可靠性的其他形式的代码准确性。
.Net
.net 就是由.net core 演进而来,在底层有很多性能和架构优化改造,上层应用api和用法和.NET Framework大多数相同。
.NET 是一种用于构建多种应用的免费开源开发平台,使用 .NET 时,无论你正在构建哪种类型的应用(web,api、桌面应用…),代码和项目文件看起来都一样。 可以访问每个应用的相同运行时、API 和语言功能。
NET 是开放源代码,使用 MIT 和 Apache 2 许可证。 .NET 是 .NET Foundation 的项目。
Microsoft 支持在 Windows、macOS 和 Linux 上使用 .NET。 它会定期更新以保证安全和质量。
.NET 支持三种编程语言:C#、F#、Visual Basic。
.NET Standard
.NET Standard 是针对多个 .NET 实现推出的一套正式的 .NET API 规范。 推出 .NET Standard 的背后动机是要提高 .NET 生态系统中的一致性。 但是,.NET 5 采用不同的方法来建立一致性,这种新方法在很多情况下都不需要 .NET Standard。
所以.net standard 是 .Net Api 规范,不是实现。其作用是为了提高.net 一致性,只要框架支持就能使用.net standard规范去实现。
但是!.NET 5 采用不同的方法来建立一致性,也就是说.net 5 开始过度到.net 框架后。不用.net Standard去实现一致性,但是.net5+也是支持.net standard!(名字有点绕晕啊)
NET Standard并未弃用 对于可由多个 .NET 实现使用的库,仍需要 .NET Standard。比如在 .NET Framework 和 .NET 上都要使用的内库就需要按照.net standard规范,这样两个框架都能用,但是要看.net standard版本支持,下图对照。
在创建类库的时候就可以选择不同的支持框架。
各种 .NET 实现以特定版本的 .NET Standard 为目标。 每个 .NET 实现版本都会公布它所支持的最高 .NET Standard 版本,这种声明意味着它也支持以前的版本。
例子说明
1、创建一个.NET Standard 类库,添加一个简单的测试方法。
public class NetStandardTest
{
public static void PrintLocation()
{
//打印FileStream 路径
Console.WriteLine(typeof(FileStream).Assembly.Location);
//打印NetStandardTest 路径
Console.WriteLine(typeof(NetStandardTest).Assembly.Location);
}
}
在这个测试方法里面我们加了两行打印代码。主要是打印FileStream路径,同时我们创建的.NET Standard类库为2.0,因为我们接下来要创建.net framework 的控制台,它不支持2.1。
2、创建.net 5,.net framework 4.6.1 控制台程序
创建好两个控制台项目,在主方法里面调用内库方法。
static void Main(string[] args)
{
NetStandardTest.PrintLocation();
Console.ReadKey();
}
解决方案结构如下
3、运行分析
从结果看我们看到同一个.NET Standard类库,引用在不同的框架上,调用同一个FileStream的地址是不一样的。
然后我们在.NET Standard类库里面F12定位到FileStream看到程序集如下
三个地址我们并列对比下:
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.8\System.Private.CoreLib.dll
C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.dll
C:\Users\Administrator\.nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll
我们将三个dll 用dnspy反编译下,找到FileStream,随便找一个BeginRead方法看下代码。
- .net standard
- .net framework
- .net 5
从上面三个结果对比可以看到.net standard里面是没有实现的,只是定义了方法,也就是说定义了一种标准,说明我的类里面有哪些东西。
调用的时候再根据我们当前使用的框架去找到对应框架的实现。这就是为什么.net standard能对多框架引用,也是为什么2.1不能被.net framework使用,因为它没有去实现2.1新增的api。
至于为什么我们调用.net standard的dll会被转到对应框架的dll呢,这是利用Type Forwarding方式实现跨程序集类型转移的技术成为“垫片(Shim)”,这是实现程序集跨平台复用的重要手段。
关于这个垫片技术可以看下这篇文章https://www.cnblogs.com/artech/p/how-to-cross-platform-03.html。
自此我们已经基本搞清楚了几个概念和体验它们是如何演进而来,以后也许用不太多.net standard了,等不断升级以后直接就用.net 类库就行,也不用去理解这么多概念和技术,可以理解这些是过渡迭代的产物。
.net 温故知新:【1】 .Net 5 基本概念和开发环境搭建 - XSpringSun - 博客园
来源: .net 温故知新:【1】 .Net 5 基本概念和开发环境搭建 – XSpringSun – 博客园
最近一两年搞了很多其它事情,.net web方面的基本没做,之前做过几个小的项目零星的学习了些,从.net core 发布后其实都没正真的系统学习过。
就是上手做项目,平时也有关注和看些资料,所以项目写点业务逻辑还是没问题的,最近琢磨着重新系统学习下.net。
因为以后不叫.net core统一叫.net x, 所以就从当前稳定版本.net 5 开始吧,反正.net 6 还没正式发布而且也不会对体系进行大改,说不定搞着搞着就到6了,所以标题写成.net 5+。
平时除了工作生活也没太多空闲时间,对于我来说可能是个漫长的学习过程。熟话说开篇有益嘛,希望不会太监,一是给自己做学习笔记以免遗忘,二是和新同学一起学习,岂不快哉。
我们第一步从.net 开发环境搞起。
.NET SDK 和 运行时
首先我们先了解下.net sdk和运行时的概念。
.NET SDK 是一组用于开发和运行 .NET 应用程序的库和工具
SDK 下载包括以下组件:
- NET CLI。 可用于本地开发和持续集成脚本的命令行工具。
- dotnet 驱动程序。 用于运行依赖于框架的应用的 CLI 命令。
- Roslyn 和 F# 编程语言编译器。
- MSBuild 生成引擎。
- .NET 运行时。 提供类型系统、程序集加载、垃圾回收器、本机互操作和其他基本服务。
- 运行时库。 提供基元数据类型和基本实用程序。
- ASP.NET Core 运行时。 为连接 Internet 的应用(如 Web 应用、IoT 应用和移动后端)提供基本服务。
- 桌面运行时。 为 Windows 桌面应用(包括 Windows 窗体和 WPF)提供基本服务。
运行时下载包括以下组件:
- (可选)桌面或 ASP.NET Core 运行时。
- .NET 运行时。 提供类型系统、程序集加载、垃圾回收器、本机互操作和其他基本服务。
- 运行时库。 提供基元数据类型和基本实用程序。
- dotnet 驱动程序。 用于运行依赖于框架的应用的 CLI 命令。
这里要注意[运行时]和[.NET运行时],运行时就是上面包含的那些东西,.NET运行时包含在运行时里面。
.Net 运行时也就是那个CLR,运行时库就是基类库 (BCL),这些名字确实挺迷惑的,也许我们平时说的.net 运行时≈运行时,但是在理解概念的时候要搞清楚。
.NET CLR 是包含 Windows、macOS 和 Linux 支持的跨平台运行时。 CLR 处理内存分配和管理。 CLR 也是一个虚拟机,不仅可执行应用,还可使用实时 JIT 编译器生成和编译代码。
运行时库也称为框架库或基类库 (BCL)。 这些库为许多常规用途类型和特定于工作负载的类型和实用工具功能提供实现。
你可以看到 SDK 是包含运行时的,SDK还包含了一个重要的东西就是CLI,CLI工具是用于开发、生成、运行和发布 .NET 应用程序的跨平台工具链。
例如如下一行代码命令就是CLI 帮我们干事,在当前目录下创建 C# 控制台应用程序项目:
dotnet new console
简单点说就是SDK可以让我们开发程序,运行时让我们可以运行写好的程序,当然安装了SDK就没必要再装运行时了。
如果和java做类比,sdk 相当于 jdk,运行时 相当于 jre。
当然我们一般不会用cli开发,因为我们有更好的工具 visual studio。
SDK 安装
SDK安装有两种方式,第一种是通过工具安装的时候一起安装,第二种是自己下载SDK包安装。
1、使用 Visual Studio 安装
不同目标 .NET SDK 版本所需的 Visual Studio 最低版本。
对于.net 5来说则最少需要 visual studio 2019 版本 16.8 。
如果你已安装 Visual Studio,则可以使用以下步骤检查你的版本。
打开 Visual Studio。
选择“帮助” > “Microsoft Visual Studio”。
从“关于”对话框中读取版本号。
我本地已经安装了visual studio 2019 本来准备通过更新修改vs方式测试安装的。但是我本地环境vs有问题,一直报错。
没有装的同学可以直接下载,然后按需选择功能直接安装就OK。
捣鼓了下,没办法修复,无法升级。所以最后得重新安装,因为.net 5 最低支持16.8,我的16.7!。
如果没办法卸载vs 使用安装器目录下C:\Program Files (x86)\Microsoft Visual Studio\Installer 运行setup.exe,然后再启动installer安装。
2、下载并手动安装
这是第二种方式,如果你不需要vs的话可以用这种方式,直接下载安装SDK。
然后就可以使用CLI创建项目了,应该很少人会不使用工具去编程,毕竟没啥理由。
.NET 5 项目创建
在卸载重装了了Visual Studio 2019 后我们看看创建项目。
创建一个控制台程序,如下图可以看到有两个选项,一个是.net Framework,一个是.net core上运行的。
不知道后面正式版VS2022会不会修改这个名称叫法,毕竟后面不再叫.net core了。
但是我们选了.net core 后可以选择我们的目标框架,如果装了多个版本。
到此我们的.net 5 开发环境就搭建起来了,下一次我们再来探究下.net framwork,.net 5 和 .net standard的概念和区别。
.net 温故知新:【8】.NET 中的配置从xml转向json - XSpringSun - 博客园
来源: .net 温故知新:【8】.NET 中的配置从xml转向json – XSpringSun – 博客园
一、配置概述
在.net framework平台中我们常见的也是最熟悉的就是.config
文件作为配置,控制台桌面程序是App.config
,Web就是web.config
,里面的配置格式为xml格式。
在xml里面有系统生成的配置项,也有我们自己添加的一些配置,最常用的就是appSettings节点,用来配置数据库连接和参数。
使用的话就引用包System.Configuration.ConfigurationManager
之后取里面的配置信息:System.Configuration.ConfigurationManager.AppSettings["ConnectionString"]
随着技术的发展这种配置方式显得冗余复杂,如果配置项太多层级关系参数表达凌乱,在.net core开始也将配置的格式默认成了json格式,包括现在很多的其它配置也是支持的,比如java中常用的yaml格式,为什么能支持这么多读取源和格式,其实质在于配置提供程序
。
目前.NET 中的配置是使用一个或多个配置提供程序
执行的。 配置提供程序使用各种配置源从键值对读取配置数据,这些配置程序稍后我们会看到,读取的配置源可以是如下这些:
- 设置文件,appsettings.json
- 环境变量
- Azure Key Vault
- Azure 应用配置
- 命令行参数
- 已安装或已创建的自定义提供程序
- 目录文件
- 内存中的 .NET 对象
- 第三方提供程序
二、配置初识
IConfiguration 接口是所有配置源的单个表示形式,给定一个或多个配置源,IConfiguration 类型提供配置数据的统一视图。
上图我们可能没有直观的感受,现在写一个例子来看看
(1). 新建控制台应用程序:
创建控制台使用的是.net 6.0 框架,vs 2022。
安装 Microsoft.Extensions.Configuration.Json NuGet 包,该包提供json配置文件读取。
Install-Package Microsoft.Extensions.Configuration.Json
(2). 添加appsettings.json 文件
{
"person": {
"name": "XSpringSun",
"age": 18
}
}
(3). 使用json提供程序读取json配置
new一个ConfigurationBuilder,添加json配置,AddJsonFile
是在包中的IConfigurationBuilder扩展方法,其它配置提供程序也是用这种扩展方法实现。
static void Main(string[] args)
{
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
Console.WriteLine(configuration["person:name"]);
Console.WriteLine(configuration["person:age"]);
Console.WriteLine("Hello, World!");
Console.ReadLine();
}
可以看到已经取到json配置文件中的值了,配置值可以包含分层数据。 分层对象使用配置键中的 : 分隔符表示。在下面的调试对象中我们可以看到实际configuration的Providers 提供程序数组有一个值,就是我们的JsonConfigurationProvider,并且JsonConfigurationProvider里面已经读取了json的数据存储在Data数组中。
对于如上几行代码干了什么呢:
- 将 ConfigurationBuilder 实例化(new ConfigurationBuilder)。
- 添加 “appsettings.json” 文件,由 JSON 配置提供程序识别(AddJsonFile(“appsettings.json”))。
- 使用 configuration 实例获取所需的配置
三、选项模式
这样已经实现json进行配置读取,但是取值的方式似乎和以前没什么太大变法,所以.net提供了选项模式,选项模式就是使用类来提供对相关设置组的强类型访问。
我们创建一个Config
类用来转换json:
namespace ConfigDemo
{
public class Config
{
public Person? person { get; set; }
}
public class Person {
public string? name { get; set; }
public int age { get; set; }
}
}
绑定配置
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
Config options = new Config();
ConfigurationBinder.Bind(configuration, options);
Person person = configuration.GetSection("person").Get<Person>();
Console.WriteLine(options.person.name);
Console.WriteLine(options.person.age);
Console.WriteLine("-----------GetSection获取-------------");
Console.WriteLine(person.name);
Console.WriteLine(person.age);
用了两种方式获取配置,第一种使用ConfigurationBinder.Bind()
将整个配置绑定到对象Config
上,另外一种是使用IConfiguration的GetSection().Get<T>()
并返回指定的类型。两种方式都可以使用,看实际需求和用途。
四、选项依赖注入
在控制台程序中我们引用DI注入包,然后演示下如何进行配置的注入。关于DI和IOC不清楚的看我上篇文章.net 温故知新:【7】IOC控制反转,DI依赖注入
- 新建一个测试类TestOptionDI
public class TestOptionDI
{
private readonly IOptionsSnapshot<Config> _options;
public TestOptionDI(IOptionsSnapshot<Config> options)
{
_options = options;
}
public void Test()
{
Console.WriteLine("DI测试输出:");
Console.WriteLine($"姓名:{_options.Value.person.name}");
Console.WriteLine($"年龄:{_options.Value.person.age}");
}
}
在测试类中我们使用IOptionsSnapshot<T>
接口作为依赖注入,还有其它不同定义的接口用来配置注入,关于选项接口:。
不同接口可以配合读取配置的不同方式起作用,IOptionsSnapshot接口可以在配置文件改变后不同作用域进行刷新配置。接着我们修改main方法,引入DI,并将AddJsonFile
方法的参数reloadOnChange设置为true,optional参数是否验证文件存在,建议开发时都设置为true,这样如果文件有问题会进行报错。
注入配置这句services.AddOptions().Configure<Config>(e=>configuration.Bind(e))
是关键,通过容器调用AddOptions
方法注册,然后Configure
方法里面是一个委托方法,该委托的作用就是将配置的信息绑定到Config类型的参数e上。注册到容器的泛型选项接口,这样在TestOptionDI类构造函数注入就能注入IOptionsSnapshot了,这里有点绕。
static void Main(string[] args)
{
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json",optional:true,reloadOnChange:true)
.Build();
//IServiceCollection 服务
ServiceCollection services = new ServiceCollection();
//注入配置
services.AddOptions().Configure<Config>(e=>configuration.Bind(e));
//注入TestOptionDI
services.AddScoped<TestOptionDI>();
using (var provider = services.BuildServiceProvider())
{
//获取服务
var testOption = provider.GetRequiredService<TestOptionDI>();
testOption.Test();
}
Console.ReadLine();
}
为了测试IOptionsSnapshot接口在不同作用域会刷新配置,我们修改下main方法,用一个while循环在ReadLine时修改json文件值,不同的Scope里进行打印。
using (var provider = services.BuildServiceProvider())
{
while (true)
{
using (var scope = provider.CreateScope())
{
//获取服务
var testOption = scope.ServiceProvider.GetRequiredService<TestOptionDI>();
testOption.Test();
}
Console.ReadLine();
}
}
这个功能在web中使用很方便,因为框架的一次请求就是一个作用域,所以我们修改了配置,下次请求就能生效了,而不用重启服务。
五、其它配置
如最开始所说,不仅能配置json文件,由于各种提供程序,还可以配置其它的,但是根据配置的顺序会进行覆盖。我们只添加一个环境变量配置演示下:
首先添加提供程序包:Install-Package Microsoft.Extensions.Configuration.EnvironmentVariables
。
然后添加环境变量配置代码AddEnvironmentVariables()
:
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json",optional:true,reloadOnChange:true)
.AddEnvironmentVariables()
.Build();
在VS中配置临时环境变量
这里有个扁平化配置,就是表示层级用冒号person:age
六、托管模式
对于web项目我们没有进行这么多操作它是怎么配置的呢,其实框架已经自动帮我们做了,其它非web项目也可以使用这种托管模式,在Microsoft.Extensions.Hosting 包中,只需要使用简单的代码就能配置好。
IHost host = Host.CreateDefaultBuilder(args).Build();
await host.RunAsync();
其加载配置的优先级:
通过分析我们对整个配置如何运行的机制有了一个大体的了解,如果想详细了解托管模式的还是建议看官方文档:.NET配置
.net 温故知新【17】:Asp.Net Core WebAPI 中间件 - XSpringSun - 博客园
来源: .net 温故知新【17】:Asp.Net Core WebAPI 中间件 – XSpringSun – 博客园
一、前言
到这篇文章为止,关于.NET “温故知新”系列的基础知识就完结了,从这一系列的系统回顾和再学习,对于.NET core、ASP.NET CORE又有了一个新的认识。
不光是从使用,还包括这些知识点的原理,虽然深入原理谈不上,但对于日常使用也够了,我想的是知其然,知其所以然。
在实际开发过程中可能是知道怎么使用就行,但系统学习了这些基本的框架、组件、或者说原理后,对于我们软件设计、开发、扩展和解决问题还是有帮助的。
刚好到2023新年前赶着写完,也算对自己这个系列的一个交代,实际上我平时基本不使用ASP.NET CORE,目前我主要开发桌面程序,还是用的winform。
写这个系列的初衷是想紧跟.NET的发展进程,同时储备基础知识,平时还搞一些微服务(Java)、NLP、OCR、知识图谱、前端(Vue3),只要需要反正啥都搞,没必要固执,技术只是手段,不是目的。
那么接下来就继续简单的梳理一下中间件,欢迎对这个系列拍砖!
二、中间件
中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:
- 选择是否将请求传递到管道中的下一个组件。
- 可在管道中的下一个组件前后执行工作。
这个是关于中间件概念的概括,官方的概括是相当精准,那么我们就围绕管道、传递、组件来看看中间件。
请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。使用 Run、Map 和 Use 扩展方法来配置请求委托。
我们照例新建一个ASP.NET CORE Web API 项目:WebAPI_Middleware
namespace WebAPI_Middleware
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
}
在Program.cs 中我们看到前面部分builder
是配置依赖注入的东西,这部分可以参看.net 温故知新【13】:Asp.Net Core WebAPI 使用依赖注入DI 。
app
使用Use
扩展用于中间件添加到管道中
Map
基于给定请求路径的匹配项来创建请求管道分支
Run
委托始终为终端,用于终止管道。
中间件的执行顺序过程如下:
三、Map
我们将上面自动创建的东西全都删除,用Map
来匹配路由,然后通过不同的代理处理请求。
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//匹配map1 请求
app.Map("/map1", new Action<IApplicationBuilder>((app) =>
{
app.Run(async context =>
{
await context.Response.WriteAsync("map1 run");
});
}));
//匹配map2 请求
app.Map("/map2", new Action<IApplicationBuilder>((app) =>
{
app.Run(async context =>
{
await context.Response.WriteAsync("map2 run");
});
}));
app.Run();
}
}
- 请求map1 我们输出:map1 run
- 请求map2 我们输出:map2 run
Asp.Net Core MapControllers
的扩展方法也是类似道理,用来匹配路由调用处理程序。
四、Run
在上面的 Map 后面我们使用的处理方法中 Run 用于终止管道。也就是说在该管道中如果调用了 Run 那么就直接返回了,即使你后面还添加了 Use 也不会执行。
app.Run(async context =>
{
await context.Response.WriteAsync("map1 run");
});
Map 相当于是迎客进门,Map 上了就用指定的管道进行处理,如果没有 Map 上就调用主管道,也就是主管道上的其他中间件也会执行处理。比如我们再加一个 Run 用于没匹配上路由也输出点信息。
加了context.Response.ContentType = "text/plain; charset=utf-8";
不然中文会乱码。
因为 Run 是终结点,那这个管道中我还想加其他处理怎么办呢,这个时候就该轮到 Use 出场了。
五、Use
用 Use 将多个请求委托链接在一起。 next 参数表示管道中的下一个委托。 可通过不调用 next 参数使管道短路。
首先我们在外面添加两个 Use,不放到 Map 中,这样的话就只有未匹配到的路由会调用
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Map("/map1", new Action<IApplicationBuilder>((app) =>
{
app.Run(async context =>
{
await context.Response.WriteAsync("map1 run");
});
}));
app.Map("/map2", new Action<IApplicationBuilder>((app) =>
{
app.Run(async context =>
{
await context.Response.WriteAsync("map2 run");
});
}));
//Use1
app.Use(async (context, next) =>
{
context.Response.ContentType = "text/plain; charset=utf-8";
await context.Response.WriteAsync("第 1 个Use 开始!\r\n", Encoding.UTF8);
await next();
await context.Response.WriteAsync("第 1 个Use 结束!\r\n", Encoding.UTF8);
});
//Use2
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("第 2 个Use 开始!\r\n", Encoding.UTF8);
await next();
await context.Response.WriteAsync("第 2 个Use 结束!\r\n", Encoding.UTF8);
});
//结束管道处理
app.Run(async context =>
{
await context.Response.WriteAsync("未匹配处理!\r\n", Encoding.UTF8);
});
app.Run();
}
}
最后执行的路径和最开始的图是一致的。
为什么将context.Response.ContentType = "text/plain; charset=utf-8";
放到第一个 Use 呢,因为如果放到 Run 里面会报错,改变了 Header 标头。所以理论上也不要在 Use 里面发送响应WriteAsync,此处为了演示所以这么写。
六、中间件类
上面的代理方法可以移动到类中,这个类就是中间件类。中间件类需要如下要求:
- 具有类型为 RequestDelegate 的参数的公共构造函数。
- 名为 Invoke 或 InvokeAsync 的公共方法。 此方法必须:
返回 Task。
接受类型 HttpContext 的第一个参数。
构造函数和 Invoke/InvokeAsync 的其他参数由依赖关系注入 (DI) 填充。
将上面的未匹配路由处理逻辑移动到中间件类中:
- TestMiddleware1:
public class TestMiddleware1
{
private readonly RequestDelegate _next;
public TestMiddleware1(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
context.Response.ContentType = "text/plain; charset=utf-8";
await context.Response.WriteAsync("第 1 个Use 开始!\r\n", Encoding.UTF8);
await _next(context);
await context.Response.WriteAsync("第 1 个Use 结束!\r\n", Encoding.UTF8);
}
}
- TestMiddleware2
public class TestMiddleware2
{
private readonly RequestDelegate _next;
public TestMiddleware2(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
await context.Response.WriteAsync("第 2 个Use 开始!\r\n", Encoding.UTF8);
await _next(context);
await context.Response.WriteAsync("第 2 个Use 结束!\r\n", Encoding.UTF8);
}
}
- Program
- 运行
此处的中间件使用有顺序问题,如果我先app.UseMiddleware<TestMiddleware2>()
因为 TestMiddleware1 修改了标头,根据约定是不允许的,所以程序是有报错。
因此中间件组件的顺序定义了针对请求调用这些组件的顺序,以及响应的相反顺序。 此顺序对于安全性、性能和功能至关重要。
七、中间件顺序
以上是内置中间件的默认顺序规则,具体如何使用内置中间件,可参阅官方资料。
八、写在最后
以上就是关于中间件的部分知识,结合我自己的理解做了前后衔接的梳理逻辑。
官方网站更多的是讲解每个知识点的细节,前后需要结合起来理解,当然我还是强烈建议跟着官方文档学习,而且是最权威最可信的:ASP.NET Core 中间件
这个系列历时2年,工作生活都比较忙,也有放纵啥事不相干的时候,中间断断续续的,总算是坚持完了。很多东西就是这样,累了就休息一下贵在坚持,即使再慢,积累的成果也有收获。
.net 温故知新【16】:Asp.Net Core WebAPI 筛选器 - XSpringSun - 博客园
来源: .net 温故知新【16】:Asp.Net Core WebAPI 筛选器 – XSpringSun – 博客园
一、筛选器
通过使用筛选器可在请求处理管道中的特定阶段之前或之后运行代码。
这即是我们经常听到的面向切面编程AOP(Aspect Oriented Programming)技术,AOP通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
筛选器在 ASP.NET Core 操作调用管道(有时称为筛选器管道)内运行。 筛选器管道在 ASP.NET Core 选择了要执行的操作之后运行:
ASP.NET Core 关注的切面点
包括错误处理、缓存、配置、授权和日志记录筛选器,这个是说通过筛选器可以实现对以上关注点的一些操作。
在Asp.Net Core中有如下几种类型的筛选器:
其中部分是内置筛选器,比如授权,响应缓存已经帮我们内置进了框架,我们只需要配置即可使用;其他筛选器是可以自定义处理逻辑的。
下图展示了筛选器类型在筛选器管道中的交互方式和执行顺序:
二、操作型筛选器
第一部分主要是对筛选器的一个梳理,有些重点的提炼,详情查看文档,因为文档部分理解起来比较晦涩,比如关注点是关注点,知识说筛选器可以对这些关注点启到作用,筛选器是固定的几种,不要被文档中的这种描述搞晕了,一会儿有这几种,怎么到下面又是另外几种,要注意区分重点。
操作筛选器可以实现接口IActionFilter
,在接口中有两个方法,OnActionExecuting 在调用操作方法之前执行。 OnActionExecuted 在操作方法返回之后执行。
- 先建WebAPI项目 WebAPI_Filter
- 建一个 FilterController,并创建Get请求Test
namespace WebAPI_Filter.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class FilterController : ControllerBase
{
[HttpGet]
public string Test()
{
return "测试Filter!";
}
}
}
- 创建ActionFilter 筛选器
namespace WebAPI_Filter.Filter
{
public class MyActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine(context.HttpContext.Request.GetDisplayUrl()+ " 执行之后!");
}
public void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine(context.HttpContext.Request.GetDisplayUrl() + " 执行之前!");
}
}
}
- 在Program.cs里面添加筛选器
执行测试接口
三、筛选器作用域和执行顺序
上面直接在Program.cs里面添加筛选器的方式称为全局筛选器,所有控制器、操作都会受全局筛选器影响。还有一种筛选器实现方式是属性筛选器,通过继承属性类然后将属性标签放置在控制器或者操作上。
新建两个属性类MyAttributeFilter
用于Controller控制器类,MyOPAttributeFilter
用于操作方法上。
public class MyAttributeFilter: ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine(context.HttpContext.Request.GetDisplayUrl() + " 控制器之后-筛选器属性!");
}
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine(context.HttpContext.Request.GetDisplayUrl() + " 控制器之前-筛选器属性!");
}
}
public class MyOPAttributeFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine(context.HttpContext.Request.GetDisplayUrl() + " 操作之后-筛选器属性!");
}
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine(context.HttpContext.Request.GetDisplayUrl() + " 操作之前-筛选器属性!");
}
}
加上之前的全局筛选器,我们一共有三个作用域的筛选器,现在我们测试看看筛选器的执行顺序。
则可总结出不同作用域筛选器的执行顺序:
全局筛选器的 before 代码。
控制器筛选器的 before 代码。
操作方法筛选器的 before 代码。
操作方法筛选器的 after 代码。
控制器筛选器的 after 代码。
全局筛选器的 after 代码。
当然可以通过 Order 属性来确定执行顺序,在全局或者属性筛选器里面设置 Order 值,值越小执行优先级越高。
四、筛选器依赖注入
可按类型或实例添加筛选器。 如果添加实例,该实例将用于每个请求。
其中builder.Services.AddControllers(options => options.Filters.Add<MyActionFilter>())
即为按实例添加,该MyActionFilter
用于每个请求。
如果添加类型,则将激活该类型。 激活类型的筛选器意味着:第一种是为每个请求创建一个实例,第二种依赖关系注入 (DI) 将填充所有构造函数依赖项。
上面位置我们是为每个请求创建一个实例,这样的话无法使用依赖注入体系为我们自动注入,因为因为属性在应用时必须提供自己的构造函数参数,该参数需要手动指定。
比如我们想在操作方法的MyOPAttributeFilter
筛选属性 注入IHostEnvironment
:
public class MyOPAttributeFilter : ActionFilterAttribute
{
IHostEnvironment hostEnvironment;
public MyOPAttributeFilter(IHostEnvironment _hostEnvironment)
{
hostEnvironment = _hostEnvironment;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine(context.HttpContext.Request.GetDisplayUrl() + " 操作之后-筛选器属性!");
}
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine(context.HttpContext.Request.GetDisplayUrl() + " 操作之前-筛选器属性!");
//打印环境变量
Console.WriteLine(hostEnvironment.EnvironmentName);
}
}
这个时候直接就报错提示需要参数,而我们想的是通过依赖注入配置。
框架提供以下筛选器支持从 DI 提供的构造函数依赖项:
- ServiceFilterAttribute
- TypeFilterAttribute
- 在属性上实现 IFilterFactory。
TypeFilterAttribute:不会直接从 DI 容器解析其类型。Microsoft.Extensions.DependencyInjection.ObjectFactory 对类型进行实例化,所以不需要先将MyOPAttributeFilter
加入容器,直接使用:
[TypeFilter(typeof(MyOPAttributeFilter))]
ServiceFilterAttribute
使用需要先将MyOPAttributeFilter
注入到容器,然后再使用。
以上就是关于AOP切面编程和筛选器的梳理,其他类型的筛选器和细节可查询官方文档:ASP.NET Core 中的筛选器
.net 温故知新【15】:Asp.Net Core WebAPI 配置 - XSpringSun - 博客园
来源: .net 温故知新【15】:Asp.Net Core WebAPI 配置 – XSpringSun – 博客园
关于ASP.NET Core中的配置实际之前我已经整理过.net 中以json方式进行配置的介绍(.net 温故知新:【8】.NET 中的配置从xml转向json),当时我们说ASP.NET Core也是按照基础方法,只是组织形式的问题,有个封装过程。所以我这里就着重介绍一下ASP.NET Core中配置的重点。
1、主机配置和应用程序配置
ASP.NET Core 应用配置和启动“主机”。 主机负责应用程序启动和生存期管理。 ASP.NET Core 模板创建的 WebApplicationBuilder 包含主机。 虽然可以在主机和应用程序配置提供程序中完成一些配置,但通常,只有主机必需的配置才应在主机配置中完成。
主机配置和应用程序配置是什么意思呢,可以粗略的理解为我们ASP.NET Core 项目启动的时候要用到的一些配置为主机配置,而程序运行阶段使用的配置为应用程序配置。
而ASP.NET Core 包含一些默认的主机和应用程序配置,我们先看看这些默认配置在哪里以及配置的规范。
2、默认主机配置
从文档描述可以看到,默认主机配置是的优先级为:
命令行-> DOTNET_ 为前缀的环境变量->ASPNETCORE_ 为前缀的环境变量
并且部分变量是锁定在启动阶段,不受其他配置的影响:
命令行可以通过启动程序设置,比如:dotnet run --environment Production
而其他的则可以直接设置系统环境变量,并且按照前缀优先。由于去系统设置比较麻烦,且设置后可能还需要重启VS,所以提供一些能便捷设置的能力。
launchSettings.json
仅在本地开发计算机上使用,包含配置文件设置。可以设置启动方式和相应的环境变量。
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:21115",
"sslPort": 44325
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5007",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7211;http://localhost:5007",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
如上是创建项目默认的launchSettings.json文件,在文件中对https、http、IIS Express等配置的ASPNETCORE_ENVIRONMENT
都为Development
。
ENVIRONMENT
可以配置任意值,框架提供了Development、Staging、Production三种,当没有设置 DOTNET_ENVIRONMENT 和 ASPNETCORE_ENVIRONMENT 时的默认值为Production。
这就是为什么我们不在开发工具中调试,而是直接运行项目的时候会是Production。
同时我们如果要自己设置其他值,那么项目中对于使用的地方也要注意,比如appsettings.{Environment}.json
文件名,这个我们稍后讲。
这个值在Program.cs中是最好的体现,当是Development时启用swagger:
3、默认应用程序配置
默认应用程序配置是的优先级为:
命令行-> 非前缀的环境变量->Development环境中用户机密配置->appsettings.{Environment}.json-> appsettings.json->主机配置(非锁定)
命令行配置和非前缀环境变量不用说了,接着就是用户机密文件,稍后我们单独说下机密文件。
然后再加载appsettings.{Environment}.json文件,这个文件是根据上面主机配置的环境变量ENVIRONMENT
加载的,比如你配置ENVIRONMENT=AA,那么json文件应为appsettings.AA.json。
当读取了环境变量的json后就读取appsettings.json文件,该文件一般是正式部署中使用。
最后就是主机配置相关的变量,这部分优先级比较高。
(注意:这些配置的读取由配置提供程序
实现,也就决定了他们不同的功能或者实现,提供程序这个概念和设计是.net 中大面积使用的,可以从这个部分去理解)
4、配置机密文件
机密管理器工具可用于存储开发环境中的机密,比如开发过程中用到的一些数据库配置,这个机密文件不在项目目录下,所以不会被上传到代码管理工具,例如git或者svn。
右键项目->管理用户机密
我们看到这个文件是在C盘下,而且文件夹名是一串编码,该编码在项目文件中已经自动配置好了。
5、使用配置
使用依赖注入的方式将IConfiguration
注入,之后我们写一个测试获取上面我们配置的用户机密文件里面SecretKey
的值。
namespace WebAPI_Config.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
private IConfiguration _configuration;
public TestController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpGet]
public string Get()
{
return _configuration.GetValue(typeof(string), "SecretKey").ToString();
}
}
}
如果对依赖注入不了解的可以看看我的另外一篇:.net 温故知新:【7】IOC控制反转,DI依赖注入
测试接口可以看到已经获取到了对应的值。
.net 温故知新【14】:Asp.Net Core WebAPI 缓存 - XSpringSun - 博客园
来源: .net 温故知新【14】:Asp.Net Core WebAPI 缓存 – XSpringSun – 博客园
一、缓存
缓存指在中间层中存储数据的行为,该行为可使后续数据检索更快。 从概念上讲,缓存是一种性能优化策略和设计考虑因素。 缓存可以显著提高应用性能,方法是提高不常更改(或检索成本高)的数据的就绪性。
二、RFC9111
在最新的缓存控制规范文件RFC9111中,详细描述了浏览器缓存和服务器缓存控制的规范,其中有一个最重要的响应报文头Cache-Control
。
该报文头的设置会影响我们的缓存,包括浏览器端和服务端。
RFC911:https://www.rfc-editor.org/rfc/rfc9111#name-cache-control
三、网页端缓存
在Cache-Control
中,如果设置max-age=10
,则表示告诉浏览器缓存10s,而为什么浏览器要认这个表示呢,就是上面我们说的前后端都要根据RFC标准规范去实现,就是硬件的统一插口,不然其他生成出来的就用不了。
那么在ASP.NET Core 中只需要在接口上打上ResponseCacheAttribute
并设置max-age
的时间即可。
首先建一个ASP.NET Core WebAPI 项目,写一个获取学生的Get
接口。
namespace WebAPI_Cache.Controllers
{
[ApiController]
[Route("[controller]")]
public class CacheController : ControllerBase
{
public CacheController()
{
}
[HttpGet]
public ActionResult<Student> GetStudent()
{
return new Student()
{
Id = 1,
Name = "Test",
Age = Random.Shared.Next(0, 100),
};
}
}
}
namespace WebAPI_Cache.Model
{
public class Student
{
public int Id { get; set; }
public string? Name { get; set; }
public int Age { get; set; }
}
}
在接口中我返回Student
的age
为1-100的随机数。启动项目测试,短时间内两次调用返回的age
不一样
第一次age:
第二次age:
当我在接口方法打上[ResponseCache(Duration = 10)]
,再次调用接口返回的信息可以看到已经有了cache-control: public,max-age=10
的Header。
并且我在10秒内的请求,只有第一次请求过服务器,其他都是从缓存中取的,查看edge浏览器网络访问如下:
四、服务器缓存
网页端缓存是放在浏览器端的,对于单点请求会有用,但是如果是多个不同前端请求呢。这个时候我们可以将缓存放置在后端服务中,在ASP.NET Core 中配置响应缓存中间件。
在 Program.cs中,将响应缓存中间件服务 AddResponseCaching 添加到服务集合,并配置应用,如果使用 CORS 中间件时,必须在 UseResponseCaching 之前调用 UseCors。
如果header包含 Authorization,Set-Cookie 标头,也不会缓存,因为这些用户信息缓存会引起数据混乱。
然后对于我们需要服务器缓存的接口打上ResponseCache
属性,和设置浏览器缓存一样,还有其他参数可设置。我们通过两个进程来测试,一个用浏览器swagger,一个用postman,可以看到两个请求的age都是等于18的。所以可以确定服务器端确实存在缓存。
但是在用postman测试的时候记得在settings里面把Send no-cache header
勾掉,如果不去掉,发送的时候就会在请求头里面包含Cache-Control:no-cache
,这样服务端即便有缓存也不会使用缓存。
对于浏览器端相当于禁用缓存,如果禁用了缓存,发送的请求头也会带上Cache-Control:no-cache
,服务端看到no-cache 后便不会再使用缓存进行响应。
而这个约定就是RFC9111的规范,所以这个后端缓存策略比较鸡肋,如果用户禁用缓存就没用了,因此我们还可以使用内存缓存。
五、内存缓存
内存缓存基于 IMemoryCache。 IMemoryCache 表示存储在 Web 服务器内存中的缓存。
- 首先Nuget安装包
Install-Package Microsoft.Extensions.Caching.Memory
- 在Program.cs中添加依赖
builder.Services.AddMemoryCache();
- 缓存数据
我添加一个Post方法模拟id查询Student
这样我就将数据缓存到了内存,可以设置缓存的绝对过期时间,也可以设置滑动过期,稍后我们会看到过期策略的使用。
六、缓存击穿
缓存击穿是指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,或者是查询了不存在的数据,缓存里面没有,从而大量的请求打到数据库上形成数据库压力。
上面内存缓存中的写法我们可以看到,如果查询缓存等于null
就会再去查询数据(我这里只是模拟,没有去写真的数据库查询),如果这样暴力请求攻击就会有问题。
对于这个问题我们可以使用ImemoryCache
的GetOrCreate
方法,当然它还有异步方式。通过该方法传入缓存的key和func 委托方法返回值来进行查询并缓存,如果没查询到返回的null
也会存储在缓存中,防止恶意查询不存在的数据。
[HttpPost]
public ActionResult<Student> GetStudent2(int id)
{
//查询并创建缓存
var student = _memoryCache.GetOrCreate("student_" + id, t =>
{
t.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(20);
//模拟只有id=1有数据
if (id == 1)
{
return new Student()
{
Id = 1,
Name = "Test",
Age = Random.Shared.Next(0, 100),
};
}
else
{
//其他的返回空,但是空值也会缓存,比如查询 id=2,id=3 都会缓存
return null;
}
});
if (student == null)
{
return NotFound("未找到");
}
else
{
return student;
}
}
七、缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间,导致所有请求都会去查数据库,而查询数据量巨大,引起数据库压力过大甚至down机。
对于雪崩情况我们对缓存的策略主要是设置过期时间,部分不重要的站点,比如新闻网站我们将绝对过期时间AbsoluteExpiration设置的久一点。
对于要一定灵活性,能在请求不频繁的时候进行失效以更新数据的,我们可以用滑动过期时间,就是如果频繁请求就一值滑动过期时间。
当然为了避免滑动时间一直不过期,还可以两种方式混合使用。上面的例子,我们设置绝对过期时间是20秒,我们将滑动过期设置5秒,在5秒内有持续访问就一直续命,直到20秒绝对过期。
那么如果没人访问,在5秒后就过期了,这样数据下次访问也能及时查询最新数据。
八、分布式缓存
有了上面的缓存方案,对付一些小的简单业务系统完全够用了,但是如果你是分布式部署服务,那么像内存缓存访问的数据就是单个服务器的缓存。
你可能需要多个服务器的请求之间保持一致、在进行服务器重启和应用部署后仍然有效、不使用本地内存等情况。
这个时候我们可以使用第三方缓存,比如memecache,Redis等。Asp.Net Core 使用 IDistributedCache 接口与缓存进行交互。
- NuGet安装包
Install-Package Microsoft.Extensions.Caching.StackExchangeRedis
- 在 Program.cs 中注册 IDistributedCache 实现
Configuration: 为连接配置。
InstanceName: 为存储键前缀。
编写测试方法GetStuden3
IDistributedCache 接受字符串键并以 byte[] 数组的形式添加或检索缓存项,所以数据是以byte[]形式访问,但是扩展了一个string类型的方法可以进行使用,我这里用字符串进行操作。
以上这些就是关于asp.net core 当中使用缓存的重要点和基础使用方法,详细参数和文档可参看官方文档:ASP.NET Core 中的缓存概述
.net 温故知新【13】:Asp.Net Core WebAPI 使用依赖注入DI - XSpringSun - 博客园
来源: .net 温故知新【13】:Asp.Net Core WebAPI 使用依赖注入DI – XSpringSun – 博客园
一、使用DI注入
在之前的文章中已经讲过DI的概念(.net 温故知新:【7】IOC控制反转,DI依赖注入),基于控制台程序演示了DI依赖注入的使用,基于Microsoft.Extensions.DependencyInjection
完成。那在WebAPI中如何使用依赖注入呢?
首先新建一个WebAPI项目WebAPI_DI
,框架.net 7,其实 webapi 项目也是控制台应用程序,只是在ASP.NET Core webapi框架中很多基础工作已经帮我们封装配置好了。
项目新建完成后在Program.cs 中自动生成如下代码:
namespace WebAPI_DI
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
}
builder.Services
则是帮我已经创建好的IServiceCollection
对象。
我们再新建一个测试类 DITestClass:
public class DITestClass
{
public int Add(int i, int n)
{
return i + n;
}
}
然后我们在builder.Services中进行注册
最后我们在默认的WeatherForecastController
控制器里面加一个post方法,并用构造函数注入的方式将DITestClass注入进去。
swagger中调用测试:
二、[FromService] 注入
FromServicesAttribute 允许将服务直接注入到操作方法,而无需使用构造函数注入。
改属性的作用主要针对我们在依赖注入初始化对象(该对象初始化很耗时),这个时候不管请求的api方法有没有用到该对象都会等待很长时间。
所以使用FromService让接口在请求的时候再注入,从而不影响其他接口
三、多层架构注入
在多层架构中我们如果引用了其他项目,要使用其他项目中的类,那么要在主项目中进行DI注入,这样相当于所有其他模块或者其他人写的项目都需要主项目来维护注入,比如:
新建一个ClassLibrary1项目,Class1类,方法Sub:
我们在web项目里面引用ClassLibrary1项目,如果要使用Class1类就需要在 Programe.cs里面注册
builder.Services.AddScoped(typeof(Class1));
那么有没有方法让他们自己的项目自己管理注册呢,我们可以简单改造一下
- 新建一个类库,定义一个公共接口,安装
Microsoft.Extensions.DependencyInjection
包
- 在ClassLibarary1里面定义实现接口类
public class ModulInit : IModuleInit
{
public void Init(IServiceCollection service)
{
//所有需要DI的在此处注入
service.AddScoped<Class1>();
}
}
- 在Programe.cs里面调用引用项目的注册类
这样其他项目也按照这种方式,在主项目中只要调用一次注册管理。不过这是最简单的方式,你也可以使用反射来查找引用的项目继承了IModuleInit
的类,然后进行Init
方法调用,这样会更优雅一些。
当然你还可以使用其他依赖注入框架来取代Microsoft.Extensions.DependencyInjection
,也许这个问题就不再是问题!
.net 温故知新【12】:Asp.Net Core WebAPI 中的Rest风格 - XSpringSun - 博客园
来源: .net 温故知新【12】:Asp.Net Core WebAPI 中的Rest风格 – XSpringSun – 博客园
RPC
RPC(Remote Procedure Call),远程过程调用),这种RPC形式的API组织形态是类和方法的形式。所以API的请求往往是一个动词用来标识接口的意思,比如 https://xxxx/GetStudent?id=1 和 https://xxxx/AddStudent 这种风格,并且往往没有规范需要我们去查看接口定义文档。HTTP方法基本只用GET和POST,没有使用HTTP的其它谓词设计比较简单。
Rest
Rest:按照Http的语义来使用HTTP协议的一种风格,Rest全称Representational State Transfer(表现层状态转换)。他是一种规范或者设计风格而不是特别的技术。REST形式的API组织形态是资源和实体,请求的路由可以看出对资源的访问,规范统一接口自解释。
比如 https://xxxx/Student/1 用Get方法调用就是获取编号为1的学生。 https://xxxx/Student/1 用Delete调用就是删除编号为1的学生,用delete调用就是删除该学生。
在HTTP中这些调用方法GET、POST、PUT、PATCH、DELETE 即HTTP谓词。GET用来获取资源,POST用来新建资源,PUT用来更新指定资源,PATCH用来批量更新资源,DELETE用来删除资源,通过谓词来表示请求动作或者意图,通过url定位资源。
在请求中GET、PUT、DELETE 请求是幂等的,也就是说可以重试请求。而POST不是幂等,因为POST意思是添加数据。
在Rest风格中使用状态码来标识返回结果,其中常用200、201、400、401、404、410、500等。
ASP.NET Core WebAPI接口
在ASP.NET WebAPI中我们也能看到Rest的风格,理想很丰满,显示很骨干。如果我们严格的按照Rest风格设计接口的话,需要对技术人员有很高的要求,需要去划分不同业务不同的资源定位,而且有些业务也找不到准确的谓词去定义,响应状态码有限无法表达准确的意思,或者是时间上来不及等等原因。
而且这种方式更符合国外语言表达的方式,不太适合我国宝宝体质。
所以我们在设计接口的时候不用非要用Rest风格,我们可以靠近或者在特定的更适合使用Rest接口系统中使用。
本篇我们结合Rest看下接口如何设计和交互。
- 在http接口请求中有三种方式传递参数或者数据。
- URL:资源定位,也就是Rest风格,在请求的url中包含信息,比如https://xxxx/Student/1 1就是学生编号。
- QueryString: URL之外的额外信息,比如RPC中https://xxxx/GetStudent?id=1 id=1就是QueryString
- 请求报文体:供PUT、POST提交提供数据,请求体有多种格式application/x-www-form-urlencoded、multipart/form-data、application/json、text/plain、application/xml。
- 返回状态码
在RPC中Post请求我们习惯如果请求已经在服务器处理,不管处理结果是否正确,我们都返回200状态码。然后在返回数据中用其它信息来标识业务结果。比如{code:1,msg:"成功"}
或者{code:0,msg:"失败"}
。
而在Rest 中Post通常用201返回新增成功,delete 删除的数据不存在返回404,但是404大家知道可能也许是url错误,所以表诉不清。
因此我们在实际设计中可能会进行Rest裁剪,我们既使用RPC的返回结果,同时多用准确的状态码,不用什么都返回200。
使用RPC风格,尽量使用合理谓词,不知道使用什么谓词的时候就用POST,Get Delete参数尽量用资源定位URL,业务错误服务端返回合适的状体吗,不知道返回什么就返回400,如果请求处理成功就用200同时返回结果数据。
在上一篇中遗留的这个问题 .net 温故知新【11】:Asp.Net Core WebAPI 入门使用及介绍
所以我们在Controller中Route配置为[Controller]则不管方法接口名称是什么,仍然以Rest的方式访问。
[Route("[controller]")]
[ApiController]
public class RestCutController : ControllerBase
{
[HttpGet]
public IEnumerable<string> GetStudents()
{
//获取所有学生
return new string[] { "student1", "student2" };
}
[HttpGet("{id}")]
public string GetStudent(int id)
{
//获取id的学生
return "student"+id;
}
[HttpPost]
public void PostStudent([FromBody] string value)
{
//新增
}
[HttpPut("{id}")]
public void PutStudent(int id, [FromBody] string value)
{
//修改
}
[HttpDelete("{id}")]
public void DeleteStudent(int id)
{
//删除id学生
}
}
当我们修改Rout按照RPC方式,[Route("[controller]/[action]")]
运行后发现swagger展示的接口方式就改变了。并且保留了参数URL的方式。
关于返回状态码的问题可以有两种方式,一种是直接在ControllerBase.Response
响应中指定返回状态码。
[HttpDelete("{id}")]
public string DeleteStudent(int id)
{
//删除id学生
if (id == 1)
{
return "删除成功";
}
else {
Response.StatusCode = 404;
return "未找到!";
}
}
另外一种方式就是返回泛型ActionResult<string>
,其中OK
和NotFound
是继承自ActionResult
然后隐式转换到泛型,也可以直接返回IActionResult或者ActionResult但是类型不确定这样swagger文档就不会解析出返回值,所以我们用ActionResult泛型。
[HttpDelete("{id}")]
public ActionResult<string> DeleteStudent(int id)
{
//删除id学生
if (id == 1)
{
return Ok("删除成功");
}
else
{
return NotFound("未找到!");
}
}
[HttpDelete("{id}")]
public ActionResult DeleteStudent(int id) //返回ActionResult
{
//删除id学生
if (id == 1)
{
return Ok("删除成功");
}
else
{
return NotFound("未找到!");
}
}
最后我们在总结下关于API参数获取的方式,在[HttpGet("{id}")]
中我们看到有{id}
,这个就是占位符,从RUL中获取,不光可以配置占位符还可以配置路径的其它值,甚至可以随意组织,只要我们的参数明和占位符相同就行。
[HttpDelete("number/{id}/Name/{name}")] //自己组织的URL
public ActionResult<string> DeleteStudent(int id,string name)
{
//删除id学生
if (id == 1)
{
return Ok("删除成功");
}
else
{
return NotFound("未找到!");
}
}
当然也可以使用[FromRoute]
从route获取,另外我们还有一些Attribute用于从不同的地方获取参数,比如从QueryString获取。那么我的请求URL就应该是/RestCut/DeleteStudent?id=1
[HttpDelete]
public ActionResult<string> DeleteStudent([FromQuery] int id)
{
//删除id学生
if (id == 1)
{
return Ok("删除成功");
}
else
{
return NotFound("未找到!");
}
}
最后还有[FromHeader]
、[FromForm]
、[FromBody]
这些获取参数的方式,不清楚的使用的时候查询就行了。