The basics of the project are relatively simple. It consists of a number of adapters that enable collaboration and sharing of state between a System.Web-based ASP.NET application and one built using ASP.NET Core. This allows, for example, an ASP.NET application to share cookies, session and authentication state with an ASP.NET Core application, despite the fact that the nature of these elements have no implicit compatibility between frameworks. The adapters also act a a drop-in replacement for System.Web in business logic libraries that depend on types within System.Web - HttpContext, Session and so on, so that they "just work" in a .NET Core application, which means that you don't have to rewrite such libraries to get your .NET Core application up and running.
The adapters enable a migration based on the "strangler fig" pattern, whereby the original legacy application is slowly replaced, route by route, by the new application. You create an ASP.NET Core application that acts as the entry point. It will proxy all requests that it does not cater for to the legacy application. Over time, as you implement equivalent endpoints in the new application, the old application is slowly replaced, or strangled by the new application.
As part of the project, a Visual Studio extension has been made available that helps with the initial configuration. At the moment, it caters for migrating from .NET MVC or Web Forms to a .NET Core MVC application, and it also includes some support for migrating controllers and views to their .NET Core equivalents. The tool is in experimental stage at the moment and doesn't offer Razor Pages as a migration target, despite the fact that Razor Pages is the recommended go-to for server-side HTML generation on .NET Core. However, the SystemWebAdapters package supports this option, although it (currently?) requires manual configuration. Note: there is currently no support for migration to Blazor.
In the following walkthrough, I show the steps required to manually configure the starting point for a migration from a Web Forms application that has Identity enabled (Individual accounts) to a Razor Pages application using the adapters. The steps would be the same if you wanted to migrate from .NET Framework MVC to Razor Pages. I will refer to the .NET Framework app as the "legacy" app.
- Add the
Microsoft.AspNetCore.SystemWebAdapters
package to legacy app. This is in preview at the moment (currently preview 3), so you will need to check Include Preview option if you are using the Visual Studio UI for managing Nuget packages, or the-pre
switch when executinginstall-package
. As part of the installation process, it will addSystemWebAdapterModule
to themodules
node in web.config. - Add the following to
Application_Start
in Global.asax. This step configures the adapters and support for proxied requests in the legacy app. You will be sharing authentication state between two applications, so the ApiKey should be a strong one, and you should ensure that both applications run under HTTPS. Authentication will be handled remotely by the legacy application :SystemWebAdapterConfiguration.AddSystemWebAdapters(this) .AddProxySupport(options => options.UseForwardedHeaders = true) .AddRemoteApp(options => { options.ApiKey = "test-key"; }) .AddRemoteAppAuthentication();
- Create a new Razor Pages app and install the same
Microsoft.AspNetCore.SystemWebAdapters
package. Then installYarp.ReverseProxy
. - Add the following section to the appSettings.config file. This
provides configuration for the YARP proxy server. For now, the
value that you assign to the
fallbackApp:Address
property should be the URL that the local version of your legacy app runs under:"ReverseProxy": { "Routes": { "fallbackRoute": { "ClusterId": "fallbackCluster", "Order": "1", "Match": { "Path": "{**catch-all}" } } }, "Clusters": { "fallbackCluster": { "Destinations": { "fallbackApp": { "Address": "https://localhost:xxxx" } } } } }
- Open Program.cs and add the following to the
WebApplicationBuilder
configuration. This configures SystemWebAdapters and YARP in the .NET Core app:builder.Services.AddSystemWebAdapters().AddRemoteAppAuthentication(true).AddRemoteApp(options => { options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]); options.ApiKey = "test-key"; }); builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
- Add authentication to the ASP.NET Core app after routing but before authorization:
app.UseRouting(); app.UseAuthentication(); app.UseAuthorization();
- Then add SystemWebAdpaters and reverse proxy middleware to the pipeline using
these extension methods. This goes after routing but before
app.MapRazorPages
:app.UseSystemWebAdapters(); app.MapReverseProxy();
Make sure that the legacy app is running and then fire up the ASP.NET Core app. Initially, you should get the Razor Pages application's default home page:
In my example, I've added a link to the legacy app login page: /account/login, which has not been implemented in the new application. If I navigate to it, I reach the old Web forms app:
Once logged in, I am redirected to the home page, which is served by the new app again:
Notice that my identity, which was created by the Web Forms app is being shared with the Razor Pages app. This is the remote authentication at work.
Deployment
Deployment is pretty straightforward. You need two web sites; one
for the legacy app, and one for the ASP.NET Core app. You configure
the proxy server fallbackApp Address
to the URL of the
legacy app. You probably already have the legacy app running, so it
will most likely just be a case of changing the binding for the
legacy app to a different address, while configuring the new .NET
Core app to run under the legacy app's current address.
Summary
The SystemWebAdapters project should be of great interest if you have a large .NET Framework app that you would like to migrate to .NET Core but cannot afford to put everything on hold while undertaking the significant work that is required. It is currently in preview and supports migration to ASP.NET MVC and Razor Pages. Experimental tooling is available that facilitates configuration and porting to MVC, but as demonstrated in this article, it is relatively straightforward to manually configure a Razor Pages application as a migration target.
It should be stressed that this project is still in preview and is subject to change. You can keep up with progress by following the project at Github.