loading...

Kentico 12: Design Patterns Part 4 - Adding Dependency Injection to the CMS

seangwright profile image Sean G. Wright Updated on ใƒป10 min read

Photo by Rich Tervet on Unsplash

Now that Kentico has empowered its developers to build applications with ASP.NET MVC, we can leverage design patterns and best practices that are encouraged by the framework. ๐Ÿ‘

As our libraries and code bases change to take advantage of these patterns and practices, can we use them in the CMS application which is built on ASP.NET Web Forms? If so, how do we accomplish this? ๐Ÿค”

With the pattern of Dependency Injection, the answer is Yes and Yes! ๐Ÿ™Œ

Let's take a look!

The Pattern

One of the patterns that MVC encourages is Inversion of Control (IoC). This pattern can be implemented through Dependency Injection (DI) with both framework types and custom user defined types.

In application frameworks, the way IoC is usually accomplished is by allowing the framework to create and call specific entry point types, created by the developer โ€” from there our code executes until it returns.

IoC allows for us to use DI in an elegant manner because the framework is responsible for calling into our code (IoC) โ€” therefore it can also be responsible for creating the things our code depends on (DI).

DI in MVC

In an MVC application, one of those entry point types is the System.Web.Mvc.Controller class, which all custom controllers inherit from.

Here is an example UserController with (2) dependencies, IUserContext and IEmailService:

public class UserController : Controller
{
    public UserController(IUserContext userContext, IEmailService emailService)
    {
        // ...
    }
}

MVC needs to be able to construct instances of UserController when it decides that an HTTP request should be handled by UserController. MVC makes this decision based on URL or request body patterns, which map to the routing conventions or configuration in our app.

To see a routing configuration pattern that mixes convention and configuration read my previous post in this series. ๐Ÿ’ช


If we are using DI through constructor injection, how is MVC able to figure out how to create the dependencies of our controller class? Or the dependencies of those classes?

This recursive problem is solved by an IoC Container. IoC Containers are configured to "know" how to construct an instance of any type used in an application that is considered a dependency. This configuration of types is often known as "registration".

So, at the point in our application where MVC needs to create an instance of our UserController, it effectively tries to get one from the IoC Container.

The IoC Container figures out the entire chain of dependencies required to create a UserController, creates one, and then returns it to the framework code.

When using IoC and DI, our code does not create its own dependencies.

Instead all dependencies must be provided to our code from somewhere higher up. This is the "inversion" of "control" over our own code's dependencies. ๐Ÿ˜ฎ

DI In Web Forms

The ASP.NET Web Forms architecture is based around the Page and UserControl types.

When working with Kentico we use the CMSPage and CMSAbstractWebPart classes, accordingly.

Both of these types require public parameter-less constructors.

What happens if we specify dependencies as constructor parameters? The Web Forms framework will fail to create an instance of the type and the request will fail with an exception! ๐Ÿคฆ๐Ÿฝโ€โ™€๏ธ

ASP.NET Web Forms was not designed for the same patterns and practices that MVC encourages, but that doesn't mean we are at an impasse.

The Library - Autofac

With the design pattern established, let's dive into how we configure it, not for MVC, but for ASP.NET Web Forms, which is the framework that the Kentico content management application is built on.

We will be using Autofac as our IoC Container of choice.

There are many IoC Container libraries available in .NET. This benchmark blog by Daniel Palme contains a pretty thorough list of the most well known libraries.

In the past I've mostly used SimpleInjector because of its great performance, simple API, and container verification feature. It's a fantastic library and container verification is awesome.๐Ÿ‘Œ

That said, AutoFac provides some nice APIs for simplifying some problems .NET application frameworks require developers to face. It's also the IoC Container that Kentico uses in its example Dancing Goat site.

We will be using (2) NuGet packages (with versions specified):

  1. Autofac (v4.9.2) - This is the main IoC Container library which is application framework agnostic.

  2. Autofac.Web (v4.0.0) - This is the ASP.NET Web Forms integration package for Autofac.

The Web Forms integration package allows us to perform DI in a couple of different ways.

  1. Property Injection - Assigns instances resolved by the IoC Container to public properties of the Web Forms Page or UserControl instance. โœ”

  2. Attribute Property Injection - Same as the above, but only if a specific attribute is applied to those pages or controls. โœ”

  3. Null Attribute / Property Injection - Using either approach above, this assigns a resolved instance only if the property is null at the time of assignment. โœ”

We are going to use the second option, you can explore the other two in Autofac's documentation.

Update (2019/07/09): Kentico has issues in certain areas of the CMS (Reports Module) when using the first option above. For the most part it works but not completely, and that's not good enough! So, while I initially recommended the first option, I'm now recommending the second.

Thanks to Tony at Luminary for discovering this problem!

Go ahead and install the NuGet packages into the CMSApp project.

