Lots of notes, removes data tokens,

This commit is contained in:
Shannon
2020-12-10 18:09:32 +11:00
parent 4b85f8eb20
commit 63ab8ec52c
41 changed files with 464 additions and 831 deletions

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
@@ -19,6 +19,7 @@ using Umbraco.Web.Common.ModelBinders;
namespace Umbraco.Web.Common.AspNetCore
{
// TODO: Should be in Views namespace?
public abstract class UmbracoViewPage : UmbracoViewPage<IPublishedContent>
{
@@ -92,6 +93,8 @@ namespace Umbraco.Web.Common.AspNetCore
base.WriteLiteral(value);
}
// TODO: This trick doesn't work anymore, this method used to be an override.
// Now the model is bound in a different place
// maps model
protected async Task SetViewDataAsync(ViewDataDictionary viewData)
{
@@ -111,8 +114,6 @@ namespace Umbraco.Web.Common.AspNetCore
ViewData = (ViewDataDictionary<TModel>) viewData;
}
// viewData is the ViewDataDictionary (maybe <TModel>) that we have
// modelType is the type of the model that we need to bind to
//

View File

@@ -0,0 +1,99 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.Models;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Common.Controllers
{
/// <summary>
/// Represents the default front-end rendering controller.
/// </summary>
[TypeFilter(typeof(ModelBindingExceptionFilter))]
public class RenderController : UmbracoController, IRenderController
{
private readonly ILogger<RenderController> _logger;
private readonly ICompositeViewEngine _compositeViewEngine;
private UmbracoRouteValues _routeValues;
/// <summary>
/// Initializes a new instance of the <see cref="RenderController"/> class.
/// </summary>
public RenderController(ILogger<RenderController> logger, ICompositeViewEngine compositeViewEngine)
{
_logger = logger;
_compositeViewEngine = compositeViewEngine;
}
/// <summary>
/// Gets the current content item.
/// </summary>
protected IPublishedContent CurrentPage => UmbracoRouteValues.PublishedContent;
/// <summary>
/// Gets the <see cref="UmbracoRouteValues"/>
/// </summary>
protected UmbracoRouteValues UmbracoRouteValues
{
get
{
if (_routeValues != null)
{
return _routeValues;
}
if (!ControllerContext.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var def))
{
throw new InvalidOperationException($"No route value found with key {Core.Constants.Web.UmbracoRouteDefinitionDataToken}");
}
_routeValues = (UmbracoRouteValues)def;
return _routeValues;
}
}
/// <summary>
/// Ensures that a physical view file exists on disk.
/// </summary>
/// <param name="template">The view name.</param>
protected bool EnsurePhsyicalViewExists(string template)
{
ViewEngineResult result = _compositeViewEngine.FindView(ControllerContext, template, false);
if (result.View != null)
{
return true;
}
_logger.LogWarning("No physical template file was found for template {Template}", template);
return false;
}
/// <summary>
/// Gets an action result based on the template name found in the route values and a model.
/// </summary>
/// <typeparam name="T">The type of the model.</typeparam>
/// <param name="model">The model.</param>
/// <returns>The action result.</returns>
/// <exception cref="InvalidOperationException">If the template found in the route values doesn't physically exist and exception is thrown</exception>
protected IActionResult CurrentTemplate<T>(T model)
{
if (EnsurePhsyicalViewExists(UmbracoRouteValues.TemplateName) == false)
{
throw new InvalidOperationException("No physical template file was found for template " + UmbracoRouteValues.TemplateName);
}
return View(UmbracoRouteValues.TemplateName, model);
}
/// <summary>
/// The default action to render the front-end view.
/// </summary>
public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage));
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -38,7 +38,7 @@ namespace Umbraco.Web.Macros
private readonly IHttpContextAccessor _httpContextAccessor;
public MacroRenderer(
IProfilingLogger profilingLogger ,
IProfilingLogger profilingLogger,
ILogger<MacroRenderer> logger,
IUmbracoContextAccessor umbracoContextAccessor,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
@@ -53,7 +53,7 @@ namespace Umbraco.Web.Macros
IRequestAccessor requestAccessor,
IHttpContextAccessor httpContextAccessor)
{
_profilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger ));
_profilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger));
_logger = logger;
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
@@ -111,12 +111,14 @@ namespace Umbraco.Web.Macros
private MacroContent GetMacroContentFromCache(MacroModel model)
{
// only if cache is enabled
if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0) return null;
if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0)
return null;
var cache = _appCaches.RuntimeCache;
var macroContent = cache.GetCacheItem<MacroContent>(CacheKeys.MacroContentCacheKey + model.CacheIdentifier);
if (macroContent == null) return null;
if (macroContent == null)
return null;
_logger.LogDebug("Macro content loaded from cache '{MacroCacheId}'", model.CacheIdentifier);
@@ -145,16 +147,19 @@ namespace Umbraco.Web.Macros
private void AddMacroContentToCache(MacroModel model, MacroContent macroContent)
{
// only if cache is enabled
if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0) return;
if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0)
return;
// just make sure...
if (macroContent == null) return;
if (macroContent == null)
return;
// do not cache if it should cache by member and there's not member
if (model.CacheByMember)
{
var key = _memberUserKeyProvider.GetMemberProviderUserKey();
if (key is null) return;
if (key is null)
return;
}
// remember when we cache the content
@@ -184,10 +189,12 @@ namespace Umbraco.Web.Macros
private FileInfo GetMacroFile(MacroModel model)
{
var filename = GetMacroFileName(model);
if (filename == null) return null;
if (filename == null)
return null;
var mapped = _hostingEnvironment.MapPathContentRoot(filename);
if (mapped == null) return null;
if (mapped == null)
return null;
var file = new FileInfo(mapped);
return file.Exists ? file : null;
@@ -223,7 +230,8 @@ namespace Umbraco.Web.Macros
private MacroContent Render(MacroModel macro, IPublishedContent content)
{
if (content == null) throw new ArgumentNullException(nameof(content));
if (content == null)
throw new ArgumentNullException(nameof(content));
var macroInfo = $"Render Macro: {macro.Name}, cache: {macro.CacheDuration}";
using (_profilingLogger.DebugDuration<MacroRenderer>(macroInfo, "Rendered Macro."))
@@ -328,7 +336,8 @@ namespace Umbraco.Web.Macros
/// should not be cached. In that case the attempt may also contain an exception.</remarks>
private Attempt<MacroContent> ExecuteMacroOfType(MacroModel model, IPublishedContent content)
{
if (model == null) throw new ArgumentNullException(nameof(model));
if (model == null)
throw new ArgumentNullException(nameof(model));
// ensure that we are running against a published node (ie available in XML)
// that may not be the case if the macro is embedded in a RTE of an unpublished document
@@ -356,7 +365,7 @@ namespace Umbraco.Web.Macros
/// <returns>The text output of the macro execution.</returns>
private MacroContent ExecutePartialView(MacroModel macro, IPublishedContent content)
{
var engine = new PartialViewMacroEngine(_umbracoContextAccessor, _httpContextAccessor, _hostingEnvironment);
var engine = new PartialViewMacroEngine(_httpContextAccessor, _hostingEnvironment);
return engine.Execute(macro, content);
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Encodings.Web;
@@ -27,56 +27,71 @@ namespace Umbraco.Web.Common.Macros
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly Func<IUmbracoContext> _getUmbracoContext;
//private readonly Func<IUmbracoContext> _getUmbracoContext;
public PartialViewMacroEngine(
IUmbracoContextAccessor umbracoContextAccessor,
//IUmbracoContextAccessor umbracoContextAccessor,
IHttpContextAccessor httpContextAccessor,
IHostingEnvironment hostingEnvironment)
{
_httpContextAccessor = httpContextAccessor;
_hostingEnvironment = hostingEnvironment;
_getUmbracoContext = () =>
{
var context = umbracoContextAccessor.UmbracoContext;
if (context == null)
throw new InvalidOperationException(
$"The {GetType()} cannot execute with a null UmbracoContext.Current reference.");
return context;
};
//_getUmbracoContext = () =>
//{
// var context = umbracoContextAccessor.UmbracoContext;
// if (context == null)
// {
// throw new InvalidOperationException(
// $"The {GetType()} cannot execute with a null UmbracoContext.Current reference.");
// }
// return context;
//};
}
public bool Validate(string code, string tempFileName, IPublishedContent currentPage, out string errorMessage)
{
var temp = GetVirtualPathFromPhysicalPath(tempFileName);
try
{
CompileAndInstantiate(temp);
}
catch (Exception exception)
{
errorMessage = exception.Message;
return false;
}
//public bool Validate(string code, string tempFileName, IPublishedContent currentPage, out string errorMessage)
//{
// var temp = GetVirtualPathFromPhysicalPath(tempFileName);
// try
// {
// CompileAndInstantiate(temp);
// }
// catch (Exception exception)
// {
// errorMessage = exception.Message;
// return false;
// }
errorMessage = string.Empty;
return true;
}
// errorMessage = string.Empty;
// return true;
//}
public MacroContent Execute(MacroModel macro, IPublishedContent content)
{
if (macro == null) throw new ArgumentNullException(nameof(macro));
if (content == null) throw new ArgumentNullException(nameof(content));
if (macro == null)
{
throw new ArgumentNullException(nameof(macro));
}
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
if (string.IsNullOrWhiteSpace(macro.MacroSource))
{
throw new ArgumentException("The MacroSource property of the macro object cannot be null or empty");
}
var httpContext = _httpContextAccessor.GetRequiredHttpContext();
var umbCtx = _getUmbracoContext();
//var umbCtx = _getUmbracoContext();
var routeVals = new RouteData();
routeVals.Values.Add("controller", "PartialViewMacro");
routeVals.Values.Add("action", "Index");
routeVals.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx); //required for UmbracoViewPage
//TODO: Was required for UmbracoViewPage need to figure out if we still need that, i really don't think this is necessary
//routeVals.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx);
var modelMetadataProvider = httpContext.RequestServices.GetRequiredService<IModelMetadataProvider>();
var tempDataProvider = httpContext.RequestServices.GetRequiredService<ITempDataProvider>();
@@ -109,6 +124,7 @@ namespace Umbraco.Web.Common.Macros
return new MacroContent { Text = output };
}
private class FakeView : IView
{
/// <inheritdoc />
@@ -120,29 +136,30 @@ namespace Umbraco.Web.Common.Macros
/// <inheritdoc />
public string Path { get; } = "View";
}
private string GetVirtualPathFromPhysicalPath(string physicalPath)
{
var rootpath = _hostingEnvironment.MapPathContentRoot("~/");
physicalPath = physicalPath.Replace(rootpath, "");
physicalPath = physicalPath.Replace("\\", "/");
return "~/" + physicalPath;
}
private static PartialViewMacroPage CompileAndInstantiate(string virtualPath)
{
// //Compile Razor - We Will Leave This To ASP.NET Compilation Engine & ASP.NET WebPages
// //Security in medium trust is strict around here, so we can only pass a virtual file path
// //ASP.NET Compilation Engine caches returned types
// //Changed From BuildManager As Other Properties Are Attached Like Context Path/
// var webPageBase = WebPageBase.CreateInstanceFromVirtualPath(virtualPath);
// var webPage = webPageBase as PartialViewMacroPage;
// if (webPage == null)
// throw new InvalidCastException("All Partial View Macro views must inherit from " + typeof(PartialViewMacroPage).FullName);
// return webPage;
//private string GetVirtualPathFromPhysicalPath(string physicalPath)
//{
// var rootpath = _hostingEnvironment.MapPathContentRoot("~/");
// physicalPath = physicalPath.Replace(rootpath, "");
// physicalPath = physicalPath.Replace("\\", "/");
// return "~/" + physicalPath;
//}
//TODO? How to check this
return null;
}
//private static PartialViewMacroPage CompileAndInstantiate(string virtualPath)
//{
// // //Compile Razor - We Will Leave This To ASP.NET Compilation Engine & ASP.NET WebPages
// // //Security in medium trust is strict around here, so we can only pass a virtual file path
// // //ASP.NET Compilation Engine caches returned types
// // //Changed From BuildManager As Other Properties Are Attached Like Context Path/
// // var webPageBase = WebPageBase.CreateInstanceFromVirtualPath(virtualPath);
// // var webPage = webPageBase as PartialViewMacroPage;
// // if (webPage == null)
// // throw new InvalidCastException("All Partial View Macro views must inherit from " + typeof(PartialViewMacroPage).FullName);
// // return webPage;
// //TODO? How to check this
// return null;
//}
}
}

View File

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.Models;
namespace Umbraco.Web.Common.ModelBinders
@@ -15,26 +16,15 @@ namespace Umbraco.Web.Common.ModelBinders
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ActionContext.RouteData.DataTokens.TryGetValue(Core.Constants.Web.UmbracoDataToken, out var source) == false)
// Although this model binder is built to work both ways between IPublishedContent and IContentModel in reality
// only IPublishedContent will ever exist in the request.
if (!bindingContext.ActionContext.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var source)
|| !(source is UmbracoRouteValues umbracoRouteValues))
{
return Task.CompletedTask;
}
// This model binder deals with IContentModel and IPublishedContent by extracting the model from the route's
// datatokens. This data token is set in 2 places: RenderRouteHandler, UmbracoVirtualNodeRouteHandler
// and both always set the model to an instance of `ContentModel`.
// No need for type checks to ensure we have the appropriate binder, as in .NET Core this is handled in the provider,
// in this case ContentModelBinderProvider.
// Being defensive though.... if for any reason the model is not either IContentModel or IPublishedContent,
// then we return since those are the only types this binder is dealing with.
if (source is IContentModel == false && source is IPublishedContent == false)
{
return Task.CompletedTask;
}
BindModelAsync(bindingContext, source, bindingContext.ModelType);
BindModelAsync(bindingContext, umbracoRouteValues.PublishedContent, bindingContext.ModelType);
return Task.CompletedTask;
}
@@ -56,7 +46,7 @@ namespace Umbraco.Web.Common.ModelBinders
// If types already match, return
var sourceType = source.GetType();
if (sourceType. Inherits(modelType)) // includes ==
if (sourceType.Inherits(modelType)) // includes ==
{
bindingContext.Result = ModelBindingResult.Success(source);
return Task.CompletedTask;
@@ -71,7 +61,8 @@ namespace Umbraco.Web.Common.ModelBinders
{
// else check if we can convert it to a content
var attempt1 = source.TryConvertTo<IPublishedContent>();
if (attempt1.Success) sourceContent = attempt1.Result;
if (attempt1.Success)
sourceContent = attempt1.Result;
}
// If we have a content
@@ -129,11 +120,13 @@ namespace Umbraco.Web.Common.ModelBinders
// prepare message
msg.Append("Cannot bind source");
if (sourceContent) msg.Append(" content");
if (sourceContent)
msg.Append(" content");
msg.Append(" type ");
msg.Append(sourceType.FullName);
msg.Append(" to model");
if (modelContent) msg.Append(" content");
if (modelContent)
msg.Append(" content");
msg.Append(" type ");
msg.Append(modelType.FullName);
msg.Append(".");

View File

@@ -0,0 +1,68 @@
using System;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Extensions;
using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Common.Routing
{
/// <summary>
/// Represents the data required to route to a specific controller/action during an Umbraco request
/// </summary>
public class UmbracoRouteValues
{
/// <summary>
/// The default action name
/// </summary>
public const string DefaultActionName = nameof(RenderController.Index);
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoRouteValues"/> class.
/// </summary>
public UmbracoRouteValues(
IPublishedContent publishedContent,
string controllerName = null,
Type controllerType = null,
string actionName = DefaultActionName,
string templateName = null,
bool hasHijackedRoute = false)
{
ControllerName = controllerName ?? ControllerExtensions.GetControllerName<RenderController>();
ControllerType = controllerType ?? typeof(RenderController);
PublishedContent = publishedContent;
HasHijackedRoute = hasHijackedRoute;
ActionName = actionName;
TemplateName = templateName;
}
/// <summary>
/// Gets the controller name
/// </summary>
public string ControllerName { get; }
/// <summary>
/// Gets the action name
/// </summary>
public string ActionName { get; }
/// <summary>
/// Gets the template name
/// </summary>
public string TemplateName { get; }
/// <summary>
/// Gets the Controller type found for routing to
/// </summary>
public Type ControllerType { get; }
/// <summary>
/// Gets the <see cref="IPublishedContent"/>
/// </summary>
public IPublishedContent PublishedContent { get; }
/// <summary>
/// Gets a value indicating whether the current request has a hijacked route/user controller routed for it
/// </summary>
public bool HasHijackedRoute { get; }
}
}