Skip to main content

Umbraco Package Migration to .NET Core: A Clean Start - Controllers, Services, Configuration and Caching

This is one of a series of posts looking at migrating Umbraco packages to V9 and .NET Core. Other posts in this series:

  1. Introduction
  2. A False Start
  3. A Clean Start - Controllers, Services, Configuration and Caching
  4. Criteria Providers - Working With HttpContext
  5. Leaning on Umbraco
  6. Migrating Tests
  7. Extension Methods
  8. Migrations
  9. Wiring It All Up
  10. Distributing and Wrapping Up

File > New Project

Having decided on a new repository for the migration of the Personalisaton Groups package to Umbraco V9 and .NET Core, I started with a brand new solution, containing a single project targetting .NET Standard 2.0. I picked this version mainly just to match the decison made by Umbraco themselves, when updating the class libraries that make up the code for the CMS itself. We can see if there's any reason to change it later.

Adding Reference to Umbraco

I then added the first external dependency - Umbraco itself. At the time of writing, the release intended for being stable for package development - alpha 4 - was not yet out, so I took a reference to the latest nightly release. These can be accessed using a custom package source, using the details described here. With the package source referenced, I could then add the reference to the latest release of Umbraco.CMS.Core:

Migrating Controllers

With migrations like this it's a tricky decision whether to start at the top of the dependency chain and work down, or from the bottom up, but I decided to start with copying in the controller files and then to also bring over what was necessary for their dependencies.

With .NET Framework controllers copied over there are a few changes required to amend the code to match what is needed for .NET Core ones:

  • Controllers should inherit from ControllerBase, which is available in Microsoft.AspNetCore.Mvc
  • Controllers should return IActionResult
  • To return a result for an API response, there are various helpers available - e.g. OkObjectResult(data), BadRequest(), and NotFound()

To add the necessary dependencies for these classes, you can use the package manager console, add them manually to the .csproj file, or rely on the VS.Net tooling to suggest what you need and add them. When I was done, I had the following referenced in the .csproj file.

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
    <PackageReference Include="Umbraco.Cms.Core" Version="9.0.0-beta001.20210222.10" />
  </ItemGroup>

It's worth noting that in .NET Core, controllers are used both for rendering views and API responses - there's no split between MVC and Web API anymore.

Controller Dependencies

Anything the controller needs can be injected as a dependency via the constructor. For example, I have a controller that returns the list of member types and member groups available in an Umbraco installation, which needs Umbraco's IMemberTypeService and IMemberService respectively.

And for our own logic we can also create and inject custom services - an example of one that's injected into controllers is here.

Note: all this needs wiring up with the dependency injection framework... but I'll be getting to that later. For now, code compiling is enough!

Configuration

There'll be more to discuss on configuration - though there's already a great introduction to the changes written by Unicore team member Emma - so for now the only point of note is that it's possible to have strongly typed POCO classes created to represented configuration. Such as this one here.

When that's wired up in the application start-up, it'll be available to be injected via an IOptions wrapper into controllers or other classes. It's unwrapped by accessing the Value property, and from there you can just use the strongly typed representation - see for example, this controller.

Caching

In the previous implementation of the package, I was using caching to prevent having to constantly fetch some information that would never change once the application is started, via the HttpRuntime.Cache available in .NET Framework. In .NET Core we use IMemoryCache, but better than that, given we have a reference to Umbraco, we can make use of some public helper classes. By injecting IAppPolicyCache (available in Umbraco.Cms.Core.Cache) we have access to a handy method that will get a value if it exists, and if not, run the code you provide to retrieve the data. You can see this in use here.

Update 12/4/2021: Perhaps more flexibly, you can also inject AppCaches, which then has properties available for RequestCache, RuntimeCache etc. for you to use as appropriate. You can either use the Get() method, or if you add a reference to Umbraco.Extensions you can access GetCacheItem() which has a few more overloads for control of how long your item is cached for.

Miscelleneous Others

The final difference I needed to work around was no longer having access to Server.MapPath to resolve the path to a file on disk. The .NET Core equivalent is IHostingEnvironment, as usual injectable, and from this we can get a reference to the WebRootPath. This is found in the code here and explained clearly by Mike Brind on his blog.

Other than that, I believe everything else for the migration so far was simply a copy/paste/adjust namespaces process.

Following Along

The repository containing the code for the migrated package is here. At the time of writing, the state of the migrated code can be seen using this link.

Comments