1

In one of our app I am already using the dependency injection of AppTenant class like follows

public void ConfigureServices(IServiceCollection services)
{

services.AddMultitenancy<AppTenant, CachingAppTenantResolver>();
services.Configure<MultitenancyOptions>(Configuration.GetSection("Multitenancy"));
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
 app.UseMultitenancy<AppTenant>();
}

and in controller i am able to access it easily as follows

public AccountController(AppTenant tenant)
    {
        this.tenant = tenant;
    }

Now, I want to access the same AppTenant OR HttpContext in other project class in the same solution. So, I have tried like this

public SqlStringLocalizerFactory(
       AppTenant tenant)
    {
     _tenant = tenant;

    }

But it is coming null, so what I need to do, to get the AppTenant OR HttpContext in the other project class ?

For SqlStringLocalizerFactory class the services are written in ConfigureServices method like follows

public static class SqlLocalizationServiceCollectionExtensions
{

    public static IServiceCollection AddSqlLocalization(this IServiceCollection services)
    {
        if (services == null)
        {
            throw new ArgumentNullException(nameof(services));
        }

        return AddSqlLocalization(services, setupAction: null);
    }

   public static IServiceCollection AddSqlLocalization(
        this IServiceCollection services,
       Action<SqlLocalizationOptions> setupAction)
    {

            if (services == null)
        {
            throw new ArgumentNullException(nameof(services));
        }

        services.TryAdd(new ServiceDescriptor(
            typeof(IStringExtendedLocalizerFactory),
            typeof(SqlStringLocalizerFactory),
            ServiceLifetime.Singleton));
        services.TryAdd(new ServiceDescriptor(
            typeof(IStringLocalizerFactory),
            typeof(SqlStringLocalizerFactory),
            ServiceLifetime.Singleton));
        services.TryAdd(new ServiceDescriptor(
            typeof(IStringLocalizer),
            typeof(SqlStringLocalizer),
            ServiceLifetime.Singleton));

        if (setupAction != null)
        {
            services.Configure(setupAction);
        }
        return services;
    }
}

I have even tried with IHttpContextAccessor, but still not getting any success.

Any help on this appreciated !

6
  • 1
    Did you configure/add IHttpContextAccessor to services. It is not automatically added. you will have to add it manually in order for services to be aware of it. ie services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); Commented Aug 26, 2016 at 13:12
  • 1
    You've registered SqlStringLocalizerFactory as singleton so there will only be one for the lifetime of your app. The singleton lifestyle does not match the lifetime of HttpContext which is per request and leaves you with a captive dependency. Commented Aug 26, 2016 at 13:15
  • @Nkosi Yes I have added and in the class I have written as private IHttpContextAccessor _contextAccessor public SqlStringLocalizerFactory( IHttpContextAccessor contextAccessor) Commented Aug 26, 2016 at 13:16
  • @qujck So how do I need to register SqlStringLocalizerFactory class ? With AddScoped OR AddTransient ? Commented Aug 26, 2016 at 13:19
  • @Rohit sorry I can't help you there, I've not invested in .NET Core DI Commented Aug 26, 2016 at 16:48

2 Answers 2

2

Edit-2

New Solution:

public SqlStringLocalizerFactory(IHttpContextAccessor _accessor)
{
   _accessor= accessor;
}
public void SomeMethod()
{
    var tenant = _accessor.HttpContext.RequestServices
            .GetRequiredService<AppTenant>();
}

Edit : IServiceProvider way doesn't work as i expect. See @Sock's solution

First, i assumes the problem occurs because of captive dependency as pointed by @qujck. To avoid captive dependency:

If lifetime of SqlStringLocalizerFactory must be singleton(some cases must be), in this case use IServiceProvider:

public SqlStringLocalizerFactory(IServiceProvider serviceProvider)
{
   _serviceProvider = serviceProvider;
}
public void SomeMethod()
{
    var tenant =  _serviceProvider.GetService<AppTenant>();
}

Otherwise using AddScoped seems reasonable for your case.

Sign up to request clarification or add additional context in comments.

8 Comments

In this case, if the SqlStringLocalizerFactory is registered as a singleton, the AppTenant being resolved from the IServiceProvider will be singleton scoped too according to this twitter exchange: twitter.com/julielerman/status/761179861935394817
I assumes SomeMethod will be called in a request scope because it needs AppTenant(not in the ConfigureServices etc.). In this case i think IServiceProvider resolves right AppTenant. Am i wrong?
Event though you are calling SomeMethod in the context of a request, if the SqlStringLocalizerFactory is registered as a singleton, the IServiceProvider that is injected will create objects of Singleton scope. So every call you make to _serviceProvider.GetService<AppTenant>(); will return the same AppTenant instance, even though AppTenant is registered as Scoped. In order to make the AppTenant have a scoped lifetime, you need to use the ServiceScopeFactory as shown below.
If you ues this approach (edit 2), be aware that you must explicitly register the IHttpContextAccessor in your services using services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();. It is no longer registered by default due to 'non-trivial performance costs'. See github.com/aspnet/Hosting/issues/793
Did you register IHttpContextAccessor explicitly as pointed in previous Sock's comment? Try services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
2

You have two options, the best option, if the SqlStringLocalizerFactory can be a scoped dependency (you get a new instance for every request) then you can register it as a scoped dependency:

services.TryAdd(new ServiceDescriptor(
        typeof(IStringLocalizerFactory),
        typeof(SqlStringLocalizerFactory),
        ServiceLifetime.Scoped));

If the SqlStringLocalizerFactory must be a a Singleton dependency, then you need to make sure you resolve a scoped dependency for the Tenant by using a ServiceSope:

public class SqlStringLocalizerFactory
{
    private readonly IServiceProvider _serviceProvider;

    public SqlStringLocalizerFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void SomeMethod()
    {
        using (var serviceScope = _serviceProvider
            .GetRequiredService<IServiceScopeFactory>().CreateScope())
        {
            var tenant = serviceScope.ServiceProvider.GetService<AppTenant>();

            // do something with tenant...
        }
    }
}

4 Comments

I tested your solution, if AppTenant is resolved before calling SomeMethod, two different AppTenant is created for same request. It might be a problem.
This is true, depending on how the AppTenant itself is resolved I think. In SaasKit, the tenant is resolved once per requests and stored in the HttpContext.Items property. When it is injected via DI, the AppTenant from the HttpContext is returned. In that case, resolving it more than once will still return the same object. However, if a new object is resolved every time it is injected, then you are correct.
@Sock Thanks for the reply. I have tried your solution but 'IServiceProvider' is coming NULL
I think maybe you have a problem with the location where you are using SqlStringLocalizerFactory. I tested this solution injecting it into a controller and it worked as expected. I note you have also added a comment above saying you can't resolve IHttpContextAccessor either - this suggests to me you're not using SqlStringLocalizerFactory in the context of a request. Is that the case?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.