Migrating from ASP.NET to ASP.NET Core with Project Migrations Part 4

Sayed Ibrahim Hashimi

Taylor Southwick

ASP.NET Core is a unified and modern web framework for .NET. Migrating existing ASP.NET apps to ASP.NET Core has many advantages, including better performance, cross-platform support (Windows, macOS, Linux), and access to all the latest improvements to the modern .NET web platform. But migrating from ASP.NET to ASP.NET Core can be very challenging and time consuming due to the many differences between the two frameworks. That’s why we’ve been working to provide libraries and tooling for performing incremental migrations from ASP.NET to ASP.NET Core so that you can slowly migrate existing ASP.NET apps a piece at a time while still enabling ongoing app development. We’ve been working on tooling in Visual Studio to simplify the migration experience, and a new update to the incremental ASP.NET migration tooling is now available.

Download and install the extension

In this series we’ve previously posted the following posts related to project migrations:

In this blog post we’ll cover the significant updates to the Project Migrations Extension as well as updates to the System.Web Adapters.

Updates to Visual Studio tooling

We’ve made the following improvements to the incremental migration tooling:

  • Incrementally migrate class library projects, including both C# and VB.
  • Incrementally migrate ASP.NET Framework projects with references to class library project(s).
  • Incrementally migrate VB ASP.NET Framework projects, excluding VB view files.
  • Various UX improvements across the extension.

We’ll expand on each of these topics under the headings below.

Support for Class Library projects

The extension now has support for incrementally migrating .NET Framework class library projects to .NET 6 or 7. This includes both C# and VB class library projects.

Migrating class library projects is typically much simpler than migrating ASP.NET projects. However, class library projects may contain lots of code that needs to be migrated. This may make it difficult to migrate the entire project at once. And while the class library is being migrated, projects that reference the class library still need to function.

Incrementally migrating class library projects enables you to migrate to the latest .NET versions in manageable steps. In addition, when you migrate items from an ASP.NET project that depends on code in a class library, the extension can determine those dependencies and migrate them as needed. With this approach the original .NET Framework project is not modified and can still be referenced by other projects.

If you’re planning to incrementally migrate an ASP.NET Framework project that depends on one or more class library projects, it’s recommended to use the Migrate Project feature on all the class library projects first. When you migrate a .NET Framework class library project, a new class library project will be created targeting .NET 6/7. You’ll be prompted to select the version of .NET to target. The class library projects do not need to be fully migrated before the web project. At that point, none of the content from the original project will be migrated. Items will be migrated as you migrate items in the ASP.NET Framework project. From here, you can start to incrementally migrate code directly from the original class library to the new .NET Core class library, or you can migrate code from the web project which will automatically migrate dependent code from the original class library to the new class library project. In the section below we’ll describe how to migrate ASP.NET Framework projects that depend on class library projects.

Incrementally migrate ASP.NET Framework projects with references to class library project(s)

Now that the extension can migrate class library projects, that has been integrated into the process of migrating content from ASP.NET Framework projects to ASP.NET Core projects. For example, if you migrate an MVC controller from your ASP.NET Framework project and the controller being migrated depends on code in a dependent class library project, the dependent code will be migrated as well as long as the code lives in a project with a corresponding migration project. Note that this means you must perform Migrate Project on each .NET Framework class library project that the ASP.NET Framework project depends on. Once this association has been made, the extension can automatically migrate items across projects as needed. This works best for C# ASP.NET Framework projects. Support for VB Framework project have some special considerations which we’ll expand on in the next section.

If you’ve already migrated some items from the ASP.NET Framework project before migrating the class library project(s), you can migrate the class library project(s) and then re-run the migrations for the items which have already been migrated. On the next run, the dependencies in the class library project(s) will be detected and migrated.

Incrementally migrate VB ASP.NET Framework projects

In previous releases we didn’t have any support for VB ASP.NET Framework projects. In this release we added support for incrementally migrating VB projects. You can incrementally migrate VB class library projects to .NET 6 or 7.

When migrating a VB ASP.NET Framework project, a new C# ASP.NET Core project will be created. Razor (.cshtml, .razor) support in ASP.NET Core is based on C# and requires a C# project. ASP.NET Core doesn’t support VB views (.vbhtml), so they need to be manually migrated. The tool also cannot automatically migrate VB code to C#. If the amount of VB code in the ASP.NET project is large, it’s recommended to move the code to a new VB class library targeting .NET Framework and then use the new class library incremental migration experience. The migrated VB class library can then be referenced from the ASP.NET Core project.

User experience improvements

In this release we’ve made several user experience (UX) improvements to the incremental migration tooling. We’ll briefly go over the significant updates here.

We improved the dialog for selecting whether to create a new migration project or use an existing one. You can see the improved dialog in the following image:

