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)) { 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)) { _shellHost.GetScopeAsync(settings.Tenant).GetAwaiter().GetResult().UsingAsync(async 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. scope.ShellContext.AddDependentShell(await _shellHost.GetOrCreateShellContextAsync(_shellSettings)); // 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(); }).GetAwaiter().GetResult(); } // 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); CreateTenantScope(settings.Tenant).UsingAsync(async scope => { var service = scope.ServiceProvider.GetService <IOpenIdServerService>(); if (service == null) { return; } var configuration = await GetServerSettingsAsync(service); if (configuration == null) { return; } options.UseReferenceTokens = configuration.UseReferenceTokens; }).GetAwaiter().GetResult(); }
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 = shell.CreateHttpContext(); foreach (var scheduler in schedulers) { if (stoppingToken.IsCancellationRequested) { break; } var shellScope = await _shellHost.GetScopeAsync(shell.Settings); if (shellScope.ShellContext.Pipeline == null) { break; } await shellScope.UsingAsync(async scope => { var taskName = scheduler.Name; var task = scope.ServiceProvider.GetServices <IBackgroundTask>().GetTaskByName(taskName); if (task == null) { return; } 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) { var shellScope = recipeStep.RecipeDescriptor.RequireNewScope ? await _shellHost.GetScopeAsync(_shellSettings) : ShellScope.Current; await shellScope.UsingAsync(async scope => { var recipeStepHandlers = scope.ServiceProvider.GetServices <IRecipeStepHandler>(); var scriptingManager = scope.ServiceProvider.GetRequiredService <IScriptingManager>(); // Substitutes the script elements by their actual values EvaluateJsonTree(scriptingManager, recipeStep, recipeStep.Step); if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Executing recipe step '{RecipeName}'.", recipeStep.Name); } await _recipeEventHandlers.InvokeAsync((handler, recipeStep) => handler.RecipeStepExecutingAsync(recipeStep), recipeStep, _logger); foreach (var recipeStepHandler in recipeStepHandlers) { await recipeStepHandler.ExecuteAsync(recipeStep); } await _recipeEventHandlers.InvokeAsync((handler, recipeStep) => handler.RecipeStepExecutedAsync(recipeStep), recipeStep, _logger); if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Finished executing recipe step '{RecipeName}'.", recipeStep.Name); } }); }
public async Task <ContentItem> AssignGroupAsync(ContentItem profile, string groupContentItemId) { if (profile == null) { return(null); } using (var scope = await _shellHost.GetScopeAsync(_shellSettings)) { var contentManager = scope.ServiceProvider.GetRequiredService <IContentManager>(); profile.Alter <ProfileGroupedPart>(x => x.GroupContentItemId = groupContentItemId); profile.Apply(nameof(ProfileGroupedPart), profile.As <ProfileGroupedPart>()); ContentExtensions.Apply(profile, profile); await contentManager.UpdateAsync(profile); await contentManager.PublishAsync(profile); await _session.CommitAsync(); return(profile); } }
private async Task <IList <ContentItem> > CreateUpdateAsync(IList <Posting> postings) { var contentItems = new List <ContentItem>(); var shellScope = await _shellHost.GetScopeAsync(_shellSettings); await shellScope.UsingAsync(async scope => { var contentManager = scope.ServiceProvider.GetRequiredService <IContentManager>(); var postingContentItems = await GetAllAsync(); // Remove old postings await RemoveAsync(contentManager, postingContentItems.Where(x => !postings.Any(y => y.Id == x.As <LeverPostingPart>().LeverId)).ToList()); // Add/Update posings _logger.LogInformation($"Lever: Add/Update ({0}) content items", postings.Count); foreach (var posting in postings) { var contentItem = postingContentItems.SingleOrDefault(x => x.As <LeverPostingPart>().LeverId == posting.Id); // If not already exists create a new one if (contentItem == null) { contentItems.Add(await CreateAsync(contentManager, posting)); continue; } contentItems.Add(await UpdateAsync(contentManager, contentItem, posting)); } }); return(contentItems); }
public async Task PaymentSuccess(string tenantId, string tenantName, BillingPeriod billingPeriod, decimal amount, PaymentMethod paymentMethod, string planName) { //TODO: Should billing info be saved in default tenant only, in the tenant's db, or both ? // Retrieve settings for speficified tenant. var settingsList = await _shellSettingsManager.LoadSettingsAsync(); if (settingsList.Any()) { var settings = settingsList.SingleOrDefault(s => string.Equals(s.Name, tenantName, StringComparison.OrdinalIgnoreCase)); var shellScope = await _shellHost.GetScopeAsync(settings); await shellScope.UsingAsync(async scope => { //Check if billing history exists var tenantBillingRepo = scope.ServiceProvider.GetServices <ITenantBillingHistoryRepository>().FirstOrDefault(); var tenantBillingHistory = await tenantBillingRepo.GetTenantBillingDetailsByNameAsync(tenantName); if (tenantBillingHistory == null) { tenantBillingHistory = new TenantBillingDetails(tenantId, tenantName, planName); } if (tenantBillingHistory.IsNewPaymentMethod(paymentMethod)) { tenantBillingHistory.AddNewPaymentMethod(paymentMethod); } tenantBillingHistory.AddMonthlyBill(billingPeriod, PaymentStatus.Success, amount, paymentMethod.CreditCardInfo); await tenantBillingRepo.CreateAsync(tenantBillingHistory); }); } }
private async Task <bool> IsTokenValid(string token) { try { using (var scope = await _shellHost.GetScopeAsync(ShellHelper.DefaultShellName)) { var dataProtectionProvider = scope.ServiceProvider.GetRequiredService <IDataProtectionProvider>(); var dataProtector = dataProtectionProvider.CreateProtector("Tokens").ToTimeLimitedDataProtector(); var tokenValue = dataProtector.Unprotect(token, out var expiration); if (_clock.UtcNow < expiration.ToUniversalTime()) { if (_shellSettings["Secret"] == tokenValue) { return(true); } } } } catch (Exception ex) { _logger.LogError(ex, "Error in decrypting the token"); } return(false); }
public async Task RunRecipeAsync(IShellHost shellHost, string recipeName, string recipePath) { var shellScope = await shellHost.GetScopeAsync(TenantName); await shellScope.UsingAsync(async scope => { var shellFeaturesManager = scope.ServiceProvider.GetRequiredService <IShellFeaturesManager>(); var recipeHarvesters = scope.ServiceProvider.GetRequiredService <IEnumerable <IRecipeHarvester> >(); var recipeExecutor = scope.ServiceProvider.GetRequiredService <IRecipeExecutor>(); var recipeCollections = await Task.WhenAll( recipeHarvesters.Select(recipe => recipe.HarvestRecipesAsync())); var recipes = recipeCollections.SelectMany(recipeCollection => recipeCollection); var recipe = recipes .FirstOrDefault(recipe => recipe.RecipeFileInfo.Name == recipeName && recipe.BasePath == recipePath); var executionId = Guid.NewGuid().ToString("n"); await recipeExecutor.ExecuteAsync( executionId, recipe, new Dictionary <string, object>(), CancellationToken.None); }); }
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; } // Makes 'RequestServices' aware of the current 'ShellScope'. httpContext.UseShellScopeServices(); var shellScope = await _shellHost.GetScopeAsync(shellSettings); // Holds the 'ShellContext' for the full request. httpContext.Features.Set(new ShellContextFeature { ShellContext = shellScope.ShellContext, OriginalPath = httpContext.Request.Path, OriginalPathBase = httpContext.Request.PathBase }); await shellScope.UsingAsync(scope => _next.Invoke(httpContext)); } }
private ShellScope CreateTenantScope(string tenant) { // Optimization: if the specified name corresponds to the current tenant, use the current 'ShellScope'. if (string.IsNullOrEmpty(tenant) || string.Equals(tenant, _shellSettings.Name)) { return(ShellScope.Current); } return(_shellHost.GetScopeAsync(tenant).GetAwaiter().GetResult()); }
public async Task <ActionResult> IndexPOST(CustomSetupViewModel model) { if (!await IsValidRequest(model.Secret)) { return(BadRequest(S["Error with tenant setup link. Please contact support to issue a new link"])); } if (!IsModelValid(model)) { return(View(model)); } var setupContext = await CreateSetupContext(model.SiteName, model.SiteTimeZone); await _setupService.SetupAsync(setupContext); // Check if a component in the Setup failed if (setupContext.Errors.Any()) { foreach (var error in setupContext.Errors) { ModelState.AddModelError(error.Key, error.Value); } return(View(model)); } var shellSetting = await _shellSettingsManager.LoadSettingsAsync(_shellSettings.Name); await(await _shellHost.GetScopeAsync(shellSetting)).UsingAsync(async scope => { void reportError(string key, string message) { setupContext.Errors[key] = message; } // Invoke modules to react to the custom setup event var customsetupEventHandlers = scope.ServiceProvider.GetServices <ICustomTenantSetupEventHandler>(); var logger = scope.ServiceProvider.GetRequiredService <ILogger <CustomSetupController> >(); await customsetupEventHandlers.InvokeAsync(x => x.Setup(model.Email, model.Password, "CourseAdmin", reportError), logger); }); return(Redirect($"~/{_adminOptions.AdminUrlPrefix}")); }
private IServiceScope CreateTenantScope(string tenant) { // Optimization: if the specified name corresponds to the current tenant, use the // service provider injected via the constructor instead of using the host APIs. if (string.IsNullOrEmpty(tenant) || string.Equals(tenant, _shellSettings.Name, StringComparison.Ordinal)) { return(_serviceProvider.CreateScope()); } return(_shellHost.GetScopeAsync(tenant).GetAwaiter().GetResult()); }
public async Task ResetLuceneIndiciesAsync(IShellHost shellHost, string indexName) { var shellScope = await shellHost.GetScopeAsync(TenantName); await shellScope.UsingAsync(async scope => { var luceneIndexSettingsService = scope.ServiceProvider.GetRequiredService <LuceneIndexSettingsService>(); var luceneIndexingService = scope.ServiceProvider.GetRequiredService <LuceneIndexingService>(); var luceneIndexSettings = await luceneIndexSettingsService.GetSettingsAsync(indexName); luceneIndexingService.ResetIndex(indexName); await luceneIndexingService.ProcessContentItemsAsync(indexName); }); }
public async Task <string> Index() { var scope = await _shellHost.GetScopeAsync("Tenant1"); var result = 0; await scope.UsingAsync(async scope => { var siteService = scope.ServiceProvider.GetRequiredService <ISiteService>(); var site = await siteService.GetSiteSettingsAsync(); var mySettings = site.As <MySettings>(); result = mySettings.IntProperty; }); return($"My int property is: {result}"); }
public async Task <ContentItem> CreateAsync(IUser user) { using (var scope = await _shellHost.GetScopeAsync(_shellSettings)) { var contentManager = scope.ServiceProvider.GetRequiredService <IContentManager>(); var contentItem = await contentManager.NewAsync(Constants.ContentTypeName); var profile = contentItem.As <ProfilePart>(); profile.UserIdentifier = await _userManager.GetUserIdAsync(user); profile.Apply(); contentItem.DisplayText = user.UserName; contentItem.Apply(nameof(ProfilePart), profile); ContentExtensions.Apply(contentItem, contentItem); await contentManager.CreateAsync(contentItem); await contentManager.PublishAsync(contentItem); return(contentItem); } }
public async Task <ImmutableArray <ValidationResult> > ValidateSettingsAsync(OpenIdValidationSettings settings) { if (settings == null) { throw new ArgumentNullException(nameof(settings)); } var results = ImmutableArray.CreateBuilder <ValidationResult>(); if (!(settings.Authority == null ^ string.IsNullOrEmpty(settings.Tenant))) { results.Add(new ValidationResult(S["Either a tenant or an authority must be registered."], new[] { nameof(settings.Authority), nameof(settings.Tenant) })); } if (settings.Authority != null) { if (!settings.Authority.IsAbsoluteUri || !settings.Authority.IsWellFormedOriginalString()) { results.Add(new ValidationResult(S["The specified authority is not valid."], new[] { nameof(settings.Authority) })); } if (!string.IsNullOrEmpty(settings.Authority.Query) || !string.IsNullOrEmpty(settings.Authority.Fragment)) { results.Add(new ValidationResult(S["The authority cannot contain a query string or a fragment."], new[] { nameof(settings.Authority) })); } } if (!string.IsNullOrEmpty(settings.Tenant) && !string.IsNullOrEmpty(settings.Audience)) { results.Add(new ValidationResult(S["No audience can be set when using another tenant."], new[] { nameof(settings.Audience) })); } if (settings.Authority != null && string.IsNullOrEmpty(settings.Audience)) { results.Add(new ValidationResult(S["An audience must be set when configuring the authority."], new[] { nameof(settings.Audience) })); } if (!string.IsNullOrEmpty(settings.Audience) && settings.Audience.StartsWith(OpenIdConstants.Prefixes.Tenant, StringComparison.OrdinalIgnoreCase)) { results.Add(new ValidationResult(S["The audience cannot start with the special 'oct:' prefix."], new[] { nameof(settings.Audience) })); } // If a tenant was specified, ensure it is valid, that the OpenID server feature // was enabled and that at least a scope linked with the current tenant exists. if (!string.IsNullOrEmpty(settings.Tenant) && !string.Equals(settings.Tenant, _shellSettings.Name)) { if (!_shellHost.TryGetSettings(settings.Tenant, out var shellSettings)) { results.Add(new ValidationResult(S["The specified tenant is not valid."])); } else { var shellScope = await _shellHost.GetScopeAsync(shellSettings); await shellScope.UsingAsync(async scope => { var manager = scope.ServiceProvider.GetService <IOpenIdScopeManager>(); if (manager == null) { results.Add(new ValidationResult(S["The specified tenant is not valid."], new[] { nameof(settings.Tenant) })); } else { var resource = OpenIdConstants.Prefixes.Tenant + _shellSettings.Name; var scopes = await manager.FindByResourceAsync(resource); if (scopes.IsDefaultOrEmpty) { results.Add(new ValidationResult(S["No appropriate scope was found."], new[] { nameof(settings.Tenant) })); } } }); } } return(results.ToImmutable()); }
public async Task ProcessContentItemsAsync() { // TODO: Lock over the filesystem in case two instances get a command to rebuild the index concurrently. var allIndices = new Dictionary <string, int>(); // Find the lowest task id to process int lastTaskId = int.MaxValue; foreach (var indexName in _indexManager.List()) { var taskId = _indexingState.GetLastTaskId(indexName); lastTaskId = Math.Min(lastTaskId, taskId); allIndices.Add(indexName, taskId); } if (!allIndices.Any()) { return; } IndexingTask[] batch; do { // Create a scope for the content manager using (var scope = await _shellHost.GetScopeAsync(_shellSettings)) { // Load the next batch of tasks batch = (await _indexingTaskManager.GetIndexingTasksAsync(lastTaskId, BatchSize)).ToArray(); if (!batch.Any()) { break; } foreach (var task in batch) { var contentManager = scope.ServiceProvider.GetRequiredService <IContentManager>(); var indexHandlers = scope.ServiceProvider.GetServices <IContentItemIndexHandler>(); foreach (var index in allIndices) { // TODO: ignore if this index is not configured for the content type if (index.Value < task.Id) { _indexManager.DeleteDocuments(index.Key, new string[] { task.ContentItemId }); } } if (task.Type == IndexingTaskTypes.Update) { var contentItem = await contentManager.GetAsync(task.ContentItemId); if (contentItem == null) { continue; } var context = new BuildIndexContext(new DocumentIndex(task.ContentItemId), contentItem, new string[] { contentItem.ContentType }); // Update the document from the index if its lastIndexId is smaller than the current task id. await indexHandlers.InvokeAsync(x => x.BuildIndexAsync(context), Logger); foreach (var index in allIndices) { if (index.Value < task.Id) { _indexManager.StoreDocuments(index.Key, new DocumentIndex[] { context.DocumentIndex }); } } } } // Update task ids lastTaskId = batch.Last().Id; foreach (var index in allIndices) { if (index.Value < lastTaskId) { _indexingState.SetLastTaskId(index.Key, lastTaskId); } } _indexingState.Update(); } } while (batch.Length == BatchSize); }
public async Task <string> SetupInternalAsync(SetupContext context) { string executionId; if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Running setup for tenant '{TenantName}'.", context.ShellSettings.Name); } string[] hardcoded = { _applicationName, "SeedModules.Features", "OrchardCore.Scripting", "SeedModules.Recipes", }; context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty <string>()).Distinct().ToList(); context.ShellSettings.State = TenantState.Initializing; var shellSettings = new ShellSettings(context.ShellSettings); if (string.IsNullOrEmpty(shellSettings["DatabaseProvider"])) { shellSettings["DatabaseProvider"] = context.DatabaseProvider; shellSettings["ConnectionString"] = context.DatabaseConnectionString; shellSettings["TablePrefix"] = context.DatabaseTablePrefix; } if (String.IsNullOrWhiteSpace(shellSettings["DatabaseProvider"])) { throw new ArgumentException($"{nameof(context.DatabaseProvider)} is required"); } var shellDescriptor = new ShellDescriptor { Features = context.EnabledFeatures.Select(id => new ShellFeature { Id = id }).ToList() }; using (var shellContext = await _shellContextFactory.CreateDescribedContextAsync(shellSettings, shellDescriptor)) { await shellContext.CreateScope().UsingAsync(async scope => { IStore store; try { store = scope.ServiceProvider.GetRequiredService <IStore>(); // await store.InitializeAsync(scope.ServiceProvider); } catch (Exception e) { // 表已存在或数据库问题 _logger.LogError(e, "An error occurred while initializing the datastore."); context.Errors.Add("DatabaseProvider", T["An error occurred while initializing the datastore: {0}", e.Message]); return; } await scope .ServiceProvider .GetService <IShellDescriptorManager>() .UpdateShellDescriptorAsync(0, shellContext.Blueprint.Descriptor.Features, shellContext.Blueprint.Descriptor.Parameters); }); if (context.Errors.Any()) { return(null); } executionId = Guid.NewGuid().ToString("n"); var recipeExecutor = shellContext.ServiceProvider.GetRequiredService <IRecipeExecutor>(); await recipeExecutor.ExecuteAsync(executionId, context.Recipe, new { SiteName = context.SiteName, AdminUsername = context.AdminUsername, AdminEmail = context.AdminEmail, AdminPassword = context.AdminPassword, DatabaseProvider = context.DatabaseProvider, DatabaseConnectionString = context.DatabaseConnectionString, DatabaseTablePrefix = context.DatabaseTablePrefix }, _applicationLifetime.ApplicationStopping); } await(await _shellHost.GetScopeAsync(shellSettings)).UsingAsync(async scope => { void reportError(string key, string message) { context.Errors[key] = message; } // Invoke modules to react to the setup event var setupEventHandlers = scope.ServiceProvider.GetServices <ISetupEventHandler>(); var logger = scope.ServiceProvider.GetRequiredService <ILogger <SetupService> >(); await setupEventHandlers.InvokeAsync((handler, context) => handler.Setup( context.SiteName, context.AdminUsername, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, context.DatabaseConnectionString, context.DatabaseTablePrefix, context.SiteTimeZone, reportError ), context, logger); }); if (context.Errors.Any()) { return(executionId); } shellSettings.State = TenantState.Running; await _shellHost.UpdateShellSettingsAsync(shellSettings); return(executionId); }
// NB: Async void should be avoided; it should only be used for event handlers.Timer.Elapsed is an event handler.So, it's not necessarily wrong here. // c.f. http://stackoverflow.com/questions/25007670/using-async-await-inside-the-timer-elapsed-event-handler-within-a-windows-servic private async void DoWorkAsync(object group) { // DoWork needs to be re-entrant as Timer may call the callback before the previous callback has returned. // So, because a task may take longer than the period itself, DoWork needs to check if it's still running. var groupName = group as string ?? ""; // Because the execution flow has been suppressed before creating timers, here the 'HttpContext' // is always null and can be replaced by a new fake 'HttpContext' without overriding anything. _httpContextAccessor.HttpContext = new DefaultHttpContext(); foreach (var task in _tasks[groupName]) { var taskName = task.GetType().FullName; using (var scope = await _orchardHost.GetScopeAsync(_shellSettings)) { try { if (_states[task] != BackgroundTaskState.Idle) { return; } lock (_states) { // Ensure Terminate() was not called before if (_states[task] != BackgroundTaskState.Idle) { return; } _states[task] = BackgroundTaskState.Running; } if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation("Start processing background task '{BackgroundTaskName}'.", taskName); } await task.DoWorkAsync(scope.ServiceProvider, _applicationLifetime.ApplicationStopping); if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation("Finished processing background task '{BackgroundTaskName}'.", taskName); } } catch (Exception ex) { Logger.LogError(ex, "Error while processing background task '{BackgroundTaskName}'", taskName); } finally { lock (_states) { // Ensure Terminate() was not called during the task if (_states[task] != BackgroundTaskState.Stopped) { _states[task] = BackgroundTaskState.Idle; } } } } } }
/// <summary> /// Creates a standalone service scope that can be used to resolve local services. /// </summary> /// <param name="tenant">The tenant name related to the service scope to get.</param> public static Task <ShellScope> GetScopeAsync(this IShellHost shellHost, string tenant) { return(shellHost.GetScopeAsync(shellHost.GetSettings(tenant))); }
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 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); } } }
private async Task <string> SetupInternalAsync(SetupContext context) { string executionId; if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Running setup for tenant '{TenantName}'.", context.ShellSettings.Name); } // Features to enable for Setup string[] hardcoded = { _applicationName, "OrchardCore.Features", "OrchardCore.Scripting", "OrchardCore.Recipes" }; context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty <string>()).Distinct().ToList(); // Set shell state to "Initializing" so that subsequent HTTP requests are responded to with "Service Unavailable" while Orchard is setting up. context.ShellSettings.State = TenantState.Initializing; // Due to database collation we normalize the userId to lower invariant. // During setup there are no users so we do not need to check unicity. context.AdminUserId = _setupUserIdGenerator.GenerateUniqueId().ToLowerInvariant(); var shellSettings = new ShellSettings(context.ShellSettings); if (string.IsNullOrEmpty(shellSettings["DatabaseProvider"])) { shellSettings["DatabaseProvider"] = context.DatabaseProvider; shellSettings["ConnectionString"] = context.DatabaseConnectionString; shellSettings["TablePrefix"] = context.DatabaseTablePrefix; } if (String.IsNullOrWhiteSpace(shellSettings["DatabaseProvider"])) { throw new ArgumentException($"{nameof(context.DatabaseProvider)} is required"); } // Creating a standalone environment based on a "minimum shell descriptor". // In theory this environment can be used to resolve any normal components by interface, and those // components will exist entirely in isolation - no crossover between the safemode container currently in effect // It is used to initialize the database before the recipe is run. var shellDescriptor = new ShellDescriptor { Features = context.EnabledFeatures.Select(id => new ShellFeature { Id = id }).ToList() }; using (var shellContext = await _shellContextFactory.CreateDescribedContextAsync(shellSettings, shellDescriptor)) { await shellContext.CreateScope().UsingServiceScopeAsync(async scope => { IStore store; try { store = scope.ServiceProvider.GetRequiredService <IStore>(); } catch (Exception e) { // Tables already exist or database was not found // The issue is that the user creation needs the tables to be present, // if the user information is not valid, the next POST will try to recreate the // tables. The tables should be rolled back if one of the steps is invalid, // unless the recipe is executing? _logger.LogError(e, "An error occurred while initializing the datastore."); context.Errors.Add("DatabaseProvider", S["An error occurred while initializing the datastore: {0}", e.Message]); return; } // Create the "minimum shell descriptor" await scope .ServiceProvider .GetService <IShellDescriptorManager>() .UpdateShellDescriptorAsync(0, shellContext.Blueprint.Descriptor.Features, shellContext.Blueprint.Descriptor.Parameters); }); if (context.Errors.Any()) { return(null); } executionId = Guid.NewGuid().ToString("n"); var recipeExecutor = shellContext.ServiceProvider.GetRequiredService <IRecipeExecutor>(); await recipeExecutor.ExecuteAsync(executionId, context.Recipe, new { context.SiteName, context.AdminUsername, context.AdminUserId, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, context.DatabaseConnectionString, context.DatabaseTablePrefix, Properties = context.Properties }, _applicationLifetime.ApplicationStopping); } // Reloading the shell context as the recipe has probably updated its features await(await _shellHost.GetScopeAsync(shellSettings)).UsingAsync(async scope => { void reportError(string key, string message) { context.Errors[key] = message; } // Invoke modules to react to the setup event var setupEventHandlers = scope.ServiceProvider.GetServices <ISetupEventHandler>(); var logger = scope.ServiceProvider.GetRequiredService <ILogger <SetupService> >(); await setupEventHandlers.InvokeAsync((handler, context) => handler.Setup( context.SiteName, context.AdminUsername, context.AdminUserId, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, context.DatabaseConnectionString, context.DatabaseTablePrefix, context.SiteTimeZone, reportError, context.Properties ), context, logger); }); if (context.Errors.Any()) { return(executionId); } // Update the shell state shellSettings.State = TenantState.Running; await _shellHost.UpdateShellSettingsAsync(shellSettings); return(executionId); }
public async Task ProcessContentItemsAsync(string indexName = default) { // TODO: Lock over the filesystem in case two instances get a command to rebuild the index concurrently. var allIndices = new Dictionary <string, int>(); var lastTaskId = Int32.MaxValue; IEnumerable <LuceneIndexSettings> indexSettingsList = null; if (String.IsNullOrEmpty(indexName)) { indexSettingsList = await _luceneIndexSettingsService.GetSettingsAsync(); if (!indexSettingsList.Any()) { return; } // Find the lowest task id to process foreach (var indexSetting in indexSettingsList) { var taskId = _indexingState.GetLastTaskId(indexSetting.IndexName); lastTaskId = Math.Min(lastTaskId, taskId); allIndices.Add(indexSetting.IndexName, taskId); } } else { var settings = await _luceneIndexSettingsService.GetSettingsAsync(indexName); if (settings == null) { return; } indexSettingsList = new LuceneIndexSettings[1] { settings }.AsEnumerable(); var taskId = _indexingState.GetLastTaskId(indexName); lastTaskId = Math.Min(lastTaskId, taskId); allIndices.Add(indexName, taskId); } if (allIndices.Count == 0) { return; } var batch = Array.Empty <IndexingTask>(); do { // Create a scope for the content manager var shellScope = await _shellHost.GetScopeAsync(_shellSettings); await shellScope.UsingAsync(async scope => { // Load the next batch of tasks batch = (await _indexingTaskManager.GetIndexingTasksAsync(lastTaskId, BatchSize)).ToArray(); if (!batch.Any()) { return; } var contentManager = scope.ServiceProvider.GetRequiredService <IContentManager>(); var indexHandlers = scope.ServiceProvider.GetServices <IContentItemIndexHandler>(); // Pre-load all content items to prevent SELECT N+1 var updatedContentItemIds = batch .Where(x => x.Type == IndexingTaskTypes.Update) .Select(x => x.ContentItemId) .ToArray(); var allPublished = await contentManager.GetAsync(updatedContentItemIds); var allLatest = await contentManager.GetAsync(updatedContentItemIds, latest: true); // Group all DocumentIndex by index to batch update them var updatedDocumentsByIndex = new Dictionary <string, List <DocumentIndex> >(); foreach (var index in allIndices) { updatedDocumentsByIndex[index.Key] = new List <DocumentIndex>(); } if (indexName != null) { indexSettingsList = indexSettingsList.Where(x => x.IndexName == indexName); } var needLatest = indexSettingsList.FirstOrDefault(x => x.IndexLatest) != null; var needPublished = indexSettingsList.FirstOrDefault(x => !x.IndexLatest) != null; var settingsByIndex = indexSettingsList.ToDictionary(x => x.IndexName, x => x); foreach (var task in batch) { if (task.Type == IndexingTaskTypes.Update) { BuildIndexContext publishedIndexContext = null, latestIndexContext = null; if (needPublished) { var contentItem = await contentManager.GetAsync(task.ContentItemId); if (contentItem != null) { publishedIndexContext = new BuildIndexContext(new DocumentIndex(task.ContentItemId), contentItem, new string[] { contentItem.ContentType }); await indexHandlers.InvokeAsync(x => x.BuildIndexAsync(publishedIndexContext), _logger); } } if (needLatest) { var contentItem = await contentManager.GetAsync(task.ContentItemId, VersionOptions.Latest); if (contentItem != null) { latestIndexContext = new BuildIndexContext(new DocumentIndex(task.ContentItemId), contentItem, new string[] { contentItem.ContentType }); await indexHandlers.InvokeAsync(x => x.BuildIndexAsync(latestIndexContext), _logger); } } // Update the document from the index if its lastIndexId is smaller than the current task id. foreach (var index in allIndices) { if (index.Value >= task.Id || !settingsByIndex.TryGetValue(index.Key, out var settings)) { continue; } var context = !settings.IndexLatest ? publishedIndexContext : latestIndexContext; //We index only if we actually found a content item in the database if (context == null) { //TODO purge these content items from IndexingTask table continue; } var cultureAspect = await contentManager.PopulateAspectAsync <CultureAspect>(context.ContentItem); var culture = cultureAspect.HasCulture ? cultureAspect.Culture.Name : null; var ignoreIndexedCulture = settings.Culture == "any" ? false : culture != settings.Culture; // Ignore if the content item content type or culture is not indexed in this index if (!settings.IndexedContentTypes.Contains(context.ContentItem.ContentType) || ignoreIndexedCulture) { continue; } updatedDocumentsByIndex[index.Key].Add(context.DocumentIndex); } } } // Delete all the existing documents foreach (var index in updatedDocumentsByIndex) { var deletedDocuments = updatedDocumentsByIndex[index.Key].Select(x => x.ContentItemId); await _indexManager.DeleteDocumentsAsync(index.Key, deletedDocuments); } // Submits all the new documents to the index foreach (var index in updatedDocumentsByIndex) { await _indexManager.StoreDocumentsAsync(index.Key, updatedDocumentsByIndex[index.Key]); } // Update task ids lastTaskId = batch.Last().Id; foreach (var indexStatus in allIndices) { if (indexStatus.Value < lastTaskId) { _indexingState.SetLastTaskId(indexStatus.Key, lastTaskId); } } _indexingState.Update(); }, activateShell : false); } while (batch.Length == BatchSize); }
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 = shell.CreateHttpContext(); foreach (var scheduler in schedulers) { if (stoppingToken.IsCancellationRequested) { break; } var shellScope = await _shellHost.GetScopeAsync(shell.Settings); if (!_options.ShellWarmup && shellScope.ShellContext.Pipeline == null) { break; } var distributedLock = shellScope.ShellContext.ServiceProvider.GetRequiredService <IDistributedLock>(); // Try to acquire a lock before using the scope, so that a next process gets the last committed data. (var locker, var locked) = await distributedLock.TryAcquireBackgroundTaskLockAsync(scheduler.Settings); if (!locked) { _logger.LogInformation("Timeout to acquire a lock on background task '{TaskName}' on tenant '{TenantName}'.", scheduler.Name, tenant); return; } await using var acquiredLock = locker; await shellScope.UsingAsync(async scope => { var taskName = scheduler.Name; var task = scope.ServiceProvider.GetServices <IBackgroundTask>().GetTaskByName(taskName); if (task == null) { return; } var siteService = scope.ServiceProvider.GetService <ISiteService>(); if (siteService != null) { try { _httpContextAccessor.HttpContext.SetBaseUrl((await siteService.GetSiteSettingsAsync()).BaseUrl); } catch (Exception ex) when(!ex.IsFatal()) { _logger.LogError(ex, "Error while getting the base url from the site settings of the tenant '{TenantName}'.", tenant); } } var context = new BackgroundTaskEventContext(taskName, scope); var handlers = scope.ServiceProvider.GetServices <IBackgroundTaskEventHandler>(); await handlers.InvokeAsync((handler, context, token) => handler.ExecutingAsync(context, token), context, stoppingToken, _logger); 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 ex) when(!ex.IsFatal()) { _logger.LogError(ex, "Error while processing background task '{TaskName}' on tenant '{TenantName}'.", taskName, tenant); context.Exception = ex; await scope.HandleExceptionAsync(ex); } await handlers.InvokeAsync((handler, context, token) => handler.ExecutedAsync(context, token), context, stoppingToken, _logger); }); } }); }