这篇文章是基于我们将现有项目从ASP.NET MVC迁移到ASP.NET Core的经验而产生的。 我们试图以结构化的形式汇总整个迁移过程,并描述各种瓶颈,以便开发人员可以继续依靠此材料并遵循解决此类问题的路线图。
关于我们的项目的几句话。 我们是ASP.NET上的开源电子商务平台,到转让之时,该平台已经成功存在了9年。 我们在2年前进行了迁移-但是直到现在我们才开始写有关迁移的文章。 当时,我们是决定采取这一步骤的首批重大项目之一。
为什么要切换到ASP.NET Core?
在继续执行从ASP.NET MVC迁移到ASP.NET Core的步骤之前,请先简单介绍一下此平台的好处。

ASP.NET Core的好处因此,ASP.NET Core已经是一个相当知名的开发框架,它已经进行了几次重大更新,这意味着今天它已经相当稳定,技术先进并且可以抵抗XSRF / CSRF攻击。
跨平台是使他获得越来越多知名度的标志之一。 从现在开始,您的Web应用程序可以在Windows和Unix环境中运行。
模块化 -ASP.NET Core完全以NuGet软件包的形式提供,这使您可以优化应用程序,包括所选的必需软件包。 这样可以提高解决方案的性能,并减少升级单个零件所需的时间。 这是第二个重要功能,使开发人员可以更灵活地将新功能集成到他们的解决方案中。
性能是构建高性能应用程序的又一步,ASP.NET Core每秒处理的请求比ASP.NET 4.6多2300%,比node.js每秒多800%。 您可以
在此处或
此处自己检查详细的性能测试。
中间件是针对应用程序内请求的新型轻量级高性能模块化管道。 每个中间件都处理一个HTTP请求,然后决定返回结果,或者传递下一个中间件。 这种方法使开发人员可以完全控制HTTP管道,并有助于开发应用程序的简单模块,这对于不断发展的开源项目很重要。
ASP.NET Core MVC提供了简化Web开发的功能。 NopCommerce已经使用了诸如“模型-视图-控制器”模板,Razor语法,模型绑定和验证之类的功能,但是出现了新工具:
- 标记助手。 这是服务器端代码,用于在Razor文件中创建和呈现HTML元素。
- 查看组件。 这是一个新工具,类似于局部视图,但功能更强大。 当需要重用渲染逻辑并且任务对于部分视图而言过于复杂时,nopCommerce使用视图组件。
- DI的观点。 尽管视图中显示的大多数数据都是从控制器传递的,但nopCommerce的视图中的依赖项注入更为方便。
当然,ASP.NET Core具有更多功能,但是我们只是研究了最有趣的功能。
现在让我们讨论将应用程序移植到新平台时应考虑的事项。
迁移
文本将包含大量指向ASP.NET Core官方文档的链接,以帮助获取有关该主题的更多详细信息。 对于首次遇到类似任务的开发人员尤其重要。
步骤1.准备工具
您需要做的第一件事是将Visual Studio 2017升级到15.3版或更高版本。 并安装最新版本的.NET Core SDK。
开始迁移之前,建议您使用.NET
.Net Portability Analyzer可移植性分析工具。 这是了解从一个平台到另一个平台的转换过程的艰巨起点。 但是,当然,此工具不能解决所有问题,并且在此过程中会存在很多陷阱。 接下来,将描述需要通过的主要阶段,并显示本项目中使用的解决方案。
您需要做的第一件事就是更新指向支持.NET Standard的项目中使用的库的链接。
步骤2.支持.Net Standard的NuGet软件包的兼容性分析
如果在项目中使用NuGet软件包,则需要检查它们是否与.NET Core兼容。 一种方法是使用
NuGetPackageExplorer工具。
步骤3..NET Core使用新的csproj文件格式
使用新方法添加.NET Core中引入的第三方链接很重要:将新的类库添加到解决方案时,您应该打开主项目文件,并将其内容替换为以下内容:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp2.2</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.6" /> ... </ItemGroup> ... </Project>
来自连接的库的链接将自动下载。 有关project.json和CSPROJ属性之间映射的更多信息,请参见
此处和
此处的官方文档。
步骤4.更新名称空间
您必须删除对System.Web的所有使用并将其替换为Microsoft.AspNetCore。
步骤5.您必须配置Startup.cs文件。 而不是使用global.asax
ASP.NET Core具有加载应用程序的新机制。 该应用程序的入口点变成
Startup
,并且对
Global.asax文件的依赖关系消失了。
Startup
在应用程序中注册中间件套件。
Startup
必须包括
Configure
方法。 在“
Configure
将所需的中间件添加到管道中。
Startup.cs问题- 为MVC和WebAPI请求配置中间件
- 的配置设置:
app.UseMvc(routes => { routes.MapRoute("areaRoute", "{area:exists}/{controller=Admin}/{action=Index}/{id?}"); routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); });
同时,应将名称为Area的文件夹(位于Admin文件夹所在的文件夹)放置在应用程序的根目录中。 现在,
[Area("Admin")] [Route("admin")]
属性将用于将控制器与此区域连接。
仅保留为控制器中描述的所有操作创建视图。
[Area("Admin")] [Route("admin")] public class AdminController : Controller { public IActionResult Index() { return View(); } }
验证方式现在,您无需将IFormCollection传递给控制器,因为在这种情况下,asp.net服务器验证已禁用-如果IFormCollection不为null,则MVC将禁止进一步验证。 解决问题的方法可以是将此属性添加到模型中,这将阻止我们直接传递给控制器方法。 仅当存在模型时此规则才有效,但是如果没有模型,则将没有验证。
子属性不再自动验证。 必须手动指定。
步骤6.将HTTP处理程序和HTTP模块传输到中间件
HTTP处理程序和HTTP模块在本质上与
ASP.NET Core中的
中间件概念非常相似,但是与模块不同,中间件顺序是基于将它们插入请求管道的顺序。 在大多数情况下,模块的顺序基于
应用程序生命周期事件。 答案的中间件顺序与请求的顺序相反,而请求和答案的模块顺序相同,基于此,您可以继续升级。
因此,有待更新的内容:
- 中间件模块的迁移(AuthenticationMiddleware,CultureMiddleware等)
- 中间件处理程序
- 使用新的中间件
我们项目中的身份验证未使用内置的凭据系统;出于这些目的,使用了根据新ASP.NET Core结构开发的中间件AuthenticationMiddleware。
public class AuthenticationMiddleware { private readonly RequestDelegate _next; public AuthenticationMiddleware(IAuthenticationSchemeProvider schemes, RequestDelegate next) { Schemes = schemes ?? throw new ArgumentNullException(nameof(schemes)); _next = next ?? throw new ArgumentNullException(nameof(next)); } public IAuthenticationSchemeProvider Schemes { get; set; } public async Task Invoke(HttpContext context) { context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature { OriginalPath = context.Request.Path, OriginalPathBase = context.Request.PathBase }); var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>(); foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync()) { try { if (await handlers.GetHandlerAsync(context, scheme.Name) is IAuthenticationRequestHandler handler && await handler.HandleRequestAsync()) return; } catch {
ASP.NET提供了许多嵌入式中间件,您可以在应用程序中使用它们,但是请注意,开发人员
可以创建自己的中间件并将其添加到HTTP请求管道中。 为了简化此机制,我们添加了一个特殊的接口,现在足以创建一个实现该接口的类。
public interface INopStartup {
您可以在此处添加和配置中间件:
步骤7.使用集成DI
依赖注入是ASP.NET Core应用程序设计过程中的关键功能之一。 它使您可以创建松散耦合的应用程序,这些应用程序更易于测试,模块化并且因此更易于维护。 通过遵循依赖倒置的原理,可以实现这一点。 要安装依赖项,请使用IoC容器(控制反转)。 在ASP.NET Core中,这样的容器由IServiceProvider接口表示。 服务通过
Startup.ConfigureServices()
方法安装在应用程序中。
任何注册的服务都可以配置三个范围:
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddSingleton<Isingleton,MySingleton>();
步骤8.使用WebAPI项目兼容性Shell(Shim)
为了简化现有Web API实现的迁移,建议使用
Microsoft.AspNetCore.Mvc.WebApiCompatShim NuGet包。 支持以下
兼容功能 :
- 添加ApiController类型
- 启用Web API样式模型绑定
- 扩展模型绑定,以便控制器操作可以接受HttpRequestMessage类型的参数。
- 添加消息格式化程序,该消息格式化程序允许操作返回HttpResponseMessage类型的结果
services.AddMvc().AddWebApiConventions(); routes.MapWebApiRoute(name: "DefaultApi", template: "api/{controller}/{id?}" );
步骤9.传输应用程序配置
以前,一些设置保存在web.config文件中。 现在,我们正在基于
配置提供者建立的键值对采取一种
新方法 。 这是ASP.NET Core中推荐的机制,我们使用appsettings.json文件。
如果出于某些原因要继续使用* .config,则也可以使用NuGet包
System.Configuration.ConfigurationManager
。 在这种情况下,您将不得不放弃在Unix平台上运行该应用程序的功能,而只能在IIS下运行它。
如果要使用
Azure Key Vault配置提供程序,则应参考“内容
迁移到Azure Key Valut内容” 。 在我们的项目中,这不是任务。
步骤10.将静态内容传输到wwwroot
要提供
静态内容,您必须告诉Web主机当前目录内容的根。 默认值为wwwroot。 您可以通过设置中间件来自定义目录以存储静态文件。
步骤11.将EntityFramework移植到EF Core
如果项目使用了
EF Core
不支持 的Entity Framework 6的某些特定
功能 ,则可以在
NET Framework
上运行该应用程序。 但是,在这种情况下,您必须牺牲多平台。 该应用程序只能在Windows和IIS下运行。
让我们看一下要考虑的主要变化:- System.Data.Entity命名空间替换为Microsoft.EntityFrameworkCore
- DbContext构造函数的签名已更改。 现在您需要注入DbContextOptions
- HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)方法替换为ValueGeneratedNever()
- WillCascadeOnDelete(false)方法替换为OnDelete(DeleteBehavior.Restrict)
- 被OnModelCreating(ModelBuilder modelBuilder)取代的OnModelCreating(DbModelBuilder modelBuilder)方法
- HasOptional方法不再可用
- 对象的配置已更改,现在您需要使用OnModelCreating,因为 EntityTypeConfiguration不再可用
- ComplexType属性不再可用
- 用DbSet替换IDbSet接口
- ComplexType-复杂类型支持在EF Core 2中以拥有的实体类型( https://docs.microsoft.com/en-us/ef/core/modeling/owned-entities )出现,并且在EF Core中没有带有QueryType的主键的表2.1( https://docs.microsoft.com/zh-cn/ef/core/modeling/query-types )
- 与使用[Entity] _Id模式的EF6不同,EF Core中的外键使用[Entity] ID模式生成阴影属性 。 因此,首先将外键作为常规属性添加到实体。
- 要为DbContext支持DI,请在
ConfigureServices
中ConfigureServices
DbContex
使用
SQL比较工具来验证
EF Core
在
迁移期间是否生成了与
Entity Framework
类似的数据库架构。
步骤12.删除所有HttpContext引用,替换过时的类,并更改名称空间
在项目迁移期间,您会发现已重命名或移动了足够多的类,现在您需要使所有内容与新要求保持一致。 这是您可能会遇到的主要过渡的列表:
- HttpPostedFileBase-> IFormFile
- 现在可以通过IHttpContextAccessor访问HttpContext。
- HtmlHelper-> IHtmlHelper
- ActionResult-> IActionResult
- HttpUtility-> WebUtility
- 可以从HttpContext.Session访问ISession而不是HttpSessionStateBase。 来自Microsoft.AspNetCore.Http
- Request.Cookies返回IRequestCookieCollection:IEnumerable <KeyValuePair <字符串,字符串>>,所以不是Microsoft.AspNetCore.Http的HttpCookie,KeyValuePair <字符串,字符串>
命名空间替换:
- SelectList-> Microsoft.AspNetCore.Mvc.Rendering
- UrlHelper-> WebUtitlity
- MimeMapping-> FileExtensionContentTypeProvider
- MvcHtmlString-> IHtmlString和HtmlString
- ModelState,ModelStateDictionary,ModelError-> Microsoft.AspNetCore.Mvc.ModelBinding
- FormCollection-> IFormCollection
- Request.Url.Scheme-> this.Url.ActionContext.HttpContext.Request.Scheme
其他:
- MvcHtmlString.IsNullOrEmpty(IHtmlString)-> String.IsNullOrEmpty(variable.ToHtmlString())
- [ValidateInput(false)]-通常不再使用,不需要
- HttpUnauthorizedResult-> UnauthorizedResult
- [AllowHtml]-没有更多的指令,也不需要
- TagBuilder.SetInnerText方法已替换-现在是InnerHtml.AppendHtml
- 返回Json时不再需要JsonRequestBehavior.AllowGet
- HttpUtility.JavaScriptStringEncode。 -> JavaScriptEncoder.Default.Encode
- Request.RawUrl。 有必要分别连接Request.Path + Request.QueryString
- AllowHtmlAttribute-没有更多的类
- XmlDownloadResult-现在您可以使用返回的文件(Encoding.UTF8.GetBytes(xml),“ application / xml”,“ filename.xml”);
- [ValidateInput(false)]-不再有指令,因此不需要
步骤13.更新身份验证和授权
我已经在上面写过,在我们的项目中,身份验证不是使用内置的身份系统来实现的,而是在单独的中间件层中进行的。 但是,ASP.NET Core具有自己的提供凭据的机制。 可以在
此处的文档中找到更多详细信息。
至于数据保护-我们不再使用
MachineKey 。 相反,我们使用内置的数据保护功能。 默认情况下,启动应用程序时会生成密钥。 数据仓库可以是:
- 文件系统-基于文件系统的密钥库
- Azure存储-Azure Blob存储中的数据保护密钥
- Redis-Redis缓存中的数据保护密钥
- 注册表-如果应用程序无权访问文件系统,则必须使用注册表
- EF Core-密钥存储在数据库中
如果内置机制不合适,则可以通过提供自定义
IXmlRepository来指定自己的密钥存储机制。
步骤14.更新JS / CSS
静态资源的使用方式已更改:现在,所有这些资源都应存储在
wwwroot项目的根文件夹中,除非指定了其他设置。
使用javascript内置块时,建议将其移至页面末尾。 只需对标签使用asp-location =“ Footer”属性。 相同的规则适用于js文件。
使用
BundlerMinifier扩展名替代System.Web.Optimization-这将使您在构建项目时绑定并最小化JavaScript和CSS。
链接到文档。
步骤15.迁移视图
不再使用子操作。 相反,ASP.NET Core提供了一个新的强大工具
-ViewComponents ,该工具被异步调用。
如何从ViewComponent获取字符串:
不再需要使用HtmlHelper-ASP.NET Core具有大量内置的帮助程序标记功能(
Tag Helpers )。 当应用程序运行时,它们由服务器端的Razor引擎处理,并最终转换为标准html元素。 这大大简化了应用程序开发。 而且,当然,您可以实现自己的标记助手。
我们开始在视图中使用依赖项注入,而不是使用
EngineContext
允许设置和服务。
因此,关于视图迁移的要点:
- 将
Views/web.config Views/_ViewImports.cshtml
转换Views/web.config Views/_ViewImports.cshtml
用于导入名称空间和Views/web.config Views/_ViewImports.cshtml
依赖项。 该文件不支持其他Razor
功能,例如功能和节定义。 - 将
namespaces.add
转换为@using
- 将所有设置转移到主应用程序配置
Scripts.Render
和Styles.Render
不存在。 用输出链接替换libman
或BundlerMinifier
总结
从我们的经验中可以看出,迁移大型Web应用程序的过程非常耗时,如果没有陷阱就很难执行。 我们计划在第一个稳定版本发布后立即切换到新框架,但是我们无法立即完成它:到那时,一些关键功能尚未转移到.NET Core,尤其是与EntityFramework相关的功能。 因此,我们必须首先使用混合方法发布下一个版本-具有.NET Framework依赖项的.NET Core体系结构。
.NET Core 2.1发行后,我们能够完全适应该项目,当时有一个稳定的解决方案已在新架构上工作-剩下的就是替换一些软件包并用EF Core重写工作。 因此,完全迁移到新框架需要花费几个月的时间。
您可以从
GitHub上的
存储库中了解有关我们项目的更多信息。