select to create a new project or select an existing project

In previous releases the selection for a New Project or Existing Project was a radio button. We have also significantly updated the Summary page which appears after you migrate items. The updated dialog is shown next.

summary of changes

We feel that with the updates to this page, its much clearer what actions have taken place as well as being easier to read. We’ve also made several smaller UX improvements that you may notice as you use the new version of this extension.

Known tooling issues

This extension is still in the early stages, so you may run into some issues. Here are the significant known issues that exist in the current version of the extension.

  • Web content files (CSS/JS/etc.) are not migrated.
  • Limited number of code updates currently. Migrated code may need manual updates.
  • No migration support for ASP.NET Identity related files.
  • No migration support for ASP.NET Web Forms (.aspx) files.
  • Limited support for migrating Razor (.cshtml) views migration. No support for partial views currently.
  • No migration support for MVC areas.

This is not a complete list of known issues, but these are the most important ones that we’ve identified.

Please give this new extension a try and please provide feedback so that we can improve this feature as we proceed.

System.Web Adapters Updates

We’ve also released an updated version of the System.Web adapters for ASP.NET Core. This release of the System.Web adapters is mostly a stabilization of APIs and functionality that was available in previous previews. The main change for this release is that the NuGet package structure has been modified:

  • Microsoft.AspNetCore.SystemWebAdapters: Subset of the APIs from System.Web.dll backed by Microsoft.AspNetCore.Http types. This is available for .NET 4.5+, .NET Standard 2.0, and .NET 6+ and is intended for libraries. This package contains the APIs from System.Web.
  • Microsoft.AspNetCore.SystemWebAdapters.CoreServices: Support for adding services to ASP.NET Core applications to enable migration efforts. This is available for .NET 6+. This package contains services that can be added to your ASP.NET Core application to enable scenarios exposed by the System.Web APIs.
  • Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices: Support for adding services to ASP.NET Framework applications to enable migration efforts. This is available for .NET 4.7.2+. This package contains services that can be added to your ASP.NET Framework application to enable incremental migration.

Update to latest version

To upgrade to the latest version:

  1. Uninstall Microsoft.AspNetCore.SystemWebAdapters.SessionState. This is no longer necessary.
  2. Upgrade Microsoft.AspNetCore.SystemWebAdapters to the latest version (1.0.0-rc.1.22477.1) in library projects.
  3. Install Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices to your ASP.NET Framework application
  4. Install Microsoft.AspNetCore.SystemWebAdapters.CoreServices in your ASP.NET Core projects.

Configuration Changes

This release adds adapters for a few more APIs from HttpContext, HttpRuntime, and others that should help with migrating code that relies on these types.

Beyond these changes, the main change to the APIs is that the builders used to construct the services have been separated for ASP.NET Core vs Framework. This results in changes to what packages you use where and how you register services.

For example, in ASP.NET Core you now register services for the System.Web adapters as follows:

var builder = WebApplication.CreateBuilder();

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSystemWebAdapters()
    .AddJsonSessionSerializer(options => 
    {
      options.RegisterKey<int>("key1");
    })
    .AddRemoteAppClient(options =>
    {
        options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]);
        options.ApiKey = builder.Configuration["RemoteAppApiKey"];
    })
    .AddSessionClient();

builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseSystemWebAdapters();
app.MapDefaultControllerRoute()
    .BufferResponseStream()
    .PreBufferRequestStream()
    .RequireSystemWebAdapterSession();

app.Run();

The new ASP.NET Framework registration looks like this:

protected void Application_Start()
{
    ...

    SystemWebAdapterConfiguration.AddSystemWebAdapters(this)
        .AddProxySupport(options => options.UseForwardedHeaders = true)
        .AddJsonSessionSerializer(options => 
        {
          options.RegisterKey<int>("key1");
        })
        .AddRemoteAppServer(options => options.ApiKey = ConfigurationManager.AppSettings["RemoteAppApiKey"])
        .AddSessionServer();
}

These registrations allow you to build up the services that you need for your migration process. This process is often a slow one that allows you to move functionality from your ASP.NET Framework application to the ASP.NET Core application. Please see the previous blog posts and the documentation for the project to see features that these libraries enable.

New APIs

This release brings in a number of API changes. Some of them are:

  • Implicit casting to HttpContextBase, HttpRequestBase, and HttpResponseBase in addition to the existing casts available for code that make use of these abstract base classes
  • HttpRequest.GetBufferedStream() and HttpRequest.GetBufferlessStream() have been added
  • Better logging and diagnostics have been added for session key/value serialization for cases where the type is not known
  • VirtualPathUtility has been added

For the full changelog, please see the release on GitHub.

Give feedback