Managing NuGet packages in .NET Framework projects can be a lot easier if you use modern .NET features and tools. Check out my post to learn more. ๐Ÿ’ช

OK, now let's setup our application!

In .NET 4.7.2 a constructor based DI solution has been added to ASP.NET for Web Forms and this solution can be used as a hook point, with an adapter layer, to integrate an IoC Container.

Here is an example integrating SimpleInjector.

I'm going to continue using Property Injection since it's what Autofac supports without customization.

The Implementation - Kentico 12 CMS

The Autofac documentation has a great walkthrough of how to configure our application to start using the library, and I will summarize the steps below.

Configuring The Project

First, Add the following ASP.NET module entries to the application web.config under the <configuration><system.webServer><modules> node.

<add
    name="ContainerDisposal"
    type="Autofac.Integration.Web.ContainerDisposalModule, Autofac.Integration.Web"
    preCondition="managedHandler"/>
<add
    name="PropertyInjection"
    type="Autofac.Integration.Web.Forms.AttributedInjectionModule, Autofac.Integration.Web"
    preCondition="managedHandler"/>

Next, update our Global class in Global.asax.cs to support Autofac.

// Add the IContainerProviderAccessor interface
public class Global : CMSHttpApplication, IContainerProviderAccessor
{
    static Global()
    {
        /// leave existing code
    }

    // Holds the application container
    private static IContainerProvider containerProvider;

    // Used by Autofac to resolve dependencies via IContainerProviderAccessor
    public IContainerProvider ContainerProvider => containerProvider;

    // Creates a container builder, builds it, and then sets the container
    protected void Application_Start(object sender, EventArgs e)
    {
        var builder = new ContainerBuilder();

        var container = builder.Build();

        containerProvider = new ContainerProvider(container);
    }
}

That's it! DI is now set up for our CMS application!

... except we haven't registered any types, so our IoC Container and its integration into Web Forms isn't very helpful at the moment. ๐Ÿ˜‘

So, let's register some types with Autofac!

Registering Our Types

What kind of types do we want to register?

Why don't we use the ISiteContext and KenticoSiteContext types from previous posts in this series? ๐Ÿ˜Š

First, create the ISiteContext interface:

public interface ISiteContext
{
    string SiteName { get; }
    int SiteId { get; }
    SiteInfo Site { get; }
}

And now the KenticoSiteContext implementation:

public class KenticoSiteContext : ISiteContext
{
    public string SiteName => SiteContext.CurrentSiteName;

    public int SiteId => SiteContext.CurrentSiteID;

    public SiteInfo Site => SiteContext.CurrentSite;
}

While there is nothing preventing us from adding all the Autofac type registrations to our Global.asax.cs file, I'd recommend moving type registration to a different class.

I typically create a DependencyResolverConfig.cs class (or something similar).

In this class we can register our types and then build the container:

public static class DependencyResolverConfig
{
    public static IContainer BuildContainer()
    {
        var builder = new ContainerBuilder();

        builder
            .RegisterSource<KenticoRegistrationSource>();

        builder
            .RegisterType<KenticoSiteContext>()
            .As<ISiteContext>();

        var container = builder.Build();

        return container;
    }
}

As this file grows in registrations and complexity, I always move the registrations out into separate classes that each focus on a specific part of the application (ex: Application, Data Access, Authorization).

You can find an example in a gist here.

Notice the line builder.RegisterSource<KenticoRegistrationSource>();.

This tells Autofac that when it doesn't know how to resolve a type, forward the request for an instance of that type on to Kentico. An example type that this could resolve for us would be CMS.Ecommerce.ICurrentShoppingCartService.

The code for KenticoRegistrationSource is a bit long, so it can be found in this gist.

Now we need to update our Global class in the Global.asax.cs file to use the new DependencyResolverConfig class. The only change that needs to be made is to replace the Application_Start method with the code below.

protected void Application_Start(object sender, EventArgs e)
{
    var container = DependencyResolverConfig.BuildContainer();

    containerProvider = new ContainerProvider(container);
}

Resolving Our Types in a Page

Let's create a Web Form class and test resolving our types.

Create a new Web Form Page under CMSApp -> CMSPages with the name DIPage. Replace the contents of DIPage.aspx.cs with the code below.

[InjectPropertiesAttribute]
public partial class DIPage : CMSPage
{
    public ISiteContext SiteContext { get; set; }
    public ICurrentShoppingCartService CurrentShoppingCartService { get; set; }

    protected void Page_Load(object sender, EventArgs e)
    {
        var service = CurrentShoppingCartService;

        var cart = service.GetCurrentShoppingCart(
            CurrentUser, 
            SiteContext.SiteName);
    }
}

We will set a breakpoint in Visual Studio on the opening brace of the Page_Load method, and run the CMS project with debugging.

