Web API – Passing State or Context Across Layers in C#

cdependency-injectionweb-api

I am running into an interesting scenario that I just haven't been able to get answers on. I would like to see if there are elegant solutions for this. Here are some use cases:

  1. I want to pass the identity/user principal of the user that called an API Action in a Controller to Services and Repository layers. There are important decisions that get taken at Service layer depending on the User's claims and/or privileges/permissions.

  2. I want to identify a tenant (occurs using HTTP Headers or Hostnames) but pass this Tenant information to Services and Repository layers so that different Redis Caches and Databases can be used depending on the Tenant.

I can easily build a method in a base service called WithTenant(object tenant); which I can use to pass tenants.

This means every service call I make will need this WithTenant called.

For e.g. a call:

 _inventoryService.CheckInventory() 

will now change to:

_inventoryService.WithTenant(tenant).CheckInventory();

Likewise, if I want to pass the HttpRequest in addition to the Tenant, I will have to do this:

_inventoryService.WithHttpRequest(Request).WithTenant(tenant).CheckInventory();

This gets even more complex as I need to pass this to the Repository layer. My Mongo DB Context is a singleton. I might need to change this to accept the tenant so the connection string can be changed based on the tenant.

So I was thinking:

  1. Isn't there a way by which I can pass a Context object across these layers? Maybe build a Context class and set properties like Request, or Tenant or anything else I need and then pass it across layers?
  2. More importantly, is there a way to dynamically inject this into the Constructors of these services so I don't need to manually wire it up again? I know that I can store my context in the Request object or maybe even an Owin Context. Could I somehow inject that? If so I haven't seen any good examples of this across multiple layers.

I guess I'm simply trying to simplify the ability to pass state between layers. Cross cutting functions like Caching, Logging, Database Access change on a per Tenant basis so if I could create a central context, that would greatly simplify things.

Any thoughts? Would love to see what others are doing to solve such problems.

Best Answer

to everyone that may want to know how this was solved, it turned out to be an easy fix with a little bit of understanding of Dependency Injection Lifetimes as well as clean Architecture strategies.. The following was the logic I applied:

  1. Identify the Tenant - Look up the Tenant based on the available strategies (i.e. either a Tenant Id in a Claim, or a Host Header, or the originating request).

  2. Get the Tenant's Profile - Once the Tenant was identified, his profile information was obtained. This included Tenant's Data center information and various other parameters (queues, caching cluster etc)

  3. Build a Tenant Context (instantiate singletons for Redis, Mongo and maintain a Connection string for SQL Databases). Keep this Tenant Context in a Memory Cache so future retrieval is easy.

  4. Built a Per Resolve Tenant Context resolution using Unity (or a similar DI tool). Per Resolve ensures that the Tenant Context resolution only occurs once during a request and the same instance can be used across the board. This was important because the same Tenant Context was needed at Attribute Filter level, Application and Domain Services and sometimes all the way up to the Repository layer. All these would have the same context instead of building a new one for each class up/down the food-chain.

  5. Use the context at each layer to get a connection factory to connect to Mongo, Redis, SQL or Queues.

Once I had the factory, a global replace allowed me to use it across the board (got lucky because I was already using a cleaner architecture).

If anyone is really interested in peeking into the code/details, here is a link that might help: http://www.hypertrends.com/2017/10/saaskit-dependency-injection-tenants/