Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manually set tenant for new scope after migration to 7.0.1 version #844

Open
lil-kita opened this issue Jun 21, 2024 · 6 comments
Open

Manually set tenant for new scope after migration to 7.0.1 version #844

lil-kita opened this issue Jun 21, 2024 · 6 comments
Labels

Comments

@lil-kita
Copy link

lil-kita commented Jun 21, 2024

Hi, I updated the package to the 7.0.1 version and I have a question:
How do we set the tenant to the current scope?

in 6.. versions I use this approach:

using IServiceScope scope = _serviceScopeFactory.CreateScope(); // where _serviceScopeFactory is IServiceScopeFactory

MyTenantDbContext ctx = scope.ServiceProvider.GetRequiredService<MyTenantDbContext>();
MyTenantInfo tenant = await ctx.TenantInfo.FirstOrDefaultAsync(t => t.Identifier == tenantIdentifier);

IMultiTenantContextAccessor accessor = scope.ServiceProvider.GetRequiredService<IMultiTenantContextAccessor>();
accessor.MultiTenantContext = new MultiTenantContext<MyTenantInfo>() { TenantInfo = tenant };

// do smth

but now it has become read-only:

Property or indexer 'IMultiTenantContextAccessor.MultiTenantContext' cannot be assigned to -- it is read-only

@robinabganpat
Copy link

robinabganpat commented Jun 25, 2024

I'm currently looking into this as well. My use case is that when processing a message from a queue, I want to set the specific tenant based on a specific message property.

I've currently solved it as follows:
Create your own strategy and put it before any other strategies.
For example:

    public class MyCustomStrategy: IMultiTenantStrategy
    {
        public Task<string> GetIdentifierAsync(object context)
        {
            if (context is string identifier)
            {
                return Task.FromResult(identifier);
            }

            return Task.FromResult<string>(null);
        }
    }

Then add it to DI as follows:

.WithStrategy<MyCustomStrategy>(ServiceLifetime.Singleton)

Then wherever you want to manually set the tenant, use this:

var mtcSetter = scope.ServiceProvider.GetRequiredService<IMultiTenantContextSetter>();
var tenantResolver = scope.ServiceProvider.GetRequiredService<ITenantResolver>();
var multiTenantContext = await tenantResolver.ResolveAsync("IDENTIFIER HERE");
mtcSetter.MultiTenantContext = multiTenantContext;

Mind that this solution currently abuses the fact that the object context is of type object. Certain default strategies (like the HeaderStrategy) will fail because of this, since it expects to 'context' be of type HttpContext. Therefore make sure the .WithStrategy<MyCustomStrategy>(ServiceLifetime.Singleton) is called before any other strategies that are depending on the HttpContext.

Imho, the HttpContext dependent strategies should not throw an exception when the context is not a HttpContext. It should check if it is and otherwise just skip the strategy processing. Or at least make it configurable to explicity throw. Curious as to what others think of this.

@AndrewTriesToCode
Copy link
Sponsor Contributor

AndrewTriesToCode commented Jun 25, 2024

Edit: @robinabganpat beat me to the details above, thanks!

Hi, I abstracted setting it behind IMultiTenantContextSetter. Take a look in the middle where to see how it can be used:

var mtcSetter = context.RequestServices.GetRequiredService<IMultiTenantContextSetter>();
            
var resolver = context.RequestServices.GetRequiredService<ITenantResolver>();
            
var multiTenantContext = await resolver.ResolveAsync(context);

mtcSetter.MultiTenantContext = multiTenantContext;
@robinabganpat
Copy link

Edit: @robinabganpat beat me to the details above, thanks!

Hi, I abstracted setting it behind IMultiTenantContextSetter. Take a look in the middle where to see how it can be used:

var mtcSetter = context.RequestServices.GetRequiredService<IMultiTenantContextSetter>();
            
var resolver = context.RequestServices.GetRequiredService<ITenantResolver>();
            
var multiTenantContext = await resolver.ResolveAsync(context);

mtcSetter.MultiTenantContext = multiTenantContext;

No problem. Thank you for creating this library.

Given my comment:

Imho, the HttpContext dependent strategies should not throw an exception when the context is not a HttpContext. It should check if it is and otherwise just skip the strategy processing. Or at least make it configurable to explicity throw. Curious as to what others think of this.

What is your opinion on this and what is your idea behind the object context implementation? The choice for the object type makes it possible to use the context 'flexibly', which is nice, but then strategies should not always assume that the context is of type HttpContext, right?

@AndrewTriesToCode
Copy link
Sponsor Contributor

I like this change and it fits in with a new strategy I am adding that ‘HttpContextStrategy` that just takes a delegate taking http context and wraps the cast from object to http context so the user doesn’t need to worry about it. It could return null as you describe rather than an exception.

Although the current checks that return exceptions only make sense in http context use cases. Which strategy are you using with a non http context that is throwing the exception? I’d like to learn more about your use case.

@robinabganpat
Copy link

Although the current checks that return exceptions only make sense in http context use cases. Which strategy are you using with a non http context that is throwing the exception? I’d like to learn more about your use case.

I'm currently using the HeaderStrategy, which is a HttpContext use case, however in my use case I mix between a HttpContext use case and the use case mentioned above: A message is received from a queue, where a message property mentions the TenantIdentifier.

The TenantResolver just loops through the Strategies using the context that is given, so it doesn't know which Strategy is using the context as HttpContext or something else.

I've now tackled this issue by first registering my custom Strategy before any HttpContext related strategy to prevent an Exception to be thrown in case it tries to resolve the tenant when a message is received from a queue.
Although, it would be nice if the HeaderStrategy would just check whether the given context is a HttpContext and would 'skip' the strategy if it isn't, instead of throwing an exception.

What also would be nice, if this behaviour would be configurable if you really want the strategy to throw an exception if it doesn't know what to do with the given context. This could be handy if you really want to force a strategy to be adhered to.

@AndrewTriesToCode
Copy link
Sponsor Contributor

AndrewTriesToCode commented Jun 26, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 participants