Kentico Xperience Design Patterns: Registering Xperience Dependencies
Tags: xperience, kentico, csharp, dotnet
Published: 2022-06-27 14:03:29

---
title: Kentico Xperience Design Patterns: Registering Xperience Dependencies
published: true
description: Learn why we should register our Kentico Xperience dependencies where they are consumed, not where they are authored, to create more understandable code, flexible applications, and reusable libraries.
tags: xperience, kentico, csharp, dotnet
series: Kentico Xperience Design Patterns
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/hcusgcjdsp4aozej50g9.jpg
---

Kentico Xperience has supported dependency resolution (both in ASP.NET Core and .NET 4.x) for a few versions, through the [service locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern) 🧐.

Now, with Kentico Xperience 13, it fully supports [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) (DI) out of the box using ASP.NET Core. We can always inject any of Xperience's built-in services into our custom types without needing to worry about how those services are constructed 🤩.

However, we still need to be conscious of Xperience's underlying architecture when doing anything beyond simple use-cases 😮.

If we don't pull back the curtain, there are things we might do to improve convenience that have significant impacts on code reusability, discoverability, and automated testing 😟.

## 📚 What Will We Learn?

- How does Xperience manage internal dependencies?
- How can we customize Xperience's internal types?
- When do customizations become inflexible?
- Best practices for registering custom types with Xperience.

## 💎 Xperience's DI Container

In Kentico Xperience's core, there is a static service locator class `CMS.Core.Service`. With it, we can request any service that Xperience has registered in its internal DI container.

```csharp
IUserInfoProvider provider = Service.Resolve<IUserInfoProvider>();

UserInfo user = provider.Get(1);
```

> Note: This DI container is actually backed by `Microsoft.Extensions.DependencyInjection.ServiceCollection`, which is the same type that backs ASP.NET Core's DI container 🤓 and it works in both .NET 4.8 and .NET 6.0.

> However, it is a completely separate container from the one ASP.NET Core provides us 🤔 so we should follow Xperience's documentation for dependency registration for Xperience type customization.

We can also use this service locator to register types with Xperience's DI container. The service registration needs to be performed in a custom Xperience `Module` during the `PreInit` phase of application startup:

```csharp
public class CustomUserInfoProvider : UserInfoProvider
{
    public override ObjectQuery<UserInfo> Get()
    {
        // do something special 🤷‍♂️
    }
}

// ...

public class CustomModule : Module
{
    public CustomModule : base(nameof(CustomModule)) { }

    protected override OnPreInit()
    {
        Service.Use<IUserInfoProvider, CustomUserInfoProvider>();
    }
}
```

Since it's using the .NET `ServiceCollection` underneath, we can even use it to register configuration via the [options pattern](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-6.0) (again in the `Module.OnPreInit` method):

```csharp
// I'm making up a `CustomUserOptions` type here as an example
public class CustomUserOptions
{
    public int DefaultUserID { get; set; }
}

// ...

Service.Configure<CustomUserOptions>(o =>
{
    o.DefaultUserID = 1;
});
```

This means we should also be able to retrieve this configuration through `Service.Resolve` anywhere in our application:

```csharp
var options = Service.Resolve<IOptions<CustomUserOptions>>();

int userID = options.Value.DefaultUserID;
```

> This isn't very useful outside of an ASP.NET Core application or modern (.netcoreapp3.1 +) .NET class libraries. We should use the built-in .NET DI container for this kind of thing, so it's more just an interesting observation.

