diff --git a/14/umbraco-cms/reference/using-ioc.md b/14/umbraco-cms/reference/using-ioc.md index e4e97d33957..4bb48fa31ad 100644 --- a/14/umbraco-cms/reference/using-ioc.md +++ b/14/umbraco-cms/reference/using-ioc.md @@ -4,9 +4,9 @@ description: Inversion of Control/Dependency Injection in Umbraco # Inversion of Control / Dependency injection -Umbraco v9+ supports dependency injection out of the box. Umbraco uses the [ASP.NET Core built-in dependency injection](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0#service-lifetimes). 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. +Umbraco supports dependency injection out of the box using the [ASP.NET Core built-in dependency injection](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection). This means that working with dependencies in Umbraco is similar to working with them in ASP.NET Core. -`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` 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 accessors, and so on. You can access the `IServiceCollection` directly to add custom services through the `Services` property. See below for a concrete example: ```csharp IUmbracoBuilder.Services @@ -14,11 +14,31 @@ IUmbracoBuilder.Services ## Registering dependencies -There are two strategies for registering your own dependencies to the container, which one you should use depends on whether you're making a package, or making custom services for your own site. +There are different strategies for registering your dependencies and not one strategy is better than the other. -### Registering dependencies for your site +In this article, we will cover the following three strategies: -When working with your site, and not a package, the recommended way to register dependencies is with the `Program.cs` file: +* [Registering dependencies in the `Program.cs` file](#registering-dependencies-in-the-programcs-file) +* [Registering dependencies in a composer](#registering-dependencies-in-a-composer) +* [Registering dependencies in bundles](#registering-dependencies-in-bundles) + +Which strategy to choose depends on the scenario requiring dependency registration. + +### Choosing a strategy for registering dependencies + +Are you **[working directly on your site](#registering-dependencies-in-the-programcs-file)**? You can choose whichever strategy you prefer working with. + +Are you **[building a package](#registering-dependencies-in-a-composer)** 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](#registering-dependencies-in-bundles)**? 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: + +{% code title="Program.cs" %} ```csharp builder.CreateUmbracoBuilder() @@ -26,15 +46,24 @@ builder.CreateUmbracoBuilder() .AddWebsite() .AddDeliveryApi() .AddComposers() - // If you need to add something Umbraco specific, do it in the "AddUmbraco" builder chain, using the IUmbracoBuilder extension methods. + // When you need to add something Umbraco-specific, do it in the "AddUmbraco" builder chain, using the IUmbracoBuilder extension methods. .AddNotificationHandler() .Build(); ``` -### Registering dependencies in packages +{% endcode %} -When working with packages, you won't have access to the `Program.cs` file. Instead you must use a [composer](../implementation/composing.md) to register your own dependencies in the container. -Using the `Services` property, or appropriate extension method, of the `IUmbracoBuilder`: +{% hint style="info" %} +Learn more about the uses of the `Program.cs` file in [the official ASP.NET Core Fundamentals documentation](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/?view=aspnetcore-8.0&tabs=windows). +{% endhint %} + +### Registering dependencies in a composer + +When working with packages, you do not have access to the `Program.cs` file. Instead, you can use a [composer](../implementation/composing.md) to register your dependencies. + +Below is an example of a composer using the `Services` property of the `IUmbracoBuilder`: + +{% code title="MyComposer.cs" %} ```csharp using IOCDocs.NotificationHandlers; @@ -56,13 +85,21 @@ public class MyComposer : IComposer } ``` +{% endcode %} + {% hint style="info" %} -Remember to add `Umbraco.Cms.Core.DependencyInjection` and `Microsoft.Extensions.DependencyInjection` as 'using' statements where you register your services, to gain access to the `IUmbracoBuilder`, its extension methods, and the Microsoft \`IServiceProvider. +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`. {% endhint %} -### Builder extension methods +### 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 might become cluttered and hard to manage. A great way to manage multiple services is by creating your own custom extension methods for the `IUmbracoBuilder`. This way you can group similar dependencies in extension methods and register them all in as little as a single call: +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. + +{% code title="MyCustomBuilderExtensions.cs" %} ```csharp using IOCDocs.NotificationHandlers; @@ -75,6 +112,7 @@ namespace IOCDocs; public static class MyCustomBuilderExtensions { + // The first dependency is registered public static IUmbracoBuilder RegisterCustomNotificationHandlers(this IUmbracoBuilder builder) { builder.AddNotificationHandler(); @@ -82,6 +120,7 @@ public static class MyCustomBuilderExtensions return builder; } + // The second dependency is registered public static IUmbracoBuilder RegisterCustomServices(this IUmbracoBuilder builder) { builder.Services.AddSingleton(); @@ -89,6 +128,7 @@ public static class MyCustomBuilderExtensions return builder; } + // The two dependencies are bundled together public static IUmbracoBuilder AddCustomServices(this IUmbracoBuilder builder) { builder.RegisterCustomNotificationHandlers(); @@ -98,8 +138,10 @@ public static class MyCustomBuilderExtensions } ``` +{% endcode %} + {% hint style="info" %} -It is not required to have an interface for your dependency: +It is not required to have an interface registering your dependencies: ```csharp services.AddSingleton(); @@ -107,7 +149,12 @@ services.AddSingleton(); {% endhint %} -Now you can call your `AddCustomServices` in either the `Program.cs` file, or your composer like so: +With the dependencies bundled together, you can call the `AddCustomServices` method in either the `Program.cs` file or your composer: + +{% tabs %} +{% tab title="Program.cs" %} + +{% code title="Program.cs" %} ```csharp builder.CreateUmbracoBuilder() @@ -115,11 +162,18 @@ builder.CreateUmbracoBuilder() .AddWebsite() .AddDeliveryApi() .AddComposers() - // Register all our custom services in one go. + // Register all custom dependencies in one go using the custom extension method .AddCustomServices() .Build(); ``` +{% endcode %} + +{% endtab %} +{% tab title="Composer" %} + +{% code title="MyComposer.cs" %} + ```csharp using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; @@ -130,15 +184,20 @@ public class MyComposer : IComposer { public void Compose(IUmbracoBuilder builder) { - // Register all our custom services in one go. + // Register all custom dependencies in one go using the custom extension method builder.AddCustomServices(); } } ``` -### Service lifetime +{% endcode %} + +{% endtab %} +{% endtabs %} + +## Service lifetime -During registration you have to define the lifetime of your service: +During registration of your dependencies, you have to define the lifetime of your service: ```csharp IServiceCollection.AddTransient(); @@ -146,30 +205,30 @@ IServiceCollection.AddScoped(); IServiceCollection.AddSingleton(); ``` -There is three possible lifetimes: +There are three possible lifetimes: -* Transient - always creates a new instance - * A new instance will be created each time it's injected. -* Scoped - one unique instance per web request (connection) - * Scoped services are disposed at the end of the request - * Be very careful not to resolve a scoped service from a singleton, since it may cause it to have an incorrect state in subsequent requests. -* Singleton - one unique instance for the whole web application - * The single instance will be shared across all web requests. +| Name | Lifetime | Description | +|---|---|---| +| **Transient** | Creates a new instance | A new instance will be created each time it's injected. | +| **Scoped** | One unique instance per web request (connection) | Scoped services are disposed of at the end of the request. Be careful not to resolve a scoped service from a singleton, as it may lead to an incorrect state in subsequent requests. | +| **Singleton** | One unique instance for the whole web application | The single instance will be shared across all web requests. | -For more information, have a look at the official [Microsoft documentation](https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes). +For more information, read the official [Microsoft documentation on dependency injections](https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes). ## Injecting dependencies -Once you have registered your services, factories, helpers or whatever you need for you application, you can go ahead and inject them where needed. +Once you have registered the dependencies inject them into your project where needed. ### Injecting dependencies into a class -If you need to inject your service into a controller, or another service, you'll do so through the class +If you need to inject your service into a controller or another service, you will do so through the class. {% hint style="warning" %} The example below uses UmbracoApiController which is obsolete in Umbraco 14 and will be removed in Umbraco 15. {% endhint %} +{% code title="FooController.cs" %} + ```csharp using IOCDocs.Services; using Umbraco.Cms.Web.Common.Controllers; @@ -193,7 +252,11 @@ public class FooController : UmbracoApiController } ``` -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: +{% endcode %} + +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`. This is what you would expect as all the `Foobar.Foo()` method does is to return `Bar` as a string: + +{% code title="Foobar.cs" %} ```csharp namespace IOCDocs.Services; @@ -204,18 +267,22 @@ public class Foobar : IFooBar } ``` +{% endcode %} + ### Injecting dependencies into a View or Template -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: +In some cases you might need to use services within your templates or view files. Services can be injected directly into your views using the `@inject` keyword. This means that you can inject the `Foobar` from above into a view like shown below: + +{% code title="Home.cshtml" %} ```html @using Umbraco.Cms.Web.Common.PublishedModels; @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage @using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; -@* Add a using for the namespace of the service *@ +@* Add a using statement for the namespace of the service *@ @using IOCDocs.Services -@* Now you can inject it *@ +@* Inject the service *@ @inject IFooBar _fooBar @{ @@ -225,19 +292,25 @@ You might need to use services within your templates or views, fortunately, you

