public async Task Invoke(HttpContext httpContext) { // Ensure all ShellContext are loaded and available. _orchardHost.Initialize(); var shellSetting = _runningShellTable.Match(httpContext); // Register the shell settings as a custom feature. httpContext.Features[typeof(ShellSettings)] = shellSetting; // We only serve the next request if the tenant has been resolved. if (shellSetting != null) { ShellContext shellContext = _orchardHost.GetOrCreateShellContext(shellSetting); using (var scope = shellContext.CreateServiceScope()) { httpContext.RequestServices = scope.ServiceProvider; if (!shellContext.IsActivated) { lock (shellSetting) { // The tenant gets activated here if (!shellContext.IsActivated) { var eventBus = scope.ServiceProvider.GetService<IEventBus>(); eventBus.NotifyAsync<IOrchardShellEvents>(x => x.ActivatingAsync()).Wait(); eventBus.NotifyAsync<IOrchardShellEvents>(x => x.ActivatedAsync()).Wait(); shellContext.IsActivated = true; } } } await _next.Invoke(httpContext); } using (var scope = shellContext.CreateServiceScope()) { var deferredTaskEngine = scope.ServiceProvider.GetService<IDeferredTaskEngine>(); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var context = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(context); } } } }
public async Task ExecuteTasksAsync(DeferredTaskContext context) { for (var i = 0; i < _deferredTaskState.Tasks.Count; i++) { var task = _deferredTaskState.Tasks[i]; try { await task(context); } catch (Exception e) { _logger.LogError("An error occured while processing a deferred task: {0}", e); } } _deferredTaskState.Tasks.Clear(); }
public async Task ExecuteTasksAsync(DeferredTaskContext context) { for(var i=0; i < _deferredTaskState.Tasks.Count; i++) { var task = _deferredTaskState.Tasks[i]; try { await task(context); } catch(Exception e) { _logger.LogError("An error occured while processing a deferred task: {0}", e); } } _deferredTaskState.Tasks.Clear(); }
public async Task Invoke(HttpContext httpContext) { await _next.Invoke(httpContext); // Register the shell settings as a custom feature. var shellSettings = httpContext.Features[typeof(ShellSettings)] as ShellSettings; // We only serve the next request if the tenant has been resolved. if (shellSettings != null) { ShellContext shellContext = _orchardHost.GetOrCreateShellContext(shellSettings); using (var scope = shellContext.CreateServiceScope()) { var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var context = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(context); } } } }
public async Task<string> SetupInternalAsync(SetupContext context) { string executionId; if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Running setup for tenant '{0}'.", _shellSettings.Name); } // Features to enable for Setup string[] hardcoded = { "Orchard.Hosting", // shortcut for built-in features "Orchard.Modules", "Orchard.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. _shellSettings.State = TenantState.Initializing; var shellSettings = new ShellSettings(_shellSettings); if (string.IsNullOrEmpty(shellSettings.DatabaseProvider)) { shellSettings.DatabaseProvider = context.DatabaseProvider; shellSettings.ConnectionString = context.DatabaseConnectionString; shellSettings.TablePrefix = context.DatabaseTablePrefix; } // 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(name => new ShellFeature { Name = name }).ToList() }; using (var shellContext = _shellContextFactory.CreateDescribedContext(shellSettings, shellDescriptor)) { using (var scope = shellContext.CreateServiceScope()) { var store = scope.ServiceProvider.GetRequiredService<IStore>(); try { await store.InitializeAsync(); } catch { // 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 rollbacked if one of the steps is invalid, // unless the recipe is executing? } // Create the "minimum shell descriptor" await scope .ServiceProvider .GetService<IShellDescriptorManager>() .UpdateShellDescriptorAsync(0, shellContext.Blueprint.Descriptor.Features, shellContext.Blueprint.Descriptor.Parameters); // Apply all migrations for the newly initialized tenant var dataMigrationManager = scope.ServiceProvider.GetService<IDataMigrationManager>(); await dataMigrationManager.UpdateAllFeaturesAsync(); var deferredTaskEngine = scope.ServiceProvider.GetService<IDeferredTaskEngine>(); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } _orchardHost.UpdateShellSettings(shellSettings); executionId = Guid.NewGuid().ToString("n"); // Create a new scope for the recipe thread to prevent race issues with other scoped // services from the request. using (var scope = shellContext.CreateServiceScope()) { var recipeExecutor = scope.ServiceProvider.GetService<IRecipeExecutor>(); // Right now we run the recipe in the same thread, later use polling from the setup screen // to query the current execution. //await Task.Run(async () => //{ await recipeExecutor.ExecuteAsync(executionId, context.Recipe); //}); } } // Reloading the shell context as the recipe has probably updated its features using (var shellContext = _orchardHost.CreateShellContext(shellSettings)) { using (var scope = shellContext.CreateServiceScope()) { // Apply all migrations for the newly initialized tenant var dataMigrationManager = scope.ServiceProvider.GetService<IDataMigrationManager>(); await dataMigrationManager.UpdateAllFeaturesAsync(); bool hasErrors = false; Action<string, string> reportError = (key, message) => { hasErrors = true; context.Errors[key] = message; }; // Invoke modules to react to the setup event var eventBus = scope.ServiceProvider.GetService<IEventBus>(); await eventBus.NotifyAsync<ISetupEventHandler>(x => x.Setup( context.SiteName, context.AdminUsername, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, context.DatabaseConnectionString, context.DatabaseTablePrefix, reportError )); if (hasErrors) { // TODO: check why the tables creation is not reverted var session = scope.ServiceProvider.GetService<YesSql.Core.Services.ISession>(); session.Cancel(); return executionId; } var deferredTaskEngine = scope.ServiceProvider.GetService<IDeferredTaskEngine>(); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } // Update the shell state shellSettings.State = TenantState.Running; _orchardHost.UpdateShellSettings(shellSettings); } return executionId; }
public async Task<string> ExecuteAsync(string executionId, RecipeDescriptor recipeDescriptor) { await _eventBus.NotifyAsync<IRecipeEventHandler>(x => x.RecipeExecutingAsync(executionId, recipeDescriptor)); try { var fileInfo = _fileSystem.GetFileInfo(recipeDescriptor.Location); var parsersForFileExtension = _recipeOptions .RecipeFileExtensions .Where(rfx => Path.GetExtension(rfx.Key) == Path.GetExtension(fileInfo.PhysicalPath)); using (var stream = fileInfo.CreateReadStream()) { RecipeResult result = new RecipeResult { ExecutionId = executionId }; List<RecipeStepResult> stepResults = new List<RecipeStepResult>(); foreach (var parserForFileExtension in parsersForFileExtension) { var recipeParser = _recipeParsers.First(x => x.GetType() == parserForFileExtension.Value); await recipeParser.ProcessRecipeAsync(stream, (recipe, recipeStep) => { // TODO, create Result prior to run stepResults.Add(new RecipeStepResult { ExecutionId = executionId, RecipeName = recipeDescriptor.Name, StepId = recipeStep.Id, StepName = recipeStep.Name }); return Task.CompletedTask; }); } result.Steps = stepResults; _session.Save(result); } using (var stream = fileInfo.CreateReadStream()) { foreach (var parserForFileExtension in parsersForFileExtension) { var recipeParser = _recipeParsers.First(x => x.GetType() == parserForFileExtension.Value); await recipeParser.ProcessRecipeAsync(stream, async (recipe, recipeStep) => { var shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings); using (var scope = shellContext.CreateServiceScope()) { if (!shellContext.IsActivated) { var eventBus = scope.ServiceProvider.GetService<IEventBus>(); await eventBus.NotifyAsync<IOrchardShellEvents>(x => x.ActivatingAsync()); await eventBus.NotifyAsync<IOrchardShellEvents>(x => x.ActivatedAsync()); shellContext.IsActivated = true; } var recipeStepExecutor = scope.ServiceProvider.GetRequiredService<IRecipeStepExecutor>(); if (_applicationLifetime.ApplicationStopping.IsCancellationRequested) { throw new OrchardException(T["Recipe cancelled, application is restarting"]); } await recipeStepExecutor.ExecuteAsync(executionId, recipeStep); } // 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. shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings); using (var scope = shellContext.CreateServiceScope()) { var deferredTaskEngine = scope.ServiceProvider.GetService<IDeferredTaskEngine>(); // The recipe might have added some deferred tasks to process if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } }); } } await _eventBus.NotifyAsync<IRecipeEventHandler>(x => x.RecipeExecutedAsync(executionId, recipeDescriptor)); return executionId; } catch (Exception) { await _eventBus.NotifyAsync<IRecipeEventHandler>(x => x.ExecutionFailedAsync(executionId, recipeDescriptor)); throw; } }
public async Task<string> SetupInternalAsync(SetupContext context) { string executionId; if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Running setup for tenant '{0}'.", _shellSettings.Name); } // Features to enable for Setup string[] hardcoded = { "Orchard.Hosting" // shortcut for built-in features }; 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. _shellSettings.State = TenantState.Initializing; var shellSettings = new ShellSettings(_shellSettings); if (string.IsNullOrEmpty(shellSettings.DatabaseProvider)) { shellSettings.DatabaseProvider = context.DatabaseProvider; shellSettings.ConnectionString = context.DatabaseConnectionString; shellSettings.TablePrefix = context.DatabaseTablePrefix; } // 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. using (var shellContext = _orchardHost.CreateShellContext(shellSettings)) { using (var scope = shellContext.CreateServiceScope()) { executionId = CreateTenantData(context, shellContext); var store = scope.ServiceProvider.GetRequiredService<IStore>(); await store.InitializeAsync(); // Create the "minimum shell descriptor" await scope .ServiceProvider .GetService<IShellDescriptorManager>() .UpdateShellDescriptorAsync( 0, shellContext.Blueprint.Descriptor.Features, shellContext.Blueprint.Descriptor.Parameters); // Apply all migrations for the newly initialized tenant var dataMigrationManager = scope.ServiceProvider.GetService<IDataMigrationManager>(); await dataMigrationManager.UpdateAllFeaturesAsync(); var deferredTaskEngine = scope.ServiceProvider.GetService<IDeferredTaskEngine>(); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } // Invoke modules to react to the setup event var eventBus = scope.ServiceProvider.GetService<IEventBus>(); await eventBus.NotifyAsync<ISetupEventHandler>(x => x.Setup( context.SiteName, context.AdminUsername, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, context.DatabaseConnectionString, context.DatabaseTablePrefix) ); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } } shellSettings.State = TenantState.Running; _orchardHost.UpdateShellSettings(shellSettings); return executionId; }