Converting a Umbraco Package to a Razor Class Library

By Markus Johansson

Since ASP.NET Core 2.1 the project type "Razor Class Library", or RCL, has been around. This feature allows shipping C#, Razor views, and front-end assets (like JavaScript, CSS, HTML files) in a single DLL file. This is perfect for component vendors and an excellent fit for packages/plugins. We also use Razor Class Libraries for internal utility/helper projects when building websites. 

When using a RCL in a web project, any assets or files from the RCL can be served from the website and we can override certain files from the consuming project if we need - which is a really great feature.

Umbraco 11.0.0 was the first version where full support for RCL-packages was introduced. It might be possible to partially ship a package as RCL and run on earlier versions and certainly, you can use a RCL for your utility/helper projects on earlier versions of Umbraco.

Why use Razor Class Library for my package?

I've been building several packages for the "vNext"-versions of Umbraco using the old recommended approach with build-targets to copy App_Plugin-files into the consuming web project. With a Razor Class Library, there is no need for this copy-step and the developer experience is a lot better! We even got "hot reload" when editing assets in the RCL and saving it will automatically update the backoffice (no js-builders, webpack, grunt or so needed).

The main benefits that I see are:

  • A lot simpler deployments. All files are in one place.
  • Easier for developers that install the package
  • Better developer experience
  • Has a built-in mechanism for making overrides possible which is awesome.

Are you ready to try it out? If you're starting from scratch, use the official Umbraco Package Template which ships with a RCL-version since 11.4.

Upgrade or Migrate an existing Umbraco package to use Razor Class Library

It turns out that moving to a RCL for your package is quite easy, I did this for my Newsletter Studio-package in less than a day and this package is huge. The SeoVizualiser only took me a couple of minutes to move into a RCL.

Here are the basic steps that I performed:

1.  Change the csproj

We need to update the .csproj-file to use the "Microsoft.NET.Sdk.Razor"-SDK and add two new elements to the configuration:

  • AddRazorSupportForMvc
  • StaticWebAssetBasePath

Should look something like this

<Project Sdk="Microsoft.NET.Sdk.Razor">

Depending on how you used to ship the client side files, you might need to cleanup includes from the csproj file, if you've been using the Umbraco HQ-templates this part needs to be removed:

<Content Include="App_Plugins\{YourPackageFolder}\**\*.*">


2. Add wwwroot-folder

The project should now reload and we need to add a wwwroot-folder to the project. This folder works in the same way as the wwwroot-folder in a web-project, any files in this folder will be available in the consuming website just as if they were a part of the website. They can also be overridden in the consuming website which is great. In my projects. I've just moved the App_Plugins-folder into the wwwroot of my RCL:

3. Modify or remove any .target file included in the NuGet package.

The Umbraco HQ-templates for packages used to include a {package-name}.target-file that was used to copy files from the NuGet-package into the project using it. This needs to be removed, or at least cleaned up.

That's it! You will have to build your project and you might need to rebuild and clean the project, I've also had to restart Visual Studio after making these changes. 

To create a NuGet-package just use this command: dotnet pack --configuration Release

It's important to make sure that your package includes the folder "staticwebassets". I did have issues with multi-targeted packages so if you don't see this folder, make sure that your package is not multitargeted. In my case, the element "TargetFrameworks" was present in the .csproj but only had NET7 as target, after changing this to "TragetFramework" the pack-command worked.


Other things that are good to know about Razor Class Libraries

Shipping Razor Views

At the end of the day, this is a Razor Class Library. Just as in a web project, the view engine of ASP.NET will look for views (.cshtml) in the application root and not in the wwwroot. So let's say that you're shipping a package that need to contain a Partial view and some special views in the App_Pluggins-folder, your structure might look like this:

  • wwwroot/App_Plugins/MyPackage - for Javascript, CSS, Html
  • App_Plugins/MyPackage/Views - for some special view
  • Views/Partial/Lorem.cshtml - For a partial view that can be reused in the consuming project.

Using files from the RCL in code

Most of the time "things just work", views are used, assets are served and life is happy. Sometimes you might need some more fine-grained control over stuff, you might need to read the content of a file that you know is inside a Razor Class Library, or maybe you need to check if a file is present at a given path?

This intersection between files on the filesystem and the content of RCLs is handled by a IFileProvider, the easiest way to get your hands on the one used in your project is to inject the IWebHostEnvironment from Microsoft.AspNetCore.Hosting into your class and use the WebRootFileProvider:

var fileInfo = _webHostEnvironment.WebRootFileProvider.GetFileInfo(classLibraryFilePath);

using Stream stream = fileInfo.CreateReadStream();
using var reader = new StreamReader(stream, Encoding.UTF8);
var fileContent = reader.ReadToEnd();

I hope that this blog post was helpful and that you're excited to try building your next Umbraco package or extension as a Razor Class Library. Feel free to leave a comment or question!




More blog posts

15 januari 2021

Umbraco and MiniProfiler