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.