We’re celebrating all things .NET this week here at Stormpath, fresh on the heels of Visual Basic’s 25th birthday and the wide release of .NET Core RC2. The future of .NET is looking bright, and we’re excited about the direction of multi-platform .NET Core.
Last week, Mark Rendle posted a call to action for library authors to adopt .NET Core, and we couldn’t agree more.
Tl;dr – it’s time to migrate to .NET Core!
At Stormpath, we made an early decision to go all-in on .NET Core, which meant first porting our .NET SDK codebase to Core RC1. There was a lot involved, but it wasn’t insurmountable. The main difficulties we ran into were:
Depending on how complex your codebase is, you may run into code that depends on APIs and libraries that have been deprecated in .NET Core. That code will need to be refactored to use newer or replacement libraries. Microsoft provides some tooling that helps you quickly scan and find this code.
Our library made pretty heavy use of the .NET Reflection API, some of which has been deprecated in favor of the TypeInfo API. This meant refactoring to use
a buttload of some places we were previously using
Type. (This apparently will be changing back, according to Microsoft.)
In the RC1 days (and earlier), it was difficult to find compatible packages, and there was a lot of versioning and framework confusion. Many supporting libraries (such as testing and mocking packages) have limited support for .NET Core as of now, and it was necessary to depend on beta/pre-release packages just to get things working. Even Visual Studio’s support for building .NET Core projects was buggy. Fortunately, this is being ironed out and improved – especially now that RC2 is here and is considered a stable release.
We released our first .NET Core-compatible packages with version 0.6.3 of our .NET SDK, and version 0.1 of our ASP.NET Core plugin. Then, the .NET Core team released RC2 to the world
on The Day Microsoft Broke Everyone’s Stuff two weeks ago. This meant going back and porting our code from RC1 to RC2.
Compared to porting code to RC1, migrating our projects to .NET Core RC2 was pretty easy. The only code changes were a few .NET packages that got renamed in the RC2 transition.
The main pain we ran into was related to tooling in Visual Studio, which still needs improvement.
dotnet on the command line was invaluable since Visual Studio wouldn’t always behave correctly.
A word to the wise who are doing this migration: instead of re-using your existing solution and
.xproj files, use
dotnet new to scaffold a fresh solution structure. This will get rid of any leftover references to DNX and pre-RC2 tooling and make the Visual Studio experience smoother. (This bug alone was responsible for a lot of hair-pulling.)
The modular nature of .NET Core forces you to be more explicit about the dependencies you’re taking and think through why you’re taking them. The ASP.NET Core team has adopted the pattern of splitting many of their packages up into
Library, with interfaces and shared code living in a separate abstractions package that the main library depends on.
This pattern is something we adopted as well, and I wish we’d done it sooner. It forces a very clear separation of concerns between the external interface and the internal code, which is doubly important for a library that other developers will be consuming. Pushing interfaces and shared code into a separate assembly also means it’s harder to “cheat” and make poor design decisions that break the separation and mix concerns between layers.
Good interfaces and a strong separation of concerns are important when you’re hit with the need to refactor a significant chunk of internal code. In our case, the reflection code that needed to be refactored to use
TypeInfo was some of the lowest-level stuff in our library. Thanks to these layers being appropriately abstracted from the user-facing code, this wasn’t as scary as it could have been.
Good test coverage is a must-have in any refactoring or migration. Unit tests can be a joint blessing/curse during refactoring because they pour cement on the contracts they test. Integration or functional tests, on the other hand, are happily oblivious to under-the-hood changes and are especially suited for multi-layered code. We have over a thousand functional tests in our suite, plus another 500 unit tests of specific classes, and the instant feedback of green (or red!) checkmarks from the testing suite is a huge help during refactoring.
What about our users who haven’t made the jump to .NET Core yet? Backward compatibility is important to us, and we were able to achieve it by adding the frameworks we already support to our project definition:
This allows us to produce a single NuGet package that can be installed on .NET 4.5+ and any platform compatible with .NET Platform Standard 1.3: .NET Core, Universal Windows Platform, and Mono/Xamarin. Because of this, we are able to support both ASP.NET 4.5+ and ASP.NET Core projects with minimal duplication of code and effort.
Refactoring our code to support .NET Core was a lot of work (and some pretty large PRs on Github). In our opinion, being able to support customers who wanted to use our SDKs in early .NET Core projects was well worth the effort. The SDK is already powering tens of thousands of requests per day from .NET Core applications. The demand is there!
The refactoring efforts also resulted in a cleaner code structure, and we got broader platform support for “free” (after the cost of admission). Multi-targeting to get backwards compatibility with existing .NET 4.5+ projects from the same codebase is just icing on the cake.
Whether you’re on tried-and-true ASP.NET 4.5 or on the slightly-bleeding edge with ASP.NET Core, check out the Stormpath plugins that allow you to add user management to your application with only a few lines of code.
During our migration efforts, we found these resources extremely helpful:
Since we’re celebrating .NET this week at Stormpath, here are some other great .NET reads from our blog:
Viva .NET! <3