Fixing templates rendering in background tasks

This commit is contained in:
Sebastien Ros
2013-12-13 11:06:10 -08:00
parent ca5334c016
commit 8c901ccc19
6 changed files with 54 additions and 216 deletions

View File

@@ -169,7 +169,6 @@
<Compile Include="Migrations.cs" />
<Compile Include="Services\ITemplateProcessor.cs" />
<Compile Include="Services\ITemplateService.cs" />
<Compile Include="Services\StubHttpContext.cs" />
<Compile Include="Services\TemplateProcessorImpl.cs" />
<Compile Include="Services\RazorTemplateProcessor.cs" />
<Compile Include="Services\TemplateBindingStrategy.cs" />

View File

@@ -6,7 +6,6 @@ using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.DisplayManagement;
using Orchard.DisplayManagement.Implementation;
using Orchard.Mvc;
using Orchard.Templates.Models;
namespace Orchard.Templates.Services {
@@ -17,7 +16,7 @@ namespace Orchard.Templates.Services {
private readonly IWorkContextAccessor _workContextAccessor;
private readonly IContentManager _contentManager;
private readonly IEnumerable<ITemplateProcessor> _processors;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly HttpContextBase _httpContextBase;
public DefaultTemplateService(
IShapeFactory shapeFactory,
@@ -25,13 +24,13 @@ namespace Orchard.Templates.Services {
IWorkContextAccessor workContextAccessor,
IContentManager contentManager,
IEnumerable<ITemplateProcessor> processors,
IHttpContextAccessor httpContextAccessor) {
HttpContextBase httpContextBase) {
_shapeFactory = shapeFactory;
_displayHelperFactory = displayHelperFactory;
_workContextAccessor = workContextAccessor;
_contentManager = contentManager;
_processors = processors;
_httpContextAccessor = httpContextAccessor;
_httpContextBase = httpContextBase;
}
public string ExecuteShape(string shapeType) {
@@ -40,16 +39,12 @@ namespace Orchard.Templates.Services {
public string ExecuteShape(string shapeType, INamedEnumerable<object> parameters) {
var shape = _shapeFactory.Create(shapeType, parameters);
var result = "";
ExecuteInHttpContext(httpContext => {
var viewContext = new ViewContext { HttpContext = httpContext };
viewContext.RouteData.DataTokens["IWorkContextAccessor"] = _workContextAccessor;
var display = _displayHelperFactory.CreateHelper(viewContext, new ViewDataContainer());
result = ((DisplayHelper)display).ShapeExecute(shape).ToString();
});
var viewContext = new ViewContext { HttpContext = _httpContextBase };
viewContext.RouteData.DataTokens["IWorkContextAccessor"] = _workContextAccessor;
var display = _displayHelperFactory.CreateHelper(viewContext, new ViewDataContainer());
return result;
return ((DisplayHelper)display).ShapeExecute(shape).ToString();
}
public string Execute<TModel>(string template, string name, string language, TModel model = default(TModel)) {
@@ -65,26 +60,6 @@ namespace Orchard.Templates.Services {
return _contentManager.Query<ShapePart>(versionOptions ?? VersionOptions.Published).List();
}
/// <summary>
/// Executes the action in an HttpContext, regardless of whether or not we're in a ThreadStaticScope or HttpContextScope.
/// </summary>
private void ExecuteInHttpContext(Action<HttpContextBase> action) {
var httpContext = _httpContextAccessor.Current();
if (httpContext != null) {
action(httpContext);
return;
}
// We're not using the ViewContext.HttpContext because that will be an EmptyHttpContext when we're on a background thread.
// EmptyHttpContext does not implement Items, which is necessary for Orchard when storing and accessing certain services such as IWorkContextAccessor.
httpContext = new StubHttpContext();
using (var scope = _workContextAccessor.CreateWorkContextScope(httpContext)) {
_httpContextAccessor.Set(httpContext);
action(httpContext);
}
}
private class ViewDataContainer : IViewDataContainer {
public ViewDataDictionary ViewData { get; set; }

View File

@@ -2,31 +2,26 @@
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;
using Orchard.Compilation.Razor;
using Orchard.ContentManagement;
using Orchard.DisplayManagement.Implementation;
using Orchard.Logging;
using Orchard.Mvc;
using Orchard.Settings;
using Orchard.Themes.Models;
namespace Orchard.Templates.Services {
using Orchard.Logging;namespace Orchard.Templates.Services {
public class RazorTemplateProcessor : TemplateProcessorImpl {
private readonly IRazorCompiler _compiler;
private readonly ISiteService _siteService;
private readonly IHttpContextAccessor _hca;
private readonly HttpContextBase _httpContextBase;
public override string Type {
get { return "Razor"; }
}
public RazorTemplateProcessor(IRazorCompiler compiler, ISiteService siteService, IHttpContextAccessor hca) {
public RazorTemplateProcessor(
IRazorCompiler compiler,
HttpContextBase httpContextBase) {
_compiler = compiler;
_siteService = siteService;
_hca = hca;
_httpContextBase = httpContextBase;
Logger = NullLogger.Instance;
}
@@ -48,24 +43,32 @@ namespace Orchard.Templates.Services {
private string ActivateAndRenderTemplate(IRazorTemplateBase obj, DisplayContext displayContext, string templateVirtualPath, object model) {
var buffer = new StringBuilder(1024);
using (var writer = new StringWriter(buffer)) {
var htmlWriter = new HtmlTextWriter(writer);
var httpContext = _hca.Current();
using (var htmlWriter = new HtmlTextWriter(writer)) {
// this should be done better - if no display context is provided we should fallback to current controller context somehow, if possible
if (displayContext != null) {
var shapeViewContext = new ViewContext(displayContext.ViewContext.Controller.ControllerContext, displayContext.ViewContext.View, displayContext.ViewContext.ViewData, displayContext.ViewContext.TempData, htmlWriter);
obj.WebPageContext = new WebPageContext(displayContext.ViewContext.HttpContext, obj as WebPageRenderingBase, model);
obj.ViewContext = shapeViewContext;
obj.ViewData = new ViewDataDictionary(displayContext.ViewDataContainer.ViewData) { Model = model };
}
else {
obj.ViewData = new ViewDataDictionary(model);
obj.WebPageContext = new WebPageContext(httpContext, obj as WebPageRenderingBase, model);
}
if (displayContext != null && displayContext.ViewContext.Controller != null) {
var shapeViewContext = new ViewContext(
displayContext.ViewContext.Controller.ControllerContext,
displayContext.ViewContext.View,
displayContext.ViewContext.ViewData,
displayContext.ViewContext.TempData,
htmlWriter
);
obj.VirtualPath = templateVirtualPath ?? "~/Themes/" + _siteService.GetSiteSettings().As<ThemeSiteSettingsPart>().CurrentThemeName;
obj.InitHelpers();
obj.Render(htmlWriter);
obj.WebPageContext = new WebPageContext(displayContext.ViewContext.HttpContext, obj as WebPageRenderingBase, model);
obj.ViewContext = shapeViewContext;
obj.ViewData = new ViewDataDictionary(displayContext.ViewDataContainer.ViewData) {Model = model};
obj.InitHelpers();
}
else {
obj.ViewData = new ViewDataDictionary(model);
obj.WebPageContext = new WebPageContext(_httpContextBase, obj as WebPageRenderingBase, model);
}
obj.VirtualPath = templateVirtualPath ?? "~/Themes/Orchard.Templates";
obj.Render(htmlWriter);
}
}
return buffer.ToString();

View File

@@ -1,101 +0,0 @@
using System.Collections;
using System.Collections.Specialized;
using System.Security.Principal;
using System.Web;
using System.Web.ClientServices;
using System.Web.Routing;
using System.Web.Security;
namespace Orchard.Templates.Services {
internal class StubHttpContext : HttpContextBase {
private IDictionary _items;
private HttpRequestBase _request;
private HttpSessionStateBase _session;
private IPrincipal _user;
public override IDictionary Items {
get { return _items ?? (_items = new Hashtable()); }
}
public override HttpRequestBase Request {
get { return _request ?? (_request = new StubHttpRequest(this)); }
}
public override HttpSessionStateBase Session {
get { return _session ?? (_session = new StubHttpSessionState()); }
}
public override IPrincipal User {
get { return _user ?? (_user = new ClientRolePrincipal(new FormsIdentity(new FormsAuthenticationTicket(FormsAuthentication.FormsCookieName, true, int.MaxValue)))); }
set { _user = value; }
}
public override IHttpHandler Handler { get; set; }
private class StubHttpRequest : HttpRequestBase {
private readonly HttpContextBase _httpContext;
private RequestContext _requestContext;
private NameValueCollection _queryString;
private NameValueCollection _headers;
public StubHttpRequest(HttpContextBase httpContext) {
_httpContext = httpContext;
}
public override RequestContext RequestContext {
get { return _requestContext ?? (_requestContext = new StubRequestContext(_httpContext)); }
set { _requestContext = value; }
}
public override NameValueCollection QueryString {
get { return _queryString ?? (_queryString = new NameValueCollection()); }
}
public override NameValueCollection Headers {
get { return _headers ?? (_headers = new NameValueCollection()); }
}
public override bool IsAuthenticated {
get { return true; }
}
public override string AppRelativeCurrentExecutionFilePath {
get { return "/"; }
}
}
private class StubRequestContext : RequestContext {
private HttpContextBase _httpContext;
public StubRequestContext(HttpContextBase httpContext) {
_httpContext = httpContext;
}
public override HttpContextBase HttpContext {
get { return _httpContext; }
set { _httpContext = value; }
}
}
private class StubHttpSessionState : HttpSessionStateBase {
private readonly Hashtable _stub = new Hashtable();
public override object this[int index] {
get { return _stub[index]; }
set { _stub[index] = value; }
}
public override object this[string index] {
get { return _stub[index]; }
set { _stub[index] = value; }
}
public override int Count {
get { return _stub.Count; }
}
public override void Clear() {
_stub.Clear();
}
}
}
}

View File

@@ -1,54 +1,33 @@
using System;
using System.Linq;
using System.Web;
using Orchard.Caching;
using Orchard.Compilation.Razor;
using Orchard.ContentManagement;
using Orchard.DisplayManagement.Descriptors;
using Orchard.DisplayManagement.Implementation;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
using Orchard.Mvc.Spooling;
using Orchard.Settings;
using Orchard.Themes.Models;
namespace Orchard.Templates.Services {
public class TemplateBindingStrategy : IShapeTableProvider {
private readonly IWorkContextAccessor _wca;
private readonly ITemplateService _templateService;
private readonly IRazorTemplateHolder _templateProvider;
public TemplateBindingStrategy(
IWorkContextAccessor wca,
ITemplateService templateService,
IRazorTemplateHolder templateProvider) {
_wca = wca;
_templateService = templateService;
_templateProvider = templateProvider;
}
public virtual Feature Feature { get; set; }
public void Discover(ShapeTableBuilder builder) {
EnsureWorkContext(() => BuildShapes(builder));
BuildShapes(builder);
}
private void BuildShapes(ShapeTableBuilder builder) {
var templateService = _wca.GetContext().Resolve<ITemplateService>();
var siteService = _wca.GetContext().Resolve<ISiteService>();
var extensionManager = _wca.GetContext().Resolve<IExtensionManager>();
var currentTheme = extensionManager.GetExtension(siteService.GetSiteSettings().As<ThemeSiteSettingsPart>().CurrentThemeName);
var themeFeature = currentTheme.Features.FirstOrDefault();
var hackedDescriptor = new FeatureDescriptor
{
Category = themeFeature.Category,
Dependencies = themeFeature.Dependencies,
Description = themeFeature.Description,
Extension = themeFeature.Extension,
Id = themeFeature.Id,
Name = themeFeature.Name,
Priority = int.MaxValue
};
var shapes = templateService.GetTemplates().Select(r =>
var shapes = _templateService.GetTemplates().Select(r =>
new {
r.Name,
r.Language,
@@ -61,7 +40,7 @@ namespace Orchard.Templates.Services {
var shapeType = AdjustName(record.Name);
builder.Describe(shapeType)
.From(new Feature { Descriptor = hackedDescriptor })
.From(new Feature { Descriptor = Feature.Descriptor })
.BoundAs("Template::" + shapeType,
descriptor => context => {
var template = _templateProvider.Get(record.Name);
@@ -72,13 +51,12 @@ namespace Orchard.Templates.Services {
private IHtmlString PerformInvoke(DisplayContext displayContext, string name, string type, string template)
{
var service = _wca.GetContext().Resolve<ITemplateService>();
var output = new HtmlStringWriter();
if (String.IsNullOrEmpty(template))
if (String.IsNullOrEmpty(template)) {
return null;
}
output.Write(CoerceHtmlString(service.Execute(template, name, type, displayContext, displayContext.Value)));
var output = new HtmlStringWriter();
output.Write(CoerceHtmlString(_templateService.Execute(template, name, type, displayContext, displayContext.Value)));
return output;
}
@@ -115,16 +93,5 @@ namespace Orchard.Templates.Services {
return invoke as IHtmlString ?? (invoke != null ? new HtmlString(invoke.ToString()) : null);
}
private void EnsureWorkContext(Action action) {
var workContext = _wca.GetContext();
if (workContext != null) {
action();
}
else {
using (_wca.CreateWorkContextScope()) {
action();
}
}
}
}
}

View File

@@ -1,11 +1,6 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.IO;
using System.Web.Mvc;
using System.Web.UI;
using System.Web.WebPages;
using System.Web.WebPages.Instrumentation;
namespace Orchard.Compilation.Razor {
public interface IRazorTemplateBase
@@ -22,13 +17,13 @@ namespace Orchard.Compilation.Razor {
public interface IRazorTemplateBase<TModel> : IRazorTemplateBase
{
TModel Model { get; }
ViewDataDictionary<TModel> ViewData { get; set; }
new TModel Model { get; }
new ViewDataDictionary<TModel> ViewData { get; set; }
}
public abstract class RazorTemplateBase<T> : Mvc.ViewEngines.Razor.WebViewPage<T>, IRazorTemplateBase<T> {
public WebPageContext WebPageContext { get; set; }
public void Render(TextWriter writer) {
public virtual void Render(TextWriter writer) {
PushContext(WebPageContext, writer);
OutputStack.Push(writer);
Execute();