ASP.Net Core学习文档-LearningPath(概述版)
ASP.Net Core 学习文档
题前
这是我刚参加工作不久写的文档,直接搬上来了。目前学习和使用.Net Core系列已经有2年了,简单整理一下这2年间ASP.Net Core的学习经验和心得体会。
本篇内容将会随着工作的深入,逐渐细化。
一、环境的搭建和基础概念
1、程序使用环境:
1.Hosts文件 2.RunTime 3.SDK
hosts:我们知道在网络上访问网站,首先要通过DNS服务器将要访问的域名(xxx.com),解析成对应的IP地址,计算机才能解读。而Hosts文件的作用就是将主机名和IP做了一个映射,且优先级在DNS之前。具体的内容也可以打开hosts文件描述:# Copyright (c) 1993-1999 Microsoft Corp.#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host
这个文件是按照TCP/IP for windows的标准来工作的。使用方式很简单 127.0.0.1 localhost 这种格式。
作用:加快域名解析;方便局域网用户;屏蔽网站;注意一点:Hosts文件配置的映射关系是静态的!!!
ASP.Net Core 源码地址 进入后点击FIndFile,然后按需查找文件。
2..net Core 的两种托管模式
1.InProcess
工作进程:IIS Worker Process是一个IIS工作进程,这个工作进程我们平时也见到过。当我们运行.Net Framework MVC的时候,系统会为我们创建一个该进程。如果我们将该进程释放掉,程序也就跟着结束了,这也说明我们的项目工程是寄宿在IIS工作进程中的。而.netCore之所以可以实现跨平台,就是不再依靠IIS工作进程。调用或依赖工作进程就属于进程内,依赖于IIS,IIS靠w3wp.exe或iis express.exe这两个执行文件。
2.out of process
相反,不依赖工作进程就属于进程外线程啦,Asp.NET Core进程外不依赖于IIS的w3wp.exe可执行文件。这里我们使用的是Kestrel,它使用的是dotnet.exe可执行文件。
还可以与反向代理服务器结合使用:
在项目中如何设置启动方式以及更改使用的Server的。除了项目启动文件配置以外,我们还可以在工程配置文件中设置。通过以下代码:
<PropertyGroup> <AspNetCoreHostingModel>inProcess</AspNetCoreHostingModel>//设置进程内托管 <AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>//设置进程外托管 </PropertyGroup>
3、工程的基本结构
这里我创建了一个ASP.Net Core API工程.说明一下工程中包含的具体内容,以及使用方法。
项目依赖和框架:.net core 完全作为NuGet包提供,借助NuGet包可以将应用优化减少到只包含到必须的依赖项,提升了安全性,减少了维护和提高性能
项目启动文件配置:这里我们可以将iis启动配置删除,默认使用Kestrel服务器
1.项目整体配置文件:
Program:
它是程序的入口文件,其实Asp.Net Core Application实际上就是控制台应用程序(Console Application)其中包含Main方法,主要是用来配置和运行程序的。
我们的Web程序需要一个Host宿主,所以通过Main方法中的CreateHostBuilder这个方法创建了一个IHostBuilder。
有了host后还需要一个servers,Asp.net Core自带了两种http servers, WebListener 它只能用于Windows系统,另一个就是Kestrel,由于不依赖IIS,所以可以跨平台。Kestrel是默认的Web server, 它通过UseKestrel()这个方法来启用的。
由于我们在开发的时候使用的是IIS Express,需要通过调用UseIISIntegration()这个方法来启动,它会作为Kestrel的Reverse Proxy Server来用。如果在Windows服务器上部署,就应该使用iis作为Kestrel的反向代理服务器。
如果是在Linux上的话,可以使用Apache,nginx等作为Kestrel的反向代理服务器。
Startup.cs:
这个类是配置Service 和中间件的启动类。
Startup默认构造函数,注入了IConfiguration,有两个方法:
ConfigureService方法:
该方法是用来把各种服务例如EF MVC Identity 自定义的service注册到Asp.net.Core的容器中去。并且配置这些Services.这个容器是用来进行DI的,该方法在Configure方法配置应用服务之前,由主机调用。
目的:经服务添加到服务容器中,使其可以在应用和Configure方法中可用。
Configure方法:
该方法是Asp.net Core程序用来具体制定如何处理每个Http请求的。我们收到一个Http请求后,可以在这里设置自定义的中间件去捕获httpContext,并处理它,这里类似于Framework MVC中的HttpModel。
4、主机&.net Core的执行过程&项目启动顺序
Asp.Net Core应用在启动时构建主机。主机是封装所有应用资源的对象。例如:Http服务器实现,中间件组件,logging,DI,配置等。
MS提供两个主机:通用主机和Web主机。通用主机是推荐的机型,而Web主机仅适用于向后兼容。由于我们使用的是3.0版本所以我们采用通用主机也叫做泛型主机。
主机通常由Program类中的代码配置(CreateHostBuilder),生成(Build)和运行(Run)。
了解的大致理论,接下来我们从代码层面理解一下WebHost的运行流程:
构建主机:Main()函数中要执行几个方法,分别是CreateHostBuilder、Build、Run.
- CreateHostBuilder:该函数主要是为Host做准备工作,其中包括,获取哪里的配置文件,配置哪些命令,需要注入哪些服务等。
- Build:用于构建CreateHostBuilder准备好的相关配置,并且托管到Host服务中去。例如appsettring.json,log,以及一些常见的服务。
- Run:用于启动构建,运行Web应用程序并阻塞调用线程,直到主机关闭。
这里最重要的就是第一步,CreateHostBuilder.它调用了CreateDefaultBuilder函数进行默认的配置,例如,启动内容根目录,将Kestrel服务器配置为Web服务器、加载应用配置、日志配置、使用iis中间件等,
我们也可以自己定义Host的配置通过UseStartup<Startup>()函数。Startup.cs这个类主要做两件事情的配置Service DI和http管道,这些都是在WebHost启动之前就需要确定下来的。
参考官方文档:ASP.NET Core 中的 .NET 通用主机
1..Net Core的执行过程
浏览器发送一个Http请求,经过IIS/Apache,Nginx的代理转发,通过.netCore框架的处理,交给Kestrel服务器,通过中间件管道,进入到要访问的API,最后反向的推送给浏览器。如图3.1所示:
2.net Core的代码执行顺序:
Main()->CreateHostBuilder(args)->CreateDefaultBuilder()->Build()->Startup()->ConfigureServices()->Run()->Configure()->执行各个中间件->filter()->action()->异步后的中间件逻辑。
5.IOC和DI(依赖注入)
一提到依赖注入就会联想到另外一个东西控制反转IOC(Inversion of Control),相对于DI而言IOC并不是一种技术而是一种编程设计思想,而DI是用来实现这个思想的方法,注意这里不要搞混淆了。IOC≠DI。
解除依赖的思想是如何产生的?
看到一个例子,用以描述IOC这个设计思想。举例场景:工厂伐木。
在原始社会中,没有社会分工,我们需要开采木材,就需要一把用来开采木材的斧子,这里需要斧子的人就是(调用者),由于没有社会分工,所以只能自己去做一把斧子用来开采木材,这里斧子就是(被调用者)。就相当于我们调用者自己创建了一个被调用者。一个(调用者)类中通过new 实例化(被调用者)的实例对象。
进入工业社会,由于工厂的出现,斧子不需要调用者自己去创建了,而是由工厂加工制作出来,此时调用者需要斧头的时候就到工厂中购买斧头,使用这种方式的好处是,调用者无需担心被调用者斧子是有什么材料和什么工序加工制作出来的。相应的软件程序,请参考简单工厂的设计模式。
到了“按需分配”社会。调用者不需要找到工厂去获取被调用者,他仅仅需要坐在家中,挂出他需要斧子的提示,就会有专门的机构在合适的时间分配合适的被调用者斧子到家。这就是依赖注入的思想。这也符合了好莱坞思想:不要打电话给我们,我们会打电话给你。
DI和IOC的关系:它只是实现IOC的一种设计方法,依赖的对象注入到调用者,不应该由调用者自己去创建它,而是通过构造函数由你的调用者给你--(容器)
使用DI框架:
1.ASP.Net Core 自带的DI容器
2.使用第三方提供的容器,这里举例的是Autofac(据说是使用最多的)
1.自带的DI注册
使用方法还是很简单的,在ConfigureServices(IserviceCollection services)中添加如下代码:services.AddScoped<IDI,DI>();即可完成将IDI和DIservice存放到DI容器中。
有三种注入方法,如果还不是很了解这三种注入方法的实现,建议出门左转百度知道了解一下。
- 构造函数注入(推荐)
- 属性注入
- 方法注入(不推荐)
例如:在WeatherForecastController构造方法中加入相应的参数IDI DI 通过构造函数的方法注入进去即可使用。肥肠滴方便。
注入对象的生命周期:
这里DI给了我们三种创建其生命周期的方法
- AddTransient: 每次注入请求时都会创建转瞬即逝的服务,每次创建都是独立的一个实例,即用即销。
- AddScoped: Web级别,每次请求都会创建一个新的独立服务的范围,贯穿整个请求。
- AddSingleton: 应用程序级别,类似于单例服务,在一次请求是创建,应用于整个应用程序的生命周期。
官方文档:ASP.Net Core 依赖注入
2.使用Autofac
先引入Autofac nuget包:
Autofac.Configuration
Autofac.Extensions.DependencyInjection
首先我们需要在Program.cs中修改ServiceFactory,内置的是ServiceProviderFactory,我们将其修改为AutofacServiceProviderFactory
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
然后我们在Startup.cs这个类添加
ConfigureContainer(ContainerBuilder conainerBuilder)
方法,并且注入创建注册组件的ContainerBuilder,通过注入的ContainerBuilder实例调用RegisterTyper<DI>来创建,如果想暴露其借口则加上.As<IDI>(),最后加上生命周期.InstancePerLifetimeScope();
containerBuilder.RegisterType<DI>().As<IDI>().InstancePerLifetimeScope();
然后你可以正常使用了,Atuofac使用文档:官方文档
以上是DI的基础篇,下面是我在实战开发中遇到一些问题汇总:DI实战
6.Middle Ware
Http请求资源的过程可以看成一个管道,我们称之为请求处理管道。由于不是所有的请求都是安全合法的,所以我们在这个管道中装配一些处理程序来处理这些请求,处理程序就是中间件。
知道了请求处理管道中的处理程序由一系列中间件组件组成。每个组件在HttpContext上执行异步操作,然后调用管道中的下一个中间件或终止请求。例如中间件可能处理对静态文件的请求或将Http请求重定向到https。每个中间件在HttpContext上执行异步操作,然后调用管道中的下一个中间件或终止请求。我们将这类配置请求处理管道的代码添加到Startup.Configure方法中。
每个Middle Ware中间件有两个基本功能:
- 选择是否将请求传递到管道中的下一个组件。
- 可在管道中的下一个组件前后执行工作。
如何理解这两句话呢,请看下面间件执行过程
ASP.NET Core请求管道包含一系列请求委托,依次调用。通过next传递给下一个中间件,当发生无next调用,或调用某些短路中间件使其终止向下继续执行,程序就会按照原路返回执行异步后的代码逻辑,这也说明了每个委托均可以在下一个委托前后执行操作。
从代码层面上来看:
有三种方法使用中间件:use、run、map。后面两个方法都是短路中间件,无法向下继续传递。这里我们只拿出use方法的实例。详情请参考:Net Core 中间件
//中间件执行顺序测试 app.Use(async (Context, next) => { Console.WriteLine("1、start"); await next(); Console.WriteLine("1、end"); }); app.Use(async (Context, next) => { Console.WriteLine("2、start"); await next(); Console.WriteLine("2、end"); }); app.Use(async (Context, next) => { Console.WriteLine("3、start"); await next(); Console.WriteLine("3、end"); }); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); });
运行结果:
可以看出Use将多个委托链接在一起,next参数表示管道中的下一个委托。可通过不调用next参数使管道短路。在短路后执行异步后的操作。
中间件参考资料:官方文档
7.Filter
由于不同的过滤器在管道中的不同阶段执行,因此具有各自的工作场景。根据要执行任务以及需要执行的请求管道中的位置,选择要创建的过滤器类型。过滤器在MVC操作调用管道中运行,当MVC选择要执行的操作后,执行操作管道中的过滤器。
不同过滤器在管道中的执行循序是不同的,像Authorization filter在管道中靠前的位置执行,其他过滤器,例如Action filter可以在管道的之前和之后执行。
实现过滤器可以通过不同的接口定义支持同步和异步的实现。从框架的角度看,他们是可以互换的。
这里举例Exceptionfilter.同步过滤器定义了OnException,当全局有异常发生的时候,进入到OnException中。
public class ExceptionFilter : IExceptionFilter { public void OnException(ExceptionContext context) { if (context.ExceptionHandled == false)//判断该异常是否被处理过 { string msg = context.Exception.Message; context.Result = new RedirectResult("/MyException/Error"); //重定向 } context.ExceptionHandled = true; //用于标识当前发生的异常是否已经过处理 } }
异步过滤器定义了一个单一的OnExceptionAsync方法
public class AsyncExceptionFilter : IAsyncExceptionFilter { public void OnException(ExceptionContext context) { if (context.ExceptionHandled == false) { string msg = context.Exception.Message; context.Result = new RedirectResult("/MyException/Error"); //重定向 } context.ExceptionHandled = true; } public Task OnExceptionAsync(ExceptionContext context) { OnException(context); return Task.CompletedTask; } }
filter的注册,和之前的.NET Framwork一样,过滤器有三种不同级别的作用域。可以在特定的操作使用特性方式定义过滤器,也可以在控制器上使用特性的方式,或者采用注册全局的方式。下面介绍一下关于全局和特性方法注册filter的方式。
1.全局注册
由于我使用的是core3.1版本这里和3.0有些不一样的地方。
注册Filter首先要在ConfigureService中注册,3.0中是通过AddMvcOptions()方式注册,到了3.1采用AddMvc()进行操作。其方法体都相同,只是调用的方法名不同。三种注册口味任由选择。
services.AddMvc(options => { options.Filters.Add(typeof(ExceptionFilter)); //类型注入 //options.Filters.Add(new ExceptionFilter()); //实例注入 //options.Filters.Add<ExceptionFilter>(); //泛型注入 });
特性注册
添加特性Filter,与实现接口的方式不同,这里依然使用Exceptionfilter举例。
需要创建一个继承于ExceptionFilterAttribute的类,重写其中的OnException。使用特性的方法也分同步或异步两种,具体实现和全局是相同的。使用的时候在需要添加filter的Action或Controller上打上特性标签即可。
参考资料1(Action):Asp.Net Core MVC Filter
参考资料2(Exception):Exception Filter
8.Log4net
.NET Core支持适用于各种内置和第三方日志记录提供程序的日志记录API。这里我用的是Log4net,下面我简单介绍一下.NET Core下如何使用log4net记录日志。
首先引入Log4Net package
然后需要添加一个Log4net.config文件,用做配置文件使用。配置文件的写法有很多这里不做过多的介绍了,百度一下会有很多的。
接下来是编写公共类,这里我创建了一个Log4NetConfig的公共类,这个类用来初始化Log4net。
创建一个初始化log4net的方法init();将Iconfiguration 作为参数,该参数用来通过读取appsettings.json配置文件中的数据。
//获取配置文件中的存储名字 var repositoryName = configuration.GetSection("Log4Net:RepositoryName").Value; //读取log4net配置文件的路径 var configFilePath = configuration.GetSection("Log4Net:ConfigFilePath").Value; //设置存储log的对象名,这些对象可分别写入日志 var repository = LogManager.CreateRepository(repositoryName); //log4net从log4net.config文件中读取配置信息 XmlConfigurator.Configure(repository, new FileInfo(configFilePath));
上面这些配置好了以后就可以通过LogManager.GetLogger方法获取Ilog对象的实例了。
ILog InfoLog = LogManager.GetLogger(Log4NetConfig.RepositoryName, "Info"); xxx public static void Info(string message, Exception exception = null) { if (exception == null) InfoLog.Info(message); else InfoLog.Info(message, exception); }
获取.NET Core配置参考:.net Core 配置
.net Core 引用Log4net 参考:net core Log4net