We could use `Service` to get access to any Xperience type we want in our ASP.NET Core app, but in user-land code (code that isn't part of a framework), using a service locator for retrieving services [is an anti-pattern](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) 😩.

Instead, use constructor dependency injection wherever possible 🙂, and hide away your use of the `Service` class if you must use it. This will make your code more testable and predictable.

## Customizing Xperience's Internal Types

Above, we looked at an example of how to customize Xperience's built-in types (like `IUserInfoProvider`) using the `Service.Use` method.

However, there's a much more common pattern using `assembly` attributes for registering dependencies that you'll see in blog posts and [Xperience's documentation](https://docs.xperience.io/custom-development/customizing-providers/custom-info-provider-example):

```csharp
// This tells Xperience to add our custom type to its services container
[assembly: RegisterCustomProvider(typeof(CustomUserInfoProvider))]

namespace Sandbox.Data
{
    public class CustomUserInfoProvider : UserInfoProvider
    {
        public override ObjectQuery<UserInfo> Get()
        {
            // do something special 🤷‍♂️
        }
    }
}
```

This assembly attribute registration pattern is also used for [custom modules](https://docs.xperience.io/custom-development/creating-custom-modules/initializing-modules-to-run-custom-code), for example when creating a custom module to handle [global events](https://docs.xperience.io/custom-development/handling-global-events):

```csharp
[assembly: RegisterModule(typeof(CustomGlobalEventsModule))]

namespace Sandbox.Data
{
    public class CustomGlobalEventsModule : Module
    {
        public CustomGlobalEventsModule
            : base(nameof(CustomGlobalEventsModule)) { }

        protected override void OnInit()
        {
            base.OnInit();

            DocumentEvents.Insert.After += Document_Insert_After;
        }

        private void Document_Insert_After(
            object sender, DocumentEventArgs e)
        {
            // Add custom actions here
        }
    }
}
```

These `assembly` attributes are a lot simpler and seem to be the way to go:

- Create our custom class
- Add an attribute above 
- Our customization is registered and ready to run 😁!

> Note: We'll typically only use `Service.Use` when we want to have an explicit ordering to service decoration, because the `assembly` attributes for dependency registration [are non-deterministic](https://docs.xperience.io/custom-development/decorating-system-services#Decoratingsystemservices-Decorateservicesviadependencyinjection).

## Non Flexible Dependency Registration

Xperience's documentation provides [plenty](https://docs.xperience.io/custom-development/customizing-providers/custom-info-provider-example) of [examples](https://docs.xperience.io/custom-development/customizing-providers/custom-email-provider-example) of [registering customizations](https://docs.xperience.io/custom-development/creating-custom-modules/initializing-modules-to-run-custom-code) in the same assembly, and often the same file, where they are defined.

This is perfect for helping developers understand concepts 👍🏾. However, as our applications grow in size and complexity, and we discover use-cases for using Xperience's APIs [outside of traditional web applications](https://docs.xperience.io/integrating-3rd-party-systems/using-the-xperience-api-externally), we will find we've strung ourselves up in our dependencies 😒.

In ASP.NET Core, we are used to adding NuGet packages containing types that customize our applications. We enable those customizations when we call extension methods that add types to the `IServiceCollection` or modify request processing of the `IApplicationBuilder`.

Simply adding a NuGet package or reference to a .NET project (typically) does not change the behavior of our application - this is going to be a .NET developer's expectation 😤.

The problem with assembly registration attributes like `[assembly: RegisterModule()]` and `[assembly: RegisterCustomProvider()]` is they are DI container configuration calls that **always** 😲 execute in a running application, even if they aren't in our main application's code.

### External Applications

Imagine we create a `.netstandard2.0` class library (so that we can share its code with both our CMS .NET 4.8 application and live site ASP.NET Core application) and this library contains our custom Page Type definitions and some other code we want to share between both applications.

It's also likely that this library registers several custom providers and modules using `assembly` attributes.

Now, if we decide we want to use this library in [an external application](https://docs.xperience.io/integrating-3rd-party-systems/using-the-xperience-api-externally) like [a console application](https://github.com/wiredviews/xperience-appsettings-json-registration), we cannot opt-out of those registered modules and custom providers 😔. They always come along for the ride and automatically augment our system.

At [WiredViews](https://www.wiredviews.com/services/web-design-and-development#section903d04b3-6baa-4a65-ba32-59acc66f196d) we often use .NET 6 console applications to iterate quickly on ETL/data import or migration processes and often we want to selectively opt-in to specific custom modules or types and opt-out of others.

When the referenced code contains these `assembly` attributes, our hands are tied - we can't turn them off.

### Testing and Troubleshooting

Other times, a developer might want to test a specific scenario locally with a custom provider that behaves slightly differently or use an updated custom module implementation.

If class libraries are distributed through NuGet packages and those libraries have `assembly` registration attributes, what are the answers to the following questions 🤔?

- How would a developer easily set up their testing scenario?
- How can they control what dependencies are registered and executed?
- How do new developers even know what customizations are running in an application? Do they have to go digging through all the source code to find out 😩?

In the modern ASP.NET Core world, we're used to commenting out an extension method in our middleware pipeline or services registration to turn functionality off.

We could use app settings to replicate this behavior even when our NuGet packages contain `[assembly: RegisterModule()]` calls. Those modules or custom types would need to check the app settings to enable/disable the custom behavior 😯.

But, this requires developers to have one set of expectations for non-Xperience application customizations and a completely different set for Xperience customizations 😑.

There's a better way!

## Flexible Dependency Registration

All we need to do to bring some consistency and flexibility to our applications is to remove our dependency registration from our class libraries and move it into our consuming applications.

This way customizations only turn 'on' when they are explicitly registered in the applications that want them 🤗.

In the ASP.NET Core world, if we are [practicing good Startup.cs hygeine](https://dev.to/seangwright/kentico-xperience-design-patterns-good-startup-cs-hygiene-3klm) we might end up with a `XperienceRegistrations.cs` class:

```csharp
[assembly: RegisterModule(CustomGlobalEventsModule)]
[assembly: RegisterCustomProvider(CustomUserInfoProvider)]

namespace Sandbox.Web.Configuration;

public static class XperienceRegistrations
{
    public static IServiceCollection AddAppXperience(
        this IServiceCollection)
    {
        // register our Xperience services with ASP.NET Core
        services.AddKentico();

        // ...

        return services;
    }
}
```

Notice, we opt-in to our Xperience customizations that require `assembly` attributes by registering them in the same locationn that all our other Xperience-specific customizations are defined.

This makes it extremely clear what is being customized in an app and allows developers to turn things on/off in a way that is very similar to all the other code they work with in ASP.NET Core 💪🏽.

We can follow this pattern in our CMS ASP.NET 4.8 application by creating a `DependencyRegistrationModule` in that application's project:

```csharp
[assembly: RegisterModule(typeof(DependencyRegistrationModule))]

[assembly: RegisterModule(typeof(CustomGlobalEventsModule))]
[assembly: RegisterCustomProvider(typeof(CustomUserInfoProvider))]

[assembly: RegisterModule(typeof(CMSCustomGlobalEventsModule))]

namespace CMSApp.Configuration
{
    public class DependencyRegistrationModule : Module
    {
        public DependencyRegistrationModule 
            : base(nameof(DependencyRegistrationModule)) { }

        protected override OnPreInit()
        {
            // register any services we want to access through
            // Service.Resolve in custom modules or elsewhere
            Service.Use(() =>
            {
                return new CreditCardService(...);
            });
        }
    }
}
```

In this example, we intentionally registered `CMSCustomGlobalEventsModule` in the CMS but not in our ASP.NET Core application. Had we defined these `assembly` registration attributes in a shared class library, we wouldn't be able to do this 😏.

### Managing Registration Complexity

We might look at this and think we traded one form of complexity for another. Sure, we now have full control over which services and modules are registered in each application, but we also have to remember to register them in all applications that need them 🤨. Before, all we needed to do was reference the class library and ... bam, it was done for us 🤷🏻‍♀️.

This is true, however we can recreate another pattern that ASP.NET Core libraries often use to make things easier for ourselves - gathering all dependencies up into a single place to be registered.

Since the `assembly` attributes are really just calls into Xperience's internals to 'register' things, we can reproduce this with a single Custom Module that makes these calls explicitly:

```csharp
namespace Sandbox.Library
{
    public class LibraryRegistrationModule : Module
    {
        public LibraryRegistrationModule
            : base(nameof(LibraryRegistrationModule) { }

        protected override OnPreInit()
        {
            Service.Use<IUserInfoProvider, CustomUserInfoProvider>();
        }

        protected override OnInit()
        {
            new CustomGlobalEventsModule().OnInit();
        }
    }
}
```

Now, we can just include 1 `assembly` registration attribute to register everything in our library:

```csharp
[assembly: RegisterModule(typeof(LibraryRegistrationModule))]
```

This is like an ASP.NET Core library's `UseX()` extension method on `IServiceCollection`. If we are ok with the defaults, we call that extension and the library handles everything for us, but we still get to explicitly opt-in.

Likewise, if we are ok with our class library's Xperience customization defaults, adding a single `assembly` registration attribute brings in everything 👏🏿.

They key here is that adding a reference to an assembly (either via a .NET project or NuGet reference) doesn't change the behavior of our system. We have to be clear that we want a customization and add a line of code 😎. This means we can just as easily opt-out without having to throw away the entire class library's code.

## 🏁 Conclusion

Kentico Xperience 13 has been able to support both the older ASP.NET Web Form CMS application and modern ASP.NET Core applications with shared libraries and APIs. This is an impressive feat of engineering.

Until we are in a fully ASP.NET Core world with Kentico Xperience (hopefully, just a few weeks away from the June 2022 publication of this blog post), we will need to juggle platform dependencies and customizations in a couple different ways.

> If you are interested in the future of Kentico Xperience from a technical perspective, checkout this video below from the [Kentico Xperience Connection: 2022 Conference](https://connection.xperience.io/).

The Xperience `assembly` attributes for dependency registration let us tap into the platform for full customization.

However, if we aren't careful, referencing a class library or NuGet package forces consumers to use our customizations because with these `assembly` attributes the dependencies are automatically 'on'. This makes our code less reusable and our applications less flexible.

Thinking about dependencies in a modern way and following the patterns of ASP.NET Core gives us some better approaches that make our applications more understandable and debuggable, and let's us opt-in to the Xperience customizations we want.

We should be explicit about which types and modules our applications use by registering them next our our applications other dependency injection management.

{% youtube oi3c46K7SUs %}

As always, thanks for reading 🙏!

## References

- Wikipedia: [Service Locator Pattern](https://en.wikipedia.org/wiki/Service_locator_pattern)
- Wikipedia: [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection)
- ASP.NET Core Docs: [Options Pattern](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-6.0)
- Mark Seemann Blog: [Service Locator Anti-Pattern](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/)
- Xperience Docs: [Custom Info Provider Example](https://docs.xperience.io/custom-development/customizing-providers/custom-info-provider-example)
- Xperience Docs: [Custom Module Initialization](https://docs.xperience.io/custom-development/creating-custom-modules/initializing-modules-to-run-custom-code)
- Xperience Docs: [Global Events](https://docs.xperience.io/custom-development/handling-global-events)
- Xperience Docs: [Service Decoration](https://docs.xperience.io/custom-development/decorating-system-services#Decoratingsystemservices-Decorateservicesviadependencyinjection)
- Xperience Docs: [Custom Email Provider Example](https://docs.xperience.io/custom-development/customizing-providers/custom-email-provider-example)
- Xperience Docs: [Using Xperience APIs Externally](https://docs.xperience.io/integrating-3rd-party-systems/using-the-xperience-api-externally)
- GitHub Xperience Community: [App Settings JSON Registration Library](https://github.com/wiredviews/xperience-appsettings-json-registration)
- WiredViews: [Web Design and Development Services](https://www.wiredviews.com/services/web-design-and-development#section903d04b3-6baa-4a65-ba32-59acc66f196d)
- Kentico Xperience Design Patterns: [Good Startup.cs Hygiene](https://dev.to/seangwright/kentico-xperience-design-patterns-good-startup-cs-hygiene-3klm)

---

We've put together a list over on [Kentico's GitHub account](https://github.com/Kentico/Home/blob/master/RESOURCES.md) of developer resources. Go check it out!

Also check out the [Kentico Xperience Developer Hub](https://xperience.io/services/training/developer-hub).

If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.

{% tag kentico %}

{% tag xperience %}

Or my [Kentico Xperience blog series](https://dev.to/seangwright/series), like:

- [Kentico Xperience Xplorations](https://dev.to/seangwright/series/8185)
- [Kentico Xperience MVC Widget Experiments](https://dev.to/seangwright/series/9483)
- [Kentico Xperience Design Patterns](https://dev.to/seangwright/series/10963)