If you are upgrading to Optimizely CMS 12, one of the breaking changes to consider is the change to the Dependency Injection(DI) framework. Earlier versions of Optimizely CMS had their own DI hosting framework that supported other concrete DI implementations, like StructureMap. With CMS 12 and ASP.Net Core, DI framework is built into the system. We can still register and use other third party implementations, but the recommendation is to use the built in DI framework.
So how do we upgrade an existing CMS 11 site, that extensively used StructureMap for DI, to CMS 12 and the built in DI framework, considering StructureMap is being sunsetted?
Lets look at a few use cases :
1. Concrete implementation registration
With Optimizely CMS and its DI framework, we only registered interface implementations and not concrete classes. But with ASP.Net Core’s DI, we need to register concrete classes as well. For example, if we have a class like
We’ll need to register this explicitly in our IOC container :
2. Delegate Registration
If a concrete implementation is injected as a delegate, then along with above, its delegate needs to be registered too :
3. Passing Constructor Parameters with DI
Sometimes we have classes with constructors where they inject other services for use later in the logic. Now when we register these classes in our IOC container, just registering the names won’t do. Because as soon as those references will need to be created, the compiler will look for the injected classes on the constructor and won’t be able to resolve those. In CMS 11 implementation, StructureMap aided with these. Lets see with an example :
Lets say we have a custom ILoggerFactory implementation setup like this :
This is injecting the LoggingConfiguration class from above in the constructor. With StructureMap, we were supplying the Type parameter to this constructor like this :
With ASP.Net Core’s DI, this would be achievable by doing something like this :
Some interesting facts to note in the new DI registration :
- We are using a namespace called Microsoft.Extensions.DepenedencyInjection. This allows us to use System.IServiceProvider object to obtain previously registered services via the GetRequiredService() call and use them for other service registrations. In our example above, we use this to supply LoggingConfiguration object to the LoggerFactory constructor.
- The other is the use of Extensions. As you note the highlighted epiServices above, instead of registering directly on context.Services, we are registering on a ServiceCollectionExtension instead. Reason for doing this was because the Add<Lifetime> calls on context.Services is ambiguous between Microsoft.Extenesions.DependencyInjection and Episerver.ServiceLocation and there is no way to distinguish between those on direct calls.
So now the above logic lets us register a service with specific lifetime and allows us to pass constructor parameters to it.
4. Registering multiple implementations of same type
Sometimes our logic requires one interface that can be implemented multiple different ways for use in different scenarios. Registering such services requires additional logic as if we simply use the standard approach and register all classes against the same interface, only the last one actually gets picked every time the interface is referenced, thus not being quite effective. Lets understand with an example :
Scenario 1 : normal interface with multiple implementations
StructureMap way of registration :
.Net Core DI equivalent :
Scenario 2 : generic interface with multiple implementations and generic attribute types
StructureMap way :
ASP.Net Core DI equivalent :
We can do this the way Scenario 1 is done too, but that works for open generic types or closed generic types with fixed Argument types. I had scenarios where my generic type arguments were also decided at runtime for different implementations, so this is what worked for them.
5. Named Registrations
Lets consider the example of Event based logic. We have different events, even in CMS context, and there are cases when we need to take different actions per event under a common context, like Indexing or Search. So how to register multiple event based implementations of a single interface so the correct service can be used for the corresponding event?
We use named registration concept here. It ties service registrations to names, in this case event names and uses that to map the right call later on.
Here’s the way StructureMap enabled us to achieve this :
And here’s its ASP.Net Core DI equivalent :
You basically register all the concrete implementations first and then map the interface implementation to the right one based on the key, which in our case is the event name.
This may not be the complete list and these are not the only ways of achieving the end result. I myself encountered different approaches on different forums and posts and I can say that its definitely a fair bit of trial and error, for those of us not too experienced with ASP.Net Core way of doing things. I had to look through several different places to help translate each individual use case, so hoping this one stop guide comes in handy for others on the same path.