[su_note note_color=”#fafafa”]A lot has been written on Dependency Injection with Sitecore. In this blog post I want to specifically focus on one important aspect – multitenancy – and look into how we can make our Dependency Injection multi-tenant friendly and as much native to Sitecore as possible.[/su_note]
Multitenancy
All good commercial grade CMS systems support multitenancy – hosting multiple sites on the same instance. None of them (as far as I know), however, provide complete isolation to individual tenants. Sitecore is not an exception. One simple example. Pipelines and event handlers are global and while you can scope some of your extensions to a particular tenant you can’t do it for all of them. Not all have that context – not everything is bound to an HTTP request and editing happens in context of shell
site anyway – plus the underlying ASP.NET framework has no knowledge of the multitenancy up the stack.
Wiring in an IoC container into Sitecore to inject dependencies into your controllers declaratively requires taking over the default controller factory. It’s an ASP.NET MVC artifact and it’s global for all Sitecore tenants. Sitecore iteslf wires its own in <initialize>
via a processor:
&amp;amp;lt;pipelines&amp;amp;gt; &amp;amp;lt;initialize&amp;amp;gt; ... &amp;amp;lt;processor type="Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory, Sitecore.Mvc" patch:source="Sitecore.Mvc.config"/&amp;amp;gt; ... &amp;amp;lt;/initialize&amp;amp;gt; &amp;amp;lt;/pipelines&amp;amp;gt;Getting ready for a multi-tenant co-existence with other sites requires discipline. And collaboration. It’s easy when all tenants are under your control, but what if they are not? A large multi-brand enterprise running its online properties on multiple multi-tenant Sitecore farms is not unheard of. It’s actually more and more the norm as Sitecore makes big steps into the large enterprises where it’s been long awaited for.
The Tenant Law
We can’t protect ourselves from a rouge or hostile tenant but we can at least make our tenants obey the scout law:
[su_note note_color=”#fafafa”]A [tenant] is trustworthy, loyal, helpful, friendly, courteous, kind, obedient, cheerful, thrifty, brave, clean, and reverent[/su_note]
Controller Factory
Sitecore has respect for the underlying ASP.NET infrastructure and when it overrides the default MVC controller factory it wraps around it and delegates downstream everything it doesn’t recognize as belonging to it:
namespace Sitecore.Mvc.Pipelines.Loader { public class InitializeControllerFactory { // ... protected virtual void SetControllerFactory(PipelineArgs args) { ControllerBuilder.Current.SetControllerFactory( new SitecoreControllerFactory(ControllerBuilder.Current.GetControllerFactory())); } } }And then later:
protected virtual IController CreateControllerInstance(RequestContext requestContext, string controllerName) { if (Sitecore.Mvc.Extensions.StringExtensions.EqualsText(controllerName, this.SitecoreControllerName)) { return this.CreateSitecoreController(requestContext, controllerName); } if (TypeHelper.LooksLikeTypeName(controllerName)) { // ... } return this.InnerFactory.CreateController(requestContext, controllerName); }Let’s follow the lead and wrap around the current controller factory:
&amp;amp;lt;pipelines&amp;amp;gt; &amp;amp;lt;initialize&amp;amp;gt; &amp;amp;lt;processor patch:after="*[last()]" type="Sample.Custom.Mvc.InitializeControllerFactory, Sample.Custom"/&amp;amp;gt; &amp;amp;lt;/initialize&amp;amp;gt; &amp;amp;lt;/pipelines&amp;amp;gt;And then:
namespace Sample.Custom.Mvc { public class InitializeControllerFactory : Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory { protected override void SetControllerFactory(PipelineArgs args) { ControllerBuilder.Current.SetControllerFactory( new SampleControllerFactory(ControllerBuilder.Current.GetControllerFactory())); } } }More Respect
Our DI-ready controller factory will show more respect to the other tenants – we will let them go first. I will tell you in a minute why we’re safe doing so: (the code was simplified for illustration)
protected override IController CreateControllerInstance(RequestContext requestContext, string controllerName) { Assert.ArgumentNotNull(requestContext, "requestContext"); HttpException httpException = null; IController controller = null; try { controller = base.InnerFactory.CreateController(requestContext, controllerName); } catch (HttpException ex) // would be thrown by the default MVC controller factory { httpException = ex; } catch (ControllerCreationException ex) // would be thrown by the Sitecore controller factory { httpException = CaptureWrappedHttpExceptionOrRethrow(ex); } // The factory upstream failed to create an instance if (controller == null) { controller = CreateControllerUsingConfigurationFactory(controllerName); } if (controller == null &amp;amp;amp;&amp;amp;amp; httpException != null) { // rethrow the original throw httpException; } if (controller == null) { // follow the patterns used in the default MVC factory and throw a 404 HttpException throw new HttpException(404); } return controller; }Sitecore Native IoC
Last but not least, where is the IoC container? Who will be resolving chains of dependencies and injecting them into the right places? And why did we allow other factories to run before us? They could have screwed everything up, couldn’t they?
Yes, the could have. Like I said earlier we can’t protect ourselves from a rouge or a hostile tenant. That’s why discipline on our side isn’t enough. We need collaboration with the other tenants too. We have to assume the other tenants will a) follow our lead and do a proper chaining on their end and b) will also either throw a 404
HttpException
or return anull
instance if they couldn’t resolve a controller name.And let’s see if Sitecore can do the rest – be our IoC container of choice. Sitecore Configuration Factory may not be your favorite container, may not even be on your list of containers to chose from, but it can do almost everything you’ll need with basic dependency injection. And using it helps in a multitenant environment by simply not introducing another dependency that might conflict with another version or the same library used by another tenant.
Declare your controllers:
&amp;amp;lt;configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"&amp;amp;gt; &amp;amp;lt;sitecore&amp;amp;gt; &amp;amp;lt;sample&amp;amp;gt; &amp;amp;lt;controllers&amp;amp;gt; &amp;amp;lt;Comparison type="Sample.Web.Areas.Sample.Controllers.ComparisonController"&amp;amp;gt; &amp;amp;lt;param hint="1" ref="sample/services/ComparisonService"/&amp;amp;gt; &amp;amp;lt;/Comparison&amp;amp;gt; &amp;amp;lt;/sample&amp;amp;gt; &amp;amp;lt;/sitecore&amp;amp;gt; &amp;amp;lt;/configuration&amp;amp;gt;Expect that a dependency be constructor-injected:
namespace Sample.Web.Areas.Sample.Controllers { public class ComparisonController : Controller { private readonly IComparisonService _comparisonService; public ComparisonController(IComparisonService comparisonService) { Assert.ArgumentNotNull(comparisonService, "comparisonService"); _comparisonService = comparisonService; } // ... } }Declare the dependency:
&amp;amp;lt;configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"&amp;amp;gt; &amp;amp;lt;sitecore&amp;amp;gt; &amp;amp;lt;sample&amp;amp;gt; &amp;amp;lt;services&amp;amp;gt; &amp;amp;lt;ComparisonService type="Sample.Data.Services.Impl.ComparisonService, Sample.Data" /&amp;amp;gt; &amp;amp;lt;/services&amp;amp;gt; &amp;amp;lt;/sample&amp;amp;gt; &amp;amp;lt;/sitecore&amp;amp;gt; &amp;amp;lt;/configuration&amp;amp;gt;Make it all work in the controller factory:
protected virtual IController CreateControllerUsingConfigurationFactory(string controllerName) { // ToDo: You would take it out into your "service locator" so you could use it elsewhere XmlNode node = Factory.GetConfigNode(controllerName); if (node != null) { return Factory.CreateObject&amp;amp;lt;IController&amp;amp;gt;(node); } Log.Warn(string.Format("Couldn't find {0} controller in Web.config", controllerName), GetType()); return null; }Use XPath when referring to your controllers. You now see why we were safe letting other factories go first? They wouldn’t recognize these names anyway:
routes.MapRoute("Comparison", "products/compare", new { controller = "sample/controllers/Comparison", action = "CompareProducts" });Or, for controller renderings:
Enjoy … and obey the tenant law
Pingback: Changes to Dependency Injection in Sitecore 8.1 | The Runtime Report
Pingback: Safe Dependency Injection for MVC and WebApi within Sitecore | Sean Holmesby
I’m impressed, I must say. Rarely do I encounter a blog that’s equally educative and entertaining, and let me tell you, you’ve hit the nail on the head. The problem is something that not enough men and women are speaking intelligently about. I am very happy I stumbled across this in my search for something regarding this.|
Admiring the hard work you put into your blog and in depth information you present. It’s great to come across a blog every once in a while that isn’t the same outdated rehashed information. Wonderful read! I’ve saved your site and I’m adding your RSS feeds to my Google account.|