增量式ASP.NET向ASP.NET Core迁移问题和使用演示

435 阅读10分钟

ASP.NET Core是现代的、统一的、用于.NET的网络框架,可以处理你所有的网络开发需求。它是完全开源的,跨平台的,并且充满了创新的功能。Blazor、SignalR、gRPC、最小的API等。在过去的几年里,我们看到许多客户希望将他们的应用程序从ASP.NET转移到ASP.NET Core。迁移到ASP.NET Core的客户已经实现了巨大的成本节约,包括Azure Cosmos DBMicrosoft GraphAzure Active Directory

我们也看到了客户在经历这个过程中所面临的许多挑战。这包括ASP.NET Core上没有的依赖性,以及这种迁移所需的时间。我们一直在努力帮助客户减轻这种迁移,包括在过去一年中对.NET升级助手的工作。今天,我们宣布了更多的工具、库和模式,这些工具、库和模式将使应用程序能够以更少的努力和增量的方式迁移到ASP.NET Core。这项工作将在GitHub的一个新仓库中完成,我们很高兴能与社区合作,以确保我们建立正确的适配器和工具集。

请看下面Mike Rousos的BUILD会议,看看新的增量式ASP.NET迁移体验,或者继续阅读以了解更多。

目前面临的挑战

这个过程缓慢而困难的原因有很多,我们退一步看了一下外部和内部合作伙伴的例子,看看什么是他们最迫切关注的问题。这可以归结为我们想要解决的几个关键主题。

  • 一个大型的应用程序如何能够逐步转移到ASP.NET Core,同时仍然能够创新并为其业务提供价值?
  • 如何使针对System.Web.HttpContext 编写的库在不重复和完全重写的情况下实现现代化?

让我深入了解这些问题,分享我们所看到的和我们正在做的工作,以提供帮助。

增量式迁移

许多大型的应用程序每天都被用于关键业务的应用。这些需要继续运行,不能因为可能长时间迁移到ASP.NET Core而被搁置。这意味着,在迁移过程中,应用程序仍然需要为生产做好准备,新的功能可以照常添加和部署。

一个已经被证明适用于这种过程的模式是Strangler Fig Pattern。Strangler Fig模式被用来逐个替换现有的遗留系统,直到整个系统被更新,旧系统可以退役。这种模式在抽象上是相当容易掌握的,但问题是如何在实践中真正使用它。这是增量迁移过程中的一部分,我们想围绕它提供具体指导。

System.Web.dll在支持库中的用法

ASP.NET应用程序依赖System.Web.dll中的API来访问诸如cookie、头文件、会话和其他来自当前请求的值。ASP.NET应用程序经常依赖依赖这些API的库,如HttpContext,HttpRequest,HttpResponse 等。然而,这些类型在ASP.NET Core中是不可用的,在现有的代码库中重构这些代码是非常困难的。

为了简化这部分的迁移旅程,我们引入了一组适配器,实现了System.Web.HttpContextMicrosoft.AspNetCore.Http.HttpContext 的形状。这些适配器包含在新的包Microsoft.AspNetCore.SystemWebAdapters 中。这个包将允许你继续使用你现有的逻辑和库,同时额外针对.NET Standard 2.0、.NET Core 3.1或.NET 6.0,支持在ASP.NET Core上运行。

例子

让我们来看看一个应用程序的增量迁移会是什么样子。 我们将从一个ASP.NET应用程序开始,它有使用基于System.Web的API的支持库。

这个应用程序被托管在IIS上,并有一套用于部署和维护的流程。迁移过程的目的是在不影响当前部署的情况下向ASP.NET Core迁移。

第一步是引入一个基于ASP.NET Core的新应用程序,它将成为入口点。流量将进入ASP.NET Core应用程序,如果该应用程序不能匹配路由,它将通过YARP将请求代理给ASP.NET应用程序。大部分代码将继续在ASP.NET应用程序中,但ASP.NET Core应用程序现在已经设置好,可以开始迁移路由。

这个新的应用程序可以被部署到任何有意义的地方。有了ASP.NET Core,你有多种选择。IIS/Kestrel,Windows/Linux,等等。然而,保持与ASP.NET应用类似的部署将简化迁移过程。

为了开始迁移依赖于HttpContext 的业务逻辑,需要针对Microsoft.AspNetCore.SystemWebAdapters 构建库。这允许使用System.Web APIs的库以.NET Framework、.NET Core或.NET Standard 2.0为目标。这将确保这些库使用的是ASP.NET和ASP.NET Core都可以使用的表面区域。

现在我们可以开始把路由一个一个地转移到ASP.NET Core应用程序中。这些可以是MVC或Web API控制器(甚至是控制器中的单个方法)、Web Forms ASPX页面、HTTP处理程序或路由的一些其他实现。一旦路由在ASP.NET Core应用程序中可用,它就会被匹配并从那里提供服务。

在这个过程中,将确定必须转移到.NET Core上运行的额外服务和基础设施。一些选项包括(按可维护性顺序列出):

  1. 将代码移到共享库中
  2. 在新项目中链接代码
  3. 复制代码

随着时间的推移,核心应用程序将开始处理比.NET框架应用程序更多的路由服务。

在这个过程中,你可能在ASP.NET Core和ASP.NET Framework应用程序中都有路由。这可以让你进行一些A/B测试,以确保功能符合预期。

一旦不再需要.NET框架应用程序,它可以被删除。

