#5305: Fixing the issue where httpcontext would be resolved from the wrong lifetime scope container in certain cases.

This changeset fixes a number of issues:
- Fixes the Autofac.Core.DependencyResolutionException: A delegate registered to create instances of 'System.Web.HttpContextBase' returned null error in VS 2015.
- Fixes indexing of layout part. The issue was that elements being rendered from a background task context and an element template uses an HTML helper, that would cause an exception since HttpContext.Current would normally be null.
- Replaces the need for injecting HttpContextBase with injecting IHttpContextAccessor (actually one should never inject HttpContextBase anymore - use IHttpContextAccessor instead).
This commit is contained in:
Sipke Schoorstra
2015-05-27 16:36:51 +02:00
parent 6183c8783e
commit ce630f980c
18 changed files with 197 additions and 61 deletions

View File

@@ -1,4 +1,5 @@
using System.Web;
using Autofac;
using Orchard.Mvc;
namespace Orchard.Tests.Stubs {
@@ -16,6 +17,10 @@ namespace Orchard.Tests.Stubs {
return _httpContext;
}
public HttpContextBase CreateContext(ILifetimeScope lifetimeScope) {
return _httpContext;
}
public void Set(HttpContextBase httpContext) {
_httpContext = httpContext;
}

View File

@@ -13,7 +13,7 @@ namespace Orchard.Layouts.Handlers {
private readonly IContentPartDisplay _contentPartDisplay;
private readonly IShapeDisplay _shapeDisplay;
private readonly ILayoutSerializer _serializer;
private readonly ISignals _signals;
private readonly IStaticHttpContextScopeFactory _staticHttpContextScopeFactory;
public LayoutPartHandler(
IRepository<LayoutPartRecord> repository,
@@ -22,14 +22,14 @@ namespace Orchard.Layouts.Handlers {
IContentPartDisplay contentPartDisplay,
IShapeDisplay shapeDisplay,
ILayoutSerializer serializer,
ISignals signals) {
IStaticHttpContextScopeFactory staticHttpContextScopeFactory) {
_layoutManager = layoutManager;
_contentManager = contentManager;
_contentPartDisplay = contentPartDisplay;
_shapeDisplay = shapeDisplay;
_serializer = serializer;
_signals = signals;
_staticHttpContextScopeFactory = staticHttpContextScopeFactory;
Filters.Add(StorageFilter.For(repository));
OnPublished<LayoutPart>(UpdateTemplateClients);
@@ -38,13 +38,22 @@ namespace Orchard.Layouts.Handlers {
private void IndexLayout(IndexContentContext context, LayoutPart part) {
var layoutShape = _contentPartDisplay.BuildDisplay(part);
var layoutHtml = _shapeDisplay.Display(layoutShape);
var layoutHtml = RenderShape(layoutShape);
context.DocumentIndex
.Add("body", layoutHtml).RemoveTags().Analyze()
.Add("format", "html").Store();
}
/// <summary>
/// This method of rendering is safe even in background tasks.
/// </summary>
private string RenderShape(dynamic shape) {
using (_staticHttpContextScopeFactory.CreateStaticScope()) {
return _shapeDisplay.Display(shape);
}
}
private void UpdateTemplateClients(PublishContentContext context, LayoutPart part) {
UpdateTemplateClients(part);
}

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using System.Web.WebPages;
@@ -15,7 +14,6 @@ namespace Orchard.Templates.Services {
[OrchardFeature("Orchard.Templates.Razor")]
public class RazorTemplateProcessor : TemplateProcessorImpl {
private readonly IRazorCompiler _compiler;
private readonly HttpContextBase _httpContextBase;
private readonly IWorkContextAccessor _wca;
public override string Type {
@@ -24,11 +22,9 @@ namespace Orchard.Templates.Services {
public RazorTemplateProcessor(
IRazorCompiler compiler,
HttpContextBase httpContextBase,
IWorkContextAccessor wca) {
_compiler = compiler;
_httpContextBase = httpContextBase;
_wca = wca;
Logger = NullLogger.Instance;
}
@@ -41,7 +37,7 @@ namespace Orchard.Templates.Services {
public override string Process(string template, string name, DisplayContext context = null, dynamic model = null) {
if (String.IsNullOrEmpty(template))
return string.Empty;
return String.Empty;
var compiledTemplate = _compiler.CompileRazor(template, name, new Dictionary<string, object>());
var result = ActivateAndRenderTemplate(compiledTemplate, context, null, model);
@@ -73,9 +69,10 @@ namespace Orchard.Templates.Services {
// Setup a fake view context in order to support razor syntax inside of HTML attributes,
// for instance: <a href="@WorkContext.CurrentSite.BaseUrl">Homepage</a>.
var viewData = new ViewDataDictionary(model);
var httpContext = _wca.GetContext().HttpContext;
obj.ViewContext = new ViewContext(
new ControllerContext(
_httpContextBase.Request.RequestContext,
httpContext.Request.RequestContext,
new StubController()),
new StubView(),
viewData,
@@ -83,7 +80,7 @@ namespace Orchard.Templates.Services {
htmlWriter);
obj.ViewData = viewData;
obj.WebPageContext = new WebPageContext(_httpContextBase, obj as WebPageRenderingBase, model);
obj.WebPageContext = new WebPageContext(httpContext, obj as WebPageRenderingBase, model);
obj.WorkContext = _wca.GetContext();
}

View File

@@ -179,8 +179,8 @@ namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy {
private ControllerContext CreateControllerContext() {
var controller = new StubController();
var httpContext = _workContextAccessor.GetContext().Resolve<HttpContextBase>();
var requestContext = _workContextAccessor.GetContext().Resolve<RequestContext>();
var httpContext = _workContextAccessor.GetContext().HttpContext;
var requestContext = httpContext.Request.RequestContext;
var routeData = requestContext.RouteData;
routeData.DataTokens["IWorkContextAccessor"] = _workContextAccessor;

View File

@@ -1,8 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.DisplayManagement.Implementation;
using Orchard.DisplayManagement.Shapes;
@@ -10,28 +8,24 @@ namespace Orchard.DisplayManagement {
public class ShapeDisplay : IShapeDisplay {
private readonly IDisplayHelperFactory _displayHelperFactory;
private readonly IWorkContextAccessor _workContextAccessor;
private readonly HttpContextBase _httpContextBase;
private readonly RequestContext _requestContext;
public ShapeDisplay(
IDisplayHelperFactory displayHelperFactory,
IWorkContextAccessor workContextAccessor,
HttpContextBase httpContextBase,
RequestContext requestContext) {
IWorkContextAccessor workContextAccessor) {
_displayHelperFactory = displayHelperFactory;
_workContextAccessor = workContextAccessor;
_httpContextBase = httpContextBase;
_requestContext = requestContext;
}
public string Display(Shape shape) {
return Display((object) shape);
return Display((object)shape);
}
public string Display(object shape) {
var workContext = _workContextAccessor.GetContext();
var httpContext = workContext.HttpContext;
var viewContext = new ViewContext {
HttpContext = _httpContextBase,
RequestContext = _requestContext
HttpContext = httpContext,
RequestContext = httpContext.Request.RequestContext
};
viewContext.RouteData.DataTokens["IWorkContextAccessor"] = _workContextAccessor;
var display = _displayHelperFactory.CreateHelper(viewContext, new ViewDataContainer());

View File

@@ -63,7 +63,7 @@ namespace Orchard.Environment {
builder.RegisterType<AppDomainAssemblyNameResolver>().As<IAssemblyNameResolver>().SingleInstance();
builder.RegisterType<GacAssemblyNameResolver>().As<IAssemblyNameResolver>().SingleInstance();
builder.RegisterType<OrchardFrameworkAssemblyNameResolver>().As<IAssemblyNameResolver>().SingleInstance();
builder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().InstancePerDependency();
builder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
builder.RegisterType<ViewsBackgroundCompilation>().As<IViewsBackgroundCompilation>().SingleInstance();
builder.RegisterType<DefaultExceptionPolicy>().As<IExceptionPolicy>().SingleInstance();
builder.RegisterType<DefaultCriticalErrorProvider>().As<ICriticalErrorProvider>().SingleInstance();

View File

@@ -60,11 +60,14 @@ namespace Orchard.Environment {
return CreateWorkContextScope(httpContext);
var workLifetime = _lifetimeScope.BeginLifetimeScope("work");
httpContext = _httpContextAccessor.CreateContext(workLifetime);
workLifetime.Resolve<WorkContextProperty<HttpContextBase>>().Value = httpContext;
var events = workLifetime.Resolve<IEnumerable<IWorkContextEvents>>();
events.Invoke(e => e.Started(), NullLogger.Instance);
return new ThreadStaticScopeImplementation(
httpContext,
events,
workLifetime,
EnsureThreadStaticContexts(),
@@ -113,8 +116,9 @@ namespace Orchard.Environment {
readonly WorkContext _workContext;
readonly Action _disposer;
public ThreadStaticScopeImplementation(IEnumerable<IWorkContextEvents> events, ILifetimeScope lifetimeScope, ConcurrentDictionary<object, WorkContext> contexts, object workContextKey) {
public ThreadStaticScopeImplementation(HttpContextBase httpContext, IEnumerable<IWorkContextEvents> events, ILifetimeScope lifetimeScope, ConcurrentDictionary<object, WorkContext> contexts, object workContextKey) {
_workContext = lifetimeScope.Resolve<WorkContext>();
httpContext.Items[workContextKey] = _workContext;
contexts.AddOrUpdate(workContextKey, _workContext, (a, b) => _workContext);
_disposer = () => {
@@ -123,6 +127,11 @@ namespace Orchard.Environment {
WorkContext removedContext;
contexts.TryRemove(workContextKey, out removedContext);
lifetimeScope.Dispose();
var staticHttpContext = httpContext as IDisposable;
if(staticHttpContext != null)
staticHttpContext.Dispose();
};
}

View File

@@ -0,0 +1,15 @@
using System;
namespace Orchard {
/// <summary>
/// A factory class that creates an <see cref="StaticHttpContextScope"/> instance and initializes the HttpContext.Current property with that instance until the scope is disposed of.
/// This is useful when rendering views from a background thread, as some Html Helpers access HttpContext.Current directly, thus preventing a NullReferenceException.
/// </summary>
public interface IStaticHttpContextScopeFactory : IDependency {
/// <summary>
/// Creates a disposable static HttpContext scope. This is safe to use even if there is an actual HttpContext.Current instance.
/// </summary>
/// <returns></returns>
IDisposable CreateStaticScope();
}
}

View File

@@ -1,9 +0,0 @@
using System.Web;
namespace Orchard.Mvc.Extensions {
public static class HttpContextBaseExtensions {
public static bool IsBackgroundContext(this HttpContextBase httpContextBase) {
return httpContextBase == null || httpContextBase is MvcModule.HttpContextPlaceholder;
}
}
}

View File

@@ -0,0 +1,13 @@
using System.Web;
namespace Orchard.Mvc.Extensions {
public static class HttpContextExtensions {
public static bool IsBackgroundContext(this HttpContextBase httpContext) {
return httpContext == null || httpContext is MvcModule.HttpContextPlaceholder;
}
public static bool IsBackgroundContext(this HttpContext httpContext) {
return httpContext == null || httpContext.Items.Contains(StaticHttpContextScopeFactory.IsBackgroundHttpContextKey);
}
}
}

View File

@@ -1,18 +1,39 @@
using System.Web;
using System;
using System.Collections.Concurrent;
using System.Web;
using Autofac;
using Orchard.Mvc.Extensions;
using Orchard.Settings;
namespace Orchard.Mvc {
public class HttpContextAccessor : IHttpContextAccessor {
private readonly IComponentContext _context;
readonly object _contextKey = new object();
public HttpContextAccessor(IComponentContext context) {
_context = context;
}
[ThreadStatic]
static ConcurrentDictionary<object, HttpContextBase> _threadStaticContexts;
public HttpContextBase Current() {
// TODO: HttpContextBase is not registred in the "shell" lifetime scope, so resolving it will cause an exception.
if (!HttpContext.Current.IsBackgroundContext())
return new HttpContextWrapper(HttpContext.Current);
return HttpContext.Current != null ? new HttpContextWrapper(HttpContext.Current) : null;
return GetContext();
}
public HttpContextBase CreateContext(ILifetimeScope lifetimeScope) {
return new MvcModule.HttpContextPlaceholder(_threadStaticContexts, _contextKey, () => {
return lifetimeScope.Resolve<ISiteService>().GetSiteSettings().BaseUrl;
});
}
private HttpContextBase GetContext() {
HttpContextBase context;
return ThreadStaticContexts.TryGetValue(_contextKey, out context) ? context : null;
}
static ConcurrentDictionary<object, HttpContextBase> ThreadStaticContexts {
get {
return _threadStaticContexts ?? (_threadStaticContexts = new ConcurrentDictionary<object, HttpContextBase>());
}
}
}
}

View File

@@ -1,7 +1,10 @@
using System.Web;
using System;
using System.Web;
using Autofac;
namespace Orchard.Mvc {
public interface IHttpContextAccessor {
HttpContextBase Current();
HttpContextBase CreateContext(ILifetimeScope lifetimeScope);
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
@@ -9,6 +10,7 @@ using System.Web.Instrumentation;
using System.Web.Mvc;
using System.Web.Routing;
using Autofac;
using Orchard.Mvc.Extensions;
using Orchard.Mvc.Routes;
using Orchard.Settings;
@@ -28,7 +30,7 @@ namespace Orchard.Mvc {
return false;
try {
// The "Request" property throws at application startup on IIS integrated pipeline mode
// The "Request" property throws at application startup on IIS integrated pipeline mode.
var req = HttpContext.Current.Request;
}
catch (Exception) {
@@ -50,7 +52,7 @@ namespace Orchard.Mvc {
// which requires activating the Site content item, which in turn requires a UrlHelper, which in turn requires a RequestContext,
// thus preventing a StackOverflowException.
var baseUrl = new Func<string>(() => siteService.GetSiteSettings().BaseUrl);
var httpContextBase = new HttpContextPlaceholder(baseUrl);
var httpContextBase = context.Resolve<IHttpContextAccessor>().Current();
context.Resolve<IWorkContextAccessor>().CreateWorkContextScope(httpContextBase);
return httpContextBase;
}
@@ -58,7 +60,7 @@ namespace Orchard.Mvc {
static RequestContext RequestContextFactory(IComponentContext context) {
var httpContextAccessor = context.Resolve<IHttpContextAccessor>();
var httpContext = httpContextAccessor.Current();
if (httpContext != null) {
if (!httpContext.IsBackgroundContext()) {
var mvcHandler = httpContext.Handler as MvcHandler;
if (mvcHandler != null) {
@@ -85,16 +87,23 @@ namespace Orchard.Mvc {
/// <summary>
/// Standin context for background tasks.
/// </summary>
public class HttpContextPlaceholder : HttpContextBase {
public class HttpContextPlaceholder : HttpContextBase, IDisposable {
private readonly Lazy<string> _baseUrl;
private readonly IDictionary _items = new Dictionary<object, object>();
readonly Action _disposer;
public HttpContextPlaceholder(Func<string> baseUrl) {
public HttpContextPlaceholder(ConcurrentDictionary<object, HttpContextBase> contexts, object contextKey, Func<string> baseUrl) {
_baseUrl = new Lazy<string>(baseUrl);
contexts.AddOrUpdate(contextKey, this, (a, b) => this);
_disposer = () => {
HttpContextBase removedContext;
contexts.TryRemove(contextKey, out removedContext);
};
}
public override HttpRequestBase Request {
get { return new HttpRequestPlaceholder(new Uri(_baseUrl.Value)); }
get { return new HttpRequestPlaceholder(this, new Uri(_baseUrl.Value)); }
}
public override IHttpHandler Handler { get; set; }
@@ -103,6 +112,10 @@ namespace Orchard.Mvc {
get { return new HttpResponsePlaceholder(); }
}
public override HttpSessionStateBase Session {
get { return null; }
}
public override IDictionary Items {
get { return _items; }
}
@@ -118,6 +131,10 @@ namespace Orchard.Mvc {
public override object GetService(Type serviceType) {
return null;
}
public void Dispose() {
_disposer();
}
}
public class HttpResponsePlaceholder : HttpResponseBase {
@@ -136,9 +153,12 @@ namespace Orchard.Mvc {
/// standin context for background tasks.
/// </summary>
public class HttpRequestPlaceholder : HttpRequestBase {
private readonly HttpContextBase _httpContext;
private readonly Uri _uri;
private RequestContext _requestContext;
public HttpRequestPlaceholder(Uri uri) {
public HttpRequestPlaceholder(HttpContextBase httpContext, Uri uri) {
_httpContext = httpContext;
_uri = uri;
}
@@ -215,6 +235,16 @@ namespace Orchard.Mvc {
return new HttpBrowserCapabilitiesPlaceholder();
}
}
public override RequestContext RequestContext {
get {
if (_requestContext == null) {
_requestContext = new RequestContext(_httpContext, new RouteData());
}
return _requestContext;
}
set { _requestContext = value; }
}
}
public class HttpBrowserCapabilitiesPlaceholder : HttpBrowserCapabilitiesBase {

View File

@@ -149,6 +149,8 @@
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="StaticHttpContextScope.cs" />
<Compile Include="StaticHttpContextScopeFactory.cs" />
<Compile Include="Caching\DefaultCacheContextAccessor.cs" />
<Compile Include="Caching\DefaultParallelCacheContext.cs" />
<Compile Include="Caching\ICacheContextAccessor.cs" />
@@ -317,7 +319,7 @@
<Compile Include="Mvc\DataAnnotations\LocalizedModelValidatorProvider.cs" />
<Compile Include="Mvc\DataAnnotations\LocalizedRequiredAttribute.cs" />
<Compile Include="Mvc\Extensions\RouteExtension.cs" />
<Compile Include="Mvc\Extensions\HttpContextBaseExtensions.cs" />
<Compile Include="Mvc\Extensions\HttpContextExtensions.cs" />
<Compile Include="Mvc\FormValueRequiredAttribute.cs" />
<Compile Include="Mvc\HttpContextAccessor.cs" />
<Compile Include="Mvc\HttpContextWorkContext.cs" />
@@ -647,6 +649,7 @@
<Compile Include="WebApi\Filters\OrchardApiActionFilterDispatcher.cs" />
<Compile Include="WebApi\Routes\IHttpRouteProvider.cs" />
<Compile Include="WebApi\Routes\StandardExtensionHttpRouteProvider.cs" />
<Compile Include="IStaticHttpContextScopeFactory.cs" />
<Compile Include="WorkContextExtensions.cs" />
<Compile Include="Mvc\ViewEngines\Razor\RazorCompilationEventsShim.cs" />
<Compile Include="Mvc\ViewEngines\Razor\RazorViewEngineProvider.cs" />

View File

@@ -0,0 +1,17 @@
using System;
using System.Web;
namespace Orchard {
public class StaticHttpContextScope : IDisposable {
private readonly HttpContext _previousHttpContext;
public StaticHttpContextScope(HttpContext stub) {
_previousHttpContext = HttpContext.Current;
HttpContext.Current = stub;
}
public void Dispose() {
HttpContext.Current = _previousHttpContext;
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.IO;
using System.Web;
using Orchard.Settings;
namespace Orchard {
public class StaticHttpContextScopeFactory : IStaticHttpContextScopeFactory {
private readonly Func<ISiteService> _siteService;
public StaticHttpContextScopeFactory(Func<ISiteService> siteService) {
_siteService = siteService;
}
public const string IsBackgroundHttpContextKey = "IsBackgroundHttpContext";
public IDisposable CreateStaticScope() {
// If there already is a current HttpContext, use that one as the stub.
if(HttpContext.Current != null)
return new StaticHttpContextScope(HttpContext.Current);
// We're in a background task (or some other static context like the console),
// so create a stub context so that Html Helpers can still be executed when rendering shapes in background tasks
// (sadly enought some Html Helpers access HttpContext.Current directly).
var url = _siteService().GetSiteSettings().BaseUrl;
var stub = new HttpContext(new HttpRequest("", url, ""), new HttpResponse(new StringWriter()));
stub.Items[IsBackgroundHttpContextKey] = true;
return new StaticHttpContextScope(stub);
}
}
}

View File

@@ -15,17 +15,15 @@ namespace Orchard.Tasks {
private readonly IEnumerable<IBackgroundTask> _tasks;
private readonly ITransactionManager _transactionManager;
private readonly string _shellName;
private readonly IContentManager _contentManager;
public BackgroundService(
IEnumerable<IBackgroundTask> tasks,
ITransactionManager transactionManager,
ShellSettings shellSettings,
IContentManager contentManager) {
ShellSettings shellSettings) {
_tasks = tasks;
_transactionManager = transactionManager;
_shellName = shellSettings.Name;
_contentManager = contentManager;
Logger = NullLogger.Instance;
}

View File

@@ -41,7 +41,7 @@ namespace Orchard.Tasks {
}
void Elapsed(object sender, ElapsedEventArgs e) {
// current implementation disallows re-entrancy
// Current implementation disallows re-entrancy.
if (!System.Threading.Monitor.TryEnter(_timer))
return;
@@ -60,7 +60,7 @@ namespace Orchard.Tasks {
public void DoWork() {
using (var scope = _workContextAccessor.CreateWorkContextScope()) {
// resolve the manager and invoke it
// Resolve the manager and invoke it.
var manager = scope.Resolve<IBackgroundService>();
manager.Sweep();
}