Recently, I’ve been working on a multi-tenancy project. Like most projects I tend to gravitate towards, it wasn’t that simple.

For those of you who know don’t know what that means, multi-tenancy is when an application is designed to be used by multiple companies/users at the same time, keeping all data separated from each other.

Typically if you want something like this done, you add a ClientId or some other restriction to your Models to help identify which tenant that Thing belongs to.

In the world of Entity Framework, you’d probably do Thing.Where(x => x.ClientId == clientId). You’d do this for each select statement, each update, and each delete to verify that the client that was logged in had the correct permissions to that resource. That’s boring, annoying, tedious and prone to errors.

Enter stage left comes Entity Framework interceptors. EF interceptors do what you’d expect them to do and intercept your EF queries before they hit the SQL server. Because they do this, it is possible to manipulate or log those queries.

Following some code by Charalampos Karypidis, I was able to get the basics setup. Because the rest of this article is an expansion of Charalampos’ code, I’ll not be reposting parts of his code that I didn’t change. You’ll want to go read and understand his article (or get the code on GitHub) before continuing. 

Where I part from Charalampos’ code is where I think things get interesting. Charalampos’ setup involves using the interceptor to assign TenantId to the UserId. That’s all fine and well, but what if there is actually a group of individuals all utilizing the same Tenant?

Note: I added the [ScaffoldColumn(false)] annotation to the base models so that VS generated views do not attempt to scaffold those columns since the interceptors rip them out and they’re private set;.
Note: There are possibly other ways to accomplish this, and I’m happy to read about it in the comments, but this is the way I did it and it works. :) 

The requirement for this project was that the Tenant be based on the subdomain (tenant.domain.com) and allow for several users to access that Tenant. We also ended up with an additional level of multi-tenancy, but I’ll get to that in a second post. 

For the subdomain we implemented this custom MVC route. From this code we’re able to specify our first level of multi-tenancy with a route that looks like this: 

routes.MapSubdomainRoute("SubdomainRoute",  "{controller}/{action}/{id}", 
    new {controller = "Home", action = "Index", id = UrlParameter.Optional
});

I created a “Subdomain” base controller class. The base class will make it easier to get the subdomain from the request on all of our controllers that expect there to be a subdomain. This is slightly different from the SO answer above since I created a helper method to get the subdomain from the context since I knew I was going to need it other places.

public class SubdomainBaseController : Controller
{
	protected string Subdomain => SubdomainRoutes.GetSubdomain();

	protected int Level1Id => GetIdFromSubDomain();//get Level1 Id based on the subdomain;

	protected override void OnActionExecuting(ActionExecutingContext filterContext)
	{
		if (string.IsNullOrEmpty(Subdomain))
		{
			filterContext.Result = RedirectToAction("NoSubdomain", "Oops");
			return;
		}

		var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;

		if (!identity.IsAuthenticated) return;

		var claim = identity.Claims.FirstOrDefault(c => c.Type == CustomClaims.Level1Id );

		if (claim == null || (claim.Value != Level1Id.ToString())) Users.AddSubdomainClaim();
	}
}
Note: To test this, in IIS Express you have to run VS as Administrator and setup the IIS site to accept wildcard host headers. We’re finally getting support for wildcard host headers in IIS 10, but this works as of this post on Azure websites.

A fun /sarc part about using the EF command interceptors is that there’s no HttpContext. You only get to access what’s in the thread. This worked great for Charalampos since his code is based on the UserId and the thread has the CurrentPrincipal, but I needed to check roles and permissions and how the current subdomain related to the level 1 tenant structure.

The end result is stuffing the Principal with some custom claims.

Any controller that inherits from this controller will check that the user has a valid subdomain claim and that there’s a subdomain in the request.

I changed the RedirectToLocal ActionResult in the AccountsController to this:

public ActionResult RedirectToLocal(string returnUrl)
{
	if (!CurrentUser.IsInRole(AllRoles.DomainUser))
	{
		LogOut();
		return RedirectToAction("BadLogin", "Oops");
	}

	Users.AddSubdomainClaim();

	//now return you to you regularly schedule code

	if (Url.IsLocalUrl(returnUrl))
	{
		return Redirect(returnUrl);
	}

	return RedirectToAction("Index", "Home");
}

AllRoles.DomainUser is simply the subdomain concatenated with a suffix string.

Here’s what AddSubdomainClaim looks like:

public static void AddSubdomainClaim()
{
    var claims = new List<Claim>
    {
		new Claim(CustomClaims.Level1Id, GetLevel1IdFromSubdomain().ToString())
    };

    var authenticationManager = HttpContext.Current.GetOwinContext().Authentication; 

    var identity = new ClaimsIdentity(HttpContext.Current.User.Identity);

    identity.AddClaims(claims);

    var claimsPrincipal = new ClaimsPrincipal(identity);

    Thread.CurrentPrincipal = claimsPrincipal;

    authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = false }, identity);
} 

Another fun /sarc obstacle to deal with is that if you use dynamic Claims like this there’s 1) no guarantee that the claim will still be there later, and 2) you must sign the user back in again. The primary purpose of this code is to search the database for our Subdomain on the Level1 table to find the Level1Id and stuff it into a claim on the user. 

Other than some slight code cleanup, this is where we can return to Charalampos’ code. In the CommandInterceptor class:

private static void SetParameterValues(DbCommand command)
{
    if ((command == null) || (command.Parameters.Count == 0) ||
			command.CommandText.ToLower().Contains("migration") || 
			command.CommandText.ToLower().Contains("aspnet"))
    {
		return; //why waste time if we know for sure that our command doesn’t use the subdomain
    }

    var level1IdFromSubdomain = GetLevel1IdFromClaim();

    var level1Param = command.Parameters.Cast<DbParameter>()
			    .FirstOrDefault(x => x.ParameterName == Level1AwareAttribute.Level1IdFilterParameterName);
    if (level1Param != null) level1Param.Value = level1IdFromSubdomain;
} 

The changes here are slight: we’re going to ignore commands we don’t care about, we’re getting the Level1Id from our custom Claim, and I’m using a much cleaner Linq query instead of a for loop.

The CommandTreeInterceptor class gets more complicated. 

public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
    if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace) return;
    var queryCommand = interceptionContext.Result as DbQueryCommandTree;
    if (queryCommand != null)
    {
		var newQuery = queryCommand.Query.Accept(new QueryVisitor());
		interceptionContext.Result = new DbQueryCommandTree(		
			    queryCommand.MetadataWorkspace,
			    queryCommand.DataSpace,
			    newQuery);
			return;
    }

    var level1Id= Companies.General.GetLevel1IdFromClaim();
    if (InterceptInsertCommand(interceptionContext, level1Id)) return;
	//...
}

In each of the intercept commands we are including the Level1Id (again from the claim). Instead of the UserId. Of course, each command interceptor got changed to match the differences between looking at the UserId field to the Level1Id.

Other than some slight changes to Charalampos’ code to handle the subdomain, this is pretty straightforward. Tune in to part 2 to see how I made this even more complicated by adding our second level of multi-tenancy.