ASP.NET与ASP.NET Core很相似,但它们之间存在一些细微区别以及ASP.NET Core中新增特性的使用方法,在此以前也写过一篇简单的对比文章ASP.NET MVC应用迁移到ASP.NET Core及其异同简介,但没有进行深刻的分析和介绍,在真正使用ASP.NET Core进行开发时,若是忽略这些细节可能会出现奇怪的问题,特此将这些细节进行分享。
本文主要内容有:css
注:本文基于ASP.Net Core 2.1版本,.Net Core SDK版本须要2.1.401+。长篇预警( ╯□╰ )html
ASP.NET与ASP.NET Core之间最大区别之一就是内置了依赖注入机制,虽然ASP.NET中也有DI机制,但没有内置容器,通常都须要使用第三方的容器来提供服务,另外依赖注入的概念也不像ASP.NET Core中这样无处不在。
简单来讲依赖注入的目的是为了让代码解耦以提升代码的可维护性,同时也要求代码设计符合依赖致使原则使得代码更加灵活,而其原理实际上就是在应用程序中添加一个对象容器,在应用初始化时将实际的服务“放”到容器中,而后当须要相应服务时从容器中获取,由容器来组装服务。web
ASP.NET Core的Startup(注:Startup仅仅只是约定名称,实际使用是在Program类型中建立 WebHost时使用的),该类型中包含两个方法分别是ConfigureServices和Configure,其中ConfigureServices的主要做用就是用来将服务“放”置到容器中json
代码来自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1api
ASP.NET Core的默认容器仅提供了构造注入功能,若是须要使用属性注入等功能或者在迁移时原有应用依赖于其它容器,那么能够经过使用第三方容器实现。
将默认容器替换为其它容器仅需三步:
1. 将ConfigureServices方法的返回类型改成IServiceProvider。
2. 将ASP.NET Core中的服务注册到第三方容器中。
3. 使用第三方容器实现IServiceProvider接口并返回。服务器
官方文档以Autofac为例,Autofac已经实现了ASP.NET Core服务注册到Autofac容器中,以及Autofac容器的IServiceProvider接口封装,仅需安装Autofac以及Autofac.Extensions.DependencyInjection包便可。mvc
详情参考:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1#default-service-container-replacement
使用windsor或其它容器能够参考:
https://stackoverflow.com/questions/47672614/how-to-use-windsor-ioc-in-asp-net-core-2app
虽然Controller在激活时是经过容器来获取Controller的依赖(即构造方法须要的参数),在代码运行的时候给人一种Controller是从容器中组装的错觉,可是实际上默认状况下Controller的组装过程不是直接由容器组装的,若是要让Controller从容器组装,那么在配置MVC服务时须要经过.AddControllerAsServices()方法将Controller注册到容器中:ide
注:通常状况下是否将Controller注册为服务对Controller的开发和代码的运行并无很大区别,可是若是当容器变动为其它容器,而且使用了容器提供的如属性注入等功能时,若是没有将Controller注册为服务,那么相应的属性注入的过程也不会被触发,简单来讲就是只有将Controller注册为服务,那么实例化Controller的工做才会由容器完成,才会触发或者使用到容器提供的其它特性。工具
前面介绍了服务的注册,如今来介绍一下在ASP.NET Core中有哪些方法能够获取服务:
1. Controller构造方法参数。
2. 经过Controller注入IServiceProvider类型,经过IServiceProvider来获取服务:
3. 在Action方法或者Mvc过滤器(过滤器的上下文参数中包含HttpContext)中经过HttpContext的RequestServices对象获取服务:
4. 在View上经过@inject注入服务:
5. 在Action方法中,经过FormServices特性注入:
注:通常来讲尽量显式的标明类型的依赖(即经过构造参数的方式声明当前类型所依赖的组件),上面的2和3两点分别都是经过服务提供器在方法内部来获取依赖,这样作依赖对于外界来讲是不可知的,可能会对代码的可维护、可测试性等形成必定影响,这种模式被称为Service Locator模式,在开发过程当中尽量避免Service Locator模式的使用。
ASP.NET Core相对于ASP.NET来讲取消了一些经常使用的静态类型,好比HttpContext、ConfigurationManager等,取而代之的是经过将相似的组件以服务的形式注册到容器中,使用时经过容易来获取相应的服务组件,这些经常使用的服务有:
1. IHostingEnvironment:包含了环境名称、应用名称以及当前应用程序所处的根目录及Web静态内容的根目录(默认wwwroot)。
2. IHttpContextAccessor:从名字能够看出,它用来访问当前请求的HttpContext。
3. IConfiguration:ASP.NET Core配置信息对象。
4. IServiceProvider: ASP.NET Core服务提供器。
5. DbContext: 这里的DbContext指的是EFCore的DbContext,在ASP.NET Core中,EFCore的DbContext也是在ConfigureServices方法中进行配置并添加到容器,使用时直接从容器中获取(但要注意的是对于分层结构的开发风格来讲,DbContext不会直接被Controller依赖,而是被Controller中依赖的业务服务类型因此来,就是说编写Controller代码的时候不会直接与DbContext发生直接交互)。
在ASP.NET的开发中,一般某个变量须要从配置文件读取,通常都是在相应类型的构造方法中,经过静态类型ConfigurationManager的AppSettings方法来读取并初始化变量。虽然ASP.NET Core也能够在类型中注入IConfiguration实例来直接读取配置文件,但该方法因为Options模式的出现已经再也不建议使用,使用组件经过依赖相应的组件Options能够作到关注点分离,提升程序的灵活性、可拓展性,Options使用方法见文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.1
ASP.NET因为是基于IIS请求管道的,ASP.NET应用程序仅仅是管道中的一个处理环节,管道中还包含如身份验证、静态文件处理等环节,但ASP.NET Core不同,它脱离了IIS处理管道,因此整个管道的创建均须要靠程序自身完成,而ASP.NET Core创建管道的代码就是Startup类型的Configure方法,该方法经过IApplicationBuilder实例来添加不一样功能的中间件,经过中间件的串联造成处理管道,下图是ASP.NET Mvc模板生成的管道代码:
图片来自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-2.1#the-configure-method
该管道主要包含了错误处理(开发环境显示异常信息,其它环境跳转错误页面)中间件、静态文件处理中间件以及Mvc中间件。
更多中间件可参考文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/index?view=aspnetcore-2.1
ASP.NET Core Mvc与ASP.NET Mvc相比总体上区别不大,但仍然有不少细节上的变化,下面就开始一一介绍:
路由的做用是将请求根据Url映射到“对应”的处理器上,在Mvc中请求的终点就是Controller的Action方法,而这里所谓的“对应”指的是Url与路由模板的匹配,ASP.NET Core Mvc经过如下的方式添加路由模板:
上图中的路由模板是最经常使用的路由模板,使用花括号内的内容为路由参数及其默认值,Url中经过路由参数控制器名称、活动方法名称来匹配到相应控制器的活动方法。
在注册路由时能够为相应路由添加默认值、路由参数约束以及对应路由的相关附加数据(datatokens):
路由的功能除了处理请求匹配外,还具备连接生成的功能,特别是Mvc程序的View中使用IUrlHelper或TagHelper来生成页面的超连接:
其生成原理是经过连接参数(如上图所示的Controller和Action)去路由表中匹配,而后使用匹配结果中的第一个路由(可能会匹配到多个路由对象,具体内容在后续Area章节介绍)来生成连接。
更多路由信息及路由模板定义参考文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-2.1
ASP.NET Core Mvc的Controller通常继承Controller类型实现,基类Controller中包含了Mvc中经常使用的返回方法(如Json以及View等)以及用于数据存储的ViewBag、ViewData、TempData。
Area是Mvc应用中用来进行功能拆分或分组的一种方式,Area通常有本身的命名空间和目录结构,通常Area的默认目录结构以下:
ASP.NET Core Mvc和ASP.NET Mvc中的概念和用法基本上是一致的,但也存在一些区别:
1. Area下面的Controller须要使用Area特性标明当前Controller属于哪个Area:
注:Area的目录结构不是必须的,只须要经过特性标记的Controller都会被正确识别,但目录结构的改变会致使没法找到View,关于View的查找路径会在后续介绍。
2. Area的路由注册也是在UseMvc方法中完成:
注:携带Area的路由模板须要放在前面,不然在生成经过IUrlHelper或TagHelper生成连接时,因为Controller以及action会匹配到没有area的模板并使用该模板生成连接,致使area参数被忽略,而生成相似:/controller/action?area=area的结果(在生成Url时,ASP.NET Core会将多余的路由参数放置到查询字符串中)
View是基于Razor的HTML模板,Razor的详细语法参考文档:
https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-2.1
ASP.NET Core Mvc的View与ASP.NET Mvc中的使用方法基本一致,主要区别以下:
1. 引入了TagHelper,使用TagHelper可让View的代码更接近Html。更多TagHelper信息参考文档:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-2.1
2. Controller将参数传输到View的方法添加了ViewData特性,使用方法以下:
View中访问被ViewData标记的方式:
更多详情参考文档:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/overview?view=aspnetcore-2.1#passing-data-to-views
3. 新增View组件:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-2.1
配置View的查找路径:
ASP.NET Core能够在ConfigureServices方法中对RazorViewEngineOptions进行配置,以下图所示,在默认查找位置基础上添加了View以及AreaView的查找路径:
模型绑定指的是ASP.NET Core Mvc将请求携带的数据绑定到Action参数的过程,ASP.NET Core Mvc的模型绑定数据源默认使用Form Values、Route Values以及Query Strings,全部值都以Name-Value的形式存在,模型绑定时主要经过参数名称、参数名称.属性名称、参数名称[索引]等方式与数据源的Name进行匹配。
除了默认的数据源以外还能够从Http请求Header、Http请求Body甚至从依赖注入容器中获取数据,要从这些数据源中获取数据须要在相应参数上使用[FromHeader]、[FromBody]、[FromServices]特性。
若是须要获取的数据在不一样数据源中都存在时(Name存在于多个数据源中),还能够经过特性指明从哪个数据源中获取,如[FromForm]、[FromQuery]及[FromRoute]。
须要注意的是[FromBody]默认只支持Json格式的内容,若是须要支持其它格式,如XML须要添加相应的格式化器,添加方法以下图所示:
更多模型绑定及验证内容请参考文档:https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-2.1
https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-2.1
其中模型验证的使用方式与ASP.NET Mvc一致,仍然是经过相应的验证特性对模型或模型属性进行标记。
说完Action方法参数的绑定,再来看一下Action方法的返回类型,在ASP.NET Mvc中Controller提供了返回页面内容的View方法以及返回Json内容的Json方法(固然还有文件、重定向、404等等其它内容返回方法,详见Controller与ControllerBase类型)。
这里有一个须要注意的地方是当使用Json方法返回一个对象实例时,默认使用首字母小写的驼峰命名方式序列化实例的属性名称,以下图所示:
访问结果:
要使用大写驼峰形式命名须要在配置Mvc服务时添加如下代码来修改Json默认的序列化配置:
注:一样的问题也存在于WebAPI的Ok方法以及Signalr的Json格式协议。
因为ASP.NET Core已经再也不使用IIS请求管道,因此对于静态资源的访问来讲须要在请求管道中添加相应的处理中间件来完成:
默认的无参UseStaticFiles方法将wwwroot目录做为静态资源存放目录,若是要添加其它静态内容目录能够再次使用UseStaticFiles方法,并经过StaticFileOptions对目录的访问路径以及实际路径进行配置:
注:因为ASP.NET Core能够在Linux下运行,因此对于Linux来讲路径是大小写敏感的,另外因为Windows和Linux类系统的路径分隔符也不一致,因此为了保证路径的统一,可使用Path.Combine方法,该方法会根据操做系统的不一样对路径进行不一样的处理。
另外对于css及js资源文件的打包、压缩功能,最新版本(ASP.NET Core 2.1)的应用模板以及不会自动添加相关功能,须要在拓展工具中添加Bunlder& Minifier拓展:
而后经过右键js等资源文件来建立bundleconfig.json文件:
ASP.NET Core将Mvc和WebAPI进行了合并,它们的实现都直接或间接继承了ControllerBase类型,只不过Mvc的基类Controller在ControllerBase的基础上添加了一些用于处理View的功能。
用ASP.NET Core开发WebAPI时,Controller类型直接继承ControllerBase。而后这个API的Controller就具备了基类的特性,返回一个结果仅须要使用Ok方法便可,以下图所示:
而后在路由表中添加路由:
便可经过/api/default/index访问到这个API:
但对于REST风格的API来讲,它须要经过ApiController特性对Controller类型进行标记,而且经过Route特性来设置路由:
而后就能够经过HTTP谓词来访问API:
但要注意的是在ASP.NET Core中实现的REST风格的Controller,它不会再根据action方法的名称来匹配谓词,因此存在多个方法时会,那怕对方法进行了命名,但仍然会出现如下错误:
为了解决这个问题,须要经过添加谓词特性解决:
WebAPI中的模型绑定与MVC存在一些区别,首先当使用ApiController标记Controller类型时,若是模型绑定验证未经过,会直接返回400错误,不会执行Action方法(免去了使用!ModelState.IsValid进行判断):
执行结果:
其次使用ApiController标记的Controller在执行模型绑定时会使用默认的推断规则,该规则分别从Body、Form、Header、Query、Route、Services(它们分别对应FromBody、FromForm、FromHeader、FromQuery、FromRoute、FromServices特性)中推断获取数据并绑定,为何说推断?
由于有一些特殊的规则:
1. FromBody用于复杂类型推断,若是不是复杂类型(如int、string等)以及特殊的内置类型(IFormCollection文档例子),则不会从Body中获取数据,除非经过[FromBody]特性指明,例子以下:
请求结果:
当使用[FormBody]指明参数数据源后能够正常访问:
注:当请求参数为简单类型时,请求体内容类型须要为application/json,内容不能为Json字符串,使用参数值做为内容便可(上图id没有提供的异常并非由于Json格式问题,而是没有指明从body中获取数据致使的)。
2. 只能存在一个参数从Body中获取数据,若是出现多个参数时,只能保证一个参数从Body中获取数据,其它参数须要指明获取数据的位置:
该API的调用方式以下:
3. FromForm默认只推断文件(IFormFile)及文件集合类型(IFormFileCollection),其他类型默认均不会从Form中获取。
4. 使用FromForm特性时会推断multipart/form-data请求内容类型。
以上推断行为能够经过以下配置禁用:
更多信息参考文档:https://docs.microsoft.com/zh-cn/aspnet/core/web-api/?view=aspnetcore-2.1
SignalR是用于客户端服务器实时通讯的工具库,从ASP.NET中就具备该功能,ASP.NET Core中的SignalR概念与用法与原来基本一致,但也存在一些区别:
1. 支持更多的客户端,.Net客户端、Java客户端、Js客户端以及非官方的C++客户端、Swift客户端。
2. 当连接SignalR并经过身份验证后,SignalR会保存当前用户连接SignalR的ID以及经过验证后的用户名,能够经过用户名向用户客户端推送消息。
3. 在应用程序中能够经过IHubContext<HubType>方式,对SignalR上下文进行注入,而且能够直接经过该上下文推送数据给已经连接的客户端,IHubContext<HubType>其实是GlobalHost.ConnectionManager.GetHubContext<HubType>()的替代方式。
4. ASP.NET Core中经过app.UserSignalR以及route参数来映射一个Hub,每个Hub拥有独立的上下文,所以若是要使用IHubContext<HubType>来向客户端推送信息,那么必须准确注明Hub的类型,以下图代码应该使用IHubContext<ChatHub>,不能使用除ChatHub之外的类型(基类也不行)。
5. SignalR默认使用Json协议传输数据,默认状况下使用首字母小写的驼峰命名方式序列化对象,要更改该默认行为须要经过一下代码,替换默认的序列化行为:
6. ASP.NET Core的客户端代码(特指Js客户端)有变动,须要对应版本使用。
关于更多SignalR内容请参考文档:https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-2.1
本文主要介绍了ASP.NET Core中Mvc、WebAPI以及SignalR开发时与原来ASP.NET中的一些细小区别和新特性,总体来讲ASP.NET Core与ASP.NET从使用方式上基本上是一致的,这也使得从ASP.NET迁移到ASP.NET Core变得更加容易,但可能由于这些细小的问题每每会向代码中埋入一些坑,因此特别编写了本文来解释这些问题。
总的来讲ASP.NET Core的文档至关齐全,本文中大部份内容实际都是文档中提到的,因此建议你们在使用ASP.NET Core开发时,首先第一步就是熟读文档,避免遗漏细节。但愿本篇文章对你们有帮助(*^_^*)
参考:
https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-2.1