Migration to ASP.NET Core: Considerations and Strategies

An ASP.NET application developed on Windows and hosted in IIS on Windows Server is what we've known since the inception of the popular web framework in January of 2002. Such applications have been developed inside of Visual Studio proper, and the expansive .NET Framework has furnished the requisite APIs to solve complex business problems. In later iterations of Windows, the .NET Framework began shipping with the operating system; and, life became even easier as a .NET developer. What could possibly improve?

This setup served us well, but Microsoft's burly .NET ecosystem had grown stale. There was one glaring issue in particular — the ecosystem's reliance upon the Windows platform. To maintain relevancy in a rapidly-evolving application development landscape, Microsoft needed to meet developers outside of the Windows community.

But enticing that elusive audience of developers was no small feat. It would entail supporting Mac OS X and the most widely-used Linux distributions. Doing so would lower the barrier to entry into the .NET ecosystem and attract developers who otherwise wouldn't have considered Microsoft technologies.

June 27, 2016 marked the day on which Microsoft addressed the aforementioned platform limitation with the release of .NET Core 1.0, ASP.NET Core 1.0, and Entity Framework Core 1.0. This day also marked the beginning of much bewilderment for millions of .NET developers worldwide. Questions were aplenty, but they mostly centered around migration to this reincarnation of the ASP.NET proper framework. Is my ASP.NET proper investment now obsolete? How does ASP.NET Core differ from ASP.NET proper? What tools are available to assist in my migration?

This article attempts to demystify and to provide guidance on this migration, although it isn't intended to serve as an exhaustive upgrade manual. Let's take a look at some architectural and tooling considerations, as well as upgrade paths.

Ed. note: This post is the first part of a full week dedicated to ASP.NET Core content. Check back every day this week for a new article.

Upgrade Paths

If your day job involves writing ASP.NET applications, it's important to understand the major differences between ASP.NET Core and ASP.NET proper. Many of us can describe our current ASP.NET investment as follows:

ASP.NET proper stack

Where does one go from here? The graphic below paints the upgrade path landscape quite well.

ASP.NET upgrade paths

Whoa! Why the influx of choices?!? With this graphic in mind, let's dive deeper into the specifics of each category.

Target Framework

It's a common misconception that selecting ASP.NET Core alone provides cross-platform capabilities. When coupled with .NET Core as a target framework, the nirvana of true cross-platform support is achieved. Targeting the .NET Framework, the battle-tested monolith many of us have used for years, shackles the application to Windows.

It's also important to recognize that while .NET Core is the new kid on the block, .NET Framework is still thriving and will continue to receive investment. Don't think of .NET Framework as a poor choice because of its age — think of it as a more mature, feature-complete .NET flavor for Windows users. .NET Core aims to serve the previously ignored audiences of developers on Mac and Linux.

One of the lesser-known options is a dual target scenario, whereby the ASP.NET Core application targets both .NET Core and .NET Framework. This results in a dual compilation of the application bits, since APIs available in .NET Framework may not be available in .NET Core.

As a rule of thumb, ties to Windows APIs are barred from .NET Core. The same has generally been true for any API regarded internally as legacy, which explains the difference in API surface area. Refer to the .NET Core API Reference to determine whether your desired APIs are present. .NET Standard aims to close this API gap by consolidating the various base class libraries into one governing library to rule them all.

Distribution Channel

The term distribution channel refers to the install location of the desired .NET platform and the application deployment model. The .NET Framework APIs are installed on the developer's machine and on the web server hosting the ASP.NET application. Think of this conceptually as a Framework-Dependent Deployment (FDD).

With .NET Core applications, it's also possible to support FDD, whereby the application targets a .NET Core version installed on the machine. However, .NET Core introduces a second option: Self-Contained Deployment (SCD). In the latter deployment model, the necessary runtime bits are packaged and deployed alongside the application.

If Docker containerization is a goal, SCD with .NET Core boasts great curb appeal. This deployment model provides a degree of isolation from any machine-level patches that may be installed down the road. Furthermore, it better supports the running of multiple .NET Core versions side-by-side on the same machine. See Scott Hanselman's "Self-contained .NET Core Applications" blog post or the ".NET Core Application Deployment" article for more detail.

Editor / IDE

Only upon identification of the operating system requirements should an editor and/or IDE be selected. If your IT department has mandated development on Windows, Visual Studio 2015/2017 is the best choice when targeting .NET Framework. Since debugging against .NET Framework isn't currently supported in Visual Studio Code, it's best to avoid in this scenario.

