Register scoped dependency linked with request or background process

For my multi-tenant app I want to resolve the current active Tenant from:

  1. The url of request => working
  2. Background process

Any sample how to create register the Tenant in an environment not linked with a request, a background process?

Thanks

Include any info you need along with your request, for MQ Requests your Request DTO can implement IHasSessionId or IHasBearerToken to executed authenticated requests. If you can’t derive the Tenant Id from the authenticated user you’d need to include the TenantId as well or if you’re using Sever Sessions you can retrieve the Users Session from their SessionId (i.e. IRequest.GetSessionId()) with:

var key = SessionFeature.GetSessionKey(sessionId);
var session = HostContext.AppHost.GetCacheClient(null).Get<IAuthSession>(key);

Thanks!
The background process can be a scheduler like Quartz running tasks for one or more tenants. Each
scheduled task is linked to a tenant… Is is possible to inject a tenant into a scope (that is created for each scheduled task)

Whatever’s scope is available is going to be up to whatever solution you’re using, I’d personally wouldn’t rely on an implementation-specific scope and include anything your Task requires in the same request as everything else.

I’m trying a different approach:

public new void ConfigureServices(IServiceCollection services) {

  services.AddScoped<TenantContext>();

}

PreRequestFilters.Add((request, response) => {
	var context = request.Resolve<TenantContext>();
	context.Tenant ??= request.GetCurrentTenant();
});

But when I resolve the TenantContext within a Service instance, it seems that a new TenantContext is created (I confirmed this with a breakpoint inside the constructor).
I also tried with a GlobalRequestFilter, but same result.

ps. the end goal is a single scoped registration for this:

  	services.AddScoped<IAsyncDocumentSession>((c) => {
  		return c.Resolve<IDocumentStore>().OpenAsyncSession(new Raven.Client.Documents.Session.SessionOptions() {
  			Database = c.Resolve<TenantContext>().Tenant?.Database ?? Db.SharedDatabase,
  		});
  	});

(due to the ravendb session cache it needs to be scoped instance and not a transient instance)

Thanks

If you’re using .NET Core IOC scoped dependencies you can use the TryResolvedScoped and ResolvedScoped APIs instead which resolves it from .NET Core’s RequestServices Scope but this is for HTTP Requests.

But that seems a bit weird, the caller should not decide if it’s a singleton/transient or scoped dependency. What will happen with .NET core IOC scoped depencies that are resolved inside a service. Are they properly disposed when the service is dispose?

Thanks
Marco

Note: you’re circumventing ServiceStack and using ASP.NET Core IOC APIs directly here, you’ve registered your dependency in ASP.NET Core’s ConfigureServices() and are resolving it from ASP.NET’s HttpContext.RequestServices Request Scope. If you need to access your .NET Core IOC scoped dependencies you should use these APIs to resolve them within .NET’s Request Scope.

My understanding was that Servicestack handles this transparently via the NetCoreAdapter

So I shoud only use RegisterAutoWired* functions and then c.Resolve will use the correct scopes (even in not-httprequest based background tasks with a child scope)

ServiceStack’s IOC APIs lets you resolve ServiceStack IOC registered dependencies and .NET Core IOC ApplicationServices dependencies (i.e. Transient/Singletons), to resolve .NET Core IOC Scoped dependencies you’ll need to use the ResolveScoped APIs mentioned above which resolves them directly from .NET Core’s RequestServices.

And you can only use ServiceStack’s Request Scope in HTTP Requests, i.e. not Background Tasks.

But how does the c.Resolve function inside a service know when to call the Resolve or ResolveScoped method to resolve the dependencies from the .NET core IOC (I need them there for my background tasks)

If you want to resolve .NET Core IOC scoped dependencies from within a Service you’ll need to resolve them from IRequest.

The IRequest.TryResolve API on IRequest in .NET Core resolves them from RequestServices first before falling back to ServiceStack IOC Resolve behavior.

Ok, this is my registration for the IAsyncDocumentSession:

services.AddScoped<IAsyncDocumentSession>((c) => {
				return c.Resolve<IDocumentStore>().OpenAsyncSession(new Raven.Client.Documents.Session.SessionOptions() {
					Database = c.Resolve<TenantContext>().Tenant?.Database ?? Db.SharedDatabase,
				});
			});

when i do

> request.TryResolve<IAsyncDocumentSession>

does not use the correct scope for the TenantContext
What should be proper registration to resolve this with the correct scope in requests and background tasks?

and/or support for this registration:

