Skip to main content

Umbraco Package Migration to .NET Core: Criteria Providers - Distributing and Wrapping Up

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

Update 29th March, 2021, 18:15 - I've updated this article to discuss how to properly handle the NuGet distribution of content files, which was a problem I was having and discussed in the first publication of this post. Thanks to Bjarke and Warren for sorting me out!

Installing the Package

This post should be the last one, as - good news - the package works now in V9, and you can try it out if you like, by doing the following.

  1. Create an Umbraco project based on the alpha 4 template.
  2. Install Umbraco and check the back-office is up and running.
  3. Install the package - either via the VS.Net package console and Install-Package UmbracoPersonalisationGroups -Version 3.0.0-alpha-2 or the CLI and dotnet add package UmbracoPersonalisationGroups --version 3.0.0-alpha-2
  4. Open up Startup.cs and add:
    • A using statement of using Our.Umbraco.PersonalisationGroups;
    • .AddPersonalisationGroups(_config) as the last extension method in ConfigureServices()
    • .UsePersonalisationGroups() as the last extension method in Configure()
  5. Build and run.

You should find you can create and edit personalisation groups in the back-office:

To run a simple test on the front-end, I've created a document type with a title and an instance of nested content. The nested content is based on an element type that just has a single text property as well as an a personalisation group picker (a data type installed with the package), that I've given the default alias of personalisationGroups.

I've then created a content item based on that document type, with three elements in the nested content - the first of which I've associated with the "Weekday Visitors" personalisation group shown above.

The template to render the page looks like this:

@using Umbraco.Cms.Core.Models.PublishedContent;
@using Umbraco.Extensions;
@using Our.Umbraco.PersonalisationGroups.Services; 

@inject IGroupMatchingService GroupMatchingService;
  
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@{
	Layout = null;
}

<h1>@Model.Value("headline")</h1>

<ul>
    @foreach(var item in Model.Value<IEnumerable<IPublishedElement>>("nestedItems")
        .Where(x => x.ShowToVisitor(GroupMatchingService)))
    {
        <li>@item.Value("title")</li>
    }
</ul>

Note 1: See how as I discussed in an earlier post, as my ShowToVisitor() extension method requires access to logic in registered service, we can inject it into the view pass it on as a parameter to the extension method.

Note 2: I'm not using Models Builder here, as I had some trouble with getting this to work with nested content - maybe an alpha bug - so I had to disable that in configuration and use the extension methods working with IPublishedContent. And then found an issue with the models builder configuration in appSettings.json not being picked up, which when I looked into, I found it had already been found and fixed, and so updated to the latest nightly. The fun of working with alpha software!).

And you should find that the first item in the list of nested elements only renders if the day of the week matches what's configured in the criteria. When the screenshot below was taken it was a Sunday... so the first item doesn't show as it's configured only for weekdays.

Creating the Package

In order to prepare the NuGet package, I used the dotnet CLI and the dotnet pack command, which worked after some amends to the .csproj file. This best reference for this is the package template made available by Umbraco, for creating and working on a new package for V9.

  <ItemGroup>
    <Content Include="App_Plugins\PersonalisationGroups\**\*.*">
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      <CopyToPublishDirectory>Always</CopyToPublishDirectory>
    </Content>
    <None Include="build\**\*.*">
      <Pack>True</Pack>
      <PackagePath>build</PackagePath>
    </None>
  </ItemGroup>

This change causes the non-compiled assets - i.e. the JavaScript and angularjs views - to be included in the generated NuGet package, and, importantly, for them to be restored to the required location when the package is installed.

After some local testing, I pushed the package to nuget.org via dotnet nuget push UmbracoPersonalisationGroups.3.0.0-alpha-2.nupkg --api-key ... --source https://api.nuget.org/v3/index.json.

Odds and Sods

In this section I'll just collate a things that may be of interest that I needed to resolve, some of which may be relevant to this package and not much else, but it could be that others might run into too.

Running From Visual Studio

This first one is a silly one... but I wasted some time on it so I'll share to maybe prevent someone else doing so too! Having installed my test web application, I could run it via the command line with dotnet run, but not through Visual Studio.

To use Kestrel when debugging, make sure you switch this setting from the default of IISExpress to the name of your web application (hat-tip):

Activating Class Instances

I mentioned in an earlier post an issue that I parked, which was where I'm using runtime assembly scanning to pick up all instances of an interface IPersonalisationGroup. The reason for this is that rather than making available only the criteria that the package ships with for constructing a persona, I also want to support those that might be created within an Umbraco solution, or in an add-on package.

The problem I had was that, having scanned and found all the instances, in order to use them, they need to be activated, which I was doing using: Activator.CreateInstance(type) as IPersonalisationGroupCriteria. This code is relying on there being a parameterless constructor, but now I've refactored to use dependency injection, there is no longer one of these. Whilst you can provide constructor parameters to this method, even if I knew all the types of them at runtime - which I don't - this would be difficult as they aren't all the same.

What I really wanted, was a method that will instantiate a type and look in the IoC container for registrations of any dependency it needs. And turns out, there is just the thing: ActivatorUtilities.CreateInstance(serviceProvider, type) as IPersonalisationGroupCriteria.

You can see this in use here, with IServiceProvider being injected into the class.

Update: - I've not had chance to look at this yet to see if it's worth changing my implementation, but thanks to a comment from Kevin Jump, looks like there's some base support for this type of thing in Umbraco. If interested, see the example he shared with me here.

Abandoning Embedded Resources

For the V7 and V8 versions of this package, I used embedded resources for all the client-side assets. Back through the mists of time, this was a technique used in Umbraco, but I haven't seen much reference to it recently, and it's certainly undocumented so perhaps not officially supported. I did like the idea of just having one dll to ship though, which is why I used it before. I struggled getting it to work with Umbraco 9 though, running into an issue with Smidge, a dependency Umbraco takes for runtime bundling and minification for back-office assets. This was failing if it didn't find a physical file on disk.

Maybe that's fixable, but I also thought that as well as doing something that's likely off the beaten path, I'm also possibly going to be opting out of this bundling and minification, which doesn't seem a good idea. Hence I switched to use the documented way of creating a property editor, which I'm pleased to say, does "just work" on Umbraco 9, with files installed in the content root (not the wwwroot) folder.

Member Related Functionality

Functionality related to members has been specifically called out as not yet complete and stable for the Umbraco V9 release, so I haven't focussed too much on that. What I can see and say though is that the member services remain, so I've been able to use them as before to get lists of member groups, types and fields - see for example here.

Knowing also that the membership work is looking to migrate to use ASP.NET Core Identity, we can also likely rely on functionality using on that to work. So I've tentatively implemented code like this (hat-tip), used here to retrieve the roles for the current logged in members (which translate in Umbraco to member groups).

Back-Office Consistency

Don't look too closely at the styling used for the back-office components - there's a fair amount of inline styling in there and hacks to make things look and work the same across V7 and V8. Now the package is V9 only, I can remove much of that and look to use UI components that are known to be available in the version of Umbraco the package is running in, and, if needed, reference a single CSS file that I add to the package.manifest. I have fixing that up down as a a "to do".

It's worth being aware of these components though, that are documented and can be re-used in packages. With V9, the front-end of the Umbraco back-office isn't changing, so there should be no issues with starting or continuing to use these.

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