.NET Core学习
一、初识.NET Core
什么是.NET Core?
开源通用的开发框架,支持跨平台,部署,开发,物联网,云服务。
开发目标:跨平台的.NET应用,包含了.NET Framework的类库(3.0之前,有很多类库是移植的,之后就不在从.NET FW移植。)
.NET Core采用模块化的管理方式,需要什么组件就获取什么组件。
.NET Core特性—
跨平台、跨架构(x86\x64\ARM)、支持命令行、部署灵活、兼容性强(向下)、开源。
.NET Core和.NET Freamework的关系
- 从出生角度:
- 从发展角度:
.NET Core是.NET FW的下一代产品。
二、ASP.NET Core 3.1
注意:.NET Core 3.1只能运行在VS2019 16.4版本!
ASP.NET Core是重新设计的ASP.NET,从底层将体系结构都改变了。
新功能:Blazor,GRPC(高性能远程过程调用框架)
2.1、启动流程
(1)在VS2019新建一个ASP.NET Core的空web项目。创建完如图:
文件介绍:
launchSettings.json是项目调试配置文件,对应项目-属性-调试选项卡的配置项。
appsettings.json是项目配置文件,类似于ASP.NET中的web.config文件。
Program.cs文件,包含了Main方法,web应用的入口方法,主要就是创建主机生成器-配置主机-创建主机-运行主机。
Startup.cs文件,web应用的启动类。
ASP.NET Core Web应用启动步骤:
创建主机生成器>配置主机>创建主机>运行主机
public class Program
{
public static void Main(string[] args)
{
//创建主机生成器,然后调用创建主机的方法,然后运行主机
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
//调用Host静态类的方法,创建默认的主机生成器,返回一个IHostBuilder的接口
//默认配置:加载环境变量、加载命令行参数(命令行启动方式)、加载应用配置、配置的默认日志组件.....
Host.CreateDefaultBuilder(args)
//调用扩展方法,进行自定义的配置
//默认的配置,启用kestrel
.ConfigureWebHostDefaults(webBuilder =>
{
//指定启动类
webBuilder.UseStartup<Startup>();
});
}
解释如上代码中的主机:主机负责web应用程序的启动和生成期的管理,配置服务器和请求处理管道。
主机实际上是一个封装了应用资源的对象
Kestrel:跨平台的适用于ASP.NET Core的web服务器,担当的角色类似于IIS,在linux下性能更高。简单的说,性能高,但功能少,不支持反向代理。
(2)启动方式分两种,第一种同ASP.NET的IIS启动方式,第二种采用自宿主的方式启动,这里采用自宿主的方式启动。
启动效果如图:
2.2、主机与主机配置项
主机配置的方式(按优先级):
- 命令行
- 应用配置
- 硬编码
- 环境变量
2.3、注册服务
// 配置WEB应用所需要的服务和中间件
public class Startup
{
// 可选的
// 注册服务
public void ConfigureServices(IServiceCollection services)
{
// 服务容器 IoC(控制反转,Inversion of Control)容器
// 注册类型、请求实例
// 默认已经为我们注册了一些服务,服务,服务容器
// 添加对控制器和API相关功能的支持,但是不支持视图和页面
// 你这里是不关心生存期,你不关心有什么配置
services.AddControllers();
// 添加对控制器\API\视图相关功能的支持。
// ASP.NET CORE 3.X MVC模板默认使用
services.AddControllersWithViews();
// 添加对Razor Pages和最小控制器的支持
services.AddRazorPages();
// 这里是ASP.NET CORE 2.X
services.AddMvc();
services.AddCors();
// 比较规范的服务封装
services.AddMessage(builder => builder.UseSms());
// 内置的服务
// 第三方的,EF Core,日志框架、Swagger、
// 注册自定义服务
// 服务生存期 类型生命周期
// 注册自定义服务的时候,必须要选择一个生存周期
// 有几种生存周期
// 瞬时,每次从服务容器里进行请求实例时,都会创建一个新的实例。
// 作用域,线程单例,在同一个线程(请求)里,只实例化一次
// 单例,全局单例,每一次都是使用相同的实例,
// services.AddSingleton();
// services.AddTransient();
// services.AddScoped();
services.AddSingleton<IMessageService, EmailService>();
services.AddSingleton<IMessageService, SmsService>();
// 你这个服务,到底应该是什么生存期?你这个服务要不要配置?
// 自带不好用,你是可以换成第三方的
}
// 配置中间件,中间件组成管道
// 必须的,中间件使用、自定义中间、管道的原理、就是处理HTTP请求和响应的这么个东西
// 注入进来的,看一下管道的源码,带着大家模拟实现管道
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IMessageService messageService)
{
messageService.Send();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync(messageService.Send());
});
});
}
}
public interface IMessageService
{
string Send();
}
public class SmsService : IMessageService
{
public string Send()
{
return "Sms";
}
}
public class EmailService : IMessageService
{
public string Send()
{
return "Email";
}
}
public static class MessageServiceExtension
{
public static void AddMessage(this IServiceCollection services, Action<MessageServiceBuilder> configure)
{
// services.AddSingleton<IMessageService, EmailService>();
var builder = new MessageServiceBuilder(services);
configure(builder);
}
}
public class MessageServiceBuilder
{
public IServiceCollection ServiceCollection { get; set; }
public MessageServiceBuilder(IServiceCollection services)
{
ServiceCollection = services;
}
public void UseEmail()
{
ServiceCollection.AddSingleton<IMessageService, EmailService>();
}
public void UseSms()
{
ServiceCollection.AddSingleton<IMessageService, SmsService>();
}
}
2.4、中间件
中间件的职责:
- 选择是否将请求传递给管道中的下一个中间件。
- 在管道中的下一个中间件的前后执行工作。
每一个中间件都有权做出决定是否将请求传递给下一个中间件,也可以直接做出响应,促使管道短路。
ASP.NET Core 路由、认证、会话、缓存,都是由管道来处理的,中间件。
MVC、Web API都是建立在某个特殊的中间件上。
示例代码
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
// 配置中间件
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 封装起来,添加进来
//use方法,是有next的
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Middleware 1 Begin \r\n");
await next();
await context.Response.WriteAsync("Middleware 1 End \r\n");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Middleware 2 Begin \r\n");
await next();
await context.Response.WriteAsync("Middleware 2 End \r\n");
});
// run方法,是没有next的,终端中间件
// 专门用来短路请求管道,是放在最后面的,兜底的。
app.Run(async context =>
{
await context.Response.WriteAsync("Hello Run \r\n");
});
// 大家有没有看过ASP.NET Core的源代码?1,2,知道怎么看吗?
// 环境名称Development
if (env.IsDevelopment())
{
// 开发人员异常页面中间件
app.UseDeveloperExceptionPage();
}
// 终结点(端点)路由中间件
// ASP.NET CORE 2.X里, 是没这个东西
// ASP.NET CORE 3.X里 拆出来,都有对路由的需求,所以路由拆出来,为了复用!
app.UseRouting();
// 通用的添加中间件的方法
app.UseMiddleware<TestMiddleware>();
app.UseTest();
// 还可以添加一些其它的中间件,
// 终结点中间件,这里是配置,配置中间件和路由的之间关系,映射
// 终结点你可以简单理解为 MVC, /控制器/action
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
}
public static class CustonMiddlewareExtensions
{
public static IApplicationBuilder UseTest(this IApplicationBuilder app)
{
return app.UseMiddleware<TestMiddleware>();
}
}
public class TestMiddleware
{
private readonly RequestDelegate _next;
public TestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
// 在这里写中间件的业务代码!!
// HTTP请求部分的处理
await _next(httpContext);
// HTTP响应部分的处理
}
}
运行结果:
2.5、模拟管道模型
public delegate Task RequestDelegate(HttpContext context);
class Program
{
static void Main(string[] args)
{
var app = new ApplicationBuilder();
app.Use(async (HttpContext context,Func<Task> next) =>
{
Console.WriteLine("中间件1号 Begin");
await next();
Console.WriteLine("中间件1号 End");
});
app.Use(async (HttpContext context, Func<Task> next) =>
{
Console.WriteLine("中间件2号 Begin");
await next();
Console.WriteLine("中间件2号 End");
});
// 这时候管道已经形成,执行第一个中间件,就会依次调用下一个
// 主机创建以后运行的
var firstMiddleware = app.Build();
// 当请求进来的时候,就会执行第一个中间件
// 主机给的
firstMiddleware(new HttpContext());
}
}
public class ApplicationBuilder
{
// 中间件,独立的!互相没有关联的,只有一个顺序
private static readonly IList<Func<RequestDelegate, RequestDelegate>> _components =
new List<Func<RequestDelegate, RequestDelegate>>();
// 扩展Use
public ApplicationBuilder Use(Func<HttpContext, Func<Task>, Task> middleware)
{
return Use(next =>
{
return context =>
{
Task SimpleNext() => next(context);
return middleware(context, SimpleNext );
};
});
}
// 原始Use
public ApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
// 添加中间件
_components.Add(middleware);
return this;
}
public RequestDelegate Build()
{
RequestDelegate app = context =>
{
Console.WriteLine("默认中间件");
return Task.CompletedTask;
};
// 上面的代码是一个默认的中间件
// 重要的是下面几句,这里对Func<RequestDelegate, RequestDelegate>集合进行反转,
// 逐一执行添加中间件的委托,最后返回第一个中间件委托
// 这里的作用就是把list里独立的中间件委托给串起来,然后返回反转后的最后一个中间件(实际上的第一个)
// 管道才真正的建立起来,每一个中间件都首尾相连
foreach (var component in _components.Reverse())
{
app = component(app);
}
return app;
}
}
public class HttpContext
{
}
运行结果:
2.5、应用配置
读取配置文件内容:
- 通用读取方法
- 绑定配置模型对象方法
(1)创建模型类
(2)通过IConfiguration对象将配置选项绑定到对象。
(3)通过注册配置选项服务的方式,获取配置选项的值。
3.读取自定义配置文件的值
var config=new ConfigurationBuilder().AddJsonFile("jsonconfig.json").Builde();
这里的config对象就同IConfiguration对象的用法一致。
2.6、环境配置
- 方法多环境
当修改了项目配置文件的环境选项后
Startup类会执行与环境变量对应名称的方法,而不会执行默认的方法。- 类多环境
首先同样按照方法多环境的步骤,先修改配置文件的环境变量,再修改program类的代码。
以下代码就是获取调用此方法所在的程序集的名称,然后UseStartup方法就会按照约定去查找对应环境变量的启动类。
由于修改了配置文件,程序会运行StartupDemo类,当没有StartupDemo类时,才会执行默认的Startup类。