> services.AddScoped<IAuthRepository>(c => new RavenDbUserAuthRepository(c.Resolve<IAsyncDocumentSession>()));

I keep repeating this, you cannot use ServiceStack Request Scope in non HTTP Requests, if you want to use .NET Cores IOC in non HTTP request I can only assume you’ll need to create and manage your own scope as there is no RequestServices Scope in non HTTP Requests, so you’d need to manage it yourself outside of ServiceStack which does not support request scope in non HTTP services:

I keep repeating this, you cannot use ServiceStack Request Scope in non HTTP Requests

So if I move my registrations to the Funq container, the Funq.Container.Resolve() request will use the correct scope in a http request?

What is the function of Funq.Container.CreateChildContainer()? That can’t be used in a non HTTP request?

IN HTTP Requests, NOT in non-HTTP Requests.

To make this clear: ServiceStack’s IOC (i.e. Funq.Container) only supports Transient / Singleton dependencies in non-HTTP Services, it does NOT support scoped registrations of ANY Kind in non-HTTP Services.


But I’ve just added support for creating .NET Core IOC scopes in MQ Requests where it will create a new scope at the start of a new MqRequest where any call to the ResolveScoped APIs (already mentioned above) will resolve dependencies within that scope if defined.

A scope is only created for MQ Requests (and gRPC Requests), if you’re executing Services using alternative APIs you can start your own scope with IRequest.StartScope() ext method which will only assign and return a new scope when using BasicRequest (i.e. default IRequest impl for non-HTTP requests).

I don’t know if it will help in your case as I don’t see how you could access the per-request TenantId in your scoped registration, but at least any scoped dependencies will now be resolved within the scope created per MQ Request. If that doesn’t help resolve your issue you will need to look for an alternative, either try creating / managing the scopes yourself or not using scoped dependencies in non-HTTP Requests.

This change is available from the latest v5.8.1 that’s now on MyGet.

Thanks for your quick support, i will investigate the new options…

Not sure if it helps but we faced a similar problem trying to register our ITenant instance from the request and be able to resolve in our repositories. After a lot of trial and error, and to avoid having to refactor almost 1/2 million lines of code, we found a solution by using Autofac Lifetime Scope and creating a new scope, registering the user/tenant from inside the service method.

Obviously this requires boilerplate for each service method, so far from ideal, but allows us to proceed while we figure out a more workable solution.

In the example below we create a new scope from the injected ILifetimeScope using our extension WithAgencyScope which requires us to pass ITenant which is derived from the request.

public class CarerGroupPeopleChecksReportService : Service
{
	private readonly ILifetimeScope lifetimeScope;

	public CarerGroupPeopleChecksReportService(ILifetimeScope lifetimeScope)
	{
		this.lifetimeScope = lifetimeScope;
	}
	
	public async Task<IList<CarerGroupPeopleChecksReportRecord>> Post(
		CarerGroupPeopleChecksAgencyReportRequest request)
	{
		return await lifetimeScope.WithAgencyScope(request.ToAgencyUser(), async scope =>
		{
			var reportManager = scope.Resolve<ICarerGroupPeopleChecksReportManager>();

			var response = await reportManager.GetCarerGroupPeopleChecksReport(request);

			return response.Results;
		});
	}
}

Here’s the extensions. IAgencyUser inherits ITenant.

public static class AutofacExtensions
{
	public static async Task<T> WithAgencyScope<T>(
		this ILifetimeScope lifetimeScope,
		IAgencyUser agencyUser,
		Func<ILifetimeScope, Task<T>> agencyFn)
	{
		using (var scope = lifetimeScope.GetAgencyLifetimeScope(agencyUser))
		{
			return await agencyFn.Invoke(scope);
		}
	}
	
	public static async Task WithAgencyScope(
		this ILifetimeScope lifetimeScope,
		IAgencyUser agencyUser,
		Func<ILifetimeScope, Task> agencyFn)
	{
		using (var scope = lifetimeScope.GetAgencyLifetimeScope(agencyUser))
		{
			await agencyFn.Invoke(scope);
		}
	}

	public static ILifetimeScope GetAgencyLifetimeScope(
		this ILifetimeScope lifetimeScope,
		IAgencyUser agencyUser)
	{
		return lifetimeScope.BeginLifetimeScope(
			builder =>
			{
				builder.Register(context => new AgencyUser(agencyUser.AgencyKey, agencyUser.Username, agencyUser.TimeZone))
					.AsImplementedInterfaces()
					.SingleInstance();
			});
	}
}

Thanks! I hope that one day the servicestack IOC will be refactored so that you can use requests scopes outside http requests.