When we hit the breakpoint and step through the code, we will see the SiteContext and CurrentShoppingCartService properties are populated with the values we would expect.

Pretty nice! ๐Ÿ˜ƒโœจ

Resolving Our Types Outside of Pages or Controls

What should we do when we want to resolve our types from within other parts of our Kentico application, like a scheduled task?

In this case we can't use DI because Kentico creates the instances of the custom scheduled task classes. Unlike with the Autofac Web Forms integration package, we don't have a way to tell Kentico to resolve constructor parameters or class property values from an IoC Container. ๐Ÿค”

Kentico folks: If there is a way to customize creation of these types, I'd love to know!

What we can do instead is use the Autofac container as a Service Locator.

Normally, service location would be considered an anti-pattern, but when you are only provided lemons you put on gloves, eye protection, and then start juicing! ๐Ÿฅค

To demo this, let's create a custom scheduled task that returns the current requesting user's shopping cart Id.

public class GetCurrentUserShoppingCartIdTask : ITask
{
    private readonly ISiteContext siteContext;
    private readonly ICurrentShoppingCartService currentShoppingCartService;

    public GetCurrentUserShoppingCartIdTask()
    {
        // Get the instance of our application
        var application = HttpContext
                .Current
                .ApplicationInstance as IContainerProviderAccessor;

        // Get the global container from the application
        var container = application
                .ContainerProvider
                .ApplicationContainer;

        // Resolve our dependencies
        siteContext = container.Resolve<ISiteContext>();
        currentShoppingCartService = container.Resolve<ICurrentShoppingCartService>();
    }

    public string Execute(TaskInfo task)
    {
        string siteName = siteContext.SiteName;

        var cart = currentShoppingCartService.GetCurrentShoppingCart(
            MembershipContext.AuthenticatedUser, 
            siteName);

        return cart.ShoppingCartID.ToString();
    }
}

We take dependencies on the two types we've already seen (ISiteContext and ICurrentShoppingCartService), but instead of getting them through the class constructor or public properties, we resolve them directly from the container.

The IContainerProviderAccessor application above is the instance of the application created from our Global class in Global.asax.cs and the ILifetimeScope container is the same thing that Web Forms uses to get instances for property injection with our custom Page and UserControl types.

So, we resolve our dependencies in the constructor of our class and then use them in the Execute() method.

There's a lot of glue code in that constructor. Let's move some to an extension method.

public static class TaskExtensions
{
    public static ILifetimeScope GetContainer(this ITask task, HttpContext context)
    {
        // Get the instance of our application
        var application = context
                .ApplicationInstance as IContainerProviderAccessor;

        // Get the global container from the application
        return application
                .ContainerProvider
                .ApplicationContainer;
    }
}

Now our constructor looks as follows:

public GetCurrentUserShoppingCartIdTask()
{
    var container = this.GetContainer(HttpContext.Current);

    // Resolve our dependencies
    siteContext = container.Resolve<ISiteContext>();
    currentShoppingCartService = container.Resolve<ICurrentShoppingCartService>();
}

The GetContainer() extension method can only be called on an ITask which prevents the Service Locator pattern from leaking into the rest of our codebase! ๐Ÿ‘

My final recommendation, which I won't show here, would be to move the business logic of the Execute() method into another class that follows our patterns and practices from MVC โ€“ DI and the Single Reponsibility Principle.

This new class will take ISiteContext and ICurrentShoppingCartService as constructor dependencies and have a simple API (only a couple of methods at most) to process the scheduled task.

We can then use the IoC Container to create an instance of it instead of its dependencies. Then we call its methods in the ITask.Execute() method.

Wrap Up!

We reviewed what Inversion of Control is, how it enables Dependency Injection, and how ASP.NET MVC uses these concepts to help developers create an application. ๐Ÿง

Although ASP.NET Web Forms doesn't support either of these concepts out of the box, we can enhance the framework with Autofac to allow for Property Injection of the Page and UserControl types. ๐Ÿ˜Ž

We also took a look at the scenario where we want to resolve our registered dependencies inside Kentico types that we cannot integrate with our IoC Container. ๐Ÿค—

Instead, we said YOLO!, and used a Service Locator pattern to resolve our types. ๐Ÿ˜œ

I hope you found this walkthrough helpful and the explanations clear!

This is my first blog post since switching my blogging from Medium to DEV, and I'm looking forward to writing more in the future.

For more on Kentico Design Patterns, checkout my series or the Kentico tag here on DEV.

Posted on by:

seangwright profile

Sean G. Wright

@seangwright

dev lead @WiredViews, founding partner @craftbrewingbiz. @Kentico Xperience MVP. love to learn / teach web dev & software engineering, collecting vinyl records, mowing my lawn, craft ๐Ÿบ

Discussion

markdown guide
 

Hi Sean

Thank you very much for this article. Really appreciate it.