If your development team is divided in terms of preferred operating system, Visual Studio Code provides a consistent, first-class development and debugging experience across Windows, Mac, and Linux. Open the same project on virtually any platform, and expect the same type of development experience. With the assistance of OmniSharp, Visual Studio Code offers rich features such as syntax highlighting, CodeLens, and IntelliSense.

Visual Studio proper is largely tied to Windows, so it's not a contender in this particular scenario. Although, with the announcement of Visual Studio for Mac Preview at Microsoft's recent Connect(); event, there is renewed optimism for Mac users. As touted by John Montgomery, Director of Program Management for Visual Studio, in a recent blog post:

Visual Studio for the Mac is built from the ground up for the Mac and focused on full-stack, client-to-cloud native mobile development, using Xamarin for Visual Studio, ASP.NET Core, and Azure.

Time will tell whether Visual Studio for Mac is a viable solution for ASP.NET Core development. And unfortunately, Linux users are still left behind when it comes to Visual Studio proper support.

Web Server

As was true with editors and IDEs, web server selection hinges on the operating system requirement. For the large enterprise planning to double down on their Windows Server platform investment, IIS continues to play a major role. There is one small change, however — IIS reverse proxies HTTP to a lightweight, cross-platform web server called Kestrel. Kestrel lacks multi-port bindings at the time of writing, thus it's incapable of port 80 and 443 forwarding. Consequently, it's inadequately equipped to serve as a public-facing edge server.

When IIS isn't an option on Windows, WebListener is a practicable alternative that can be used with either .NET Framework or .NET Core. WebListener eradicates the need for Kestrel as well and doesn't require a reverse proxy server. For those hosting ASP.NET Core applications on Linux, both Nginx and Apache are viable options. As was true with IIS, either Nginx or Apache will pose as a reverse proxy to Kestrel.

Migration Strategies

There are certain frameworks under the ASP.NET umbrella for which no clear migration path exists at this time. Web Forms is constrained by its technical underpinnings, which necessitates a target of .NET Framework. SignalR support on the Core stack is in the works for a future release, again binding us to .NET Framework for now. In the realm of languages, there is no support for VB.NET in ASP.NET Core.

MVC and Web API with C#, on the other hand, are ideal candidates for a migration. My findings have proven that the heavier the reliance upon System.Web APIs, the more impractical and arduous the migration. Moreover, migrating a legacy application to ASP.NET Core targeting .NET Framework is an easier feat than targeting .NET Core.

With a particular upgrade path in mind, there are some tools and techniques which can ease the pain of migration from ASP.NET to ASP.NET Core (and even from .NET Framework to .NET Core). If targeting both .NET Core and .NET Framework in the same ASP.NET Core application, conditional preprocessor directives aid in executing code intended for only one of the two platforms. It's a solution to the following problem while porting an application:

DataSet API not available in .NET Core

The tooltip which appears when hovering over DataSet clearly states that the API is unavailable in .NET Core. The following code snippet demonstrates how a target framework moniker, such as net462, can be used in a preprocessor directive to control execution of this code:

#if NET462
    // This only executes when targeting .NET Framework 4.6.2
    DataSet set = new DataSet();
#endif

Aside from techniques such as that described above, a few popular migration utilities include I Can Has .NET Core and the .NET Portability Analyzer. For the sake of brevity, the remainder of this article focuses on the latter of the two named tools.

.NET Portability Analyzer

As depicted in the chart below, .NET Portability Analyzer is offered in the following three variations:
1. Console Application
2. Visual Studio Extension
3. .NET Core Application

.NET Portability Analyzer compatibility

The Console Application is a CLI-based tool for power users on Windows who prefer using the command shell over the IDE. Like the Console Application, the Visual Studio Extension is tied to Windows, as it also targets .NET Framework 4.6. The obvious benefit of the extension is that intricacies of the CLI are abstracted away from the developer.

What if you're not a Windows user? The .NET Core Application fills this particular void. It targets .NET Core instead of .NET Framework, which inherently means it runs on Windows, Mac OS X, and Linux. This is the best option for development teams who are unable to settle their differences in terms of preferred operating system.

How does the tool work? It scans a target folder and analyzes the assemblies within for portability to the desired platform(s) by sending the appropriate level of detail to a web service called the .NET Portability Service.

Microsoft respects your intellectual property by only sending details of Microsoft-owned assemblies to the service — specifically, assemblies prefixed with "Microsoft.", "System.", or "Mono.". Another assembly type destined for the service is one which is signed using Microsoft's public key tokens. If you're still skeptical about what's sent, here's the relevant snippet from the GitHub repository:

