For anyone working in the Umbraco community, it’s clear that there are two big challenges coming in the next couple of years. The largest probably being the back office UI rebuild, but the most imminent being the move to .NET Core. Whilst both of these will be challenging enough for the core project itself and for agencies working with Umbraco, those set to face an ongoing challenge surely has to be the package developer community.
(In this article, I jump between the terms .NET Framework and .NET Core and CMS versions v8 and v9. Please note that when referring to the Umbraco version v8 this is inferring the .NET Framework build of Umbraco, where v9 infers the .NET Core build of Umbraco)
For the longest time in the Microsoft world the standard framework on which to build anything was the .NET Framework; being a .NET based CMS, this is exactly what Umbraco was built upon.
But in the tech world nothing stands still and in June 2016 Microsoft launched the first version of what was set to be the replacement to the .NET Framework, .NET Core. More performant, cross platform with much cleaner APIs, .NET Core was set to be a vast improvement over .NET Framework, but in the early days it was very much a moving target with missing elements from its .NET Framework counterpart and constant breaking changes between releases making it difficult for developers to rely on. Fast forward to today, however, and with the release of .NET Core v5 (now known as just .NET 5.0) the framework is now the default option when creating new .NET based projects.
Unfortunately for Umbraco developers, however, is the fact that .NET Core is effectively a completely new way of building .NET applications and as such is not backwards compatible with the .NET Framework, ultimately meaning that the CMS we have all come to love and cherish just won’t work in this new environment.
In order to ensure Umbraco didn’t get left behind, a new community team was created in late December 2019, the Unicore team, set with the task of moving the project over to the new framework. And with Umbraco having such a large codebase this was no mean feat, but bit by bit, update by update, progress was made and slowly but surely we started to see things starting to Umbraco on .NET Core start to take shape.
This brings us bang up to date, and this month (April 2021) Umbraco are set to release a Alpha 4/Beta that is finally in a position that agencies and package developers can start to actually use and build upon.
The challenge for package developers
Whilst the upgrade to .NET Core is no doubt going to be challenging for everyone, I do think those set to have the most difficult time is going to be package developers.
For the Core CMS, the approach they look to be taking is once the .NET Core version is launched and released the v8 .NET Framework version of Umbraco will effectively move into a support only phase with little to no development of new features on it, and the .NET Framework and .NET Core code bases effectively being split and managed independently.
For agencies it will likely mean developers needing to reskill a little, but for the most part existing customers will likely stay on Umbraco v8 .NET Framework and new projects will gradually start to build in Umbraco v9 .NET Core as they feel it starts to reach a level of maturity. There may be some pain for those that need to migrate from v8 to v9, but once migrated, they will only need to maintain the new v9 code base for the project.
For package developers however, we are in a bit of a middle ground. Not only do we need to make the shift to .NET Core, we also need to continue to support those running on the .NET Framework version of Umbraco who are not yet comfortable or ready to make the leap.
For those developers not only do we need to make our package compatible with the v9 .NET Core build of Umbraco, but we also need to maintain support for the v8 .NET Framework builds too, potentially adding and releasing new features to both audiences.
Code Management Strategies
As one of those package developers that very much falls into the latter category with our eCommerce package Vendr, we really have to think carefully about how we migrate to .NET Core whilst still maintaining support for .NET Framework.
In reviewing our options, there really only appears to be two possible approaches we can take.
Option 1: Multiple Code Bases
The first option package developers have is to support multiple code bases. With this approach you’ll either split your code into two branches or have completely independent code repositories for the .NET Framework and .NET Core versions of your package.
If you are fortunate enough to not need to support adding new features for the .NET Framework build, you may be able to just put this particular code base into support mode and only need to fix security issues. But if you are the developer of an active package like us then you’ll need to replicate code changes across both code bases.
- Much easier migration as you can take a copy of the .NET Framework and effectively do whatever you need to do to “just make it work”.
- With this approach you can make structural code changes in your .NET Core project without it affecting your .NET Framework project.
- Breaking changes in your .NET Core project won’t affect your .NET Framework project.
- Whilst it’s easier to make the initial migration, it becomes much harder to maintain over time as any new features you add will have to be implemented twice into both code bases.
- As such it will open up the potential for bugs to be introduced if the feature replication is not managed successfully.
Option 2: One Code Base, Multiple Framework Targets
Whilst .NET Framework and .NET Core themselves aren’t backwards compatible, at the same time as introducing .NET Core, Microsoft also introduced a middle ground framework of sorts, .NET Standard. What .NET Standard provides is a common API for non-environment reliant functionality that can work in both .NET Framework and .NET Core.
It doesn’t mean you can just code everything in .NET Standard and be done, but it does mean you can put a lot of your business logic into a common, shared library and only need to create framework specific libraries for the specific features that have changed between those frameworks.
When it comes to managing the framework specific libraries, Visual Studio also provides us with some useful features to make this easier.
- Project multi-framework targeting - With this feature you can configure your project to support multiple frameworks then when the project is compiled, it compiles multiple versions targeted at each specific framework.
- Conditional project references - With this feature you can include project references only when compiling for a specific framework. So if you have code that is only relevant for one particular framework, this will allow you to include that file only for that framework.
- Conditional preprocessor directives - With this feature it allows you to create an “if” statement within your code to only include part of a file for a given framework. This is useful for things like namespace changes where the functionality remains the same, but it’s location has moved. Within conditional preprocessor directives you can define using statements that pull in the given classes from their respective namespaces of each framework.
With this option and the features above, package developers will create a single code base that targets both .NET Framework and .NET Core. Any code that is framework agnostic may be moved into a shared .NET Standard library, where code that is framework / Umbraco version specific will be placed in a dual-targeted framework project with use of conditional project references / preprocessor directives to instruct the compiler of any specific framework only elements.
- One single codebase to manage meaning you don’t have to copy features between separate projects.
- Only need one build process to generate packages suitable for both versions of Umbraco.
- Shared codebases between versions where the API has changed could result in some messy code using conditional preprocessor directives to selectively include / exclude code from Umbraco versions making it hard to know what the code is actually doing and progressively getting harder to maintain with each change.
- Higher level of investment needed to split the code up into shared + framework specific projects.
- May mean that you are carrying legacy code in your codebase around for a while (may not be in the compiled binary, but it’s clutter for day-to-day dev)
- If a project re-structure is required to move classes into specific DLLs this won’t just make a .NET Core breaking change, but also for a breaking change in the .NET Framework targeted package too. You might be able sell this as “preparation for v9”, but it depends how averse to breaking changes your users are.
What Strategy Should You Use?
I think this is completely going to depend on your package size, how active the development is on your package and how much you want to invest in migration.
For us on the Vendr team, we have a pretty large codebase and a really active project so our initial plan of action will be to follow option two and try and make everything run from a single codebase. This will potentially require some restructuring of our projects which will introduce a breaking change in our v8 version, but I’d much rather get people onto the future API as quickly as possible.
Our current plan is to look at breaking our solution up as follows:
Vendr.Core - Net Standard
Vendr.Infrastructure - Net Framework + Net Core
- Value Converters
- Product Adapters
Vendr.Web - Net Framework + Net Core
- View Models
Vendr.Web.UI - Static files
- Angular files
- Razor views for email + print templates
We still plan to remain pretty coupled to Umbraco, but we are mostly trying to make it so that we have less to manage from a maintenance perspective. We may change this as we get into things and see how complicated it actually is, but I think this gives us the best structure / architecture to move forward with.
For us, we also need to think about our add-on packages + payment providers too, however our main focus will be getting Vendr on to .NET Core first and then address these once we have something up and running.
Depending on the code management strategy chosen, it’s also potentially going to affect your versioning strategy for your package.
Going with independent code bases would require that each package maintains its own version numbers and likely it’s own build and release cycle, whereas the single code base will mean that you’ll have one version number that supports both .NET Framework and .NET Core.
Depending on whether you want to be able to release different versions of your package at different times may dictate which approach you need to follow.
If going with an approach that maintains different version numbers for each release, you may need to think how long are you going to support each specific version for. For example, if your package is currently on version 1.x.x, you could choose to release the .NET Core version as version 2.0.0 and release minor / patch releases for each one synchronously. But if you expect to support the .NET Framework version beyond a major release, you may need to start a whole new version number sequence for the .NET Core version in order to give you that flexibility. If going this route, you may also need to release the .NET Core version under a different name to prevent version number conflicts.
Another consideration us package developers are going to have to think about is the package format itself.
As it stands right now, it looks like both v8 and v9 will support packages in both NuGet format (installed through Visual Studio) or the Umbraco zip format (installed through the Umbraco Back Office).
Depending on the code management strategy chosen, if working with independent code bases then you’ll likely end up packaging the different versions independently too producing different packages for each version of Umbraco. This will likely just involve setting up a secondary build process for the .NET Core version of your package.
With the single code base option though, this would essentially dictate that you are looking to release a single version of your package that holds support for both frameworks / versions of Umbraco, with the package installer deciding on install which version is the best fit. With NuGet packages this is fine as these already support the multi-target scenario with assets stored internally in different folders and the installer choosing the relevant ones based on the project they are installed into. However, for the Umbraco format, this currently doesn’t support such a feature (though I’m speaking with members of the Package Team to see if we can make this happen).
Currently then, your only option here would be to release multiple Umbraco zip versions of your package for each specific framework, ie `Vendr_NetFramework.v2.0.0.zip` and `Vendr_NetCore.v2.0.0.zip`. This in turn may create additional complexities in your build process creating these.
The move to .NET Core is going to be a big challenge for everyone, but package devs are really going to have to think and plan how they support their users moving forward.
In the Open Source package development community, there is the risk that this may all just be too much work for the maintainer to manage and so we may see a number of popular packages stall or maybe even not make the leap at all. For those I urge the community to really rally around them and offer as much support as you can to help make it happen.
For commercial offerings, there really isn’t going to be a choice and the move will have to be made. All I would urge the community to do here is just be patient, whether that’s waiting for the .NET Core version itself, or some feature that you need developing that is being held back whilst the migration is in progress.
Whilst both this and the UI update are going to be a really huge challenge, I do believe the opportunities on the other side and benefits of both will ultimately make the pain of moving worth it for everyone.