A few weeks ago, I started using SaaSKit for an ASP.NET Web API 2.0 based Application. As you know, ASP.NET Web API2.0 doesn’t seem to be supposed by SaaSKit and majority of the articles etc. revolve around the new ASP.NET Core. While this is great, we were working on an application that would take months to migrate over to ASP.NET Core. SaaSKit by itself worked just fine on my application. My needs were a little advanced (as with any multi-tenant implementation).
Injecting SaaSKit Tenant using DI – The Problem
Once I was able to resolve the SaaSKit Tenants (which was very easy), my next task was to inject them into the other layers somehow. Here is our original post on their GitHub forum:
Hello, I’ve been struggling with this so I thought I’d ask here. I can easily resolve Tenants in a Web API using a Host or a HTTP Header information. I can stuff this tenant in the Request.Items[“Tenant”] using an Web API Actor Filter. Now I would like to pass this Tenant into a Service and the Repository layer easily without having to modify each method to accept the Tenant.
The Service and Repository classes are built up using Unity. Can I pass the Http Request to the Service (or Repository) so I can access the tenant there?
I want to avoid writing calls like _service.WithTenant(tenant).DoSomething();
Instead I would ideally like to automatically inject the tenant in the service when it is needed so I can continue doing _service.DoSomething();
The DoSomething would then be able to access a tenant inside its implementation and use it accordingly.
Would this be done using an OwinContext given that I don’t want to rely on HTTP Context/Request only?
I would love to hear some thoughts. I’ve spent quite a bit of time searching online and it looks like this maybe the best place to ask because I’m sure someone else has had a similar issue after resolving the tenant.
Injecting SaaSKit Tenant using DI – The Solution
I was able to figure this out. I’m going to try my best to explain this here just in case someone else needs it. The problem statement was simple, I was using Web API 2.x and wanted to use SaaSKit in it to resolve tenants (based on Claims embedded in Tokens and/or Hostname resolvers). Once a Tenant was identified, I would pass this tenant to my Service Layer and/or Repo Layers etc. In doing all this, I wanted to avoid adding the Tenant as a Service method argument.
Much of this is very straightforward with ASP.NET Core. So if you are using .NET Core, please ignore this.
The answer to injecting a Tenant lies with Dependency Injection (Unity in my case), obviously. What I couldn’t figure out was how I could get the Tenant on each request. After doing some research, I found out that Unity has Lifetime managers. So I started digging in on how to inject the Tenant or an Http Context if needed.
I ended up building both approaches, still not sure which one I prefer more, but one allows you to Inject the HttpContext directly into a Service, the other allows you to inject the Tenant. Once you have the Http Context, you can actually infer the Tenant from the Owin Pipeline if you want.
My Tenant is called ‘TsTenant’. If you just want to inject the Tenant Directly, you can do so quite simply by registering it like so:
container.RegisterType<TsTenant>(new TenantLifetimeManager());
Once this is registered, you can pretty much declare this Tenant as an argument in any constructor and it will resolve it using the “TenantLifetimeManager”. The Tenant Lifetime manager is where everything fun happens:
public class TenantLifetimeManager : LifetimeManager { public override object GetValue() { if (HttpContext.Current == null) return null; var owinEnv = HttpContext.Current.GetOwinContext().Environment; return owinEnv?.GetTenant<TsTenant>(); } public override void RemoveValue() { if (HttpContext.Current == null) return; var owinEnv = HttpContext.Current.GetOwinContext().Environment; //uh-oh, how to I remove? } public override void SetValue(object newValue) { if (HttpContext.Current == null) return; var owinEnv = HttpContext.Current.GetOwinContext().Environment; owinEnv?.SetTenantContext(new TenantContext<TsTenant>((TsTenant) newValue)); } }
As you can see, if the HttpContext isn’t null, I simply get the Owin Context and its Environment and then use the extension method GetTenant.
This gives me what I need. However, I was wondering, what if the HttpContext is null. So I ended up building an HttpContext registration that would either give me the real Http Context, or a fake one. This kind of stuff might be useful for Unit Testing scenarios. I am still marinating with this and haven’t really finalized it.
But here’s how you register the Http Context resolution:
container.RegisterType<HttpContextBase>(new HttpContextBaseManager());
The HttpContextBaseManager is responsible for managing the lifetime/creation:
public class HttpContextBaseManager : LifetimeManager { public override object GetValue() { return HttpContext.Current != null ? (HttpContextBase) new HttpContextWrapper(HttpContext.Current) : new FakeHttpContextBase(); } public override void RemoveValue() { throw new NotImplementedException(); } public override void SetValue(object newValue) { throw new NotImplementedException(); } }
Notice how it creates a FakeHttpContextBase. This fake class simple does this:
public class FakeHttpContextBase : HttpContextBase { private IPrincipal _principal; public FakeHttpContextBase() { } public FakeHttpContextBase(IPrincipal principal) { _principal = principal; } #region OVERRIDES public override IPrincipal User { get { return _principal; } set { _principal = value; } } #endregion }
Now I can inject the HttpContext directly, and still get to the Tenant using Owin if needed. If I wanted to unit test this stuff, I could build a fake Owin Environment (i’m sure I can) to resolve a fake tenant.
I wanted to take this one step further. Instead of passing around Tenants, I thought it might be much better to create a TenantConnectionFactory. This factory would implement an ITenantConnectionFactory and expose the various Connections (Sql Connection for OLTP, Data Warehouse Connection, MongoDB connection, Azure blob storage connection, queue connections etc). Then I could hydrate this factory using the DI and pass it around in a Work Context.
So I now have:
container.RegisterType<ITenantContextFactory, TenantContextFactory>(); container.RegisterType<IWorkContext, ApiWorkContext>();
And my Api Work Context constructor is:
public class ApiWorkContext : IWorkContext { private readonly HttpContextBase _httpContext; public ApiWorkContext(HttpContextBase httpContext, ITenantContextFactory tenantContextFactory) { _httpContext = httpContext; TenantContextFactory = tenantContextFactory; }
Now I can use this Work context to get to Http Context, and the Tenant Context Factory. Here’s an example:
public SeedDataService(ISeedDataRepository repo, IWorkContext workContext) { _repo = repo; _workContext = workContext; }
A few challenges that still remain:
I still haven’t been able to figure out Child Containers per Tenant. I would hate to create my Mongo connection again and again. In fact, the Mongo Driver I currently have for a single tenant environment is a Singleton. Mongo internally manages the connection pooling and recommends using a Singleton. I’ve seen some crazy errors if we create multiple instances of the Mongo connection. So the idea is to build a Singleton per Tenant and use it for that tenant dynamically. I have read Ben Foster’s post on Per Container Tenant Lifetimes. Now I just need to implement this in Unity and in Web API 2.0.
Any thoughts here would be greatly appreciated.
I’m still trying to figure out and hopefully minimize the impact of this change when I migrate from Web API2.0 to .Net Core. Right now, this isn’t an option given the complexity of my app (which was built as a semi-monolith).
This post is cross-posted from a GitHub Issue I opened here: https://github.com/saaskit/saaskit/issues/88
To learn more about the wonderful SaaSKit Library, check it out here.