@_fooBar.Foo()

``` -If you then load the page which uses this template you'll see a heading with "Bar", which we got from our service. +{% endcode %} -Note that in order to use our service we also have to add a using statement for the namespace of the service. +When loading a page using the template above, you will see the "Bar" heading which is retrieved from the service. + +{% hint style="info" %} +To use the service a using statement for the namespace of the service needs to be added. +{% endhint %} ## 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. +In this section, you can find examples of what you can inject when working with Umbraco. ### UmbracoHelper [Read more about the UmbracoHelper](querying/umbracohelper.md) -`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: +The `UmbracoHelper` is a scoped service, which means you can only use it in services that are also scoped or transient. To get the UmbracoHelper you must inject `IUmbracoHelperAccessor` and use that to resolve it: + +{% code title="MyCustomScopedService.cs" %} ```csharp using System.Collections.Generic; @@ -283,13 +356,17 @@ public class MyCustomScopedService } ``` +{% endcode %} + {% hint style="info" %} -The use of the UmbracoHelper is only possible when there's an instance of the UmbracoContext. [You can read more here](../implementation/services/). +Using the UmbracoHelper is only possible when there is an instance of the UmbracoContext. [You can read more in the implementation article about services](../implementation/services/). {% endhint %} ### ExamineManager -[Read more about examine](searching/examine/). +[Read more about the ExamineManager in the Searching articles](searching/examine/). + +{% code title="SearchService.cs" %} ```csharp using System; @@ -354,9 +431,13 @@ public class SearchService : ISearchService } ``` +{% endcode %} + ### ILogger -[Read more about logging](../fundamentals/code/debugging/logging.md) +[Read more about logging in the debugging section](../fundamentals/code/debugging/logging.md) + +{% code title="Foobar.cs" %} ```csharp using System; @@ -380,6 +461,10 @@ public class Foobar : IFooBar } ``` +{% endcode %} + ## Using DI in Services and Helpers -[Services and Helpers](../implementation/services/) - For more examples of using DI and gaining access to Services and Helpers, and creating your own custom Services and Helpers to inject. +In the [Services and Helpers documentation](../implementation/services/), you can find more examples of using dependency injection and gaining access to the different services and helpers. + +You will also find information about creating custom services and helpers to inject and use in your Umbraco project.