Migrating from ASP.NET to ASP.NET Core

ASP.NET to ASP.NET Core — Motivation

If you are still developing or maintaining a .NET Framework based solution (which probably should be considered legacy at this point, especially for ASP.NET), then by now you must have considered migrating it to .NET Core. Before we jump into nitty-gritty aspects of it, let us hold on for a second and ask ourselves these questions:

  • What is to be gained here?
  1. Developing and maintaining .NET Core based solutions is just easier. Many of the existing mechanics were improved, simplified and unified. Simply put — made better. Managing configuration files (sources), referencing assemblies, hosting — all has been streamlined. It leads to less bugs, less time required for “maintenance” and producing boilerplate code. More time can be spent more efficiently on more valuable stuff — new functionalities, tests, etc.
  2. It’s often the case that new useful libraries or newer versions of existing libraries are released exclusively for .NET Core. Swashbuckle.WebApi and it’s ASP.NET Core counterpart are just one of them. You may think “Oh well, I’ll just keep using the old stuff in this case”, but this will only get you so far. OpenAPI, Improved UI, being able to use Authorization Code + PKCE are just a few of many perks involved with the latest release of this one single library. Now think about all the other nuget packages your solution uses. There’s also a great possibility that some areas in your code could just be replaced with calls to external libraries (aka “there’s a library for that”). By moving to .NET Core you maximize your chances of being able to leverage that.
  3. Being able to host your .NET Core app on Linux brings new possibilities to the table. Docker? Kubernetes? Yes, please. True, you can (to some extent) use Docker and Kubernetes with Windows underneath as well, but that’s not a common scenario as Linux is just the default / native OS for doing this kind of stuff.
  4. Another fact that shouldn’t be disregarded is that job candidates are more likely to respond to your job offer if you do .NET Core, “.NET Framework 4.6.1” doesn’t look good on job offer anymore. Seeing a job offer like this begs the questions: “Where have you been for the last few years?”, “What kind of legacy project / company am I getting myself into?”. Don’t get me wrong, I do understand that there is still time and place for full/old .NET Framework, it’s just it shouldn’t be your default choice anymore.
  5. Performance. The ultimate reason. I’ve seen “performance” card being thrown way too many times in response to questions of why some things are being done “suboptimal”. It’s a one word excuse / conversation stopper. But if you have to convince one of these people who put performance on top — I got good news for you — .NET Core is blazing fast. Depending on the particular scenario, you will get improvements ranging from 20% to 6x compared to .NET Framework 4.8, with .NET Core 5.0 raising the bar even higher. This can directly translate into better user experience and lower bills for cloud hosting services. Apart from that it can open up new possibilities, like running your app in a potato powered IoT device.

ASP.NET to ASP.NET Core — Preparation

Now that you have at least some ammo for pushing forward this idea of migrating to .NET Core (I’m sure you can come up with much more), let’s take a look at how it could be achieved. We’re going to focus on Web Applications (REST APIs specifically) as those are perfect candidates for migration and that’s what my exposure mainly was. You can find a great deal of guidelines / articles on porting to .NET Core, either on microsoft websites or from other people who already went this path. Being late to the game has some advantages after all. You’re more likely to find a solution to a problem, contrary to early adopters — who often had to figure it out on their own. I strongly advise you to read those articles first as they often lay out “the basics”. I will reiterate over some of those but I will mainly focus on “what’s not there”. The whole process should start with thorough preparation. Proper planning and prerequisite activities will minimize the time your solution stays uncompilable. But make no mistake — there will be times where you will be forced to make a jump and hope for the best — figure things out as you go — but we want to keep the number of those situations to a minimum.

Refactor first

So the absolute first thing you should do before you even say “.net core” is to get your existing solution “straight”. There’s nothing worse than trying to fix dozens of problems at once while porting to .NET Core. Refactor first. It may take a week or month or longer, but it will take even longer if you try to do it while porting to .NET Core. Not to mention you boss or customer nagging you why this “.NET Core porting” takes so long. Depending on the quality of your existing solution, this may be the most time consuming step. Focus on having clear separation between abstractions and implementation. Make sure outermost layer concepts (usually like any class with “http” in its name) don’t sip into internal layers. Ensure you do things consistently (one particular way) throughout the solution and you can isolate those and hopefully hide them behind abstractions. It will save you headaches later down the road.

Change .csproj files to SDK-style format

Also known as “Visual Studio 2017 project format” as it was introduced at that time. .NET Core projects require this style of .csproj. But why is this still preparation? Isn’t that “migration to .NET Core” already? No. We’re keeping the full framework for now. While you are able to find some tools that will try to perform this conversion, I found it just easier to do it manually starting with the following “almost empty project” template:

  • You can edit .csproj file without first unloading it from solution
  • Binding redirects are being handled automatically (at compilation time)
  • You don’t need to explicitly reference nuget package if one of the projects that is already being referenced already uses them

Migrate from Entity Framework (6.*) to Entity Framework Core

