I have a multitenant application which uses scoped dependencies to retrieve a tenantId from an incoming httprequests to the Azure functions and instantiate various resources based on the tenant.
For example:
services.RegisterScoped((sp)=>
{
var httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
var tenantId = httpContextAccessor.GetTenantId(); // extension method
// Instantiate some other tenant specific dependencies e.g. DBContext.
});
Now, I have a ServiceBusTrigger which processes messages for all tenants. The ServiceBusReceivedMessage parameter contains an object which has a tenantId property so that each message can be processes for a particular tenant.
Is there a way to have the ServiceBusReceivedMessage instantiate a resource, say a DbContext, from within the function? This is necessary because each message may have to persist some data via a DbContext, which needs to be instantiated with a specific connection string based on the tenantId in the message.
Some things I have considered:
Using Activator.CreateInstance(//pass in the tenant connection string after retrieving it manually)
Can ServiceBusRecievedMessage be registered within the serviceBusTrigger per message?? This seems hacky (there isn't a straightforward way to do that) but also the most preferred approach as the rest of the DI containers can be leveraged without having to instantiate objects manually as in the first option.
[FunctionName("MyCustomTrigger")]
public Task Run(
[ServiceBusTrigger("MultiTenantEndpoint")]
ServiceBusReceivedMessage message,
ServiceBusClient client,
ServiceBusMessageActions messageActions,
ILogger logger,
ExecutionContext executionContext)
{
var tenantId = message.GetTenantId();
// register tenantId as scoped for this request only somehow?
await injectedService.Process(message);
}
Alternate approaches or designs for this?
Related
I am working on AzureFunction ServiceBus v3 which handle multi-tenant message. In message I will have TenantId and I will need to register DependencyInjection per message base on this TenantId.
What I tried so far:
At StartUp, I stored the IServiceCollection as static variable
Retrieve the TenantId from the serialized message in Function'd Run method
Update IServiceCollection based on above TenantId and retrieve the Service
_serviceCollection.AddTransient<ITenantIdResolver>(ctx => { return new CompanyResolver{TenantId=tenantId}; }); var service = _serviceCollection.BuildServiceProvider().GetService<T>();
But it throw exception: Unable to resolve service for type 'Microsoft.Azure.WebJobs.Script.IEnvironment' while attempting to activate 'Microsoft.Azure.WebJobs.Script.Configuration.ScriptHostOptionsSetup' I do some research and look like it was because I used IHttpClientFactory.
How can I fix this?
Or even better if there is a way to retrieve the message in StartUp, so I can inject the tenantId properly? Like
serviceCollection.AddTransient<ITenantIdResolver>(ctx => { var tenantId = GetServicebusMessage().TenantId; return new CompanyResolver { TenantId=tenantId }; }
I think that would be the wrong order of things. Dependency injection should be setup before the message is being processed.
A resolver could be a solution. Register the resolver with the dependency injection container, and let the function be dependent on the resolver. Based on the message you get the right instance from the resolver. In this article it is explained better under "IoC Container Registration #3 – Resolver + Factory Method Pattern": https://techcommunity.microsoft.com/t5/apps-on-azure/dependency-injection-on-azure-functions-5-ways-selecting/ba-p/1502394
According to Dependency injection for azure functions, it's not possible to use the services at an early stage.
My suggestion is to change the architecture to "Durable Orchestrations" so you call an ActivityTrigger function from the orchestrator which gets back list of tenants then you fire other ActivityTriggers that will handle them.
My ConfigureServices section of Startup.cs looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var builder = services.AddIdentityServer()
.AddInMemoryApiResources(Configurations.ApiResources.GetApiResources())
.AddInMemoryClients(Configurations.Clients.GetClients());
services.AddEntityFrameworkNpgsql();
services.AddDbContext<IdentityDbContext>();
services.BuildServiceProvider();
services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
// Login Service and User Repo Injection
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<ILoginService, LoginService>();
// Connection String Config
services.Configure<ConnectionStringConfig>(Configuration.GetSection("ConnectionStringConfig"));
if (Environment.IsDevelopment())
{
builder.AddDeveloperSigningCredential();
}
}
I am injecting my loginService into ResourceOwnerPasswordValidator, and I am injecting userRepository into my loginService. ResourceOwnerPasswordValidator is handling the validation of my user's login.
I originally added my repository and loginService as singletons but I got the error
unable to consume scoped instance of DbContext from singleton
userRepository.
As you can see above I changed both my loginService and userRepository instances to transient. Is this a safe way to do it, or is there another way I should choose?
My loginService uses userRepository to talk to the database. However if I add them as singletons,
I get a cannot consume scoped db instance
, so I thought I'd make the whole thing transient.
Is there a better way to do this which would allow me to keep loginService and userRepository as singletons?
Typically you'd only want to use a singleton in a web application if any of the following is true and if the class in question is thread-safe:
Construction of an underlying resource (e.g. a connection to a distributed cache) is expensive
You need to maintain in-memory state for the duration of the application
You need to serialize access to a resource (e.g. an append-only file)
In your case none of these are true so scoped or transient are totally fine.
I've been trying to modify injected services with values available from authenticated users.
Using the built-in DI container, I added the required service with Scoped lifetime.
services.AddScoped<ITodoRepository, TodoRepository>();
Then registered a custom middleware between authorization and MVC middlewares to modify the repository with user specific information.
// Configure authorization middleware
app.Use(async (context, next) =>
{
var todoRepository = context.ApplicationServices.GetRequiredService<ITodoRepository>();
todoRepository.UserStoreId = context.User.GetUserStoreId();
await next.Invoke();
});
// Configure MVC middleware
When the program execute a request, the injected repository within my controller does not presist the saved value.
Am i doing something wrong?
From my understanding, scoped objects are saved within request.
Also, is there another way to achieve this?
You can create your own service, i.e. IAuthenticatedUserService/AutheticatedUserService.
Into it, you inject IHttpContextAccessor.
public interface IAuthenticatedUserService
{
ClaimsPrincipal User { get; }
}
Then you inject the IAuthenticatedUserService into your repository, where you can access the logged-in user.
Of course you could also directly inject IHttpContextAccessor into your repository and access httpContextAccessor.HttpContext.User within it, but since repositories are usually defined in their own assembly, you'd also need to reference the Microsoft.AspNetCore.Http.Abstractions package from it which would cause a tight(er) coupling.
But if you don't mind this kind of coupling, just inject the IHttpContextAccessor into your repository, they are supposed to be scoped (=per request) or transient anyways.
P.S. don't forget the Dependency Injection/Inversion of Control mantra: "Don't call us, we call you". You have attempted to call "us" (the repository) to set a value.
Am i doing something wrong? From my understanding, scoped objects are saved within request.
I was able to fix the issue by replacing
context.ApplicationServices.GetRequiredService<ITodoRepository>();
with
context.RequestServices.GetRequiredService<ITodoRepository>();
I'm trying to use NHibernate for a new app with a legacy database. It's going pretty well but I'm stuck and can't find a good solution for a problem.
Let's say I have this model :
a Service table (Id, ServiceName..)
a Movie table (Id, Title, ...)
a Contents table which associates a service and a movie (IdContent, Name, IdMovie, IdService)
So I mapped this and it all went good. Now I can retrieve a movie, get all the contents associated, ...
My app is a movies shop "generator". Each "service" is in fact a different shop, when a user enter my website, he's redirected to one of the shops and obviously, I must show him only movies available for his shop. The idea is : user comes, his service is recognized, I present him movies which have contents linked to his service. I need to be able to retrieve all contents for a movie for the backoffice too.
I'm trying to find the most transparent way to accomplish this with NHibernate. I can't really make changes to the db model.
I thought about a few solutions :
Add the service condition into all my queries. Would work but it's a bit cumbersome. The model is very complex and has tons of tables/queries..
Use nhibernate filter. Seemed ideal and worked pretty good, I added the filter on serviceid in all my mappings and did the EnableFilter as soon as my user's service was recognized but.. nhibernate filtered collections don't work with 2nd lvl cache (redis in my case) and 2nd lvl cache usage is mandatory.
Add computed properties to my object like Movie.PublishedContents(Int32 serviceId). Probably would work but requires to write a lot of code and "pollutes" my domain.
Add new entities inheriting from my nhibernate entity like a PublishedMovie : Movie wich only presents the contextual data
None of these really satisfies me. Is there a good way to do this ?
Thanks !
You're asking about multi-tenancy with all the tenants in the same database. I've handled that scenario effectively using Ninject dependency injection. In my application the tenant is called "manual" and I'll use that in the sample code.
The route needs to contain the tenant e.g.
{manual}/{controller}/{action}/{id}
A constraint can be set on the tenant to limit the allowed tenants.
I use Ninject to configure and supply the ISessionFactory as a singleton and ISession in session-per-request strategy. This is encapsulated using Ninject Provider classes.
I do the filtering using lightweight repository classes, e.g.
public class ManualRepository
{
private readonly int _manualId;
private readonly ISession _session;
public ManualRepository(int manualId, ISession session)
{
_manualId = manualId;
_session = session;
}
public IQueryable<Manual> GetManual()
{
return _session.Query<Manual>().Where(m => m.ManualId == _manualId);
}
}
If you want pretty urls you'll need to translate the tenant route parameter into its corresponding database value. I have these set up in web.config and I load them into a dictionary at startup. An IRouteConstraint implementation reads the "manual" route value, looks it up, and sets the "manualId" route value.
Ninject can handle injecting the ISession into the repository and the repository into the controller. Any queries in the controller actions must be based on the repository method so that the filter is applied. The trick is injecting the manualId from the routing value. In NinjectWebCommon I have two methods to accomplish this:
private static int GetManualIdForRequest()
{
var httpContext = HttpContext.Current;
var routeValues = httpContext.Request.RequestContext.RouteData.Values;
if (routeValues.ContainsKey("manualId"))
{
return int.Parse(routeValues["manualId"].ToString());
}
const string msg = "Route does not contain 'manualId' required to construct object.";
throw new HttpException((int)HttpStatusCode.BadRequest, msg);
}
/// <summary>
/// Binding extension that injects the manualId from route data values to the ctor.
/// </summary>
private static void WithManualIdConstructor<T>(this IBindingWithSyntax<T> binding)
{
binding.WithConstructorArgument("manualId", context => GetManualIdForRequest());
}
And the repository bindings are declared to inject the manualId. There may be a better way to accomplish this through conventions.
kernel.Bind<ManualRepository>().ToSelf().WithManualIdConstructor();
The end result is that queries follow the pattern
var manual = _manualRepository
.GetManual()
.Where(m => m.EffectiveDate <= DateTime.Today)
.Select(m => new ManualView
{
ManualId = m.ManualId,
ManualName = m.Name
}).List();
and I don't need to worry about filtering per tenant in my queries.
As for the 2nd level cache, I don't use it in this app but my understanding is that you can set the cache region to segregate tenants. This should get you started: http://ayende.com/blog/1708/nhibernate-caching-the-secong-level-cache-space-is-shared
We are building a multi-tenant database application utilizing ASP.NET MVC, the Entity Framework Code First, and the soon to be released SQL Azure Federations. Our repository accepts a DbContext as a constructor argument that is injected using Ninject. The problem is that the DbContext has an int constructor argument to specify the account id that will be used for the requests made with that context. Something like this:
public class DatabaseContext : DbContext
{
public DatabaseContext(int accountId)
{
// Code here to ensure we only work with data associated with
// the account identified by accountId
}
}
The value for the account id will be stored in a Session variable. The question is how do we get Ninject to inject a value from Session into the constructor when it creates a new instance of DatabaseContext?
Or is there another way I should go about injecting the account id into the DbContext?
There are a few ways. I like using the .ToMethod approach.
Bind<DatabaseContext>().ToMethod(
c => new DatabaseContext((int)HttpContext.Current.Session["Acct"]);
Though to solve this particular problem, I've found it better to create an ISessionManager service that has session-based data on it. Anything that wants, e.g., an accountId would have this service injected into the constructor, and access its AccountId property when it needs an account ID.