Umbraco v8: changes for developers

  • Umbraco

The release of Umbraco v8 has just been announced. We have been playing around with the alpha versions that were available for testing for a while now and in this blog I try to give a quick overview of the biggest changes for developers like myself. If you are not a developer or are simply more interested in changes that impact editors instead, head over to part 1 of our blog series focused on editor changes.

This blog grew quite large due to all the changes in v8 so let me start with a little TL;DR: things are looking great!

Dependency Injection

One of the greatest changes for developers in Umbraco v8 is the usage of Dependency Injection (DI) in Umbraco Core, which developers can utilize in their own custom code as well. This means it is now much easier to write components with a large dependency graph without having to worry about creating and injecting all the dependencies, this is all done for you. Dependencies are simply declared in the constructor and will be injected by the DI framework. This works for both Umbraco core services and our own services. While it was possible to use some DI framework in v7, it is now much easier since it is already shipped with Core and supported out of the box.

Composing Services

To add your own services to the DI framework, you simply implement Umbraco's IUserComposer interface. Umbraco will call the Compose() method it requires you to implement and in there you register any services you want to become injectable. This means other classes can request an instance of the registered service and it will be injected automatically. Using composers it's even possible to swap out Umbraco core services with your own implementations if that need ever arises. A great blog about composing in v8 was written by Umbraco Core developer Stephan, so for more information please give it a read.

Let me illustrate the basic concept with an extremely simple example.
I want to create a CalculatorService which offers just two methods: Sum() and Product() with predictable implementations. This service has the following tiny interface:


public interface ICalculatorService
{
    int Sum(int x, int y);
    int Product(int x, int y);
}

Internally, our concrete CalculatorService implementation depends on an ISumService and an IProductService to do the "heavy" lifting of actually adding and multiplying the arguments, both of which are injected. In addition to performing some simple arithmetic, we want to log every call to Sum() and Product() and utilize Umbraco's standard logger for this. This is where you can see the power of DI, as the only thing necessary to obtain a logger instance is to specifcy an ILogger dependency in the constructor of the CalculatorService, and it will be injected automagically. Umbraco will register its ILogger implementation with the DI container, but does not know about an ISumService, IProductService or ICalculatorService, as those are our own services. We will have to register those ourselves by implementing the IUserComposer interface discussed earlier. For our Calculator, the implementation will look like this:


// Register your own services by implementing IUserComposer
public class CalculatorComposer : IUserComposer
{
    public void Compose(Composition composition)
    {
        composition.Register<ISumService, SumService>();
        composition.Register<IProductService, ProductService>();
        composition.Register<ICalculatorService, CalculatorService>();
    }
}

The CalculatorService itself is straightforward and simply uses its injected services to perform a calculation and log the result as well.


public class CalculatorService : ICalculatorService
{
    private readonly ISumService _sumService;
    private readonly IProductService _productService;
    private readonly ILogger _logger;

    public CalculatorService(
        ISumService sumService,
        IProductService productService,
        ILogger logger)
    {
        _sumService = sumService;
        _productService = productService;
        _logger = logger;
    }

    public int Sum(int x, int y)
    {
        int sum = _sumService.Sum(x, y);
        _logger.Info<CalculatorService>($"Sum({x}, {y}) = {sum}");
        return sum;
    }

    // Product() implementation omitted for brevity
}

The CalculatorService is now ready to be used throughout our application, without having to worry about creating any of its dependencies. To make the Sum and Product methods available to the world, let's expose it through a Web API. We create a very simple CalculatorApiController which provides access to both Sum() and Product() through HTTP GET requests.


public class CalculatorApiController : UmbracoApiController
{
    private readonly ICalculatorService _calculatorService;

    public CalculatorApiController(ICalculatorService calculatorService)
        => _calculatorService = calculatorService;

    [HttpGet]
    public int Sum(int x, int y)
        => _calculatorService.Sum(x, y);

    // Product() implementation omitted for brevity
}

That's it. The controller requests its ICalculatorService dependency and the DI framework takes care of creating an instance together with all its dependencies.

IComponent

IComponent is a new concept in v8. It is an interface for code that should be initialized and terminated by Umbraco automatically when the application starts. A component can request dependencies via its constructor and they will be injected. It appears the application of DI in Umbraco simply called for a concept like this to be created, as in v7 code that will now implement this interface would be a class inheriting from ApplicationEventHandler which specifies overrides for methods like ApplicationStarting. However, the ApplicationEventHandler was not designed with DI in mind, while the IComponent interface was.

Let's look at a simple example to illustrate a possible use case for IComponent. We want to setup some event handler for the "Published" event exposed by the ContentService. Whenever a node is published, we want to log which Umbraco user performed the action. In v7 we would probably use the ApplicationEventHandler to set this up but it was removed from v8. Instead, we now provide an implementation of the IComponent interface. 

For this example, we need to fetch the user name from the IUserService and write to the log using some ILogger instance. Both of these dependencies can be requested simply by specifying them in the constructor. The rest of the code is the same as it would have been in v7, so if you want to setup an event handler for the ContentService.Published event and log some information about it alongside information about the user you can use it like below.


public class PublishEventsComposer : ComponentComposer
{
}

public class PublishEventsComponent : IComponent
{
    private readonly IUserService _userService;
    private readonly ILogger _logger;

    public PublishEventsComponent(IUserService userService, ILogger logger)
    {
        _userService = userService;
        _logger = logger;
    }

    public void Initialize()
    {
        ContentService.Published += (contentService, e) =>
        {
            foreach (IContent ic in e.PublishedEntities)
                if (_userService.GetUserById(ic.WriterId) is IUser author)
                    _logger.Info<PublishEventsComponent>(
                        $"Content '{ic.Name}' was published by '{author.Name}'");
        };
    }