在这一点上,应用程序作为一个整体运行在ASP.NET Core应用程序堆栈上,但它仍在使用这个 repo的适配器。在这一点上,我们的目标是删除对适配器的使用,直到应用程序完全依赖ASP.NET Core应用程序框架。

通过让你的应用程序直接使用ASP.NET Core的API,你可以获得ASP.NET Core的性能优势,并利用适配器后面可能没有的新功能。

开始使用

现在我们已经奠定了Strangler Fig模式和System.Web适配器的基础,让我们拿着一个应用程序,看看我们需要做什么来应用它。

为了解决这个问题,我们将使用一个预览版的Visual Studio扩展,它可以帮助我们完成一些必要的步骤。你还需要最新的Visual Studio预览版来使用这个扩展。

我们将使用一个ASP.NET MVC应用程序并开始迁移过程。要开始,在项目上点击右键,选择迁移项目,这时会打开一个工具窗口,有一个开始迁移的选项。迁移向导会打开。

Migrate project wizard

你可以用一个新的项目来设置你的解决方案,或者你可以选择一个现有的ASP.NET Core项目来使用。这将对ASP.NET Core项目做一些事情。

  • 添加Yarp.ReverseProxy ,并为其添加初始配置,以访问原始框架应用程序(称为fallback )。
  • launchSettings.json中添加一个环境变量,以设置访问原始框架应用程序所需的环境变量
  • 添加Microsoft.AspNetCore.SystemWebAdapters ,注册服务,并为其插入中间件

现在,ASP.NET Core应用程序的启动代码将看起来像这样:

using Microsoft.AspNetCore.SystemWebAdapters;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSystemWebAdapters();
builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();
app.UseSystemWebAdapters();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapReverseProxy();

app.Run();

在这一点上,你可以运行ASP.NET Core应用程序,它将代理所有在核心应用程序中不匹配的请求到框架应用程序。如果你使用默认模板,你会看到以下行为。

如果你进入/ ,它会向你展示ASP.NET Core应用程序的/Home/Index

/

如果你转到一个在ASP.NET Core上不存在但在框架应用上存在的页面,它现在会浮现出来。

/home/contact

请注意,URL是相同的(https://localhost:7234),但在需要时将代理到框架应用程序的请求。现在你已经准备好开始把路由带到核心应用程序上,包括引用System.Web.dll的库。

代理支持

框架应用程序现在将成为一个反向代理的下游。这意味着一些值,如请求URL,将是不同的,因为它将不使用公共入口点。要更新这些值,请在Global.asax.csGlobal.asax.vb中用以下代码打开对框架应用程序的代理支持。

protected void Application_Start()
{
  Application.AddSystemWebAdapters()
      .AddProxySupport(options => options.UseForwardedHeaders = true);
}

这一变化可能会对你的应用程序造成额外的影响。解决这个问题将是实现Strangler Fig模式的初始过程的一部分。如果在这里遇到的挑战还没有被记录下来,请提交问题。

会话支持

会话在ASP.NET和ASP.NET Core之间的工作方式非常不同。为了在两者之间提供一个桥梁,适配器中的会话支持是可扩展的。为了在ASP.NET Core和ASP.NET之间有相同的会话行为(和共享的会话状态),我们提供了一种方法,用框架应用程序的值来填充Core上的会话状态。

这种设置将在框架应用程序中添加一个处理程序,允许核心应用程序查询会话状态。这有潜在的性能和安全问题需要考虑,但它允许两个应用程序之间共享会话状态。

为了设置这一点,将Microsoft.AspNetCore.SystemWebAdapters.SessionState 包添加到两个应用程序。

接下来,在ASP.NET Core应用程序上添加服务:

builder.Services.AddSystemWebAdapters()
    .AddJsonSessionSerializer(options =>
    {
        // Serialization/deserialization requires each session key to be registered to a type
        options.RegisterKey<int>("test-value");
        options.RegisterKey<SessionDemoModel>("SampleSessionItem");
    })
    .AddRemoteAppSession(options =>
    {
        // Provide the URL for the remote app that has enabled session querying
        options.RemoteApp = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]);

        // Provide a strong API key that will be used to authenticate the request on the remote app for querying the session
        options.ApiKey = "strong-api-key";
    });

这将注册键,以便会话管理器知道如何序列化和反序列化一个给定的键,以及告诉它远程应用程序的位置。请注意,现在需要通过System.Text.Json ,该对象是可序列化的,这可能需要改变一些被存储在会话中的对象。

对于一直临时使用会话值的代码库来说,识别键可能是一个挑战。第一个预览版要求你注册它们,但没有帮助识别它们。在未来的更新中,我们计划记录有用的信息,如果发现未注册的键,可以选择抛出。

最后,在框架应用程序上以类似的方式在Global.asax中添加以下代码:

protected void Application_Start()
{
    Application.AddSystemWebAdapters()
        .AddRemoteAppSession(
            options => options.ApiKey = "some-strong-key-value",
            options => options.RegisterKey<int>("some-key"));
}

一旦这些设置完成,你就可以像你所期望的那样使用(int)HttpContext.Session["some-key"]

总结

今天我们介绍了一些工具和库,以帮助解决增量迁移问题,并帮助你更快、更容易地推进你的ASP.NET应用程序。

在这个领域还有更多的工作要做,我们希望你能从中得到帮助。我们正在GitHub上开发这个工具,网址是github.com/dotnet/syst…,欢迎大家提出问题和PR来推动这个工具的发展。

其他链接

The postIncremental ASP.NET to ASP.NET Core Migrationappeared first on.NET Blog.