That’s another huge step you should take before you make the jump to .NET Core. Of course only if you use Entity Framework in your project. If you find it confusing, then let me explain: Entity Framework Core is a different ORM than the original Entity Framework that you use in your project. EF Core does not provide the same features as EF 6. For example, there is no support for many-to-many relationships, you will have to model them yourself and the “joining entity” will be first class citizen in your model. Wiring the configuration will differ, attributes will be different, your existing migrations will need to be deleted and new (initial) migration will have to be created. Since EF Core depends on .NET Standard 2.0, it can be used by both .NET Framework and .NET Core applications (.NET Standard is the “common denominator” of those two frameworks). So by switching from EF to EF Core, we perform one additional step that will relieve us from making this switch while we perform conversion to .NET Core. We found this step time consuming and failing often at execution time (due to incorrect configuration). So every bit of integration tests you have will definitely help. With new ORM come new providers for your DBMS. If you happen to be using MySql / MariaDb then I strongly suggest you to go with Pomelo. There is an official provider from Oracle but we haven’t had much luck with it. Be In either case, make sure to update your database server to up to date version first.

Libraries

Just like with Entity Framework Core, you can benefit by converting your libraries to .NET Standard 2.0. This way you’ll be able to use them in old and new solution. Start with a list of all external libraries that you use in your existing solution and try to find a substitute for it. Take note of dependency a particular library has (either .NET Core or .NET Standard), this will dictate what your library will have to target. Well, for the most part at least, since you may find yourself in need of something “built in” from .NET Core namespace. While certainly a nice place to be in, you may find that limiting yourself to .NET Standard API subset is just too much constraint. So give it a try, but don’t hesitate to change target framework to .NET Core 3.1 if limited functionality of .NET Standard starts to get in your way. In this case, trying out a new external library might be something that you will find yourself doing at later stages of the conversion process (where your solution compiles and runs relatively crash free).

ASP.NET to ASP.NET Core — Execution

So far we’ve been closing the gap that we will have to jump over, thus giving ourselves a better chance of succeeding while also doing it in a reasonable time. Now it’s time to actually make the jump.

IoC container and dependency registration

.NET Core comes with it’s own IoC container and a set of abstractions related to dependency resolution. While it is possible to use 3rd party IoC containers like Autofac, so far we haven’t found reasons to use them over what’s provided out of the box. With full .NET Framework things were quite different in that regard as it didn’t come with any IoC container. While Microsoft’s (later took over by community) approach to IoC — Unity Container — could definitely get the job done, it still left something to be desired. That’s where all the other IoC containers could shine. With .NET Core however, their relevance diminished.

OWIN and Authorization Server Middleware

With the original framework we were using OWIN and a number of libraries that relied on it, particularly Microsoft.Owin.Security.OAuth — an authorization server middleware for issuing access and refresh tokens. With ASP.NET Core having its own middleware (you plug them inside Startup.Configure method usually with “Use..” extension method call) we started looking for a replacement. We were surprised to find out that Microsoft did not deliver any libraries for issuing OAuth bearer tokens. Out of the box support is only for consuming / validating them. We found this post by Jeffrey Fritz / Mike Rousos that gave us some alternatives. Eventually we went with OpenIddict which aims to be a more lightweight alternative to IdentityServer4 and seemed more like a direct replacement to the original library from Microsoft. You can also consider external providers like Okta or Auth0 to do the heavy lifting for you, for us however it never was an option.

WebApiCompatShim

While looking for any potential time savers doing ASP.NET Web API 2 to ASP.NET Core MVC conversion, you may come across this library from Microsoft. It’s not even available for .NET Core 3.1 (not to mention 5.0) but at the time we were doing conversion to .NET Core 2.1 so it was still a viable option. We used it initially and it decreased the number of compilation errors by a magnitude. But as our solution kept evolving toward “Core”, we found this library just straying us from the way things were supposed to be done properly. Today I wouldn’t even consider using it should it be available for .NET Core 3.1 as it would only slow you down embracing ASP.NET Core’s way of doing things.

Configuration

.NET Core comes with powerful configuration schemes that allow for pulling configuration values from multiple sources as well as having dedicated configurations depending on the environment the program is run. Web.config XML files are replaced with appsettings.json JSON files. Probably the only time you will need to reintroduce web.config file to your project is when you will want to host your project in IIS. But this will merely be an addon as your main configuration will still be kept in appsettings.json. You can use .xml files or even .ini files if you really want to, but json is the default choice in .NET Core. Speaking of IIS, if you plan on hosting your ASP.NET Core app in IIS, then prepare for some tinkering. Definitely check out this blog post before you start.

  • Environment kind (Local developer machine, Azure, IIS, AWS)

SignalR

That’s a similar story to Entity Framework vs Entity Framework Core. What classic ASP.NET SignalR and ASP.NET Core SignalR have most in common it’s their name. Just like with EF and EF Core, new SignalR is not backward compatible with old SignalR. You can read all about the differences here.

Final words

It’s impossible to write about all the problems and challenges we faced while porting to .NET Core. Those are some of the most memorable ones. I’m sure most of the other questions you may have can be answered by Googling “[name of the component from .NET Framework] .NET Core” and surely some Stack Overflow threads will pop up.

human-centric software design & development. check out our website: www.iteo.com