    public void Terminate()
    {
    }
}

Note that in order for the component to be picked up by Umbraco it needs to be added to the Composition, which we can do either by hand through the Compose() method of the IComposer interface, or simply use the convenience base class ComponentComposer<TComponent> which will take care of it for us. The ComponentComposer<TComponent> class has only a single method with the following implemenation:


public virtual void Compose(Composition composition)
{
    composition.Components().Append();
}

ModelsBuilder & Nested Content

ModelsBuilder and Nested Content are amazing, and amazing things luckily will not be changed very often. Both of them are still there in v8 (yay!), and mostly the same as they were in v7. One addition in v8 is the IPublishedElement interface, which will be used for Nested Content items rather than IPublishedContent. When creating a doctype for NestedContent, you should now toggle the "Is an Element type" to signal to Umbraco instances of the doctype will be used in NestedContent and will thus be an IPublishedElement rather than a full blown content node like regular pages.

 

IPublishedElement is essentially just a collection of properties belonging to a property type, but does not worry about anything content related like the Url, WriterName, TemplateName, and Children. In fact, the full interface is very minimal as is shown here.


public interface IPublishedElement
{
    PublishedContentType ContentType { get; }
    Guid Key { get; }
    IEnumerable<IPublishedProperty> Properties { get; }

    IPublishedProperty GetProperty(string alias);
}

IPublishedElement in Nested Content works seemlessly together with ModelsBuilder, which will generate a strongly typed property for Nested Content with the concrete class implementing IPublishedElement. In the above example this would be IEnumerable<NestedItem>. In v7, ModelsBuilder would always simply generate IEnumerable<IPublishedContent> even though the actual type would be the concrete implementation. It's nice that this is now done automatically so we do not have to cast it by hand.

IPublishedContent

Talking about IPublishedContent, this familiar interface is mostly the same as in v7. Although a lot of the code surrounding IPublishedContent was changed (NuCache, PublishedSnapShots etc.), working with IPublishedContent itself as a developer seems mostly the same as in v7, which is also not a bad thing at all. This means your Razor files will look pretty much the same between v7 and v8, as the main thing they are doing is render data that is accessed through IPublishedContent.

The Backoffice

The Backoffice, while sporting a refreshed look and feel and of course receiving big upgrades with Infinite Editing and Content Apps among other things, is actually mostly the same under the hood. AngularJS has (at long last) been updated to the latest 1.x version (v1.7.5) but most of Umbraco's directives and services appear to be unchanged. This means extending the backoffice is almost identical to v7. The main way to customize the backoffice is through adding custom Property Editors, which can literally be copy-pasted from v7 and they seem to work in almost all cases. This is great news for developers who want to migrate from v7 to v8 and have a lot of custom property editors in use.

Log Viewer

A nifty new developer focused feature in the Umbraco backoffice is the Log Viewer that parses the log files in App_Data/Logs and shows them in a nice dashboard that can also be used to filter on different types of properties of each log such as the log level or source context. The logfiles itself now use a JSON format and are significantly harder to read yourself so everybody should really just use the Umbraco Log Viewer. Umbraco provides a bunch of example searches to filter your logs, and the possibilities seem quite endless. Below is a screenshot of a simple query for the content of the message, and the result is a line from the log that was written by the example CalculatorService discussed earlier.

 

Content Apps

Content Apps are a new addition to v8. They have a relation with a content node, but are not content themselves. This means they will not be part of the document history so you cannot rollback to some other "version" of a Content App. This does not make sense anyway, as a Content App would be used to display things like Google Analytics data associated with the current page. Content Apps are added much like custom Property Editors. You simply create a folder in App_Plugins and specificy a manifest file. The package.manifest from below was taken from the documentation on Our Umbraco about Content Apps. Other than a different package manifest definition it works exactly like a custom Property Editor. This makes it very easy to add Content Apps when you are familiar with property editors.


{
    "contentApps": [
      {
        "name": "Word Counter",
        "alias": "wordCounter",
        "weight": 0,
        "icon": "icon-calculator",
        "view": "~/App_Plugins/WordCounter/wordcounter.html",
      }
    ],
    "javascript": [
        "~/App_Plugins/WordCounter/wordcounter.controller.js"
    ]
}

A Content App is rendered in a different part of the UI, accessible through a new button that is added in addition to the existing "Content" and "Info" buttons, as is seen below.

 

Infinite Editing

Infinite editing sounds like an editor-only feature, but as developers we also spend a good deal of time within Umbraco to configure doctypes, datatypes, property editors and more. I especially liked the improved UX for developers when editing an existing doctype starting from the content node. With infinite editing everything can be done without leaving the content page. The doctype settings will open in an infinite editing pane and if a new property editor is created the configuration is also done in a nested pane. This can continue forever, hence the name. I love this addition to the backoffice. In the screenshot below we started at a content node, navigated to its doctype settings and edited a property of the doctype, all of that without ever leaving the page. 

 

Conclusion

Umbraco v8 is a great evolution of v7. Almost all core components have been rewritten and legacy code was removed. The usage of DI should make it much easier to organize our code into reusable, composable components with clear dependencies. The backoffice got some nice upgrades too, with my personal favorite probably being Infinite Editing as it makes for a much more fluent experience. I really like the direction of v8 and cannot wait to use it in a real world project.

Interested in helping Umbraco evolve?

At Perplex we are convinced of Umbraco’s power and versatility, which we continuously use to create complex platforms, websites and applications for interesting clients. But, we also invest in the CMS and do our part by making Umbraco even better. Sounds good no? If this caught your interest and if Umbraco makes your heart skip a beat, do not hesitate to call and come over for a cup of coffee!

Contact us