Inversion of Control/Dependency Injection in Umbraco
Umbraco v9+ supports dependency injection out of the box. Umbraco uses the ASP.NET Core built-in dependency injection. This means that you don't have to install external packages to register and use your dependencies. If you're familiar with ASP.NET Core, the experience will be similar.
IUmbracoBuilder is a Umbraco-specific abstraction on top of the IServiceCollection, its purpose is to aid in adding and replacing Umbraco-specific services, such as notification handlers, filesystems, server role accessor, and so on. You can access the IServiceCollection directly to add your custom services through the Services property, see below for a concrete example:
IUmbracoBuilder.Services
Registering dependencies
There are different strategies for registering your dependencies and not one strategy is better than the other.
In this article, we will cover the following three strategies:
Are you building a package and do not have access to the Program.cs file? In this case, you have the option to register the dependencies in a composer.
Are you in a situation where you need to register more than a few dependencies? You can bundle your dependencies in custom extension methods and register them in a single call.
Registering dependencies in the Program.cs file
When working with your Umbraco site, dependencies can be registered within the Program.cs file.
In the example below, a custom notification handler is added to the CreateUmbracoBuilder() builder chain:
Program.cs
builder.CreateUmbracoBuilder() .AddBackOffice() .AddWebsite() .AddDeliveryApi() .AddComposers() // When you need to add something Umbraco-specific, do it in the "AddUmbraco" builder chain, using the IUmbracoBuilder extension methods.
.AddNotificationHandler<ContentTypeSavedNotification,ContentTypeSavedHandler>() .Build();
To access the IUmbracoBuilder, you need to add Umbraco.Cms.Core.DependencyInjection and Microsoft.Extensions.DependencyInjection as using statements when registering your services. This, in turn, will also give you access to the IUmbracoBuilder extension methods as well as the Microsoft IServiceProvider.
Registering dependencies in bundles
Depending on your scenario, you may have a lot of dependencies you need to register. In this case, your Program.cs or Composer can become cluttered and hard to manage.
You can manage multiple services in one place by creating your custom extension methods for the IUmbracoBuilder. This way you can bundle similar dependencies in extension methods and register them all in a single call.
In the following code sample two dependencies, RegisterCustomNotificationHandlers and RegisterCustomServices are bundled together in a custom AddCustomServices extension method.
MyCustomBuilderExtensions.cs
usingIOCDocs.NotificationHandlers;usingIOCDocs.Services;usingMicrosoft.Extensions.DependencyInjection;usingUmbraco.Cms.Core.DependencyInjection;usingUmbraco.Cms.Core.Notifications;namespaceIOCDocs;publicstaticclassMyCustomBuilderExtensions{ // The first dependency is registeredpublicstaticIUmbracoBuilderRegisterCustomNotificationHandlers(thisIUmbracoBuilder builder) {builder.AddNotificationHandler<ContentTypeSavedNotification,ContentTypeSavedHandler>(); {...}return builder; } // The second dependency is registeredpublicstaticIUmbracoBuilderRegisterCustomServices(thisIUmbracoBuilder builder) {builder.Services.AddSingleton<IFooBar,Foobar>(); {...}return builder; } // The two dependencies are bundled togetherpublicstaticIUmbracoBuilderAddCustomServices(thisIUmbracoBuilder builder) {builder.RegisterCustomNotificationHandlers();builder.RegisterCustomServices();return builder; }}
It is not required to have an interface registering your dependencies:
services.AddSingleton<Foobar>();
With the dependencies bundled together, you can call the AddCustomServices method in either the Program.cs file or your composer:
Program.cs
builder.CreateUmbracoBuilder() .AddBackOffice() .AddWebsite() .AddDeliveryApi() .AddComposers() // Register all custom dependencies in one go using the custom extension method .AddCustomServices() .Build();
MyComposer.cs
usingUmbraco.Cms.Core.Composing;usingUmbraco.Cms.Core.DependencyInjection;namespaceIOCDocs;publicclassMyComposer:IComposer{publicvoidCompose(IUmbracoBuilder builder) { // Register all custom dependencies in one go using the custom extension methodbuilder.AddCustomServices(); }}
Service lifetime
During registration you have to define the lifetime of your service:
If you place a breakpoint on var bar = _foobar.Foo(), open /Umbraco/Api/foo/foo in your browser and inspect the variable, you'll see that the value is bar, which is what you'd expect since all the Foobar.Foo() method does it to return Bar as a string:
You might need to use services within your templates or views, fortunately, you can inject services directly into your views using the @inject keyword. You can for example inject the Foobar from above into a view like so:
@using Umbraco.Cms.Web.Common.PublishedModels;@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.Home>@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;@* Add a using for the namespace of the service *@@using IOCDocs.Services@* Now you can inject it *@@inject IFooBar _fooBar@{ Layout = null;}<h1>@_fooBar.Foo()</h1>
If you then load the page which uses this template you'll see a heading with "Bar", which we got from our service.
Note that in order to use our service we also have to add a using statement for the namespace of the service.
Other things you can inject
Most of (if not all) the Umbraco goodies you work with every day can be injected. Here are some examples.
UmbracoHelper is a scoped service, therefore you can only use it in services that are also scoped, or transient. To get UmbracoHelper you must inject IUmbracoHelperAccessor and use that to resolve it:
usingSystem.Collections.Generic;usingUmbraco.Cms.Core.Models.PublishedContent;usingUmbraco.Cms.Web.Common;namespaceIOCDocs.Services;// This service must be scopedpublicclassMyCustomScopedService{privatereadonlyIUmbracoHelperAccessor _umbracoHelperAccessor;publicMyCustomScopedService(IUmbracoHelperAccessor umbracoHelperAccessor) { _umbracoHelperAccessor = umbracoHelperAccessor; }publicIEnumerable<IPublishedContent> GetContentAtRoot() { // Try and get the Umbraco helpervar success =_umbracoHelperAccessor.TryGetUmbracoHelper(outvar umbracoHelper);if (success isfalse) { // Failed to get UmbracoHelper, probably because it was accessed outside of a scoped/transient service.returnnull; } public IEnumerable<IPublishedContent> GetContentAtRoot() { // Try and get the Umbraco helpervar success =_umbracoHelperAccessor.TryGetUmbracoHelper(outvar umbracoHelper);if (success isfalse) { // Failed to get UmbracoHelper, probably because it was accessed outside of a scoped/transient service.returnnull; } // We got Umbraco helper, now we can do something with it.returnumbracoHelper.ContentAtRoot(); } }}
The use of the UmbracoHelper is only possible when there's an instance of the UmbracoContext. You can read more here.
usingSystem;usingSystem.Collections.Generic;usingExamine;usingUmbraco.Cms.Core;usingUmbraco.Cms.Core.Models.PublishedContent;usingUmbraco.Cms.Core.Web;usingUmbraco.Cms.Infrastructure.Examine;usingUmbraco.Extensions;namespaceIOCDocs.Services;// This service must be scoped.publicclassSearchService:ISearchService{privatereadonlyIExamineManager _examineManager;privatereadonlyIUmbracoContextAccessor _umbracoContextAccessor;publicSearchService(IExamineManager examineManager,IUmbracoContextAccessor umbracoContextAccessor) { _examineManager = examineManager; _umbracoContextAccessor = umbracoContextAccessor; }publicIEnumerable<PublishedSearchResult> Search(string searchTerm) {if (_examineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName,outvar index) isfalse) { throw new InvalidOperationException($"No index found by name {Constants.UmbracoIndexes.ExternalIndexName}");
}if (!(index isIUmbracoIndex umbracoIndex)) {if (_examineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName,outvar index) isfalse) { throw new InvalidOperationException($"No index found by name {Constants.UmbracoIndexes.ExternalIndexName}");
}if (!(index isIUmbracoIndex umbracoIndex)) {thrownewInvalidOperationException("Could not cast"); } // Do stuff with the indexif (_umbracoContextAccessor.TryGetUmbracoContext(outvar umbracoContext) isfalse) {thrownewInvalidOperationException("Could not get Umbraco context"); } return umbracoIndex.Searcher.Search(searchTerm).ToPublishedSearchResults(umbracoContext.PublishedSnapshot.Content);
} // Do stuff with the indexif (_umbracoContextAccessor.TryGetUmbracoContext(outvar umbracoContext) isfalse) {thrownewInvalidOperationException("Could not get Umbraco context"); } return umbracoIndex.Searcher.Search(searchTerm).ToPublishedSearchResults(umbracoContext.PublishedSnapshot.Content);
}}
usingSystem;usingMicrosoft.Extensions.Logging;namespaceIOCDocs.Services;publicclassFoobar:IFooBar{privatereadonlyILogger<Foobar> _logger;publicFoobar(ILogger<Foobar> logger) { _logger = logger; }publicvoidFoo() {_logger.LogInformation("Method Foo called at {DateTime}",DateTime.UtcNow); }}
Using DI in Services and Helpers
Services and Helpers - For more examples of using DI and gaining access to Services and Helpers, and creating your own custom Services and Helpers to inject.