We want to hear from you how we can best improve the incremental ASP.NET migration experience. The best place to share your feedback with us is on GitHub in the dotnet/systemweb-adapters repo. Use the 👍 reaction to indicate features or improvements that are most important to you.

This is a brief overview of this new extension and the System.Web adapters for ASP.NET Core. For more details you can check out the additional related resources listed below. Please try it out on your existing ASP.NET projects and share feedback so that we can continue to improve the incremental ASP.NET migration experience.

Thanks, and happy coding!

Resources

3 comments

Discussion is closed. Login to edit/delete existing comments.

  • Comelin - Jean-Claude 4

    Hi, I’m on of the many that have 15 years old ASP.NET app (started with .NET framework 1.0) and I’m completly stuck in the past and barely see how to move to .NET CORE. While your tools are great for session and and http request, what can I do with my MasterPages and ~100 of .aspx / .ashx pages?

    Microsoft did the right thing by starting back with a new pipeline and all the goodness but that doesn’t solve my daily problem.

    What I would like to KEEP compiling against the .NET framework but code that would work in .NET core and then migrate. There seems to be little “common ground” in between.

    The worst part, even years after .NET Core is out, I still add more and more .aspx pages to the same master page because I can’t find a incremental way to transit. Keep me awake at night for soo many time I’m about to think to forget about it and stay on .NET framework forever.

    • Howard Richards 1

      Bonjour! I am in a similar position, an old 2005 vintage WebForms app with some MVC added later.

      Our solution was to use an open-source project called Cocoon https://github.com/VisualReCode/Cocoon that was created last year, and operates in a similar way to the new Microsoft incremental upgrade using SystemWebAdapters – it creates a front-end proxy app for the new Core app and keeps the legacy Framework app in background for the old pages.

      The key difference is Cocoon already supports Blazor (both Server and WASM) so you’ll find the transition a lot easier from WebForms if you go the Blazor route, as you can re-use a lot of page logic in that model. If you go down the MVC/RazorPages route then the client functionality has to be rewritten in JavaScript using a framework (Vue, React, Angular etc.).

      We have a Blazor Server front-end that now gives us a “page-at-a-time” upgrade strategy, while also keeping all the old app pages working. This is now running and we have a mix of Blazor and WebForms in what (to the users) is the same application.

      As well as upgrading the web app, you also need to ensure any supporting libraries used by the WebForm app are able upgrade as well. .NET Standard is very useful as a stepping stone (see my blog posts on this: https://www.blazor.expert/search/label/Blazor for more detail), and helps remove other dependencies.

      Our biggest roadblock was our data layer used LINQ2SQL so we had to rewrite that to use EF Core, but once that was done, it was a lot simpler. We had to drop some third party packages that were Framework-only and find .NET Standard compatible alternatives.

      Feel free to DM me on Twitter at @conficient if you need any help!

    • Michael Taylor 1

      There is no migration path between web forms and MVC (which is what ASP.NET Core uses). They are completely different design patterns. It would be like trying to auto-migrate from SQL to Oracle. While you could automate the conversion, the end result still wouldn’t work and you’d be left with a potentially greater mess to clean up. But I do think there are steps you can take to prep for a migration and the upgrade-assistant tool from MS can help you with that.

      1) Move anything not related to the UI into class libraries. Use the upgrade-assistant to update them to multi-target to .NET Framework and .NET 6. This is probably where the bulk of your effort is going to be.
      2) Create a new ASP.NET Core API project where you will begin migrating your client-server interactions to. This will rely on all the non-UI code that you previously multi-targeted.
      3) Update your ASPX pages such that they rely on Javascript code for things that can be done on the client (for example reacting to button clicks or changing the state of the UI). Anything that requires server work should call an API that you have added to your new API project. This should eliminate all the codebehind processing that you’re currently doing and leave just the raw HTML and a little ASPX overhead.
      4) Now that you’re not doing any server-side work in ASPX you can switch from the ASPX controls to standard HTML controls in your pages.
      5) At this point you are using Web Forms only as a runtime so you are ready to transition the main UI project to ASP.NET Core. Create a new ASP.NET 6 project for your UI.
      6) MVC is resource based so you’ll probably need to rethink your URLs so for every ASPX page identify what resource it belongs to. If you don’t yet have a controller for that resource then create one and then the action(s) that are needed. Copy the ASPX page from the original project to the new project and rename to cshtml. Replace any server render blocks with Razor syntax. Since you already did the heavy lifting in previous steps this should be straightforward and likely could be automated. Note that this is probably something you’d do in one bulk step.
      7) Clean up the code. Most likely as you migrated from ASPX you eliminated some stuff that no longer matters. Now is the time to clean those up and potentially remove some of the API calls you had before.

Feedback usabilla icon