/// <summary> /// Tries to creates a standalone service scope that can be used to resolve local services and /// replaces <see cref="HttpContext.RequestServices"/> with it. /// </summary> /// <param name="tenant">The tenant name related to the service scope to get.</param> /// <returns>An associated scope if the tenant name is valid, otherwise null.</returns> /// <remarks> /// Disposing the returned <see cref="IServiceScope"/> instance restores the previous state. /// </remarks> public static async Task <IServiceScope> TryGetScopeAsync(this IShellHost shellHost, string tenant) { if (!shellHost.TryGetSettings(tenant, out var settings)) { return(null); } return((await shellHost.GetScopeAndContextAsync(settings)).Scope); }
/// <summary> /// Tries to creates a standalone service scope that can be used to resolve local services and /// replaces <see cref="HttpContext.RequestServices"/> with it. /// </summary> /// <param name="tenant">The tenant name related to the scope and the shell to get.</param> /// <returns>Associated scope and shell if the tenant name is valid, otherwise null values.</returns> /// <remarks> /// Disposing the returned <see cref="IServiceScope"/> instance restores the previous state. /// </remarks> public static Task <(IServiceScope Scope, ShellContext ShellContext)> TryGetScopeAndContextAsync(this IShellHost shellHost, string tenant) { if (!shellHost.TryGetSettings(tenant, out var settings)) { IServiceScope scope = null; ShellContext shell = null; return(Task.FromResult((scope, shell))); } return(shellHost.GetScopeAndContextAsync(settings)); }
public void Configure(string name, OpenIddictValidationOptions options) { // Ignore validation handler instances that don't correspond to the instance managed by the OpenID module. if (!string.Equals(name, OpenIddictValidationDefaults.AuthenticationScheme, StringComparison.Ordinal)) { return; } var settings = GetValidationSettingsAsync().GetAwaiter().GetResult(); if (settings == null) { return; } // If the tokens are issued by an authorization server located in a separate tenant, // resolve the isolated data protection provider associated with the specified tenant. if (!string.IsNullOrEmpty(settings.Tenant) && !string.Equals(settings.Tenant, _shellSettings.Name, StringComparison.Ordinal)) { var shellSettings = _shellSettingsManager.GetSettings(settings.Tenant); var(scope, shellContext) = _shellHost.GetScopeAndContextAsync(shellSettings).GetAwaiter().GetResult(); using (scope) { // If the other tenant is released, ensure the current tenant is also restarted as it // relies on a data protection provider whose lifetime is managed by the other tenant. // To make sure the other tenant is not disposed before all the pending requests are // processed by the current tenant, a tenant dependency is manually added. shellContext.AddDependentShell(_shellHost.GetOrCreateShellContextAsync(_shellSettings).GetAwaiter().GetResult()); // Note: the data protection provider is always registered as a singleton and thus will // survive the current scope, which is mainly used to prevent the other tenant from being // released before we have a chance to declare the current tenant as a dependent tenant. options.DataProtectionProvider = scope.ServiceProvider.GetDataProtectionProvider(); } } // Don't allow the current tenant to choose the valid audiences, as this would // otherwise allow it to introspect tokens meant to be used with another tenant. options.Audiences.Add(OpenIdConstants.Prefixes.Tenant + _shellSettings.Name); }
public async Task Invoke(HttpContext httpContext) { // Ensure all ShellContext are loaded and available. await _shellHost.InitializeAsync(); var shellSettings = _runningShellTable.Match(httpContext); // We only serve the next request if the tenant has been resolved. if (shellSettings != null) { if (shellSettings.State == TenantState.Initializing) { httpContext.Response.Headers.Add(HeaderNames.RetryAfter, "10"); httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; await httpContext.Response.WriteAsync("The requested tenant is currently initializing."); return; } var hasPendingTasks = false; // We need to get a scope and the ShellContext that created it var(scope, shellContext) = await _shellHost.GetScopeAndContextAsync(shellSettings); using (scope) { // Register the shell context as a custom feature httpContext.Features.Set(shellContext); if (!shellContext.IsActivated) { var semaphore = _semaphores.GetOrAdd(shellSettings.Name, (name) => new SemaphoreSlim(1)); await semaphore.WaitAsync(); try { // The tenant gets activated here if (!shellContext.IsActivated) { using (var activatingScope = await _shellHost.GetScopeAsync(shellSettings)) { var tenantEvents = activatingScope.ServiceProvider.GetServices <IModularTenantEvents>(); foreach (var tenantEvent in tenantEvents) { await tenantEvent.ActivatingAsync(); } foreach (var tenantEvent in tenantEvents.Reverse()) { await tenantEvent.ActivatedAsync(); } } shellContext.IsActivated = true; } } finally { semaphore.Release(); _semaphores.TryRemove(shellSettings.Name, out semaphore); } } await _next.Invoke(httpContext); var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); hasPendingTasks = deferredTaskEngine?.HasPendingTasks ?? false; } // Create a new scope only if there are pending tasks if (hasPendingTasks) { using (var pendingScope = await _shellHost.GetScopeAsync(shellSettings)) { if (pendingScope != null) { var deferredTaskEngine = pendingScope.ServiceProvider.GetService <IDeferredTaskEngine>(); var context = new DeferredTaskContext(pendingScope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(context); } } } } }
private async Task RunAsync(IEnumerable <ShellContext> runningShells, CancellationToken stoppingToken) { await GetShellsToRun(runningShells).ForEachAsync(async shell => { var tenant = shell.Settings.Name; var schedulers = GetSchedulersToRun(tenant); _httpContextAccessor.HttpContext = ShellContextExtensions.CreateHttpContext(shell); foreach (var scheduler in schedulers) { if (stoppingToken.IsCancellationRequested) { break; } IServiceScope scope = null; ShellContext context = null; try { (scope, context) = await _shellHost.GetScopeAndContextAsync(shell.Settings); } catch (Exception e) { Logger.LogError(e, "Can't resolve a scope on tenant '{TenantName}'.", tenant); return; } using (scope) { if (scope == null || context.Pipeline == null) { break; } var taskName = scheduler.Name; var task = scope.ServiceProvider.GetServices <IBackgroundTask>().GetTaskByName(taskName); if (task == null) { continue; } try { Logger.LogInformation("Start processing background task '{TaskName}' on tenant '{TenantName}'.", taskName, tenant); scheduler.Run(); await task.DoWorkAsync(scope.ServiceProvider, stoppingToken); Logger.LogInformation("Finished processing background task '{TaskName}' on tenant '{TenantName}'.", taskName, tenant); } catch (Exception e) { Logger.LogError(e, "Error while processing background task '{TaskName}' on tenant '{TenantName}'.", taskName, tenant); } } } }); }
private async Task ExecuteStepAsync(RecipeExecutionContext recipeStep) { IServiceScope scope; ShellContext shellContext; IServiceProvider serviceProvider; if (recipeStep.RecipeDescriptor.RequireNewScope) { (scope, shellContext) = await _shellHost.GetScopeAndContextAsync(_shellSettings); serviceProvider = scope.ServiceProvider; } else { (scope, shellContext) = (null, null); serviceProvider = _httpContextAccessor.HttpContext.RequestServices; } using (scope) { if (recipeStep.RecipeDescriptor.RequireNewScope && !shellContext.IsActivated) { using (var activatingScope = shellContext.CreateScope()) { var tenantEvents = activatingScope.ServiceProvider.GetServices <IModularTenantEvents>(); foreach (var tenantEvent in tenantEvents) { await tenantEvent.ActivatingAsync(); } foreach (var tenantEvent in tenantEvents.Reverse()) { await tenantEvent.ActivatedAsync(); } } shellContext.IsActivated = true; } var recipeStepHandlers = serviceProvider.GetServices <IRecipeStepHandler>(); var scriptingManager = serviceProvider.GetRequiredService <IScriptingManager>(); scriptingManager.GlobalMethodProviders.Add(_environmentMethodProvider); // Substitutes the script elements by their actual values EvaluateScriptNodes(recipeStep, scriptingManager); foreach (var recipeStepHandler in recipeStepHandlers) { if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation("Executing recipe step '{RecipeName}'.", recipeStep.Name); } await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutingAsync(recipeStep), Logger); await recipeStepHandler.ExecuteAsync(recipeStep); await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutedAsync(recipeStep), Logger); if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation("Finished executing recipe step '{RecipeName}'.", recipeStep.Name); } } } // E.g if we run migrations defined in a recipe. if (!recipeStep.RecipeDescriptor.RequireNewScope) { return; } // The recipe execution might have invalidated the shell by enabling new features, // so the deferred tasks need to run on an updated shell context if necessary. using (var localScope = await _shellHost.GetScopeAsync(_shellSettings)) { var deferredTaskEngine = localScope.ServiceProvider.GetService <IDeferredTaskEngine>(); // The recipe might have added some deferred tasks to process if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(localScope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } }
/// <summary> /// Creates a standalone service scope that can be used to resolve local services and /// replaces <see cref="HttpContext.RequestServices"/> with it. /// </summary> /// <param name="tenant">The tenant name related to the scope and the shell to get.</param> /// <remarks> /// Disposing the returned <see cref="IServiceScope"/> instance restores the previous state. /// </remarks> public static Task <(IServiceScope Scope, ShellContext ShellContext)> GetScopeAndContextAsync(this IShellHost shellHost, string tenant) { return(shellHost.GetScopeAndContextAsync(shellHost.GetSettings(tenant))); }
/// <summary> /// Creates a standalone service scope that can be used to resolve local services and /// replaces <see cref="HttpContext.RequestServices"/> with it. /// </summary> /// <param name="tenant">The tenant name related to the service scope to get.</param> /// <remarks> /// Disposing the returned <see cref="IServiceScope"/> instance restores the previous state. /// </remarks> public static async Task <IServiceScope> GetScopeAsync(this IShellHost shellHost, string tenant) { return((await shellHost.GetScopeAndContextAsync(shellHost.GetSettings(tenant))).Scope); }