mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00

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).
199 lines
11 KiB
C#
199 lines
11 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Web;
|
|
using System.Web.Mvc;
|
|
using System.Web.Mvc.Html;
|
|
using System.Web.Routing;
|
|
using Orchard.Caching;
|
|
using Orchard.DisplayManagement.Implementation;
|
|
using Orchard.Environment;
|
|
using Orchard.Environment.Descriptor.Models;
|
|
using Orchard.Environment.Extensions;
|
|
using Orchard.Environment.Extensions.Models;
|
|
using Orchard.FileSystems.VirtualPath;
|
|
using Orchard.Logging;
|
|
using Orchard.Mvc.ViewEngines.ThemeAwareness;
|
|
using Orchard.Utility.Extensions;
|
|
|
|
namespace Orchard.DisplayManagement.Descriptors.ShapeTemplateStrategy {
|
|
public class ShapeTemplateBindingStrategy : IShapeTableProvider {
|
|
private readonly ShellDescriptor _shellDescriptor;
|
|
private readonly IExtensionManager _extensionManager;
|
|
private readonly ICacheManager _cacheManager;
|
|
private readonly IVirtualPathMonitor _virtualPathMonitor;
|
|
private readonly IVirtualPathProvider _virtualPathProvider;
|
|
private readonly IEnumerable<IShapeTemplateHarvester> _harvesters;
|
|
private readonly IEnumerable<IShapeTemplateViewEngine> _shapeTemplateViewEngines;
|
|
private readonly IParallelCacheContext _parallelCacheContext;
|
|
private readonly Work<ILayoutAwareViewEngine> _viewEngine;
|
|
private readonly IWorkContextAccessor _workContextAccessor;
|
|
|
|
public ShapeTemplateBindingStrategy(
|
|
IEnumerable<IShapeTemplateHarvester> harvesters,
|
|
ShellDescriptor shellDescriptor,
|
|
IExtensionManager extensionManager,
|
|
ICacheManager cacheManager,
|
|
IVirtualPathMonitor virtualPathMonitor,
|
|
IVirtualPathProvider virtualPathProvider,
|
|
IEnumerable<IShapeTemplateViewEngine> shapeTemplateViewEngines,
|
|
IParallelCacheContext parallelCacheContext,
|
|
Work<ILayoutAwareViewEngine> viewEngine,
|
|
IWorkContextAccessor workContextAccessor) {
|
|
|
|
_harvesters = harvesters;
|
|
_shellDescriptor = shellDescriptor;
|
|
_extensionManager = extensionManager;
|
|
_cacheManager = cacheManager;
|
|
_virtualPathMonitor = virtualPathMonitor;
|
|
_virtualPathProvider = virtualPathProvider;
|
|
_shapeTemplateViewEngines = shapeTemplateViewEngines;
|
|
_parallelCacheContext = parallelCacheContext;
|
|
_viewEngine = viewEngine;
|
|
_workContextAccessor = workContextAccessor;
|
|
Logger = NullLogger.Instance;
|
|
}
|
|
|
|
public ILogger Logger { get; set; }
|
|
public bool DisableMonitoring { get; set; }
|
|
|
|
private static IEnumerable<ExtensionDescriptor> Once(IEnumerable<FeatureDescriptor> featureDescriptors) {
|
|
var once = new ConcurrentDictionary<string, object>();
|
|
return featureDescriptors.Select(fd => fd.Extension).Where(ed => once.TryAdd(ed.Id, null)).ToList();
|
|
}
|
|
|
|
public void Discover(ShapeTableBuilder builder) {
|
|
Logger.Information("Start discovering shapes");
|
|
|
|
var harvesterInfos = _harvesters.Select(harvester => new { harvester, subPaths = harvester.SubPaths() });
|
|
|
|
var availableFeatures = _extensionManager.AvailableFeatures();
|
|
var activeFeatures = availableFeatures.Where(FeatureIsEnabled);
|
|
var activeExtensions = Once(activeFeatures);
|
|
|
|
var hits = _parallelCacheContext.RunInParallel(activeExtensions, extensionDescriptor => {
|
|
Logger.Information("Start discovering candidate views filenames");
|
|
var pathContexts = harvesterInfos.SelectMany(harvesterInfo => harvesterInfo.subPaths.Select(subPath => {
|
|
var basePath = Path.Combine(extensionDescriptor.Location, extensionDescriptor.Id).Replace(Path.DirectorySeparatorChar, '/');
|
|
var virtualPath = Path.Combine(basePath, subPath).Replace(Path.DirectorySeparatorChar, '/');
|
|
var fileNames = _cacheManager.Get(virtualPath, ctx => {
|
|
if (!_virtualPathProvider.DirectoryExists(virtualPath))
|
|
return new List<string>();
|
|
|
|
if (!DisableMonitoring) {
|
|
Logger.Debug("Monitoring virtual path \"{0}\"", virtualPath);
|
|
ctx.Monitor(_virtualPathMonitor.WhenPathChanges(virtualPath));
|
|
}
|
|
|
|
return _virtualPathProvider.ListFiles(virtualPath).Select(Path.GetFileName).ToReadOnlyCollection();
|
|
});
|
|
return new { harvesterInfo.harvester, basePath, subPath, virtualPath, fileNames };
|
|
})).ToList();
|
|
Logger.Information("Done discovering candidate views filenames");
|
|
|
|
var fileContexts = pathContexts.SelectMany(pathContext => _shapeTemplateViewEngines.SelectMany(ve => {
|
|
var fileNames = ve.DetectTemplateFileNames(pathContext.fileNames);
|
|
return fileNames.Select(
|
|
fileName => new {
|
|
fileName = Path.GetFileNameWithoutExtension(fileName),
|
|
fileVirtualPath = Path.Combine(pathContext.virtualPath, fileName).Replace(Path.DirectorySeparatorChar, '/'),
|
|
pathContext
|
|
});
|
|
}));
|
|
|
|
var shapeContexts = fileContexts.SelectMany(fileContext => {
|
|
var harvestShapeInfo = new HarvestShapeInfo {
|
|
SubPath = fileContext.pathContext.subPath,
|
|
FileName = fileContext.fileName,
|
|
TemplateVirtualPath = fileContext.fileVirtualPath
|
|
};
|
|
var harvestShapeHits = fileContext.pathContext.harvester.HarvestShape(harvestShapeInfo);
|
|
return harvestShapeHits.Select(harvestShapeHit => new { harvestShapeInfo, harvestShapeHit, fileContext });
|
|
});
|
|
|
|
return shapeContexts.Select(shapeContext => new { extensionDescriptor, shapeContext }).ToList();
|
|
}).SelectMany(hits2 => hits2);
|
|
|
|
|
|
foreach (var iter in hits) {
|
|
// templates are always associated with the namesake feature of module or theme
|
|
var hit = iter;
|
|
var featureDescriptors = iter.extensionDescriptor.Features.Where(fd => fd.Id == hit.extensionDescriptor.Id);
|
|
foreach (var featureDescriptor in featureDescriptors) {
|
|
Logger.Debug("Binding {0} as shape [{1}] for feature {2}",
|
|
hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
|
|
iter.shapeContext.harvestShapeHit.ShapeType,
|
|
featureDescriptor.Id);
|
|
|
|
builder.Describe(iter.shapeContext.harvestShapeHit.ShapeType)
|
|
.From(new Feature { Descriptor = featureDescriptor })
|
|
.BoundAs(
|
|
hit.shapeContext.harvestShapeInfo.TemplateVirtualPath,
|
|
shapeDescriptor => displayContext => Render(shapeDescriptor, displayContext, hit.shapeContext.harvestShapeInfo, hit.shapeContext.harvestShapeHit));
|
|
}
|
|
}
|
|
|
|
Logger.Information("Done discovering shapes");
|
|
}
|
|
|
|
private bool FeatureIsEnabled(FeatureDescriptor fd) {
|
|
return (DefaultExtensionTypes.IsTheme(fd.Extension.ExtensionType) && (fd.Id == "TheAdmin" || fd.Id == "SafeMode")) ||
|
|
_shellDescriptor.Features.Any(sf => sf.Name == fd.Id);
|
|
}
|
|
|
|
private IHtmlString Render(ShapeDescriptor shapeDescriptor, DisplayContext displayContext, HarvestShapeInfo harvestShapeInfo, HarvestShapeHit harvestShapeHit) {
|
|
Logger.Information("Rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
|
|
IHtmlString result;
|
|
|
|
if (displayContext.ViewContext.View != null) {
|
|
var htmlHelper = new HtmlHelper(displayContext.ViewContext, displayContext.ViewDataContainer);
|
|
result = htmlHelper.Partial(harvestShapeInfo.TemplateVirtualPath, displayContext.Value);
|
|
}
|
|
else {
|
|
// If the View is null, it means that the shape is being executed from a non-view origin / where no ViewContext was established by the view engine, but manually.
|
|
// Manually creating a ViewContext works when working with Shape methods, but not when the shape is implemented as a Razor view template.
|
|
// Horrible, but it will have to do for now.
|
|
result = RenderRazorViewToString(harvestShapeInfo.TemplateVirtualPath, displayContext);
|
|
}
|
|
|
|
Logger.Information("Done rendering template file '{0}'", harvestShapeInfo.TemplateVirtualPath);
|
|
return result;
|
|
}
|
|
|
|
private IHtmlString RenderRazorViewToString(string path, DisplayContext context) {
|
|
using (var sw = new StringWriter()) {
|
|
var controllerContext = CreateControllerContext();
|
|
var viewResult = _viewEngine.Value.FindPartialView(controllerContext, path, false);
|
|
|
|
context.ViewContext.ViewData = new ViewDataDictionary(context.Value);
|
|
context.ViewContext.TempData = new TempDataDictionary();
|
|
context.ViewContext.View = viewResult.View;
|
|
context.ViewContext.RouteData = controllerContext.RouteData;
|
|
context.ViewContext.RequestContext.RouteData = controllerContext.RouteData;
|
|
viewResult.View.Render(context.ViewContext, sw);
|
|
viewResult.ViewEngine.ReleaseView(controllerContext, viewResult.View);
|
|
return new HtmlString(sw.GetStringBuilder().ToString());
|
|
}
|
|
}
|
|
|
|
private ControllerContext CreateControllerContext() {
|
|
var controller = new StubController();
|
|
var httpContext = _workContextAccessor.GetContext().HttpContext;
|
|
var requestContext = httpContext.Request.RequestContext;
|
|
var routeData = requestContext.RouteData;
|
|
|
|
routeData.DataTokens["IWorkContextAccessor"] = _workContextAccessor;
|
|
|
|
if (!routeData.Values.ContainsKey("controller") && !routeData.Values.ContainsKey("Controller"))
|
|
routeData.Values.Add("controller", controller.GetType().Name.ToLower().Replace("controller", ""));
|
|
|
|
controller.ControllerContext = new ControllerContext(httpContext, routeData, controller);
|
|
controller.ControllerContext.RequestContext = requestContext;
|
|
return controller.ControllerContext;
|
|
}
|
|
|
|
private class StubController : Controller { }
|
|
}
|
|
}
|