1.1 .NET Core概述
.NET Core是微软推出的新一代跨平台开发技术,它吸收了.NET Framework的优点,又具有跨平台运行的特性。重要的是,.NET Core插上了云原生的翅膀,让开发人员可以开发能运行在容器环境中的微服务,以便于开发能应对高并发、高负载的系统。ASP.NET Core是在.NET Core平台下进行Web开发及后端接口开发的技术。
1.1.1 .NET平台的昨天
谈到.NET Core,就不得不提到它的“前辈”.NET Framework。.NET Framework是2002年由微软推出的开发平台,经过近20年的发展,它已经成为在微软平台上进行软件开发的主要开发平台。无论是在桌面应用软件开发、企业信息系统开发中,还是在互联网开发中,.NET Framework都有着广泛的应用。
随着软件行业的发展,系统的复杂性和访问量激增,传统的软件开发、部署模式已经力不从心。比如很多电商网站在十几年前刚开始创建的时候,可能只有电商前台系统、后台管理系统、财务系统等少量的几个系统,由于用户量不多,几台服务器就能够支撑它们了。但是发展到如今,其系统数量已经成百上千了,服务器也有上万台。因此,系统的开发、部署、运维等都和以前有了不同,云服务平台、容器、微服务等技术应运而生。
.NET Framework是约20年前诞生的技术,那时候还没有云服务平台、容器、微服务等概念,也就不能在平台中考虑这些因素,因此我们基于.NET Framework进行新项目的开发就会有些力不从心。
考虑到如上这些因素,微软于2016年推出了新一代的开发平台,并且将其命名为.NET Core。
1.1.2 为什么要跨平台
Windows Server是非常优秀的服务器操作系统,但是不少公司倾向于将Linux作为服务器操作系统,包括但不限于如下原因。
(1)安全考虑:由于Windows是闭源的,而Linux是开源的,因此有的客户认为Linux比Windows更安全。我们不谈这种认知是否客观,但是这种固有认知是很难被改变的,开发人员只能去适应这种认知。特别是在近几年,政企项目开始要求“国产化”,也就是数据库、操作系统等都使用国产的产品,而国产操作系统大部分都是基于Linux的,因此在Linux下运行系统的需求非常迫切。
(2)成本原因:由于Windows是收费的,而很多Linux发行版都是开源、免费的,对于服务器数量很多的系统,其操作系统的成本是一个不得不考虑的因素。
(3)软件生态:由于Linux是开源的,因此吸引了大批开发人员为Linux开发软件,比如Apache、Nginx、MySQL、Kafka、Redis、Docker等,数不胜数。虽然这些软件大部分也移植到了Windows下,但是这些毕竟是优先为Linux开发的软件,它们在Windows下的版本只能说是“可以运行”而已,其性能和功能与Linux版本的比起来都有一定的差距。并不是说基于Windows平台开发不出同样优秀的软件,而是说很少有人愿意优先基于Windows开发服务器端软件。这就导致了比较优秀的服务器端软件生态环境大多在Linux下。
基于以上原因,微软把.NET Core打造为可以在Windows、Linux、macOS等操作系统下开发和运行程序的框架;使用.NET Core开发的程序甚至可以运行在嵌入式设备上,这样.NET Core就成为物联网开发中的一个重要技术。
1.1.3 .NET Core是.NET Framework的升级版吗
需要特别注意的是,.NET Core不是.NET Framework的升级版,而是一个从头开始开发的全新平台,因此在.NET Framework下开发的程序并不能直接在.NET Core下运行。但是这并不意味着.NET Core和.NET Framework没有任何关系。.NET Core的很多代码都是直接从.NET Framework中迁移或改造过来的,因此.NET Core中的大部分技术、类库都和.NET Framework保持一致,绝大部分类的用法都没有变,这样就保证了.NET Framework开发人员掌握的技术并没有过时。
读者可能知道有一个技术叫作Mono。Mono是一个诞生于开源社区的、跨平台版本的.NET Framework。可以借助于Mono把由ASP.NET开发的网站放到Linux服务器上运行。既然Mono已经可以让.NET Framework跨平台运行了,那么为什么微软还要推倒重来,开发全新的.NET Core呢?
可以从微软官方对ASP.NET Core的定义中一探究竟,微软对ASP.NET Core的官方定义是:ASP.NET Core是一个跨平台的、高性能的开源框架,用来构建基于云且通过互联网连接的应用程序。这里的关键词是“跨平台”和“基于云”。
“基于云”是指程序可以运行在云服务平台上,并且可以和云服务平台的其他产品进行集成。云服务平台的大部分技术都是开放的,而不是绑定某个具体语言的,因此主流的编程语言都能用于“基于云”的开发,用.NET Framework也可以进行“基于云”的开发。但是“可以”不等于“适合”,就像C语言也能用于开发网站后台,但是很少有开发人员用C语言来开发网站后台一样。
为什么.NET Framework不适合用来开发“基于云”的程序呢?因为经典的“基于云”的场景是把程序部署到容器等运行环境中,这些运行环境不是一个完整的操作系统,所以要求运行在其中的应用程序有很好的自治能力并且占用更少的资源。
考虑到.NET Framework具有如下缺点,故其不适合用来开发“基于云”的程序。
(1).NET Framework属于系统级别安装的程序。操作系统内的所有程序共享一个.NET Framework安装实例,如果一个应用程序需要升级.NET Framework或者为.NET Framework安装补丁,则其他程序也会受影响。
(2).NET Framework必须安装到操作系统上才能使用,不能和应用程序打包到一起独立部署。
(3)ASP.NET框架和IIS(internet information services,互联网信息服务)[1]深度耦合。在生产环境中,ASP.N ET只能运行在IIS上,而IIS只能运行在Windows上,且不同Windows版本的IIS版本也不同。
(4)ASP.NET消耗的资源比较多。为了兼容旧版程序,ASP.NET在运行的时候有很多不必要的内存和CPU消耗。据估计,ASP.NET程序在运行时占用的内存是它实际需要内存的3倍。再如,在ASP.NET MVC(model-view-controller,模型-视图-控制器模式)中,当用户请求到达IIS后,中间要经过非常多的处理管道,最后才能到达控制器。这些管道大多是硬编码的,即使用不到它们,也无法将它们移除,因此ASP.N ET程序无法最大化地发挥硬件的性能。
(5).NET Framework诞生的时候是没有云计算的概念的,因此.NET Framework从创立之初其开发人员就没有考虑到程序会运行在云服务环境中,而且对很多.NET Framework组件的设置都要求被放到Windows系统级别,这导致.NET Framework程序无法做到完全自治。
除此之外,.NET Framework还有很多历史包袱问题。在.NET Framework诞生之初,ASP.NET就等同于ASP.NET Web Forms,但是用ASP.NET Web Forms开发的系统不符合新一代项目开发的要求,因此ASP.NET Web Forms早已被淘汰了,它被2009年正式发布的ASP.NET MVC以及更晚出现的ASP.NET Web API(application program interface,应用程序接口)所取代。从微软的定位来说,ASP.NET运行时应该为.NET平台下的所有Web框架提供基础支持,无论是ASP.NET Web Forms,还是ASP.NET MVC、ASP.NET Web API等,都应该是基于ASP.NET运行时的平等关系。但是微软在早期开发ASP.NET运行时的时候,在ASP.NET运行时中有很多专门为ASP.NET Web Forms编写的代码,而这些代码是ASP.NET MVC、ASP.NET Web API所不需要的,但是ASP.NET MVC、ASP.NET Web API也只能带着这些它们不需要的代码去运行,这导致使用ASP.NET开发出来的系统无法最大化地利用硬件资源。
可以看到,.NET Framework已经有很多不满足新一代的软件设计要求的地方了,而且已经背负很重的历史包袱。如果微软强行把.NET Framework进行跨平台移植,那么这个跨平台版本的.NET Framework为了兼容以前Windows版本的.NET Framework,需要做很多兼容性的处理工作,这样它身上背负的历史包袱就会越来越重,也会把.NET Framework的设计缺陷都带过来。这也是Mono在移植.NET Framework到其他平台时所遇到的困难,正因为这些困难,Mono对于.NET Framework的移植一直是不完善的,这种不完善也导致了没有大的商业项目使用Mono构建Web系统。Mono目前的成功应用反而是用来开发跨平台游戏的Unity和开发手机App的Xamarin,因为它们不是Web系统,所以只用到了.NET Framework的核心功能,没有用到历史包袱重的Web模块。
基于以上这些考虑,微软做出了艰难的决定:推倒重来,从头开发.NET Core。这样.NET Core开发团队就可以摆脱历史包袱来开发.NET Core,因此.NET Core有如下优点。
(1).NET Core采用模块化开发。.NET Core核心只包含很少的文件,所有其他模块都需要单独安装。我们开发的程序用到什么模块,就安装什么模块,这样各个模块都可以单独升级。不同的程序可以选择适合自己版本的组件,不用受系统上安装的其他程序的影响。比如,A程序可以用一个模块的1.5版本,而B程序可以用这个模块的1.8版本,它们不会互相干扰。
(2).NET Core支持独立部署,也就是说,可以把.NET Core运行时环境和开发的程序打包到一起部署。这样就不需要在服务器上安装.NET Core运行环境,只要把程序复制到服务器上,程序就能运行,这对容器化、无服务器(Serverless)等非常友好。
(3)程序的运行效率更高。.NET Core的所有管道都是可以插拔的,我们可以决定程序需要哪些管道及它们的执行顺序,因此用.NET Core开发出来的程序运行效率更高。
(4)ASP.NET Core程序内置了简单且高效的Web服务器—Kestrel。Kestrel被嵌入ASP.NET Core程序中运行,因此整个ASP.NET Core程序其实就是一个控制台程序。Kestrel可被配置上安全、HTTPS、限流、压缩、缓存等功能,从而成为直接面向终端用户的Web服务器,这样网站运行不依赖于IIS;也可以将其配置成轻量级的Web服务器,而安全、HTTPS、限流、压缩、缓存等功能则由部署在它前面的IIS、Nginx等反向代理服务器完成。
(5).NET Core更符合如今的软件设计思想。由于.NET Core是重新开发的,因此它可以更好地实现如今的编程理念,比如依赖注入、单元测试等。
虽然.NET Core是从头开发的,但.NET Core更多是对底层的调整,而对于开发人员使用的API,微软则尽力保证.NET Core和.NET Framework的一致性,也就是开发人员在.NET Framework中学到的绝大部分技术都可以迁移到.NET Core中,因此不会浪费开发人员在“.NET Framework时代”的技术投资。
1.1.4 .NET Framework中哪些技术不被支持
微软尽力保证.NET Framework开发人员的技术投资不被浪费,从而让他们可以快速地迁移到.NET Core开发上。但是.NET Core相对.NET Framework而言是无法向前兼容的破坏性创新,也就是.NET Core不支持.NET Framework中的少数功能。这样做的好处是.NET Core可以抛开历史包袱做出突破性创新。
下面是还没有或者永远不会被.NET Core支持的.NET Framework技术。
(1)WinForms、WPF(Windows presentation foundation,Windows呈现基础)这两项技术由于和Windows平台深度耦合,是很难被迁移到其他操作系统下的,因此微软官方已经声明没有对WinForms、WPF在.NET Core中进行跨平台支持的计划。从.NET Core 3.0开始,可以在.NET Core中使用WinForms、WPF,不过在.NET Core下开发出来的WinForms、WPF程序只能运行在Windows下,不能跨平台地运行在Linux、macOS等操作系统下。和.NET Framework下的WinForms、WPF相比,在.NET Core上进行WinForms、WPF开发,可以利用.NET Core的独立部署、模块化、更高性能等特性,这些是在.NET Framework下进行WinForms、WPF开发所不具备的。
(2)ASP.NET WebForms技术已经过时,因此微软没有把它移植到.NET Core中。
(3)WCF(Windows communication foundation,Windows通信基础)的服务器端开发不被.NET Core支持,而且未来也不会被支持,只有在.NET Core中调用.NET Framework中运行的WCF服务被支持。作者个人其实一直不喜欢WCF,因为WCF太复杂,不符合框架设计的KISS(keep it simple and stupid,保持简单和傻瓜化)原则。如果仅是进行网络通信,我们完全可以使用ASP.NET Core Web API、gRPC等技术,如果想要使开发出来的系统具备有序消息、队列服务、分布式事务、限流等高级特性,也可以选用有对应功能的开源组件,而不是使用WCF这样复杂的集成框架。不过开源的魅力就在于技术的发展不再受制于官方,微软WCF团队成员马特·康纽(Matt Connew)在2019年开源了他开发的CoreWCF,用这个开源项目,开发人员就可以继续在.NET Core中使用WCF进行服务器端开发了。
(4)WF(Workflow foundation,工作流框架)不被.NET Core支持,读者可以使用开源的Workflow Core。
(5)由于.NET Remoting用的是微软的私有协议,而且性能不理想,因此在.NET Core中不被支持。我们可以用谷歌开源的gRPC作为替代品,而且在ASP.NET Core中已经内置了对gRPC项目的支持。
(6)AppDomain不被.NET Core支持。在.NET Framework中,AppDomain可以用来在进程内对代码执行进行隔离,但是AppDomain技术有很多缺陷和局限性,因此在.NET Core中不被支持。在.NET Core中,我们可以用多进程模型或者容器实现类似AppDomain的效果。
.NET Framework中还有一些Windows特有的技术,这些技术在其他操作系统下没有对等的实现,这些技术包括但不限于WMI(Windows management instrumentation,Windows管理规范)、ODBC(open database connectivity,开放式数据库互连)、Windows ACL(access control list,访问控制列表)、Code Page、Windows事件日志、Windows性能计数器、Windows注册表、Directory Services。在.NET Core中,我们可以通过Windows Compatibility Pack继续使用这些技术,但是使用这些技术开发的程序只能运行在Windows下。
.NET Core做了很多创新,因此.NET Core项目的很多结构性的东西和.NET Framework的差别很大,比如csproj文件的格式、配置文件、ASP.NET Core项目结构、ASP.NET Core项目启动代码等。不过,微软在努力保证开发人员在.NET Framework时代的技术投资不被浪费,因此在编写业务代码的时候,大部分在.NET Framework时代的知识都可以直接拿过来用。目前.NET Framework上的大部分类都已经被移植到.NET Core上,而且用法变化不大。比如,作者曾经有一个基于ASP.NET MVC技术开发的项目,将它移植到.NET Core平台下只用了半小时,项目中的代码改动非常少。
1.1.5 .NET Standard是什么
在Visual Studio中新建项目的时候,除了.NET Framework和.NET Core之外,我们还会看到.NET Standard的身影,如图1-1所示。
在“类库”项目中,.NET Standard和.NET Core、.NET Framework具有同等地位,但是.NET Standard只在“类库”项目中出现过,在“控制台”“Web应用程序”等项目中都没有它的身影。那么.NET Standard到底是什么呢?
图1-1 新建项目向导
在.NET大家庭中有.NET Framework、.NET Core、Xamarin等具体的实现,在这些实现中,有一些其他实现所不具有的特性。比如,.NET Framework中有访问Windows注册表的类,很显然这是其他实现所不具备的;再如,Xamarin中有拨打电话的类,很显然这也是其他实现所不具备的。但是这些实现也有一些可以共享的类,比如读写文件的类、List集合类、字符串类等。假如每个.NET实现中,这些可以共享的类(也叫“基础库”)都有自己的一套做法,如图1-2所示,就有可能出现同样功能的类在不同的实现中各不相同的情况,比如在.NET Framework中操作文件的类叫FileStream,但是到了.NET Core中对应的类叫Storage。这样就会带来一个问题:如果我们想开发一个读写文件的代码库供.NET Framework、.NET Core等使用,代码编写起来就很麻烦了。
反之,如果微软为文件操作、集合等所有.NET实现中都具有的部分制定一个规范,无论是.NET Framework、.NET Core还是Xamarin都要遵守这个规范。比如这个规范规定操作文件的类必须叫FileStream,而且FileStream类必须要有Read、Write、Flush等方法,参数和返回值也必须统一。这样编写通用库的时候就会简单很多了。这个“各个实现通用的基础库规范”叫作.NET Standard,如图1-3所示。
.NET Standard规定了一系列需要被所有.NET Core、.NET Framework及Xamarin等共同实现的API,包括有哪些类、有哪些方法、参数和返回值是什么等。需要说明的是,.NET Standard只是一个规范,不是一个框架。不要以为.NET Standard是一个被.NET Framework、.NET Core、Xamarin等共用的基础库,.NET Standard只是规定了需要被实现的规范,但是不负责具体实现。
图1-2 不好的组件复用
图1-3 .NET Standard在.NET体系中的位置
对于.NET Standard类型的类库项目,当我们分别在.NET Core项目和.NET Framework项目中引用这个类库的时候,就可以看到它们执行时的差别。比如,编写一个.NET Standard类库项目,在其中创建一个DemoNetStandardClass类,并且在类中定义一个Test方法,使用Test方法输出FileStream类所在程序集的路径,如代码1-1所示。
代码1-1 输出FileStream类所在程序集的路径
Console.WriteLine(typeof(FileStream).Assembly.Location);
在Visual Studio中,查看FileStream类的定义,发现它是定义在netstandard.dll程序集中的,如图1-4所示。
图1-4 .NET Standard中的FileStream类所在的程序集
反编译[2].NET Standard的核心程序集netstandard.dll中的FileStream类,会发现它的所有方法都是空实现,如图1-5所示。
图1-5 .NET Standard中的FileStream类
可以发现,netstandard.dll以及同文件夹下的其他DLL(dynamic linked library,动态链接库)文件中的代码都只有类和成员的定义,没有具体实现。这说明,netstandard.dll等.NET Standard中的程序集只是在开发时给Visual Studio用的,运行的时候它们是不会被调用的。那么在运行的时候,代码实际调用的是什么呢?
分别创建一个.NET Core和一个.NET Framework控制台项目,然后这两个项目都分别引用上面开发的.NET Standard类库并且调用DemoNetStandardClass.Test方法,执行结果如图1-6和图1-7所示。
图1-6 .NET Core项目执行结果
图1-7 .NET Framework项目执行结果
可以看到,对于同样一个类库中的FileStream类,项目在.NET Framework和.NET Core中执行的时候加载的程序集不一样。也就是说,同样的.NET Standard代码在运行时对应不同的实现。
分别查看.NET Core和.NET Framework中FileStream类的BeginRead方法的代码,可以看到它们有很多区别,如图1-8和图1-9所示。
图1-8 .NET Framework中BeginRead方法的部分代码
图1-9 .NET Core中BeginRead方法的部分代码
.NET Framework和.NET Core中的FileStream类定义的方法也有区别,请仔细查看图1-10和图1-11中圈出的部分。
图1-10 .NET Framework中的FileStream类
图1-11 .NET Core中的FileStream类
虽然.NET Core和.NET Framework的类中方法的定义和方法的实现存在不同的地方,但是对于在.NET Standard中规定的类、方法,它们必须实现。因此.NET Standard相当于定义了.NET Core、.NET Framework、Xamarin的交集,只要是.NET Standard类库,都可以被.NET Core、.NET Framework、Xamarin等项目引用。
.NET Standard随着.NET技术的升级而升级,不同版本的.NET Core、.NET Framework等支持不同版本的.NET Standard,越高版本的.NET Core、.NET Framework等支持的.NET Standard版本越高。表1-1列出了.NET Standard版本和.NET Core、.NET Framework版本的对应关系,这里没有列出Xamarin、Unity等的版本,对之感兴趣的读者可以访问微软官方文档的.NET Standard部分查看详细情况。
表1-1 .NET Standard及各实现版本的对应关系
如果一个类库遵守一个版本的.NET Standard规范,那么不低于对应这个版本的.NET Core、.NET Framework的项目都可以使用这个类库。比如一个类库遵守.NET Standard 2.0规范,那么不低于.NET Core 2.0或者不低于.NET Framework 4.6.1的项目就都可以使用这个类库。
如果我们要编写一个给公众使用的类库,为了让.NET Core、.NET Framework、Xamarin等开发人员都能使用这个类库,这个类库就应该是.NET Standard类库,并且.NET Standard的版本应尽可能低一些,这样低版本的.NET Core、.NET Framework、Xamarin的项目也能使用这个类库。.NET Standard版本越高,代码中能用的API也就越多。作者的建议是先把项目的.NET Standard版本选到最低,如果发现开发时用到的类在这个.NET Standard版本中不存在,再逐步提升项目的.NET Standard版本。和其他类型的项目一样,.NET Standard类库的目标版本可以在项目属性的“目标框架”中选择,如图1-12所示。
如果要开发项目内部使用的类库,并且这个类库只会被.NET Core项目引用,不会被.NET Framework、Xamarin等项目引用,作者还是建议直接建立.NET Core类库项目,因为这样可以省去很多麻烦,而且可以使用.NET Core中一些特有的类和方法。
图1-12 选择项目的.NET Standard类库的目标版本
最后需要注意的一点就是,因为.NET Standard只是规范,不是.NET Framework、.NET Core这样的实现,所以只能建立.NET Standard类库项目,不能建立.NET Standard控制台项目或者Web应用程序等可以直接运行的项目。
总而言之,.NET Standard是一个.NET平台下的规范,使得我们开发的类库可以被.NET Framework、.NET Core、Xamarin等使用,提高了代码的复用性。.NET Standard已经完成了它的历史使命。从.NET 5开始,微软将不再更新.NET Standard,而是会把.NET 5、.NET 6等视为单一的代码库,并会通过编译期和运行时的检查来解决不同平台下它们所支持的功能具有差异这一问题。
1.1.6 项目应该使用.NET Core开发吗
我们对.NET Core已经了解了很多,那么我们的项目应该采用.NET Core开发吗?
必须知道的是,微软已经宣布,它将不会为.NET Framework增加新特性,以后对.NET Framework只会修复程序缺陷。因此,如果读者想体验.NET的新特性,并且使用跨平台、独立部署、模块化等特性,请考虑使用.NET Core。
对于现有的.NET Framework项目,如果已经运行得很好,并且不需要再升级或者增加新的功能,这个项目没必要用.NET Core重写。如果项目比较特殊,必须使用某些.NET Framework支持而.NET Core不支持的特性,这个项目也不能选择.NET Core。此外,如果项目中用到了一些.NET Framework版本的商业组件或者硬件接口组件,并且这些组件不存在.NET Core版本,那么也要谨慎选择使用.NET Core。以上提到的这些情况是很少出现的,对于绝大部分项目,都可以放心地使用.NET Core进行开发。