Question

Scenario:

I need to provide different interface implementations to the same interface definitions within the same web application (appdomain) but to different "scopes".

Imagine a simple hierarchical web content structure like this (if you are not familiar with SharePoint):

RootWeb (SPSite) (ctx here)
  |______SubWeb1 (SPWeb) (ctx here)
  |______SubWeb2 (SPWeb)
  |______SubWeb3 (SPWeb)
           |_______SubWeb3.1 (SPWeb) (ctx here)
           |_______SubWeb3.2 (SPWeb)

RootWeb, SubWeb1 und SubWeb3.1 provide Contexts. That is I implemented an AppIsolatedContext class that is specific for a certain hierarchy level. If a level does not provide a context it inherits the context from the parent node and so on. For example SubWeb3 would inherit its context from RootWeb. SubWeb3.1 however provides its own isolated context.

The isolated context is merely a static ConcurrentDictionary.

Okay so far so good. Now regarding Autofac (I'm new to Autofac and any other DI container - not to the principle of IoC though)... I'm not sure how I correctly set it up to dispose of objects correctly. Actually it shouldn't be that much of an issue because the objects are (once they are created) are supposed to live until the appdomain gets recycled (think of them as a "per isolated context singleton").

I'd be inclined to do something like that:

// For completeness.. a dummy page which creates a "dummy" context
public partial class _Default : Page
{
    private static AppIsolatedContext _dummyContainer = new AppIsolatedContext();

    public _Default()
    {
        _dummyContainer.ExceptionHandler.Execute("Test Message");            
    }
}

// The isolated context which holds all the "context" specific objects
public class AppIsolatedContext
{
    public static IContainer Container { get; set; }

    public IExceptionHandler ExceptionHandler { get; set; }
    //public ISomething Something { get; set; }
    //public ISomethingElse SomethingElse { get; set; }

    public AppIsolatedContext()
    {
        // set up autofac
        // Create your builder.
        ContainerBuilder builder = new ContainerBuilder();

        // Usually you're only interested in exposing the type
        // via its interface:
        builder.RegisterType<MailNotificationHandler>().As<INotificationHandler>();
        builder.RegisterType<ExceptionHandler>().As<IExceptionHandler>();

        Container = builder.Build();

        using (ILifetimeScope scope = Container.BeginLifetimeScope())
        {
            ExceptionHandler = scope.Resolve<IExceptionHandler>();
            //Something = scope.Resolve<ISomething>();
            //SomethingElse = scope.Resolve<ISomethingElse>();
        }
    }
}

Of course my application is not limited to these "context singleton" instances. I will have per request lifetime instances too.. but that's what the ASP.NET integration modules are there for right? I hope they can seamlessly be integrated in SharePoint (2013) too :)

So my question is is it okay what I proposed or do I need to get my hands dirty? If so some direction would be phenomenal...

Digging through Autofac's documentation I stumbled across its multi tenancy capability. I believe this might suit my purpose as well.. can anyone confirm this?

using System;
using System.Web;
using Autofac.Extras.Multitenant;

namespace DemoNamespace
{
    public class RequestParameterStrategy : ITenantIdentificationStrategy
    {
        public bool TryIdentifyTenant(out object tenantId)
        {
            tenantId = AppIsolatedContext.Current.Id; // not implemented in the dummy class above, but present in the real thing.
            return !string.IsNullOrWhiteSpace(tenantId);
        }
    }
}

If anything is not crystal - please don't hesitate to tell me :)

Was it helpful?

Solution

Disclaimer: This is a fairly non-trivial question, and given that and my somewhat lack of familiarity with SharePoint 2013, I'll do my best to answer but you'll need to adapt the answer somewhat to your needs.

The way I would structure this is with named lifetime scopes. Rather than contexts with their own containers, use a hierarchy of named scopes. This is how the multitenant support works; it's also how ASP.NET per-web-request support works.

You will first want to read the Autofac wiki page on instance scopes as well as this primer on Autofac lifetimes. Neither of these are small articles but both have important concepts to understand. Some of what I explain here will only make sense if you understand lifetime scope.

Lifetime scopes are nestable, which is how you share singletons or instance-per-web-request sorts of things. At the root of the application is a container with all of your registrations, and you spawn scopes from that.

  • Container
    • Child scope
      • Child of child scope

In a more code related format, it's like this:

var builder = new ContainerBuilder();
var container = builder.Build();
using(var child = container.BeginLifetimeScope())
{
  using(var childOfChild = child.BeginLifetimeScope())
  {
  }
}

You actually resolve components out of scopes - the container itself is a scope.

Key things about lifetime scopes:

  • You can name them, allowing you to have "singletons" within a named scope.
  • You can register things on the fly during the call to BeginLifetimeScope.

This is how the multitenant support for Autofac works. Each tenant gets its own named lifetime scope.

Unfortunately, the multitenant support is one-level: Application container spawns tenant-specific "root" scopes, but that's it. Your site hierarchy where you have these contexts has more than one level, so the multitenant support isn't going to work. You can, however, potentially look at that source code for ideas.

What I'd be doing is naming scopes at each level. Each site would get passed an ILifetimeScope from which it can resolve things. In code, it'll look a little like:

var builder = new ContainerBuilder();
// RootWeb will use the container directly and build its per-web-request
// scope from it.
var container = builder.Build();

// Each sub web will get its own scope...
using(var sw1Scope = container.BeginLifetimeScope("SubWeb"))
{
  // Each child of the sub web will get a scope...
  using(var sw11Scope = sw1Scope.BeginLifetimeScope("SubWeb"))
  {
  }
  using(var sw12Scope = sw1Scope.BeginLifetimeScope("SubWeb"))
  {
  }
}

Note I'm tagging each level of sub-web scope as "SubWeb" - that will allow you to have "instance per sub web" sort of registrations in both container-level and sub-web-level registrations.

// Register a "singleton" per sub-web:
builder.RegisterType<Foo>()
       .As<IFoo>()
       .InstancePerMatchingLifetimeScope("SubWeb");

Now, obviously, that's a conceptual thing there - you won't actually be able to wrap everything in using statements like that. You'll need to manage your creation and disposal differently because creation will happen in a different place than disposal.

You can look at both the ASP.NET and multitenant source to get ideas on how to do that. The general algorithm will be:

  • At application startup, build the root container.
  • As sub webs start up, spawn a nested lifetime scope named for the sub web.
  • If a sub web needs a specific component registered, do that during the call to BeginLifetimeScope
  • If you need the "context" at each sub web level, you'd pass it the scope created for that sub web rather than creating a whole separate container.

Now, you could take it another step by keeping a root-level dictionary of sub web ID to scope so that you'd not need per-level "context" objects at all. It'd be more like a DependencyResolver.Current.GetService<T> kind of pattern. If you look at how the MultitenantContainer in the Autofac multitenant support works, you'll see a similar sort of tenant-ID-to-scope dictionary.

In fact, that multitenant support will be a good pattern to look at, especially if you also want to have per-web-request scopes. The Autofac ASP.NET support requires you pass in a parent ILifetimeScope from which child web request lifetime scopes will be spawned. The multitenant support adds some dynamic aspect in there so when the ASP.NET support calls BeginLifetimeScope the multitenant portion of things automatically figures out (through tenant identification) which tenant should be the parent of the current request. You could do the same thing with your hierarchy of sub-webs. However, again, the multitenant support is a flat structure while your sub webs are a hierarchy, so the multitenant support won't just work.

This is all a long way of saying you have an interesting use case here, but you're going to be getting your hands pretty dirty.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top