public class DotNetFrameworkFilter : IDependencyFilter
{
    /// <summary>
    /// These keys are a collection of public key tokens derived from all the reference assemblies in
    /// "%ProgramFiles%\Reference Assemblies\Microsoft" on a Windows 10 machine with VS 2015 installed
    /// </summary>
    private static readonly ICollection<string> s_microsoftKeys = new HashSet<string>(new[]
    {
        "b77a5c561934e089", // ECMA
        "b03f5f7f11d50a3a", // DEVDIV
        "7cec85d7bea7798e", // SLPLAT
        "31bf3856ad364e35", // Windows
        "24eec0d8c86cda1e", // Phone
        "0738eb9f132ed756", // Mono
        "ddd0da4d3e678217", // Component model
        "84e04ff9cfb79065", // Mono Android
        "842cf8be1de50553"  // Xamarin.iOS
    }, StringComparer.OrdinalIgnoreCase);

    private static readonly IEnumerable<string> s_frameworkAssemblyNamePrefixes = new[]
    {
        "System.",
        "Microsoft.",
        "Mono."
    };

    public bool IsFrameworkAssembly(AssemblyReferenceInformation assembly)
    {
        if (assembly == null)
        {
            // If we don't have the assembly, default to including the API
            return true;
        }

        if (s_microsoftKeys.Contains(assembly.PublicKeyToken))
        {
            return true;
        }

        if (s_frameworkAssemblyNamePrefixes.Any(p => assembly.Name.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
        {
            return true;
        }

        if (string.Equals(assembly.Name, "mscorlib", StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }

        return false;
    }
}

To demonstrate the Visual Studio Extension, let's look at migrating an ASP.NET proper MVC application targeting .NET Framework 4.6 to ASP.NET Core 1.0 targeting .NET Core 1.0.

Getting Started in Visual Studio

  1. Install the extension via Visual Studio's Tools –> Extensions and Updates… dialog:

    Visual Studio Extensions and Updates dialog

  2. Select the desired target platform(s) via the .NET Portability Analyzer section of Visual Studio's Tools –> Options… dialog:

    .NET Portability Analyzer target platforms

  3. Create a new ASP.NET proper MVC project targeting .NET Framework 4.6 via Visual Studio's File –> New –> Project… dialog.

  4. Right-click the project name in Solution Explorer, and click Analyze Assembly Portability in the context menu.

Taking Action on Portability Analysis Results

Upon completion of the four steps listed above, it's time to make sense of the results and to take action. Open the generated Excel spreadsheet via the Open Report link found in the Portability Analysis Results window. Note that the document contains the following three sheets:
1. Portability Summary
2. Details
3. Missing assemblies

The Portability Summary sheet displays an executive summary / report card of how portable the project is to the desired target platform(s) — ASP.NET Core 1.0 and .NET Core 1.0 in this case. In the following example, the project is nearly 74% portable to .NET Core 1.0 and just over 12% portable to ASP.NET Core 1.0:

Portability Summary sheet

The Details tab provides a high degree of detail to help better understand the incompatibilities. If we were using the Console Application instead of the Visual Studio Extension, this tab would prove invaluable. In particular, this sheet's Recommended changes column sometimes provides useful hints for achieving compatibility. Since we are using the extension, focus your attention to Visual Studio's Error List window and view the Messages:

Error List window messages

Double-clicking an entry in this window sets the editor's focus to the offending line of code. In the case of the first entry, System.Web.Optimization (and all of System.Web for that matter) isn't supported in ASP.NET Core 1.0. The offending line of code is attempting to make use of that API.

Lastly, the Missing assemblies sheet provides an inventory of those assemblies which were excluded from analysis. These assemblies are referenced in the project; however, the tool doesn't handle dependency resolution during the scan. A common cause for this is the presence of a referenced assembly from the GAC. It's outside of the folder for the project, so the tool is unable to analyze the assembly.

Conclusion

In this article, we identified upgrade paths for an existing ASP.NET proper investment. We also walked through options for migrating the application to ASP.NET Core once an upgrade path has been identified. Take this information and head down the migration path so that you can begin to reap the benefits of Microsoft's hard labor.

Want a tour of changes introduced in ASP.NET Core before you migrate? Download the whitepaper "ASP.NET Core MVC Changes Every Developer Should Know" to better understand what you're missing out on.

Related resources:

Comments