Episerver Dependency Injection status

Episerver just released a new version which added a constructor injection support in scheduled jobs. It encouraged me to create a list of different Episerver infrastructure parts which still do not support a constructor injection.

Initially, I was thinking just to look at the Episerver documentation and find which parts of the infrastructure might not have constructor injection working. But Valdis Iljučonoks suggested to look up for Activator.CreateInstance usages in the Episerver code.

So I used Reflector to search for Activator.CreateInstance usages. It has several overloads but only these five are used by the Episerver:

Activator.CreateInstance methods used in Episerver

But while there are only five methods in use, there is quite a lof of usages. For example, Activator.CreateInstance<T> is used 21 times.

Usages of Activator.CreateInstance of T

I understand that there are cases when Activator.CreateInstance should be used instead of resolving type instance with IoC Container. As an Episerver developer, I care more about extension points in the framework. So the list will be based on my knowledge of the most used services and an analysis of the Activator.CreateInstance method usages.

CMS

CMS always had a constructor injection support for controllers and now it also supports a constructor injection in scheduled jobs. But there are still some parts which do not support a constructor injection.

AspNetIdentity's IUIUser

It was surprising to me that ApplicationUserProvider in the EPiServer.Cms.UI.AspNetIdentity namespace uses Activator for user creation. It means that you will not be able to use constructor injection when creating your own type of user. Here you can see how it is used:

public override IUIUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, out UIUserCreateStatus status, out IEnumerable<string> errors)
{
    errors = Enumerable.Empty<string>();
    status = UIUserCreateStatus.Success;
    TUser local1 = Activator.CreateInstance<TUser>();
    local1.set_Email(email);
    local1.IsApproved = isApproved;
    TUser user = local1;
    user.set_UserName(username);
    IdentityResult result = this._userManager().Create<TUser, string>(user, password);
    if (!result.Succeeded)
    {
        errors = result.Errors;
        status = UIUserCreateStatus.ProviderError;
        return null;
    }
    return user;
}

Luckily, you can implement your own user provider by inheriting from ApplicationUserProvider and overriding this CreateUser method. But I would better see some user factory which could have a default implementation with Activator usage. Then developers would be able to create their own implementations of this factory if needed.

ICriterionModel in visitor groups

ICriterionModel is initialized with Activator in the EPiServer.Personalization.VisitorGroups.CriterionBase class's Initialize method. But same as in IUIUser case, it is possible to override Initialize method in your own criterion model implementation.

ICriterion in visitor groups

Same as with ICriterionModel, it does not support constructor injection. EPiServer.Personalization.VisitorGroups.VisitorGroupRole class's CreateCriterion method uses Activator to instantiate ICriterion. But it is not possible to override CreateCriterion method as it is private.

ISelectionFactory in visitor groups

ISelectionFactory is used when creating Dojo dropdown list in the administrative interface. Custom implementations of such selection factory do not support constructor injection. ISelectionFactory is instantiated with Activator in the EPiServer.Personalization.VisitorGroups.DojoHtmlExtensions class's DojoDropDownFor method.

IGeneratesAdministrativeInterface in visitor groups

Some might be interested into extending visitor group administrative interface through IGeneratesAdministrativeInterface. Unfortunately, it is created with Activator in the EPiServer.Cms.Shell.UI.Controllers.Internal.VisitorGroupsController. Two controller actions - CriteriaModelDefinition and CriteriaUI initializes it with Activator and there is no way to override this behavior. EPiServer.Web.Mvc.VisitorGroups.VisitorGroupModelBinder class's ConvertDictionaryToObject method also uses Activator to initialize IGeneratesAdministrativeInterface.

IViewTemplateModelRegistrator

Sometimes there is a need to register different templates for different content types. Then a custom implementation of IViewTemplateModelRegistrator helps to achieve it. But unfortunately, instances of it are created with Activator in the EPiServer.DataAbstraction.RuntimeModel.Internal.ViewRegistrator class's InstantiateViewTemplateRegisters method. As this method is private, it is not possible to override the behavior. The only way to fix this is own implementation of IViewRegistrator.

Commerce

As Commerce is built on top of the CMS, anything which supports a constructor injection in CMS supports it also in Commerce. For example, controllers support a constructor injection. But Commerce has an additional infrastructure which might not support a constructor injection.

IPaymentPlugin

Unfortunately, payment gateways by default do not support constructor injection. IPaymentPlugin is instantiated with Activator in the EPiServer.Commerce.Order.DefaultPaymentProcessor class's CreatePaymentGatewayProvider private method. While it is possible to create an own implementation of payment processor by implementing IPaymentProcessor, it is not possible to reuse logic of the DefaultPaymentProcessor as CreatePaymentGatewayProvider method is private.

Payment

Custom Payment implementations also do not support constructor injection. There are few places where Payment is instantiated with Activator:

  • Mediachase.Commerce.Orders.PaymentCollection class's method AddNew
  • Mediachase.Commerce.Orders.PaymentConverter class's method Create

IPaymentGateway

Also, IPaymentGateway is instantiated with Activator in the Mediachase.Commerce.Workflow.Activities.Cart.ProcessPaymentActivity class's ProcessPayment method.

IShippingGateway

IShippingGateway is instantiated with Activator in the Mediachase.Commerce.Workflow.Activities.ProcessShipmentsActivity class's ProcessShipments method. It is interesting that IMarketService in the same method is resolved with service locator:

IMarket market = ServiceLocator.Current
  .GetInstance<IMarketService>()
  .GetMarket(orderGroup.MarketId);
IShippingGateway gateway = (market != null)
  ? ((IShippingGateway) Activator.CreateInstance(type, new object[] { market }))
  : ((IShippingGateway) Activator.CreateInstance(type));

IShippingPlugin

EPiServer.Commerce.Order.Calculator.DefaultShippingCalculator class's GetShippingGateway method uses Activator to instantiate IShippingPlugin. GetShippingGateway method is private. So to override the behavior, a custom implementation of IShippingCalculator is required.

Summary

When I started to write this article, I thought that there will be a lot of places where dependency injection is not supported with a constructor injection. But it seems that Episerver did a great job to make it more extensible.

There are still some areas which require improvements - visitor groups in CMS, payments, shipping in Commerce and other.