public async Task Run(Func <IWorkContextScope, Task> process, string shellName) { var shellContext = _hostContainer.Resolve <IOrchardHost>().GetShellContext(new ShellSettings { Name = shellName }); // We need a single HCA, and thus the same HttpContext throughout this scope to carry the work context. Especially // important for async code, see: https://github.com/OrchardCMS/Orchard/issues/4338 // Using InstancePerLifetimeScope() inside the shell ContinerBuilder still yields multiple instantiations. var hca = new AppHostHttpContextAccessor(); using (var scope = shellContext.LifetimeScope.BeginLifetimeScope( RunScopeTag, builder => { builder.RegisterInstance(hca).As <IHttpContextAccessor>(); // Needed so it gets the AppHostHttpContextAccessor instance registered in Run(). builder.RegisterType <WorkContextAccessor>().As <IWorkContextAccessor>().SingleInstance(); builder.RegisterType <ProcessingEngineTaskAddedHandler>() .As <IProcessingEngine>() .As <IProcessingEngineTaskAddedHandler>() .SingleInstance(); builder.RegisterType <ShellChangeHandler>() .As <IShellChangeHandler>() .As <IEventHandler>() .Named <IEventHandler>(typeof(IShellDescriptorManagerEventHandler).Name) .Named <IEventHandler>(typeof(IShellSettingsManagerEventHandler).Name) .SingleInstance(); })) { HttpContextBase httpContext; // Resolving will fail if it's just the setup shell. TryResolve() would still cause this exception. try { // Will return the stub from Orchard.Mvc.MvcModule. There are some direct resolve calls to HttpContextBase in // Orchard but it doesn't matter here (otherwise it does: https://github.com/OrchardCMS/Orchard/issues/4607) as the // point is to keep the WorkContext in HttpContext.Items the same throughout this scope (unless a new WCS is // started somewhere). httpContext = scope.Resolve <HttpContextBase>(); } catch (DependencyResolutionException ex) { // Unfortunately the only way to identify the specific exception is by its message. if (ex.Message.StartsWith("No scope with a Tag matching 'work' is visible from the scope in which the instance")) { httpContext = new AppHostHttpContextAccessor.HttpContextPlaceholder(); } else { throw; } } hca.Set(httpContext); var logger = scope.Resolve <ILoggerService>(); var orchardHost = scope.Resolve <IOrchardHost>(); var previousTenantState = TenantState.Invalid; orchardHost.BeginRequest(); try { using (var workContext = scope.CreateWorkContextScope(httpContext)) { try { await process(workContext); previousTenantState = workContext.Resolve <ShellSettings>().State; } catch (Exception ex) { if (ex.IsFatal()) { throw; } logger.Error(ex, "Error when executing work inside Orchard App Host."); throw; } } } finally { var shellSettingManagerEventHandler = _hostContainer.Resolve <IShellSettingsManagerEventHandler>(); var shellSettings = scope.Resolve <IShellSettingsManager>().LoadSettings().SingleOrDefault(settings => settings.Name == shellName); // This means that setup just run. During setup the shell-tracking services are not registered. if (previousTenantState == TenantState.Invalid && shellSettings != null && shellSettings.State == TenantState.Running) { shellSettingManagerEventHandler.Saved(shellSettings); } else { // Due to possibly await-ed calls in the process we keep track of everything that is stored in ContextState<T> normally. // This is needed because ContextState<T> looses state on thread switch. // Here we re-apply every change so the necessary services will get to know everything. var shellChangeHandler = scope.Resolve <IShellChangeHandler>(); var shellDescriptorManagerEventHandler = _hostContainer.Resolve <IShellDescriptorManagerEventHandler>(); foreach (var changedShellDescriptor in shellChangeHandler.GetChangedShellDescriptors()) { shellDescriptorManagerEventHandler.Changed(changedShellDescriptor.ShellDescriptor, changedShellDescriptor.TenantName); } foreach (var changedShellSettings in shellChangeHandler.GetChangedShellSettings()) { shellSettingManagerEventHandler.Saved(changedShellSettings); } var processingEngine = _hostContainer.Resolve <IProcessingEngine>(); var processingEngineTaskAddedHandler = scope.Resolve <IProcessingEngineTaskAddedHandler>(); foreach (var task in processingEngineTaskAddedHandler.GetAddedTasks()) { processingEngine.AddTask(task.ShellSettings, task.ShellDescriptor, task.MessageName, task.Parameters); } } // Either this or running tasks through IProcessingEngine and restarting shells manually. // EndRequest() can have unwanted side effects in the future. orchardHost.EndRequest(); } } }
public async Task Run(Func <IWorkContextScope, Task> process, string shellName) { var shellContext = _hostContainer.Resolve <IOrchardHost>().GetShellContext(new ShellSettings { Name = shellName }); // We need a single HCA, and thus the same HttpContext throughout this scope to carry the work context. // Especially important for async code, see: https://github.com/OrchardCMS/Orchard/issues/4338 // Using InstancePerLifetimeScope() inside the shell ContinerBuilder still yields multiple instantiations. var hca = new AppHostHttpContextAccessor(); using (var scope = shellContext.LifetimeScope.BeginLifetimeScope( RunScopeTag, builder => { builder.RegisterInstance(hca).As <IHttpContextAccessor>(); // Needed so it gets the AppHostHttpContextAccessor instance registered in Run(). builder.RegisterType <WorkContextAccessor>().As <IWorkContextAccessor>().SingleInstance(); builder.RegisterType <ProcessingEngineTaskAddedHandler>() .As <IProcessingEngine>() .As <IProcessingEngineTaskAddedHandler>() .SingleInstance(); builder.RegisterType <ShellChangeHandler>() .As <IShellChangeHandler>() .As <IEventHandler>() .Named <IEventHandler>(typeof(IShellDescriptorManagerEventHandler).Name) .Named <IEventHandler>(typeof(IShellSettingsManagerEventHandler).Name) .SingleInstance(); })) { var httpContext = new MvcModule.HttpContextPlaceholder(() => "http://localhost"); hca.Set(httpContext); var logger = scope.Resolve <ILoggerService>(); var orchardHost = scope.Resolve <IOrchardHost>(); var previousTenantState = TenantState.Invalid; orchardHost.BeginRequest(); try { using (var workContext = scope.CreateWorkContextScope(httpContext)) { try { await process(workContext); previousTenantState = workContext.Resolve <ShellSettings>().State; } catch (Exception ex) { if (ex.IsFatal()) { throw; } logger.Error(ex, "Error when executing work inside Orchard App Host."); throw; } } } finally { var shellSettingManagerEventHandler = _hostContainer.Resolve <IShellSettingsManagerEventHandler>(); var shellSettings = scope .Resolve <IShellSettingsManager>() .LoadSettings() .SingleOrDefault(settings => settings.Name == shellName); // This means that setup just run. During setup the shell-tracking services are not registered. if (previousTenantState == TenantState.Invalid && shellSettings != null && shellSettings.State == TenantState.Running) { shellSettingManagerEventHandler.Saved(shellSettings); } else { // Due to possibly await-ed calls in the process we keep track of everything that is stored in // ContextState<T> normally. // This is needed because ContextState<T> looses state on thread switch. // Here we re-apply every change so the necessary services will get to know everything. var shellChangeHandler = scope.Resolve <IShellChangeHandler>(); var shellDescriptorManagerEventHandler = _hostContainer.Resolve <IShellDescriptorManagerEventHandler>(); foreach (var changedShellDescriptor in shellChangeHandler.GetChangedShellDescriptors()) { shellDescriptorManagerEventHandler .Changed(changedShellDescriptor.ShellDescriptor, changedShellDescriptor.TenantName); } foreach (var changedShellSettings in shellChangeHandler.GetChangedShellSettings()) { shellSettingManagerEventHandler.Saved(changedShellSettings); } var processingEngine = _hostContainer.Resolve <IProcessingEngine>(); var processingEngineTaskAddedHandler = scope.Resolve <IProcessingEngineTaskAddedHandler>(); foreach (var task in processingEngineTaskAddedHandler.GetAddedTasks()) { processingEngine.AddTask(task.ShellSettings, task.ShellDescriptor, task.MessageName, task.Parameters); } } // Either this or running tasks through IProcessingEngine and restarting shells manually. // EndRequest() can have unwanted side effects in the future. orchardHost.EndRequest(); } } }