private void Execute(Entry entry) { // Force reloading extensions if there were extensions installed // See https://github.com/BoyingCMS/Boying/issues/1294 if (entry.MessageName == "IRecipeSchedulerEventHandler.ExecuteWork") { var ctx = _BoyingHost().GetShellContext(entry.ShellSettings); } var shellContext = _shellContextFactory.CreateDescribedContext(entry.ShellSettings, entry.ShellDescriptor); using (shellContext.LifetimeScope) { using (var standaloneEnvironment = shellContext.LifetimeScope.CreateWorkContextScope()) { ITransactionManager transactionManager; if (!standaloneEnvironment.TryResolve(out transactionManager)) { transactionManager = null; } try { var eventBus = standaloneEnvironment.Resolve <IEventBus>(); Logger.Information("Executing event {0} in process {1} for shell {2}", entry.MessageName, entry.ProcessId, entry.ShellSettings.Name); eventBus.Notify(entry.MessageName, entry.EventData); } catch { // any database changes in this using(env) scope are invalidated if (transactionManager != null) { transactionManager.Cancel(); } throw; } } } }
async Task <ConcurrentDictionary <string, IFeatureEventContext> > InvokeFeatureEventsAsync( IList <IShellFeature> features, Func <IFeatureEventContext, IFeatureEventHandler, Task <ConcurrentDictionary <string, IFeatureEventContext> > > handler, Func <IFeatureEventContext, Task <ConcurrentDictionary <string, IFeatureEventContext> > > noHandler) { // Holds the results of all our event execution contexts var contexts = new ConcurrentDictionary <string, IFeatureEventContext>(); // Build a list of all unique features we are enabling / disabling var uniqueFeatures = new ConcurrentDictionary <string, IShellFeature>(); foreach (var feature in features) { // The feature may also reference dependencies so ensure we also // add any dependencies for the features to our temporary shell descriptors if (feature.FeatureDependencies.Any()) { foreach (var dependency in feature.FeatureDependencies) { if (!uniqueFeatures.ContainsKey(dependency.ModuleId)) { uniqueFeatures.TryAdd(dependency.ModuleId, dependency); } } } if (!uniqueFeatures.ContainsKey(feature.ModuleId)) { uniqueFeatures.TryAdd(feature.ModuleId, feature); } } // Ensure minimum features are always available within the temporary shell descriptor // We may depend upon services from the required features within the features we are enabling / disabling var minimumShellDescriptor = _shellContextFactory.MinimumShellDescriptor(); // Add features and dependencies we are enabling / disabling to our minimum shell descriptor foreach (var feature in uniqueFeatures.Values) { minimumShellDescriptor.Modules.Add(new ShellModule(feature.ModuleId, feature.Version)); } // Create a new shell context with features and all dependencies we need to enable / disable using (var shellContext = _shellContextFactory.CreateDescribedContext(_shellSettings, minimumShellDescriptor)) { using (var scope = shellContext.ServiceProvider.CreateScope()) { var handlers = scope.ServiceProvider.GetServices <IFeatureEventHandler>(); var handlersList = handlers.ToList(); // Iterate through each feature we wish to invoke // Use the event handlers if available else just add to contexts collection foreach (var feature in features) { // Only invoke non required features if (feature.IsRequired) { continue; } // Context that will be passed around var context = new FeatureEventContext() { Feature = feature, ServiceProvider = scope.ServiceProvider, Logger = _logger }; // Get event handler for feature we are invoking var featureHandler = handlersList.FirstOrDefault(h => h.ModuleId == feature.ModuleId); // Get response from responsible func var handlerContexts = featureHandler != null ? await handler(context, featureHandler) : await noHandler(context); // Compile results from delegates if (handlerContexts != null) { foreach (var handlerContext in handlerContexts) { contexts.AddOrUpdate(feature.ModuleId, handlerContext.Value, (k, v) => { foreach (var error in handlerContext.Value.Errors) { v.Errors.Add(error.Key, error.Value); } return(v); }); } } // Log any errors if (context.Errors.Count > 0) { foreach (var error in context.Errors) { _logger.LogCritical(error.Value, $"An error occurred whilst invoking within {this.GetType().FullName}"); } } } } } return(contexts); }
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.Cms", "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); var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } 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()) { 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) { 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); }