Migrated MacroRenderingController + Related UmbracoComponentRenderer => TemplateRenderer + ParitalViewMacroEngine
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
namespace Umbraco.Web
|
||||
namespace Umbraco.Core.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods used to render umbraco components as HTML in templates
|
||||
@@ -15,7 +14,7 @@ namespace Umbraco.Web
|
||||
/// <param name="contentId"></param>
|
||||
/// <param name="altTemplateId">If not specified, will use the template assigned to the node</param>
|
||||
/// <returns></returns>
|
||||
IHtmlString RenderTemplate(int contentId, int? altTemplateId = null);
|
||||
IHtmlEncodedString RenderTemplate(int contentId, int? altTemplateId = null);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the macro with the specified alias.
|
||||
@@ -23,7 +22,7 @@ namespace Umbraco.Web
|
||||
/// <param name="contentId"></param>
|
||||
/// <param name="alias">The alias.</param>
|
||||
/// <returns></returns>
|
||||
IHtmlString RenderMacro(int contentId, string alias);
|
||||
IHtmlEncodedString RenderMacro(int contentId, string alias);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the macro with the specified alias, passing in the specified parameters.
|
||||
@@ -32,7 +31,7 @@ namespace Umbraco.Web
|
||||
/// <param name="alias">The alias.</param>
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <returns></returns>
|
||||
IHtmlString RenderMacro(int contentId, string alias, object parameters);
|
||||
IHtmlEncodedString RenderMacro(int contentId, string alias, object parameters);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the macro with the specified alias, passing in the specified parameters.
|
||||
@@ -41,6 +40,6 @@ namespace Umbraco.Web
|
||||
/// <param name="alias">The alias.</param>
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <returns></returns>
|
||||
IHtmlString RenderMacro(int contentId, string alias, IDictionary<string, object> parameters);
|
||||
IHtmlEncodedString RenderMacro(int contentId, string alias, IDictionary<string, object> parameters);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Web;
|
||||
using System.Web.UI;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Templates;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Macros;
|
||||
|
||||
namespace Umbraco.Web
|
||||
namespace Umbraco.Core.Net
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
@@ -22,19 +19,18 @@ namespace Umbraco.Web
|
||||
/// <remarks>
|
||||
/// Used by UmbracoHelper
|
||||
/// </remarks>
|
||||
internal class UmbracoComponentRenderer : IUmbracoComponentRenderer
|
||||
// Migrated to .NET Core
|
||||
public class UmbracoComponentRenderer : IUmbracoComponentRenderer
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IMacroRenderer _macroRenderer;
|
||||
private readonly ITemplateRenderer _templateRenderer;
|
||||
private readonly HtmlLocalLinkParser _linkParser;
|
||||
|
||||
public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer, HtmlLocalLinkParser linkParser)
|
||||
public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_macroRenderer = macroRenderer;
|
||||
_templateRenderer = templateRenderer ?? throw new ArgumentNullException(nameof(templateRenderer));
|
||||
_linkParser = linkParser;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -43,7 +39,7 @@ namespace Umbraco.Web
|
||||
/// <param name="contentId"></param>
|
||||
/// <param name="altTemplateId">If not specified, will use the template assigned to the node</param>
|
||||
/// <returns></returns>
|
||||
public IHtmlString RenderTemplate(int contentId, int? altTemplateId = null)
|
||||
public IHtmlEncodedString RenderTemplate(int contentId, int? altTemplateId = null)
|
||||
{
|
||||
using (var sw = new StringWriter())
|
||||
{
|
||||
@@ -55,7 +51,7 @@ namespace Umbraco.Web
|
||||
{
|
||||
sw.Write("<!-- Error rendering template with id {0}: '{1}' -->", contentId, ex);
|
||||
}
|
||||
return new HtmlString(sw.ToString());
|
||||
return new HtmlEncodedString(sw.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +61,7 @@ namespace Umbraco.Web
|
||||
/// <param name="contentId"></param>
|
||||
/// <param name="alias">The alias.</param>
|
||||
/// <returns></returns>
|
||||
public IHtmlString RenderMacro(int contentId, string alias)
|
||||
public IHtmlEncodedString RenderMacro(int contentId, string alias)
|
||||
{
|
||||
return RenderMacro(contentId, alias, new { });
|
||||
}
|
||||
@@ -77,7 +73,7 @@ namespace Umbraco.Web
|
||||
/// <param name="alias">The alias.</param>
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <returns></returns>
|
||||
public IHtmlString RenderMacro(int contentId, string alias, object parameters)
|
||||
public IHtmlEncodedString RenderMacro(int contentId, string alias, object parameters)
|
||||
{
|
||||
return RenderMacro(contentId, alias, parameters?.ToDictionary<object>());
|
||||
}
|
||||
@@ -89,7 +85,7 @@ namespace Umbraco.Web
|
||||
/// <param name="alias">The alias.</param>
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <returns></returns>
|
||||
public IHtmlString RenderMacro(int contentId, string alias, IDictionary<string, object> parameters)
|
||||
public IHtmlEncodedString RenderMacro(int contentId, string alias, IDictionary<string, object> parameters)
|
||||
{
|
||||
if (contentId == default)
|
||||
throw new ArgumentException("Invalid content id " + contentId);
|
||||
@@ -109,7 +105,7 @@ namespace Umbraco.Web
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <param name="content">The content used for macro rendering</param>
|
||||
/// <returns></returns>
|
||||
private IHtmlString RenderMacro(IPublishedContent content, string alias, IDictionary<string, object> parameters)
|
||||
private IHtmlEncodedString RenderMacro(IPublishedContent content, string alias, IDictionary<string, object> parameters)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException(nameof(content));
|
||||
|
||||
@@ -121,7 +117,7 @@ namespace Umbraco.Web
|
||||
|
||||
var html = _macroRenderer.Render(alias, content, macroProps).Text;
|
||||
|
||||
return new HtmlString(html);
|
||||
return new HtmlEncodedString(html);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace Umbraco.Web.Routing
|
||||
/// one document per culture), and domains, withing the context of a current Uri, assign
|
||||
/// a culture to that document.</para>
|
||||
/// </remarks>
|
||||
internal static string GetCultureFromDomains(int contentId, string contentPath, Uri current, IUmbracoContext umbracoContext, ISiteDomainHelper siteDomainHelper)
|
||||
public static string GetCultureFromDomains(int contentId, string contentPath, Uri current, IUmbracoContext umbracoContext, ISiteDomainHelper siteDomainHelper)
|
||||
{
|
||||
if (umbracoContext == null)
|
||||
throw new InvalidOperationException("A current UmbracoContext is required.");
|
||||
@@ -146,7 +146,7 @@ namespace Umbraco.Web.Routing
|
||||
/// the right one, unless it is <c>null</c>, in which case the method returns <c>null</c>.</para>
|
||||
/// <para>The filter, if any, will be called only with a non-empty argument, and _must_ return something.</para>
|
||||
/// </remarks>
|
||||
internal static DomainAndUri SelectDomain(IEnumerable<Domain> domains, Uri uri, string culture = null, string defaultCulture = null, Func<IReadOnlyCollection<DomainAndUri>, Uri, string, string, DomainAndUri> filter = null)
|
||||
public static DomainAndUri SelectDomain(IEnumerable<Domain> domains, Uri uri, string culture = null, string defaultCulture = null, Func<IReadOnlyCollection<DomainAndUri>, Uri, string, string, DomainAndUri> filter = null)
|
||||
{
|
||||
// sanitize the list to have proper uris for comparison (scheme, path end with /)
|
||||
// we need to end with / because example.com/foo cannot match example.com/foobar
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Umbraco.Web.Routing
|
||||
/// <param name="publishedRouter">The published router.</param>
|
||||
/// <param name="umbracoContext">The Umbraco context.</param>
|
||||
/// <param name="uri">The request <c>Uri</c>.</param>
|
||||
internal PublishedRequest(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IWebRoutingSettings webRoutingSettings, Uri uri = null)
|
||||
public PublishedRequest(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IWebRoutingSettings webRoutingSettings, Uri uri = null)
|
||||
{
|
||||
UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext));
|
||||
_publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter));
|
||||
|
||||
@@ -28,4 +28,8 @@
|
||||
<_Parameter1>Umbraco.Tests.Benchmarks</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Security\AuthenticationExtensions.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -18,6 +18,7 @@ using Umbraco.Core.Migrations;
|
||||
using Umbraco.Core.Migrations.Install;
|
||||
using Umbraco.Core.Migrations.PostMigrations;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.PropertyEditors.Validators;
|
||||
@@ -30,7 +31,6 @@ using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Examine;
|
||||
using Umbraco.Infrastructure.Media;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Actions;
|
||||
using Umbraco.Web.Cache;
|
||||
@@ -357,6 +357,8 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
composition.Register<IFilePermissionHelper, FilePermissionHelper>(Lifetime.Singleton);
|
||||
|
||||
composition.RegisterUnique<IUmbracoComponentRenderer, UmbracoComponentRenderer>();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Remove="Install\InstallSteps\CompleteInstallStep.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -20,4 +20,12 @@
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Umbraco.Core\Extensions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Umbraco.Core\Extensions\ClaimsPrincipalExtensionsTests.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -14,6 +14,7 @@ using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.Common;
|
||||
|
||||
@@ -200,10 +200,10 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
// "mediaTypeApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<MediaTypeController>(
|
||||
// controller => controller.GetAllowedChildren(0))
|
||||
// },
|
||||
// {
|
||||
// "macroRenderingApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<MacroRenderingController>(
|
||||
// controller => controller.GetMacroParameters(0))
|
||||
// },
|
||||
{
|
||||
"macroRenderingApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<MacroRenderingController>(
|
||||
controller => controller.GetMacroParameters(0))
|
||||
},
|
||||
{
|
||||
"macroApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<MacrosController>(
|
||||
controller => controller.Create(null))
|
||||
|
||||
@@ -6,23 +6,20 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web.Http;
|
||||
using System.Web.SessionState;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Exceptions;
|
||||
using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// API controller to deal with Macro data
|
||||
@@ -33,42 +30,34 @@ namespace Umbraco.Web.Editors
|
||||
/// Session, we don't want it to throw null reference exceptions.
|
||||
/// </remarks>
|
||||
[PluginController("UmbracoApi")]
|
||||
public class MacroRenderingController : UmbracoAuthorizedJsonController, IRequiresSessionState
|
||||
public class MacroRenderingController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IMacroService _macroService;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly ISiteDomainHelper _siteDomainHelper;
|
||||
private readonly UmbracoMapper _umbracoMapper;
|
||||
private readonly IUmbracoComponentRenderer _componentRenderer;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
|
||||
public MacroRenderingController(
|
||||
IGlobalSettings globalSettings,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ISqlContext sqlContext,
|
||||
ServiceContext services,
|
||||
AppCaches appCaches,
|
||||
IProfilingLogger logger,
|
||||
IRuntimeState runtimeState,
|
||||
IShortStringHelper shortStringHelper,
|
||||
UmbracoMapper umbracoMapper,
|
||||
IUmbracoComponentRenderer componentRenderer,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IMacroService macroService,
|
||||
IPublishedUrlProvider publishedUrlProvider)
|
||||
: base(
|
||||
globalSettings,
|
||||
umbracoContextAccessor,
|
||||
sqlContext,
|
||||
services,
|
||||
appCaches,
|
||||
logger,
|
||||
runtimeState,
|
||||
shortStringHelper,
|
||||
umbracoMapper,
|
||||
publishedUrlProvider)
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IShortStringHelper shortStringHelper,
|
||||
ISiteDomainHelper siteDomainHelper)
|
||||
|
||||
{
|
||||
_componentRenderer = componentRenderer;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
_macroService = macroService;
|
||||
_umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
|
||||
_componentRenderer = componentRenderer ?? throw new ArgumentNullException(nameof(componentRenderer));
|
||||
_variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
|
||||
_macroService = macroService ?? throw new ArgumentNullException(nameof(macroService));
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
_shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper));
|
||||
_siteDomainHelper = siteDomainHelper ?? throw new ArgumentNullException(nameof(siteDomainHelper));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,7 +76,7 @@ namespace Umbraco.Web.Editors
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
return Mapper.Map<IEnumerable<MacroParameter>>(macro).OrderBy(x => x.SortOrder);
|
||||
return _umbracoMapper.Map<IEnumerable<MacroParameter>>(macro).OrderBy(x => x.SortOrder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -103,7 +92,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public HttpResponseMessage GetMacroResultAsHtmlForEditor(string macroAlias, int pageId, [FromUri] IDictionary<string, object> macroParams)
|
||||
public IActionResult GetMacroResultAsHtmlForEditor(string macroAlias, int pageId, [FromQuery] IDictionary<string, object> macroParams)
|
||||
{
|
||||
return GetMacroResultAsHtml(macroAlias, pageId, macroParams);
|
||||
}
|
||||
@@ -116,7 +105,7 @@ namespace Umbraco.Web.Editors
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public HttpResponseMessage GetMacroResultAsHtmlForEditor(MacroParameterModel model)
|
||||
public IActionResult GetMacroResultAsHtmlForEditor(MacroParameterModel model)
|
||||
{
|
||||
return GetMacroResultAsHtml(model.MacroAlias, model.PageId, model.MacroParams);
|
||||
}
|
||||
@@ -128,24 +117,22 @@ namespace Umbraco.Web.Editors
|
||||
public IDictionary<string, object> MacroParams { get; set; }
|
||||
}
|
||||
|
||||
private HttpResponseMessage GetMacroResultAsHtml(string macroAlias, int pageId, IDictionary<string, object> macroParams)
|
||||
private IActionResult GetMacroResultAsHtml(string macroAlias, int pageId, IDictionary<string, object> macroParams)
|
||||
{
|
||||
var m = _macroService.GetByAlias(macroAlias);
|
||||
if (m == null)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
var publishedContent = UmbracoContext.Content.GetById(true, pageId);
|
||||
var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var publishedContent = umbracoContext.Content.GetById(true, pageId);
|
||||
|
||||
//if it isn't supposed to be rendered in the editor then return an empty string
|
||||
//currently we cannot render a macro if the page doesn't yet exist
|
||||
if (pageId == -1 || publishedContent == null || m.DontRender)
|
||||
{
|
||||
var response = Request.CreateResponse();
|
||||
//need to create a specific content result formatted as HTML since this controller has been configured
|
||||
//with only json formatters.
|
||||
response.Content = new StringContent(string.Empty, Encoding.UTF8, "text/html");
|
||||
|
||||
return response;
|
||||
return Content(string.Empty, "text/html", Encoding.UTF8);
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +144,7 @@ namespace Umbraco.Web.Editors
|
||||
// in a 1:1 situation we do not handle the language being edited
|
||||
// so the macro renders in the wrong language
|
||||
|
||||
var culture = publishedContent.GetCultureFromDomains();
|
||||
var culture = DomainUtilities.GetCultureFromDomains(publishedContent.Id, publishedContent.Path, null, umbracoContext, _siteDomainHelper);
|
||||
|
||||
if (culture != null)
|
||||
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
|
||||
@@ -165,18 +152,12 @@ namespace Umbraco.Web.Editors
|
||||
// must have an active variation context!
|
||||
_variationContextAccessor.VariationContext = new VariationContext(culture);
|
||||
|
||||
using (UmbracoContext.ForcedPreview(true))
|
||||
using (umbracoContext.ForcedPreview(true))
|
||||
{
|
||||
|
||||
var result = Request.CreateResponse();
|
||||
//need to create a specific content result formatted as HTML since this controller has been configured
|
||||
//with only json formatters.
|
||||
result.Content = new StringContent(
|
||||
_componentRenderer.RenderMacro(pageId, m.Alias, macroParams).ToString(),
|
||||
Encoding.UTF8,
|
||||
"text/html");
|
||||
|
||||
return result;
|
||||
return Content(_componentRenderer.RenderMacro(pageId, m.Alias, macroParams).ToString(), "text/html",
|
||||
Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,9 +170,9 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var macroName = model.Filename.TrimEnd(".cshtml");
|
||||
|
||||
var macro = new Macro(ShortStringHelper)
|
||||
var macro = new Macro(_shortStringHelper)
|
||||
{
|
||||
Alias = macroName.ToSafeAlias(ShortStringHelper),
|
||||
Alias = macroName.ToSafeAlias(_shortStringHelper),
|
||||
Name = macroName,
|
||||
MacroSource = model.VirtualPath.EnsureStartsWith("~")
|
||||
};
|
||||
@@ -27,4 +27,12 @@
|
||||
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Security\ConfigureBackOfficeSecurityStampValidatorOptions.cs" />
|
||||
<Compile Remove="Security\ConfigureBackOfficeIdentityOptions.cs" />
|
||||
<Compile Remove="Security\ConfigureBackOfficeCookieOptions.cs" />
|
||||
<Compile Remove="Security\BackOfficeSessionIdValidator.cs" />
|
||||
<Compile Remove="Security\BackOfficeSecureDataFormat.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -21,21 +21,14 @@ namespace Umbraco.Web.Common.AspNetCore
|
||||
|
||||
public abstract class UmbracoViewPage<TModel> : RazorPage<TModel>
|
||||
{
|
||||
|
||||
private IUmbracoContext _umbracoContext;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IContentSettings _contentSettings;
|
||||
private readonly IProfilerHtml _profilerHtml;
|
||||
private IUmbracoContextAccessor UmbracoContextAccessor => Context.RequestServices.GetRequiredService<IUmbracoContextAccessor>();
|
||||
private IGlobalSettings GlobalSettings => Context.RequestServices.GetRequiredService<IGlobalSettings>();
|
||||
private IContentSettings ContentSettings => Context.RequestServices.GetRequiredService<IContentSettings>();
|
||||
private IProfilerHtml ProfilerHtml => Context.RequestServices.GetRequiredService<IProfilerHtml>();
|
||||
|
||||
protected IUmbracoContext UmbracoContext => _umbracoContext ??= _umbracoContextAccessor.UmbracoContext;
|
||||
|
||||
protected UmbracoViewPage()
|
||||
{
|
||||
_umbracoContextAccessor = Context.RequestServices.GetRequiredService<IUmbracoContextAccessor>();
|
||||
_globalSettings = Context.RequestServices.GetRequiredService<IGlobalSettings>();
|
||||
_contentSettings = Context.RequestServices.GetRequiredService<IContentSettings>();
|
||||
_profilerHtml = Context.RequestServices.GetRequiredService<IProfilerHtml>();
|
||||
}
|
||||
protected IUmbracoContext UmbracoContext => _umbracoContext ??= UmbracoContextAccessor.UmbracoContext;
|
||||
|
||||
|
||||
public override void Write(object value)
|
||||
@@ -68,15 +61,15 @@ namespace Umbraco.Web.Common.AspNetCore
|
||||
{
|
||||
// creating previewBadge markup
|
||||
markupToInject =
|
||||
string.Format(_contentSettings.PreviewBadge,
|
||||
Current.IOHelper.ResolveUrl(_globalSettings.UmbracoPath),
|
||||
string.Format(ContentSettings.PreviewBadge,
|
||||
Current.IOHelper.ResolveUrl(GlobalSettings.UmbracoPath),
|
||||
Context.Request.GetEncodedUrl(),
|
||||
UmbracoContext.PublishedRequest.PublishedContent.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
// creating mini-profiler markup
|
||||
markupToInject = _profilerHtml.Render();
|
||||
markupToInject = ProfilerHtml.Render();
|
||||
}
|
||||
|
||||
var sb = new StringBuilder(text);
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
@@ -25,8 +29,10 @@ namespace Umbraco.Web.Common.Macros
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly Func<IUmbracoContext> _getUmbracoContext;
|
||||
|
||||
public PartialViewMacroEngine(IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IHttpContextAccessor httpContextAccessor, IIOHelper ioHelper)
|
||||
public PartialViewMacroEngine(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IIOHelper ioHelper)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_ioHelper = ioHelper;
|
||||
@@ -72,8 +78,18 @@ namespace Umbraco.Web.Common.Macros
|
||||
routeVals.Values.Add("action", "Index");
|
||||
routeVals.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx); //required for UmbracoViewPage
|
||||
|
||||
//lets render this controller as a child action
|
||||
var viewContext = new ViewContext();
|
||||
var modelMetadataProvider = httpContext.RequestServices.GetRequiredService<IModelMetadataProvider>();
|
||||
var tempDataProvider = httpContext.RequestServices.GetRequiredService<ITempDataProvider>();
|
||||
|
||||
var viewContext = new ViewContext(
|
||||
new ActionContext(httpContext, httpContext.GetRouteData(), new ControllerActionDescriptor()),
|
||||
new FakeView(),
|
||||
new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary()),
|
||||
new TempDataDictionary(httpContext, tempDataProvider),
|
||||
TextWriter.Null,
|
||||
new HtmlHelperOptions()
|
||||
);
|
||||
|
||||
|
||||
routeVals.DataTokens.Add("ParentActionViewContext", viewContext);
|
||||
|
||||
@@ -93,7 +109,17 @@ namespace Umbraco.Web.Common.Macros
|
||||
|
||||
return new MacroContent { Text = output };
|
||||
}
|
||||
private class FakeView : IView
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task RenderAsync(ViewContext context)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Path { get; } = "View";
|
||||
}
|
||||
private string GetVirtualPathFromPhysicalPath(string physicalPath)
|
||||
{
|
||||
var rootpath = _ioHelper.MapPath("~/");
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using Umbraco.Web.Models;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Web.Models;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
namespace Umbraco.Web.Macros
|
||||
{
|
||||
@@ -18,7 +20,9 @@ namespace Umbraco.Web.Macros
|
||||
private readonly MacroModel _macro;
|
||||
private readonly IPublishedContent _content;
|
||||
|
||||
public PartialViewMacroViewComponent(MacroModel macro, IPublishedContent content)
|
||||
public PartialViewMacroViewComponent(
|
||||
MacroModel macro,
|
||||
IPublishedContent content)
|
||||
{
|
||||
_macro = macro;
|
||||
_content = content;
|
||||
@@ -32,7 +36,37 @@ namespace Umbraco.Web.Macros
|
||||
_macro.Alias,
|
||||
_macro.Name,
|
||||
_macro.Properties.ToDictionary(x => x.Key, x => (object)x.Value));
|
||||
return View(_macro.MacroSource, model);
|
||||
var result = View(_macro.MacroSource, model);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private class MacroViewEngine : ICompositeViewEngine
|
||||
{
|
||||
private readonly ICompositeViewEngine _compositeViewEngine;
|
||||
private readonly IWebHostEnvironment _webHostEnvironment;
|
||||
|
||||
public MacroViewEngine(ICompositeViewEngine compositeViewEngine, IWebHostEnvironment webHostEnvironment)
|
||||
{
|
||||
_compositeViewEngine = compositeViewEngine;
|
||||
_webHostEnvironment = webHostEnvironment;
|
||||
}
|
||||
|
||||
public ViewEngineResult FindView(ActionContext context, string viewName, bool isMainPage)
|
||||
{
|
||||
var result = _compositeViewEngine.FindView(context, viewName, isMainPage);
|
||||
return result;
|
||||
}
|
||||
|
||||
public ViewEngineResult GetView(string executingFilePath, string viewPath, bool isMainPage)
|
||||
{
|
||||
var result = _compositeViewEngine.GetView(executingFilePath, viewPath, isMainPage);
|
||||
return result;
|
||||
}
|
||||
|
||||
public IReadOnlyList<IViewEngine> ViewEngines => _compositeViewEngine.ViewEngines;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
9
src/Umbraco.Web.Common/Macros/UmbracoMacroResult.cs
Normal file
9
src/Umbraco.Web.Common/Macros/UmbracoMacroResult.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
|
||||
namespace Umbraco.Web.Macros
|
||||
{
|
||||
internal class UmbracoMacroResult : ViewViewComponentResult
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
10
src/Umbraco.Web.Common/Routing/PublicAccessChecker.cs
Normal file
10
src/Umbraco.Web.Common/Routing/PublicAccessChecker.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Common.Routing
|
||||
{
|
||||
public class PublicAccessChecker : IPublicAccessChecker
|
||||
{
|
||||
//TODO implement
|
||||
public PublicAccessStatus HasMemberAccessToContent(int publishedContentId) => PublicAccessStatus.AccessAccepted;
|
||||
}
|
||||
}
|
||||
766
src/Umbraco.Web.Common/Routing/PublishedRouter.cs
Normal file
766
src/Umbraco.Web.Common/Routing/PublishedRouter.cs
Normal file
@@ -0,0 +1,766 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Security;
|
||||
namespace Umbraco.Web.Common.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the default <see cref="IPublishedRouter"/> implementation.
|
||||
/// </summary>
|
||||
public class PublishedRouter : IPublishedRouter
|
||||
{
|
||||
private readonly IWebRoutingSettings _webRoutingSettings;
|
||||
private readonly ContentFinderCollection _contentFinders;
|
||||
private readonly IContentLastChanceFinder _contentLastChanceFinder;
|
||||
private readonly IProfilingLogger _profilingLogger;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IPublishedUrlProvider _publishedUrlProvider;
|
||||
private readonly IRequestAccessor _requestAccessor;
|
||||
private readonly IPublishedValueFallback _publishedValueFallback;
|
||||
private readonly IPublicAccessChecker _publicAccessChecker;
|
||||
private readonly IFileService _fileService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IPublicAccessService _publicAccessService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PublishedRouter"/> class.
|
||||
/// </summary>
|
||||
public PublishedRouter(
|
||||
IWebRoutingSettings webRoutingSettings,
|
||||
ContentFinderCollection contentFinders,
|
||||
IContentLastChanceFinder contentLastChanceFinder,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IProfilingLogger proflog,
|
||||
IPublishedUrlProvider publishedUrlProvider,
|
||||
IRequestAccessor requestAccessor,
|
||||
IPublishedValueFallback publishedValueFallback,
|
||||
IPublicAccessChecker publicAccessChecker,
|
||||
IFileService fileService,
|
||||
IContentTypeService contentTypeService,
|
||||
IPublicAccessService publicAccessService)
|
||||
{
|
||||
_webRoutingSettings = webRoutingSettings ?? throw new ArgumentNullException(nameof(webRoutingSettings));
|
||||
_contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders));
|
||||
_contentLastChanceFinder = contentLastChanceFinder ?? throw new ArgumentNullException(nameof(contentLastChanceFinder));
|
||||
_profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog));
|
||||
_variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
|
||||
_logger = proflog;
|
||||
_publishedUrlProvider = publishedUrlProvider;
|
||||
_requestAccessor = requestAccessor;
|
||||
_publishedValueFallback = publishedValueFallback;
|
||||
_publicAccessChecker = publicAccessChecker;
|
||||
_fileService = fileService;
|
||||
_contentTypeService = contentTypeService;
|
||||
_publicAccessService = publicAccessService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedRequest CreateRequest(IUmbracoContext umbracoContext, Uri uri = null)
|
||||
{
|
||||
return new PublishedRequest(this, umbracoContext, _webRoutingSettings, uri ?? umbracoContext.CleanedUmbracoUrl);
|
||||
}
|
||||
|
||||
#region Request
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryRouteRequest(IPublishedRequest request)
|
||||
{
|
||||
// disabled - is it going to change the routing?
|
||||
//_pcr.OnPreparing();
|
||||
|
||||
FindDomain(request);
|
||||
if (request.IsRedirect) return false;
|
||||
if (request.HasPublishedContent) return true;
|
||||
FindPublishedContent(request);
|
||||
if (request.IsRedirect) return false;
|
||||
|
||||
// don't handle anything - we just want to ensure that we find the content
|
||||
//HandlePublishedContent();
|
||||
//FindTemplate();
|
||||
//FollowExternalRedirect();
|
||||
//HandleWildcardDomains();
|
||||
|
||||
// disabled - we just want to ensure that we find the content
|
||||
//_pcr.OnPrepared();
|
||||
|
||||
return request.HasPublishedContent;
|
||||
}
|
||||
|
||||
private void SetVariationContext(string culture)
|
||||
{
|
||||
var variationContext = _variationContextAccessor.VariationContext;
|
||||
if (variationContext != null && variationContext.Culture == culture) return;
|
||||
_variationContextAccessor.VariationContext = new VariationContext(culture);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool PrepareRequest(IPublishedRequest request)
|
||||
{
|
||||
// note - at that point the original legacy module did something do handle IIS custom 404 errors
|
||||
// ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support
|
||||
// "directory urls" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain
|
||||
// to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk.
|
||||
//
|
||||
// to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors
|
||||
// so that they point to a non-existing page eg /redirect-404.aspx
|
||||
// TODO: SD: We need more information on this for when we release 4.10.0 as I'm not sure what this means.
|
||||
|
||||
// trigger the Preparing event - at that point anything can still be changed
|
||||
// the idea is that it is possible to change the uri
|
||||
//
|
||||
request.OnPreparing();
|
||||
|
||||
//find domain
|
||||
FindDomain(request);
|
||||
|
||||
// if request has been flagged to redirect then return
|
||||
// whoever called us is in charge of actually redirecting
|
||||
if (request.IsRedirect)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// set the culture on the thread - once, so it's set when running document lookups
|
||||
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture;
|
||||
SetVariationContext(request.Culture.Name);
|
||||
|
||||
//find the published content if it's not assigned. This could be manually assigned with a custom route handler, or
|
||||
// with something like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. Those in turn call this method
|
||||
// to setup the rest of the pipeline but we don't want to run the finders since there's one assigned.
|
||||
if (request.PublishedContent == null)
|
||||
{
|
||||
// find the document & template
|
||||
FindPublishedContentAndTemplate(request);
|
||||
}
|
||||
|
||||
// handle wildcard domains
|
||||
HandleWildcardDomains(request);
|
||||
|
||||
// set the culture on the thread -- again, 'cos it might have changed due to a finder or wildcard domain
|
||||
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture;
|
||||
SetVariationContext(request.Culture.Name);
|
||||
|
||||
// trigger the Prepared event - at that point it is still possible to change about anything
|
||||
// even though the request might be flagged for redirection - we'll redirect _after_ the event
|
||||
//
|
||||
// also, OnPrepared() will make the PublishedRequest readonly, so nothing can change
|
||||
//
|
||||
request.OnPrepared();
|
||||
|
||||
// we don't take care of anything so if the content has changed, it's up to the user
|
||||
// to find out the appropriate template
|
||||
|
||||
//complete the PCR and assign the remaining values
|
||||
return ConfigureRequest(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method
|
||||
/// finalizes the PCR with the values assigned.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns false if the request was not successfully configured
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values
|
||||
/// but need to finalize it themselves.
|
||||
/// </remarks>
|
||||
public bool ConfigureRequest(IPublishedRequest frequest)
|
||||
{
|
||||
if (frequest.HasPublishedContent == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// set the culture on the thread -- again, 'cos it might have changed in the event handler
|
||||
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = frequest.Culture;
|
||||
SetVariationContext(frequest.Culture.Name);
|
||||
|
||||
// if request has been flagged to redirect, or has no content to display,
|
||||
// then return - whoever called us is in charge of actually redirecting
|
||||
if (frequest.IsRedirect || frequest.HasPublishedContent == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// we may be 404 _and_ have a content
|
||||
|
||||
// can't go beyond that point without a PublishedContent to render
|
||||
// it's ok not to have a template, in order to give MVC a chance to hijack routes
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateRequestToNotFound(IPublishedRequest request)
|
||||
{
|
||||
// clear content
|
||||
var content = request.PublishedContent;
|
||||
request.PublishedContent = null;
|
||||
|
||||
HandlePublishedContent(request); // will go 404
|
||||
FindTemplate(request);
|
||||
|
||||
// if request has been flagged to redirect then return
|
||||
// whoever called us is in charge of redirecting
|
||||
if (request.IsRedirect)
|
||||
return;
|
||||
|
||||
if (request.HasPublishedContent == false)
|
||||
{
|
||||
// means the engine could not find a proper document to handle 404
|
||||
// restore the saved content so we know it exists
|
||||
request.PublishedContent = content;
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.HasTemplate == false)
|
||||
{
|
||||
// means we may have a document, but we have no template
|
||||
// at that point there isn't much we can do and there is no point returning
|
||||
// to Mvc since Mvc can't do much either
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Domain
|
||||
|
||||
/// <summary>
|
||||
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether a domain was found.</returns>
|
||||
internal bool FindDomain(IPublishedRequest request)
|
||||
{
|
||||
const string tracePrefix = "FindDomain: ";
|
||||
|
||||
// note - we are not handling schemes nor ports here.
|
||||
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri);
|
||||
|
||||
var domainsCache = request.UmbracoContext.PublishedSnapshot.Domains;
|
||||
var domains = domainsCache.GetAll(includeWildcards: false).ToList();
|
||||
|
||||
// determines whether a domain corresponds to a published document, since some
|
||||
// domains may exist but on a document that has been unpublished - as a whole - or
|
||||
// that is not published for the domain's culture - in which case the domain does
|
||||
// not apply
|
||||
bool IsPublishedContentDomain(Domain domain)
|
||||
{
|
||||
// just get it from content cache - optimize there, not here
|
||||
var domainDocument = request.UmbracoContext.PublishedSnapshot.Content.GetById(domain.ContentId);
|
||||
|
||||
// not published - at all
|
||||
if (domainDocument == null)
|
||||
return false;
|
||||
|
||||
// invariant - always published
|
||||
if (!domainDocument.ContentType.VariesByCulture())
|
||||
return true;
|
||||
|
||||
// variant, ensure that the culture corresponding to the domain's language is published
|
||||
return domainDocument.Cultures.ContainsKey(domain.Culture.Name);
|
||||
}
|
||||
|
||||
domains = domains.Where(IsPublishedContentDomain).ToList();
|
||||
|
||||
var defaultCulture = domainsCache.DefaultCulture;
|
||||
|
||||
// try to find a domain matching the current request
|
||||
var domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture);
|
||||
|
||||
// handle domain - always has a contentId and a culture
|
||||
if (domainAndUri != null)
|
||||
{
|
||||
// matching an existing domain
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture);
|
||||
|
||||
request.Domain = domainAndUri;
|
||||
request.Culture = domainAndUri.Culture;
|
||||
|
||||
// canonical? not implemented at the moment
|
||||
// if (...)
|
||||
// {
|
||||
// _pcr.RedirectUrl = "...";
|
||||
// return true;
|
||||
// }
|
||||
}
|
||||
else
|
||||
{
|
||||
// not matching any existing domain
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Matches no domain", tracePrefix);
|
||||
|
||||
request.Culture = defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture);
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture.Name);
|
||||
|
||||
return request.Domain != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for wildcard domains in the path and updates <c>Culture</c> accordingly.
|
||||
/// </summary>
|
||||
internal void HandleWildcardDomains(IPublishedRequest request)
|
||||
{
|
||||
const string tracePrefix = "HandleWildcardDomains: ";
|
||||
|
||||
if (request.HasPublishedContent == false)
|
||||
return;
|
||||
|
||||
var nodePath = request.PublishedContent.Path;
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Path={NodePath}", tracePrefix, nodePath);
|
||||
var rootNodeId = request.HasDomain ? request.Domain.ContentId : (int?)null;
|
||||
var domain = DomainUtilities.FindWildcardDomainInPath(request.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId);
|
||||
|
||||
// always has a contentId and a culture
|
||||
if (domain != null)
|
||||
{
|
||||
request.Culture = domain.Culture;
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}No match.", tracePrefix);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rendering engine
|
||||
|
||||
internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions)
|
||||
{
|
||||
if (directory == null || directory.Exists == false)
|
||||
return false;
|
||||
|
||||
var pos = alias.IndexOf('/');
|
||||
if (pos > 0)
|
||||
{
|
||||
// recurse
|
||||
var subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault();
|
||||
alias = alias.Substring(pos + 1);
|
||||
return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions);
|
||||
}
|
||||
|
||||
// look here
|
||||
return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Document and template
|
||||
|
||||
/// <inheritdoc />
|
||||
public ITemplate GetTemplate(string alias)
|
||||
{
|
||||
return _fileService.GetTemplate(alias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the Umbraco document (if any) matching the request, and updates the PublishedRequest accordingly.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether a document and template were found.</returns>
|
||||
private void FindPublishedContentAndTemplate(IPublishedRequest request)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", request.Uri.AbsolutePath);
|
||||
|
||||
// run the document finders
|
||||
FindPublishedContent(request);
|
||||
|
||||
// if request has been flagged to redirect then return
|
||||
// whoever called us is in charge of actually redirecting
|
||||
// -- do not process anything any further --
|
||||
if (request.IsRedirect)
|
||||
return;
|
||||
|
||||
// not handling umbracoRedirect here but after LookupDocument2
|
||||
// so internal redirect, 404, etc has precedence over redirect
|
||||
|
||||
// handle not-found, redirects, access...
|
||||
HandlePublishedContent(request);
|
||||
|
||||
// find a template
|
||||
FindTemplate(request);
|
||||
|
||||
// handle umbracoRedirect
|
||||
FollowExternalRedirect(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the document matching the request, by running the IPublishedContentFinder instances.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">There is no finder collection.</exception>
|
||||
internal void FindPublishedContent(IPublishedRequest request)
|
||||
{
|
||||
const string tracePrefix = "FindPublishedContent: ";
|
||||
|
||||
// look for the document
|
||||
// the first successful finder, if any, will set this.PublishedContent, and may also set this.Template
|
||||
// some finders may implement caching
|
||||
|
||||
using (_profilingLogger.DebugDuration<PublishedRouter>(
|
||||
$"{tracePrefix}Begin finders",
|
||||
$"{tracePrefix}End finders, {(request.HasPublishedContent ? "a document was found" : "no document was found")}"))
|
||||
{
|
||||
//iterate but return on first one that finds it
|
||||
var found = _contentFinders.Any(finder =>
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("Finder {ContentFinderType}", finder.GetType().FullName);
|
||||
return finder.TryFindContent(request);
|
||||
});
|
||||
}
|
||||
|
||||
// indicate that the published content (if any) we have at the moment is the
|
||||
// one that was found by the standard finders before anything else took place.
|
||||
request.SetIsInitialPublishedContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the published content (if any).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Handles "not found", internal redirects, access validation...
|
||||
/// things that must be handled in one place because they can create loops
|
||||
/// </remarks>
|
||||
private void HandlePublishedContent(IPublishedRequest request)
|
||||
{
|
||||
// because these might loop, we have to have some sort of infinite loop detection
|
||||
int i = 0, j = 0;
|
||||
const int maxLoop = 8;
|
||||
do
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Loop {LoopCounter}", i);
|
||||
|
||||
// handle not found
|
||||
if (request.HasPublishedContent == false)
|
||||
{
|
||||
request.Is404 = true;
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: No document, try last chance lookup");
|
||||
|
||||
// if it fails then give up, there isn't much more that we can do
|
||||
if (_contentLastChanceFinder.TryFindContent(request) == false)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Failed to find a document, give up");
|
||||
break;
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Found a document");
|
||||
}
|
||||
|
||||
// follow internal redirects as long as it's not running out of control ie infinite loop of some sort
|
||||
j = 0;
|
||||
while (FollowInternalRedirects(request) && j++ < maxLoop)
|
||||
{ }
|
||||
if (j == maxLoop) // we're running out of control
|
||||
break;
|
||||
|
||||
// ensure access
|
||||
if (request.HasPublishedContent)
|
||||
EnsurePublishedContentAccess(request);
|
||||
|
||||
// loop while we don't have page, ie the redirect or access
|
||||
// got us to nowhere and now we need to run the notFoundLookup again
|
||||
// as long as it's not running out of control ie infinite loop of some sort
|
||||
|
||||
} while (request.HasPublishedContent == false && i++ < maxLoop);
|
||||
|
||||
if (i == maxLoop || j == maxLoop)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Looks like we are running into an infinite loop, abort");
|
||||
request.PublishedContent = null;
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: End");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Follows internal redirections through the <c>umbracoInternalRedirectId</c> document property.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether redirection took place and led to a new published document.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.</para>
|
||||
/// <para>As per legacy, if the redirect does not work, we just ignore it.</para>
|
||||
/// </remarks>
|
||||
private bool FollowInternalRedirects(IPublishedRequest request)
|
||||
{
|
||||
if (request.PublishedContent == null)
|
||||
throw new InvalidOperationException("There is no PublishedContent.");
|
||||
|
||||
// don't try to find a redirect if the property doesn't exist
|
||||
if (request.PublishedContent.HasProperty(Core.Constants.Conventions.Content.InternalRedirectId) == false)
|
||||
return false;
|
||||
|
||||
var redirect = false;
|
||||
var valid = false;
|
||||
IPublishedContent internalRedirectNode = null;
|
||||
var internalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Core.Constants.Conventions.Content.InternalRedirectId, defaultValue: -1);
|
||||
|
||||
if (internalRedirectId > 0)
|
||||
{
|
||||
// try and get the redirect node from a legacy integer ID
|
||||
valid = true;
|
||||
internalRedirectNode = request.UmbracoContext.Content.GetById(internalRedirectId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var udiInternalRedirectId = request.PublishedContent.Value<GuidUdi>(_publishedValueFallback, Core.Constants.Conventions.Content.InternalRedirectId);
|
||||
if (udiInternalRedirectId != null)
|
||||
{
|
||||
// try and get the redirect node from a UDI Guid
|
||||
valid = true;
|
||||
internalRedirectNode = request.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid);
|
||||
}
|
||||
}
|
||||
|
||||
if (valid == false)
|
||||
{
|
||||
// bad redirect - log and display the current page (legacy behavior)
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.",
|
||||
request.PublishedContent.GetProperty(Core.Constants.Conventions.Content.InternalRedirectId).GetSourceValue());
|
||||
}
|
||||
|
||||
if (internalRedirectNode == null)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.",
|
||||
request.PublishedContent.GetProperty(Core.Constants.Conventions.Content.InternalRedirectId).GetSourceValue());
|
||||
}
|
||||
else if (internalRedirectId == request.PublishedContent.Id)
|
||||
{
|
||||
// redirect to self
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Redirecting to self, ignore");
|
||||
}
|
||||
else
|
||||
{
|
||||
request.SetInternalRedirectPublishedContent(internalRedirectNode); // don't use .PublishedContent here
|
||||
redirect = true;
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Redirecting to id={InternalRedirectId}", internalRedirectId);
|
||||
}
|
||||
|
||||
return redirect;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that access to current node is permitted.
|
||||
/// </summary>
|
||||
/// <remarks>Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.</remarks>
|
||||
private void EnsurePublishedContentAccess(IPublishedRequest request)
|
||||
{
|
||||
if (request.PublishedContent == null)
|
||||
throw new InvalidOperationException("There is no PublishedContent.");
|
||||
|
||||
var path = request.PublishedContent.Path;
|
||||
|
||||
var publicAccessAttempt = _publicAccessService.IsProtected(path);
|
||||
|
||||
if (publicAccessAttempt)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Page is protected, check for access");
|
||||
|
||||
var status = _publicAccessChecker.HasMemberAccessToContent(request.PublishedContent.Id);
|
||||
switch (status)
|
||||
{
|
||||
case PublicAccessStatus.NotLoggedIn:
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Not logged in, redirect to login page");
|
||||
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.LoginNodeId);
|
||||
break;
|
||||
case PublicAccessStatus.AccessDenied:
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Current member has not access, redirect to error page");
|
||||
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId);
|
||||
break;
|
||||
case PublicAccessStatus.LockedOut:
|
||||
_logger.Debug<PublishedRouter>("Current member is locked out, redirect to error page");
|
||||
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId);
|
||||
break;
|
||||
case PublicAccessStatus.NotApproved:
|
||||
_logger.Debug<PublishedRouter>("Current member is unapproved, redirect to error page");
|
||||
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId);
|
||||
break;
|
||||
case PublicAccessStatus.AccessAccepted:
|
||||
_logger.Debug<PublishedRouter>("Current member has access");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Page is not protected");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPublishedContentAsOtherPage(IPublishedRequest request, int errorPageId)
|
||||
{
|
||||
if (errorPageId != request.PublishedContent.Id)
|
||||
request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a template for the current node, if any.
|
||||
/// </summary>
|
||||
private void FindTemplate(IPublishedRequest request)
|
||||
{
|
||||
// NOTE: at the moment there is only 1 way to find a template, and then ppl must
|
||||
// use the Prepared event to change the template if they wish. Should we also
|
||||
// implement an ITemplateFinder logic?
|
||||
|
||||
if (request.PublishedContent == null)
|
||||
{
|
||||
request.TemplateModel = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// read the alternate template alias, from querystring, form, cookie or server vars,
|
||||
// only if the published content is the initial once, else the alternate template
|
||||
// does not apply
|
||||
// + optionally, apply the alternate template on internal redirects
|
||||
var useAltTemplate = request.IsInitialPublishedContent
|
||||
|| (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent);
|
||||
var altTemplate = useAltTemplate
|
||||
? _requestAccessor.GetRequestValue(Core.Constants.Conventions.Url.AltTemplate)
|
||||
: null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(altTemplate))
|
||||
{
|
||||
// we don't have an alternate template specified. use the current one if there's one already,
|
||||
// which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...),
|
||||
// else lookup the template id on the document then lookup the template with that id.
|
||||
|
||||
if (request.HasTemplate)
|
||||
{
|
||||
_logger.Debug<IPublishedRequest>("FindTemplate: Has a template already, and no alternate template.");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: When we remove the need for a database for templates, then this id should be irrelevant,
|
||||
// not sure how were going to do this nicely.
|
||||
|
||||
// TODO: We need to limit altTemplate to only allow templates that are assigned to the current document type!
|
||||
// if the template isn't assigned to the document type we should log a warning and return 404
|
||||
|
||||
var templateId = request.PublishedContent.TemplateId;
|
||||
request.TemplateModel = GetTemplateModel(templateId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have an alternate template specified. lookup the template with that alias
|
||||
// this means the we override any template that a content lookup might have set
|
||||
// so /path/to/page/template1?altTemplate=template2 will use template2
|
||||
|
||||
// ignore if the alias does not match - just trace
|
||||
|
||||
if (request.HasTemplate)
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Has a template already, but also an alternative template.");
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate);
|
||||
|
||||
// IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings
|
||||
if (request.PublishedContent.IsAllowedTemplate(
|
||||
_fileService,
|
||||
_contentTypeService,
|
||||
_webRoutingSettings.DisableAlternativeTemplates,
|
||||
_webRoutingSettings.ValidateAlternativeTemplates,
|
||||
altTemplate))
|
||||
{
|
||||
// allowed, use
|
||||
var template = _fileService.GetTemplate(altTemplate);
|
||||
|
||||
if (template != null)
|
||||
{
|
||||
request.TemplateModel = template;
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: The alternative template with alias={AltTemplate} does not exist, ignoring.", altTemplate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn<PublishedRouter>("FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", altTemplate, request.PublishedContent.Id);
|
||||
|
||||
// no allowed, back to default
|
||||
var templateId = request.PublishedContent.TemplateId;
|
||||
request.TemplateModel = GetTemplateModel(templateId);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.HasTemplate == false)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: No template was found.");
|
||||
|
||||
// initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true
|
||||
// then reset _pcr.Document to null to force a 404.
|
||||
//
|
||||
// but: because we want to let MVC hijack routes even though no template is defined, we decide that
|
||||
// a missing template is OK but the request will then be forwarded to MVC, which will need to take
|
||||
// care of everything.
|
||||
//
|
||||
// so, don't set _pcr.Document to null here
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", request.TemplateModel.Id, request.TemplateModel.Alias);
|
||||
}
|
||||
}
|
||||
|
||||
private ITemplate GetTemplateModel(int? templateId)
|
||||
{
|
||||
if (templateId.HasValue == false || templateId.Value == default)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("GetTemplateModel: No template.");
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("GetTemplateModel: Get template id={TemplateId}", templateId);
|
||||
|
||||
if (templateId == null)
|
||||
throw new InvalidOperationException("The template is not set, the page cannot render.");
|
||||
|
||||
var template = _fileService.GetTemplate(templateId.Value);
|
||||
if (template == null)
|
||||
throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render.");
|
||||
_logger.Debug<PublishedRouter>("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
|
||||
return template;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Follows external redirection through <c>umbracoRedirect</c> document property.
|
||||
/// </summary>
|
||||
/// <remarks>As per legacy, if the redirect does not work, we just ignore it.</remarks>
|
||||
private void FollowExternalRedirect(IPublishedRequest request)
|
||||
{
|
||||
if (request.HasPublishedContent == false) return;
|
||||
|
||||
// don't try to find a redirect if the property doesn't exist
|
||||
if (request.PublishedContent.HasProperty(Core.Constants.Conventions.Content.Redirect) == false)
|
||||
return;
|
||||
|
||||
var redirectId = request.PublishedContent.Value(_publishedValueFallback, Core.Constants.Conventions.Content.Redirect, defaultValue: -1);
|
||||
var redirectUrl = "#";
|
||||
if (redirectId > 0)
|
||||
{
|
||||
redirectUrl = _publishedUrlProvider.GetUrl(redirectId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// might be a UDI instead of an int Id
|
||||
var redirectUdi = request.PublishedContent.Value<GuidUdi>(_publishedValueFallback, Core.Constants.Conventions.Content.Redirect);
|
||||
if (redirectUdi != null)
|
||||
redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid);
|
||||
}
|
||||
if (redirectUrl != "#")
|
||||
request.SetRedirect(redirectUrl);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,11 @@ using Umbraco.Web.Common.Controllers;
|
||||
using System;
|
||||
using Umbraco.Web.Common.Middleware;
|
||||
using Umbraco.Web.Common.ModelBinding;
|
||||
using Umbraco.Web.Common.Routing;
|
||||
using Umbraco.Web.Common.Templates;
|
||||
using Umbraco.Web.Search;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.Templates;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Web.Common.Runtime
|
||||
@@ -99,7 +102,8 @@ namespace Umbraco.Web.Common.Runtime
|
||||
|
||||
composition.RegisterUnique<UmbracoJsonModelBinder>();
|
||||
|
||||
|
||||
composition.RegisterUnique<ITemplateRenderer, TemplateRenderer>();
|
||||
composition.RegisterUnique<IPublicAccessChecker, PublicAccessChecker>();
|
||||
|
||||
|
||||
}
|
||||
|
||||
198
src/Umbraco.Web.Common/Templates/TemplateRenderer.cs
Normal file
198
src/Umbraco.Web.Common/Templates/TemplateRenderer.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Templates;
|
||||
|
||||
namespace Umbraco.Web.Common.Templates
|
||||
{
|
||||
/// <summary>
|
||||
/// This is used purely for the RenderTemplate functionality in Umbraco
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This allows you to render an MVC template based purely off of a node id and an optional alttemplate id as string output.
|
||||
/// </remarks>
|
||||
internal class TemplateRenderer : ITemplateRenderer
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IPublishedRouter _publishedRouter;
|
||||
private readonly IFileService _fileService;
|
||||
private readonly ILocalizationService _languageService;
|
||||
private readonly IWebRoutingSettings _webRoutingSettings;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly ICompositeViewEngine _viewEngine;
|
||||
|
||||
public TemplateRenderer(IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IPublishedRouter publishedRouter,
|
||||
IFileService fileService,
|
||||
ILocalizationService textService,
|
||||
IWebRoutingSettings webRoutingSettings,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ICompositeViewEngine viewEngine)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
_publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter));
|
||||
_fileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
|
||||
_languageService = textService ?? throw new ArgumentNullException(nameof(textService));
|
||||
_webRoutingSettings = webRoutingSettings ?? throw new ArgumentNullException(nameof(webRoutingSettings));
|
||||
_shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper));
|
||||
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
|
||||
_viewEngine = viewEngine ?? throw new ArgumentNullException(nameof(viewEngine));
|
||||
}
|
||||
|
||||
public void Render(int pageId, int? altTemplateId, StringWriter writer)
|
||||
{
|
||||
if (writer == null) throw new ArgumentNullException(nameof(writer));
|
||||
|
||||
var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
// instantiate a request and process
|
||||
// important to use CleanedUmbracoUrl - lowercase path-only version of the current url, though this isn't going to matter
|
||||
// terribly much for this implementation since we are just creating a doc content request to modify it's properties manually.
|
||||
var contentRequest = _publishedRouter.CreateRequest(umbracoContext);
|
||||
|
||||
var doc = contentRequest.UmbracoContext.Content.GetById(pageId);
|
||||
|
||||
if (doc == null)
|
||||
{
|
||||
writer.Write("<!-- Could not render template for Id {0}, the document was not found -->", pageId);
|
||||
return;
|
||||
}
|
||||
|
||||
//in some cases the UmbracoContext will not have a PublishedRequest assigned to it if we are not in the
|
||||
//execution of a front-end rendered page. In this case set the culture to the default.
|
||||
//set the culture to the same as is currently rendering
|
||||
if (umbracoContext.PublishedRequest == null)
|
||||
{
|
||||
var defaultLanguage = _languageService.GetAllLanguages().FirstOrDefault();
|
||||
contentRequest.Culture = defaultLanguage == null
|
||||
? CultureInfo.CurrentUICulture
|
||||
: defaultLanguage.CultureInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
contentRequest.Culture = umbracoContext.PublishedRequest.Culture;
|
||||
}
|
||||
|
||||
//set the doc that was found by id
|
||||
contentRequest.PublishedContent = doc;
|
||||
//set the template, either based on the AltTemplate found or the standard template of the doc
|
||||
var templateId = _webRoutingSettings.DisableAlternativeTemplates || !altTemplateId.HasValue
|
||||
? doc.TemplateId
|
||||
: altTemplateId.Value;
|
||||
if (templateId.HasValue)
|
||||
contentRequest.TemplateModel = _fileService.GetTemplate(templateId.Value);
|
||||
|
||||
//if there is not template then exit
|
||||
if (contentRequest.HasTemplate == false)
|
||||
{
|
||||
if (altTemplateId.HasValue == false)
|
||||
{
|
||||
writer.Write("<!-- Could not render template for Id {0}, the document's template was not found with id {0}-->", doc.TemplateId);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write("<!-- Could not render template for Id {0}, the altTemplate was not found with id {0}-->", altTemplateId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//First, save all of the items locally that we know are used in the chain of execution, we'll need to restore these
|
||||
//after this page has rendered.
|
||||
SaveExistingItems(out var oldPublishedRequest);
|
||||
|
||||
try
|
||||
{
|
||||
//set the new items on context objects for this templates execution
|
||||
SetNewItemsOnContextObjects(contentRequest);
|
||||
|
||||
//Render the template
|
||||
ExecuteTemplateRendering(writer, contentRequest);
|
||||
}
|
||||
finally
|
||||
{
|
||||
//restore items on context objects to continuing rendering the parent template
|
||||
RestoreItems(oldPublishedRequest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ExecuteTemplateRendering(TextWriter sw, IPublishedRequest request)
|
||||
{
|
||||
var httpContext = _httpContextAccessor.GetRequiredHttpContext();
|
||||
|
||||
var viewResult = _viewEngine.GetView(null, $"~/Views/{request.TemplateAlias}.cshtml", false);
|
||||
|
||||
if (viewResult.Success == false)
|
||||
{
|
||||
throw new InvalidOperationException($"A view with the name {request.TemplateAlias} could not be found");
|
||||
}
|
||||
|
||||
var modelMetadataProvider = httpContext.RequestServices.GetRequiredService<IModelMetadataProvider>();
|
||||
var tempDataProvider = httpContext.RequestServices.GetRequiredService<ITempDataProvider>();
|
||||
|
||||
var viewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary())
|
||||
{
|
||||
Model = new ContentModel(request.PublishedContent)
|
||||
};
|
||||
|
||||
var writer = new StringWriter();
|
||||
var viewContext = new ViewContext(
|
||||
new ActionContext(httpContext, httpContext.GetRouteData(), new ControllerActionDescriptor()),
|
||||
viewResult.View,
|
||||
viewData,
|
||||
new TempDataDictionary(httpContext, tempDataProvider),
|
||||
writer,
|
||||
new HtmlHelperOptions()
|
||||
);
|
||||
|
||||
|
||||
viewResult.View.RenderAsync(viewContext).GetAwaiter().GetResult();
|
||||
|
||||
var output = writer.GetStringBuilder().ToString();
|
||||
|
||||
sw.Write(output);
|
||||
}
|
||||
|
||||
private void SetNewItemsOnContextObjects(IPublishedRequest request)
|
||||
{
|
||||
//now, set the new ones for this page execution
|
||||
_umbracoContextAccessor.UmbracoContext.PublishedRequest = request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save all items that we know are used for rendering execution to variables so we can restore after rendering
|
||||
/// </summary>
|
||||
private void SaveExistingItems(out IPublishedRequest oldPublishedRequest)
|
||||
{
|
||||
//Many objects require that these legacy items are in the http context items... before we render this template we need to first
|
||||
//save the values in them so that we can re-set them after we render so the rest of the execution works as per normal
|
||||
oldPublishedRequest = _umbracoContextAccessor.UmbracoContext.PublishedRequest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores all items back to their context's to continue normal page rendering execution
|
||||
/// </summary>
|
||||
private void RestoreItems(IPublishedRequest oldPublishedRequest)
|
||||
{
|
||||
_umbracoContextAccessor.UmbracoContext.PublishedRequest = oldPublishedRequest;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,5 +31,10 @@
|
||||
<_Parameter1>Umbraco.Tests.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Security\BackOfficeSignInManager.cs" />
|
||||
<Compile Remove="Macros\UmbracoMacroResult.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -61,6 +61,14 @@
|
||||
<SubType>Designer</SubType>
|
||||
</Content>
|
||||
<Content Remove="wwwroot\Web.config" />
|
||||
<Content Update="Views\MacroPartials\Gallery.cshtml">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="Views\Root.cshtml">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -69,13 +69,13 @@
|
||||
},
|
||||
"Content": {
|
||||
"Errors": {
|
||||
"Error404": {
|
||||
"default": "1047",
|
||||
"en-US": "$site/error [@name = 'error']",
|
||||
"en-UK": "8560867F-B88F-4C74-A9A4-679D8E5B3BFC"
|
||||
}
|
||||
"Error404": {
|
||||
"default": "1047",
|
||||
"en-US": "$site/error [@name = 'error']",
|
||||
"en-UK": "8560867F-B88F-4C74-A9A4-679D8E5B3BFC"
|
||||
}
|
||||
},
|
||||
"LoginBackgroundImage": "assets/img/login.jpg"
|
||||
"LoginBackgroundImage": "assets/img/login.jpg"
|
||||
},
|
||||
"RequestHandler": {
|
||||
"AddTrailingSlash": true,
|
||||
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Core.PackageActions;
|
||||
using Umbraco.Core.Packaging;
|
||||
@@ -121,7 +122,7 @@ namespace Umbraco.Web.Composing
|
||||
=> Factory.GetInstance<IUmbracoComponentRenderer>();
|
||||
public static ITagQuery TagQuery
|
||||
=> Factory.GetInstance<ITagQuery>();
|
||||
|
||||
|
||||
public static IRuntimeMinifier RuntimeMinifier
|
||||
=> Factory.GetInstance<IRuntimeMinifier>();
|
||||
|
||||
|
||||
@@ -188,10 +188,6 @@ namespace Umbraco.Web.Editors
|
||||
"mediaTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MediaTypeController>(
|
||||
controller => controller.GetAllowedChildren(0))
|
||||
},
|
||||
{
|
||||
"macroRenderingApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MacroRenderingController>(
|
||||
controller => controller.GetMacroParameters(0))
|
||||
},
|
||||
|
||||
{
|
||||
"currentUserApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<CurrentUserController>(
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.AspNet.SignalR;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Core.Runtime;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -42,7 +43,6 @@ namespace Umbraco.Web.Runtime
|
||||
|
||||
composition.RegisterUnique<ITemplateRenderer, TemplateRenderer>();
|
||||
|
||||
composition.RegisterUnique<IUmbracoComponentRenderer, UmbracoComponentRenderer>();
|
||||
|
||||
// register the umbraco helper - this is Transient! very important!
|
||||
// also, if not level.Run, we cannot really use the helper (during upgrade...)
|
||||
|
||||
@@ -304,7 +304,6 @@
|
||||
<Compile Include="WebAssets\CDF\UmbracoClientDependencyLoader.cs" />
|
||||
<Compile Include="UmbracoDefaultOwinStartup.cs" />
|
||||
<Compile Include="HtmlStringUtilities.cs" />
|
||||
<Compile Include="IUmbracoComponentRenderer.cs" />
|
||||
<Compile Include="Mvc\ProfilingView.cs" />
|
||||
<Compile Include="Mvc\ProfilingViewEngine.cs" />
|
||||
<Compile Include="CacheHelperExtensions.cs" />
|
||||
@@ -326,7 +325,6 @@
|
||||
<Compile Include="Mvc\MinifyJavaScriptResultAttribute.cs" />
|
||||
<Compile Include="Mvc\EnsurePublishedContentRequestAttribute.cs" />
|
||||
<Compile Include="Mvc\UmbracoViewPage.cs" />
|
||||
<Compile Include="Editors\MacroRenderingController.cs" />
|
||||
<Compile Include="Editors\MemberTypeController.cs" />
|
||||
<Compile Include="Editors\EntityController.cs" />
|
||||
<Compile Include="Editors\MemberController.cs" />
|
||||
@@ -346,7 +344,6 @@
|
||||
<Compile Include="Trees\TemplatesTreeController.cs" />
|
||||
<Compile Include="Trees\TreeControllerBase.cs" />
|
||||
<Compile Include="WebAssets\CDF\DependencyPathRenderer.cs" />
|
||||
<Compile Include="UmbracoComponentRenderer.cs" />
|
||||
<Compile Include="WebApi\AngularJsonMediaTypeFormatter.cs" />
|
||||
<Compile Include="WebApi\AngularJsonOnlyConfigurationAttribute.cs" />
|
||||
<Compile Include="Editors\Binders\MemberBinder.cs" />
|
||||
|
||||
@@ -7,6 +7,8 @@ using Umbraco.Composing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Xml;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
@@ -100,7 +102,7 @@ namespace Umbraco.Web
|
||||
/// <param name="contentId"></param>
|
||||
/// <param name="altTemplateId">If not specified, will use the template assigned to the node</param>
|
||||
/// <returns></returns>
|
||||
public IHtmlString RenderTemplate(int contentId, int? altTemplateId = null)
|
||||
public IHtmlEncodedString RenderTemplate(int contentId, int? altTemplateId = null)
|
||||
{
|
||||
return _componentRenderer.RenderTemplate(contentId, altTemplateId);
|
||||
}
|
||||
@@ -112,7 +114,7 @@ namespace Umbraco.Web
|
||||
/// </summary>
|
||||
/// <param name="alias">The alias.</param>
|
||||
/// <returns></returns>
|
||||
public IHtmlString RenderMacro(string alias)
|
||||
public IHtmlEncodedString RenderMacro(string alias)
|
||||
{
|
||||
return _componentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, null);
|
||||
}
|
||||
@@ -123,7 +125,7 @@ namespace Umbraco.Web
|
||||
/// <param name="alias">The alias.</param>
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <returns></returns>
|
||||
public IHtmlString RenderMacro(string alias, object parameters)
|
||||
public IHtmlEncodedString RenderMacro(string alias, object parameters)
|
||||
{
|
||||
return _componentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters?.ToDictionary<object>());
|
||||
}
|
||||
@@ -134,7 +136,7 @@ namespace Umbraco.Web
|
||||
/// <param name="alias">The alias.</param>
|
||||
/// <param name="parameters">The parameters.</param>
|
||||
/// <returns></returns>
|
||||
public IHtmlString RenderMacro(string alias, IDictionary<string, object> parameters)
|
||||
public IHtmlEncodedString RenderMacro(string alias, IDictionary<string, object> parameters)
|
||||
{
|
||||
return _componentRenderer.RenderMacro(AssignedContentItem?.Id ?? 0, alias, parameters);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user