Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/align-namespaces
# Conflicts: # src/Umbraco.Core/IO/ViewHelper.cs # src/Umbraco.Core/Models/ContentModel.cs # src/Umbraco.Core/Models/ContentModelOfTContent.cs # src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs # src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs # src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs # src/Umbraco.Web.Common/ApplicationModels/BackOfficeIdentityCultureConvention.cs # src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs # src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs # src/Umbraco.Web.Common/Controllers/RenderController.cs # src/Umbraco.Web.Common/Macros/PartialViewMacroPage.cs # src/Umbraco.Web.Common/Views/UmbracoViewPage.cs # src/Umbraco.Web.UI.NetCore/Views/Partials/blocklist/default.cshtml # src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3-fluid.cshtml # src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3.cshtml # src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/embed.cshtml # src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/macro.cshtml # src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs # src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
$cache = 4
|
||||
$nuget = "$scriptTemp\nuget.exe"
|
||||
# ensure the correct NuGet-source is used. This one is used by Umbraco
|
||||
$nugetsourceUmbraco = "https://www.myget.org/F/umbracocore/api/v3/index.json"
|
||||
$nugetsourceUmbraco = "https://www.myget.org/F/umbracoprereleases/api/v3/index.json"
|
||||
if (-not $local)
|
||||
{
|
||||
$source = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
|
||||
|
||||
@@ -51,12 +51,12 @@
|
||||
{
|
||||
param ( $semver )
|
||||
|
||||
$release = "" + $semver.Major + "." + $semver.Minor + "." + $semver.Patch
|
||||
|
||||
Write-Host "Update IIS Express port in csproj"
|
||||
$updater = New-Object "Umbraco.Build.ExpressPortUpdater"
|
||||
$csproj = "$($this.SolutionRoot)\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj"
|
||||
$updater.Update($csproj, $release)
|
||||
$port = "" + $semver.Major + $semver.Minor + ("" + $semver.Patch).PadLeft(2, '0')
|
||||
Write-Host "Update port in launchSettings.json to $port"
|
||||
$filePath = "$($this.SolutionRoot)\src\Umbraco.Web.UI.NetCore\Properties\launchSettings.json"
|
||||
$this.ReplaceFileText($filePath, `
|
||||
"http://localhost:(\d+)?", `
|
||||
"http://localhost:$port")
|
||||
})
|
||||
|
||||
$ubuild.DefineMethod("SandboxNode",
|
||||
|
||||
@@ -26,6 +26,16 @@
|
||||
<None Remove="umbraco\Data\**" />
|
||||
<None Remove="umbraco\logs\**" />
|
||||
<None Remove="umbraco\MediaCache\**" />
|
||||
<None Include="config\**\*.*">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
|
||||
</None>
|
||||
<None Include="umbraco\**\*.*">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Remove="umbraco\Data\**" />
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<Project>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>0.5.0</Version>
|
||||
<AssemblyVersion>0.5.0</AssemblyVersion>
|
||||
<InformationalVersion>0.5.0-beta001</InformationalVersion>
|
||||
<FileVersion>0.5.0</FileVersion>
|
||||
<Version>9.0.0</Version>
|
||||
<AssemblyVersion>9.0.0</AssemblyVersion>
|
||||
<InformationalVersion>9.0.0-beta001</InformationalVersion>
|
||||
<FileVersion>9.0.0</FileVersion>
|
||||
<LangVersion Condition="'$(LangVersion)' == ''">9.0</LangVersion>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<Company>Umbraco CMS</Company>
|
||||
<Copyright>Copyright © Umbraco 2021</Copyright>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
[assembly: AssemblyCompany("Umbraco")]
|
||||
[assembly: AssemblyCopyright("Copyright © Umbraco 2021")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
[assembly: NeutralResourcesLanguage("en-US")]
|
||||
|
||||
// versions
|
||||
// read https://stackoverflow.com/questions/64602/what-are-differences-between-assemblyversion-assemblyfileversion-and-assemblyin
|
||||
|
||||
// note: do NOT change anything here manually, use the build scripts
|
||||
|
||||
// this is the ONLY ONE the CLR cares about for compatibility
|
||||
// should change ONLY when "hard" breaking compatibility (manual change)
|
||||
[assembly: AssemblyVersion("0.5.0")]
|
||||
|
||||
// these are FYI and changed automatically
|
||||
[assembly: AssemblyFileVersion("0.5.0")]
|
||||
[assembly: AssemblyInformationalVersion("0.5.0-beta001")]
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models
|
||||
@@ -11,12 +11,7 @@ namespace Umbraco.Cms.Core.Models
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentModel"/> class with a content.
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
public ContentModel(IPublishedContent content)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException(nameof(content));
|
||||
Content = content;
|
||||
}
|
||||
public ContentModel(IPublishedContent content) => Content = content ?? throw new ArgumentNullException(nameof(content));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models
|
||||
{
|
||||
@@ -8,12 +8,8 @@ namespace Umbraco.Cms.Core.Models
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentModel{TContent}"/> class with a content.
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
public ContentModel(TContent content)
|
||||
: base(content)
|
||||
{
|
||||
Content = content;
|
||||
}
|
||||
: base(content) => Content = content;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -33,7 +33,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
|
||||
private const string PartialViewHeader = "@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage";
|
||||
private const string PartialViewHeader = "@inherits Umbraco.Web.Common.Views.UmbracoViewPage";
|
||||
private const string PartialViewMacroHeader = "@inherits Umbraco.Web.Common.Macros.PartialViewMacroPage";
|
||||
|
||||
public FileService(IScopeProvider uowProvider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Website.Routing
|
||||
|
||||
private class Render2Controller : RenderController
|
||||
{
|
||||
public Render2Controller(ILogger<RenderController> logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
public Render2Controller(ILogger<Render2Controller> logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
: base(logger, compositeViewEngine, umbracoContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Website.Routing
|
||||
|
||||
private class TestController : RenderController
|
||||
{
|
||||
public TestController(ILogger<RenderController> logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
public TestController(ILogger<TestController> logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
: base(logger, compositeViewEngine, umbracoContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -35,14 +35,15 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
private readonly IControllerFactory _controllerFactory;
|
||||
private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ApplicationTreeController"/> class.
|
||||
/// </summary>
|
||||
public ApplicationTreeController(
|
||||
ITreeService treeService,
|
||||
ISectionService sectionService,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IControllerFactory controllerFactory,
|
||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider
|
||||
)
|
||||
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
|
||||
{
|
||||
_treeService = treeService;
|
||||
_sectionService = sectionService;
|
||||
@@ -56,28 +57,31 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
/// </summary>
|
||||
/// <param name="application">The application to load tree for</param>
|
||||
/// <param name="tree">An optional single tree alias, if specified will only load the single tree for the request app</param>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <param name="queryStrings">The query strings</param>
|
||||
/// <param name="use">Tree use.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<ActionResult<TreeRootNode>> GetApplicationTrees(string application, string tree, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings, TreeUse use = TreeUse.Main)
|
||||
{
|
||||
application = application.CleanForXss();
|
||||
|
||||
if (string.IsNullOrEmpty(application))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var section = _sectionService.GetByAlias(application);
|
||||
if (section == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
//find all tree definitions that have the current application alias
|
||||
// find all tree definitions that have the current application alias
|
||||
var groupedTrees = _treeService.GetBySectionGrouped(application, use);
|
||||
var allTrees = groupedTrees.Values.SelectMany(x => x).ToList();
|
||||
|
||||
if (allTrees.Count == 0)
|
||||
{
|
||||
//if there are no trees defined for this section but the section is defined then we can have a simple
|
||||
//full screen section without trees
|
||||
// if there are no trees defined for this section but the section is defined then we can have a simple
|
||||
// full screen section without trees
|
||||
var name = _localizedTextService.Localize("sections/" + application);
|
||||
return TreeRootNode.CreateSingleTreeRoot(Constants.System.RootString, null, null, name, TreeNodeCollection.Empty, true);
|
||||
}
|
||||
@@ -90,7 +94,9 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
: allTrees.FirstOrDefault(x => x.TreeAlias == tree);
|
||||
|
||||
if (t == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var treeRootNode = await GetTreeRootNode(t, Constants.System.Root, queryStrings);
|
||||
|
||||
@@ -114,9 +120,12 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
{
|
||||
return nodeResult.Result;
|
||||
}
|
||||
|
||||
var node = nodeResult.Value;
|
||||
if (node != null)
|
||||
{
|
||||
nodes.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
var name = _localizedTextService.Localize("sections/" + application);
|
||||
@@ -148,11 +157,15 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
var node = nodeResult.Value;
|
||||
|
||||
if (node != null)
|
||||
{
|
||||
nodes.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (nodes.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// no name => third party
|
||||
// use localization key treeHeaders/thirdPartyGroup
|
||||
@@ -179,7 +192,9 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
private async Task<ActionResult<TreeNode>> TryGetRootNode(Tree tree, FormCollection querystring)
|
||||
{
|
||||
if (tree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tree));
|
||||
}
|
||||
|
||||
return await GetRootNode(tree, querystring);
|
||||
}
|
||||
@@ -190,7 +205,9 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
private async Task<ActionResult<TreeRootNode>> GetTreeRootNode(Tree tree, int id, FormCollection querystring)
|
||||
{
|
||||
if (tree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tree));
|
||||
}
|
||||
|
||||
var childrenResult = await GetChildren(tree, id, querystring);
|
||||
if (!(childrenResult.Result is null))
|
||||
@@ -222,7 +239,9 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
sectionRoot.Path = rootNode.Path;
|
||||
|
||||
foreach (var d in rootNode.AdditionalData)
|
||||
{
|
||||
sectionRoot.AdditionalData[d.Key] = d.Value;
|
||||
}
|
||||
|
||||
return sectionRoot;
|
||||
}
|
||||
@@ -233,7 +252,9 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
private async Task<ActionResult<TreeNode>> GetRootNode(Tree tree, FormCollection querystring)
|
||||
{
|
||||
if (tree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tree));
|
||||
}
|
||||
|
||||
var result = await GetApiControllerProxy(tree.TreeControllerType, "GetRootNode", querystring);
|
||||
|
||||
@@ -253,7 +274,10 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
var rootNode = rootNodeResult.Value;
|
||||
|
||||
if (rootNode == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to get root node for tree \"{tree.TreeAlias}\".");
|
||||
}
|
||||
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
@@ -263,7 +287,9 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
private async Task<ActionResult<TreeNodeCollection>> GetChildren(Tree tree, int id, FormCollection querystring)
|
||||
{
|
||||
if (tree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tree));
|
||||
}
|
||||
|
||||
// the method we proxy has an 'id' parameter which is *not* in the querystring,
|
||||
// we need to add it for the proxy to work (else, it does not find the method,
|
||||
@@ -299,7 +325,8 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
// note: this is all required in order to execute the auth-filters for the sub request, we
|
||||
// need to "trick" mvc into thinking that it is actually executing the proxied controller.
|
||||
|
||||
var controllerName = controllerType.Name.Substring(0, controllerType.Name.Length - 10); // remove controller part of name;
|
||||
var controllerName = ControllerExtensions.GetControllerName(controllerType);
|
||||
|
||||
// create proxy route data specifying the action & controller to execute
|
||||
var routeData = new RouteData(new RouteValueDictionary()
|
||||
{
|
||||
@@ -324,9 +351,12 @@ namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
var proxyControllerContext = new ControllerContext(actionContext);
|
||||
var controller = (TreeController)_controllerFactory.CreateController(proxyControllerContext);
|
||||
|
||||
// TODO: What about other filters? Will they execute?
|
||||
var isAllowed = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest(actionContext);
|
||||
if (!isAllowed)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Cms.Web.Common.Attributes;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.ApplicationModels
|
||||
{
|
||||
|
||||
// TODO: This should just exist in the back office project
|
||||
|
||||
/// <summary>
|
||||
@@ -13,45 +14,43 @@ namespace Umbraco.Cms.Web.Common.ApplicationModels
|
||||
/// </summary>
|
||||
public class BackOfficeApplicationModelProvider : IApplicationModelProvider
|
||||
{
|
||||
public BackOfficeApplicationModelProvider(IModelMetadataProvider modelMetadataProvider)
|
||||
private readonly List<IActionModelConvention> _actionModelConventions = new List<IActionModelConvention>()
|
||||
{
|
||||
ActionModelConventions = new List<IActionModelConvention>()
|
||||
{
|
||||
new BackOfficeIdentityCultureConvention()
|
||||
};
|
||||
}
|
||||
new BackOfficeIdentityCultureConvention()
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Will execute after <see cref="DefaultApplicationModelProvider"/>
|
||||
/// </summary>
|
||||
public int Order => 0;
|
||||
|
||||
public List<IActionModelConvention> ActionModelConventions { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnProvidersExecuted(ApplicationModelProviderContext context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnProvidersExecuting(ApplicationModelProviderContext context)
|
||||
{
|
||||
foreach (var controller in context.Result.Controllers)
|
||||
foreach (ControllerModel controller in context.Result.Controllers)
|
||||
{
|
||||
if (!IsBackOfficeController(controller))
|
||||
continue;
|
||||
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
foreach (var convention in ActionModelConventions)
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (ActionModel action in controller.Actions)
|
||||
{
|
||||
foreach (IActionModelConvention convention in _actionModelConventions)
|
||||
{
|
||||
convention.Apply(action);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsBackOfficeController(ControllerModel controller)
|
||||
=> controller.Attributes.OfType<IsBackOfficeAttribute>().Any();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,7 @@ namespace Umbraco.Cms.Web.Common.ApplicationModels
|
||||
|
||||
public class BackOfficeIdentityCultureConvention : IActionModelConvention
|
||||
{
|
||||
public void Apply(ActionModel action)
|
||||
{
|
||||
action.Filters.Add(new BackOfficeCultureFilter());
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public void Apply(ActionModel action) => action.Filters.Add(new BackOfficeCultureFilter());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,18 @@ namespace Umbraco.Cms.Web.Common.ApplicationModels
|
||||
/// </remarks>
|
||||
public class UmbracoApiBehaviorApplicationModelProvider : IApplicationModelProvider
|
||||
{
|
||||
private readonly List<IActionModelConvention> _actionModelConventions;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoApiBehaviorApplicationModelProvider"/> class.
|
||||
/// </summary>
|
||||
public UmbracoApiBehaviorApplicationModelProvider(IModelMetadataProvider modelMetadataProvider)
|
||||
{
|
||||
// see see https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#apicontroller-attribute
|
||||
// for what these things actually do
|
||||
// NOTE: we don't have attribute routing requirements and we cannot use ApiVisibilityConvention without attribute routing
|
||||
|
||||
ActionModelConventions = new List<IActionModelConvention>()
|
||||
_actionModelConventions = new List<IActionModelConvention>()
|
||||
{
|
||||
new ClientErrorResultFilterConvention(), // Ensures the responses without any body is converted into a simple json object with info instead of a string like "Status Code: 404; Not Found"
|
||||
new ConsumesConstraintForFormFileParameterConvention(), // If an controller accepts files, it must accept multipart/form-data.
|
||||
@@ -46,32 +51,33 @@ namespace Umbraco.Cms.Web.Common.ApplicationModels
|
||||
// TODO: Need to determine exactly how this affects errors
|
||||
var defaultErrorType = typeof(ProblemDetails);
|
||||
var defaultErrorTypeAttribute = new ProducesErrorResponseTypeAttribute(defaultErrorType);
|
||||
ActionModelConventions.Add(new ApiConventionApplicationModelConvention(defaultErrorTypeAttribute));
|
||||
_actionModelConventions.Add(new ApiConventionApplicationModelConvention(defaultErrorTypeAttribute));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Will execute after <see cref="DefaultApplicationModelProvider"/>
|
||||
/// </summary>
|
||||
public int Order => 0;
|
||||
|
||||
public List<IActionModelConvention> ActionModelConventions { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnProvidersExecuted(ApplicationModelProviderContext context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnProvidersExecuting(ApplicationModelProviderContext context)
|
||||
{
|
||||
foreach (var controller in context.Result.Controllers)
|
||||
{
|
||||
if (!IsUmbracoApiController(controller))
|
||||
{
|
||||
continue;
|
||||
|
||||
|
||||
}
|
||||
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
foreach (var convention in ActionModelConventions)
|
||||
foreach (var convention in _actionModelConventions)
|
||||
{
|
||||
convention.Apply(action);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Umbraco.Cms.Web.Common.ModelBinders;
|
||||
@@ -13,14 +13,13 @@ namespace Umbraco.Cms.Web.Common.ApplicationModels
|
||||
/// </remarks>
|
||||
public class UmbracoJsonModelBinderConvention : IActionModelConvention
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Apply(ActionModel action)
|
||||
{
|
||||
foreach (var p in action.Parameters.Where(p => p.BindingInfo?.BindingSource == BindingSource.Body))
|
||||
foreach (ParameterModel p in action.Parameters.Where(p => p.BindingInfo?.BindingSource == BindingSource.Body))
|
||||
{
|
||||
p.BindingInfo.BinderType = typeof(UmbracoJsonModelBinder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
|
||||
namespace Umbraco.Web.Common.ApplicationModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies the <see cref="VirtualPageConvention"/> to any action on a controller that is <see cref="IVirtualPageController"/>
|
||||
/// </summary>
|
||||
public class VirtualPageApplicationModelProvider : IApplicationModelProvider
|
||||
{
|
||||
private readonly List<IActionModelConvention> _actionModelConventions = new List<IActionModelConvention>()
|
||||
{
|
||||
new VirtualPageConvention()
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Will execute after <see cref="DefaultApplicationModelProvider"/>
|
||||
/// </summary>
|
||||
public int Order => 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnProvidersExecuted(ApplicationModelProviderContext context) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnProvidersExecuting(ApplicationModelProviderContext context)
|
||||
{
|
||||
foreach (ControllerModel controller in context.Result.Controllers)
|
||||
{
|
||||
if (!IsVirtualPageController(controller))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (ActionModel action in controller.Actions.ToList())
|
||||
{
|
||||
if (action.ActionName == nameof(IVirtualPageController.FindContent)
|
||||
&& action.ActionMethod.ReturnType == typeof(IPublishedContent))
|
||||
{
|
||||
// this is not an action, it's just the implementation of IVirtualPageController
|
||||
controller.Actions.Remove(action);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (IActionModelConvention convention in _actionModelConventions)
|
||||
{
|
||||
convention.Apply(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsVirtualPageController(ControllerModel controller)
|
||||
=> controller.ControllerType.Implements<IVirtualPageController>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
|
||||
namespace Umbraco.Web.Common.ApplicationModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the <see cref="UmbracoVirtualPageFilterAttribute"/> as a convention
|
||||
/// </summary>
|
||||
public class VirtualPageConvention : IActionModelConvention
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Apply(ActionModel action) => action.Filters.Add(new UmbracoVirtualPageFilterAttribute());
|
||||
}
|
||||
}
|
||||
16
src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs
Normal file
16
src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Web.Common.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for custom routed controllers to execute within the context of Umbraco
|
||||
/// </summary>
|
||||
public interface IVirtualPageController
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the <see cref="IPublishedContent"/> to use as the current page for the request
|
||||
/// </summary>
|
||||
IPublishedContent FindContent(ActionExecutingContext actionExecutingContext);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -15,107 +14,32 @@ using Umbraco.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents the default front-end rendering controller.
|
||||
/// </summary>
|
||||
[ModelBindingException]
|
||||
[PublishedRequestFilter]
|
||||
public class RenderController : UmbracoController, IRenderController
|
||||
public class RenderController : UmbracoPageController, IRenderController
|
||||
{
|
||||
private readonly ILogger<RenderController> _logger;
|
||||
private readonly ICompositeViewEngine _compositeViewEngine;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private UmbracoRouteValues _umbracoRouteValues;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RenderController"/> class.
|
||||
/// </summary>
|
||||
public RenderController(ILogger<RenderController> logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
: base(logger, compositeViewEngine)
|
||||
{
|
||||
_logger = logger;
|
||||
_compositeViewEngine = compositeViewEngine;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current content item.
|
||||
/// </summary>
|
||||
protected IPublishedContent CurrentPage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!UmbracoRouteValues.PublishedRequest.HasPublishedContent())
|
||||
{
|
||||
// This will never be accessed this way since the controller will handle redirects and not founds
|
||||
// before this can be accessed but we need to be explicit.
|
||||
throw new InvalidOperationException("There is no published content found in the request");
|
||||
}
|
||||
|
||||
return UmbracoRouteValues.PublishedRequest.PublishedContent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the umbraco context
|
||||
/// </summary>
|
||||
protected IUmbracoContext UmbracoContext => _umbracoContextAccessor.UmbracoContext;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="UmbracoRouteValues"/>
|
||||
/// </summary>
|
||||
protected UmbracoRouteValues UmbracoRouteValues
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_umbracoRouteValues != null)
|
||||
{
|
||||
return _umbracoRouteValues;
|
||||
}
|
||||
|
||||
_umbracoRouteValues = HttpContext.Features.Get<UmbracoRouteValues>();
|
||||
|
||||
if (_umbracoRouteValues == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No {nameof(UmbracoRouteValues)} feature was found in the HttpContext");
|
||||
}
|
||||
|
||||
return _umbracoRouteValues;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
||||
104
src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs
Normal file
104
src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web.Common.Routing;
|
||||
using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Common.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// An abstract controller for a front-end Umbraco page
|
||||
/// </summary>
|
||||
public abstract class UmbracoPageController : UmbracoController
|
||||
{
|
||||
private UmbracoRouteValues _umbracoRouteValues;
|
||||
private readonly ICompositeViewEngine _compositeViewEngine;
|
||||
private readonly ILogger<UmbracoPageController> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoPageController"/> class.
|
||||
/// </summary>
|
||||
protected UmbracoPageController(ILogger<UmbracoPageController> logger, ICompositeViewEngine compositeViewEngine)
|
||||
{
|
||||
_logger = logger;
|
||||
_compositeViewEngine = compositeViewEngine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="UmbracoRouteValues"/>
|
||||
/// </summary>
|
||||
protected virtual UmbracoRouteValues UmbracoRouteValues
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_umbracoRouteValues != null)
|
||||
{
|
||||
return _umbracoRouteValues;
|
||||
}
|
||||
|
||||
_umbracoRouteValues = HttpContext.Features.Get<UmbracoRouteValues>();
|
||||
|
||||
if (_umbracoRouteValues == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No {nameof(UmbracoRouteValues)} feature was found in the HttpContext");
|
||||
}
|
||||
|
||||
return _umbracoRouteValues;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current content item.
|
||||
/// </summary>
|
||||
protected virtual IPublishedContent CurrentPage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!UmbracoRouteValues.PublishedRequest.HasPublishedContent())
|
||||
{
|
||||
// This will never be accessed this way since the controller will handle redirects and not founds
|
||||
// before this can be accessed but we need to be explicit.
|
||||
throw new InvalidOperationException("There is no published content found in the request");
|
||||
}
|
||||
|
||||
return UmbracoRouteValues.PublishedRequest.PublishedContent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
|
||||
// TODO: We could add parameters to configure each of these for flexibility
|
||||
|
||||
/// <summary>
|
||||
@@ -102,6 +103,10 @@ namespace Umbraco.Extensions
|
||||
ILoggerFactory loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false));
|
||||
TypeLoader typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), webHostEnvironment, tempHostingEnvironment, loggerFactory, appCaches, config, profiler);
|
||||
|
||||
// adds the umbraco startup filter which will call UseUmbraco early on before
|
||||
// other start filters are applied (depending on the ordering of IStartupFilters in DI).
|
||||
services.AddTransient<IStartupFilter, UmbracoStartupFilter>();
|
||||
|
||||
return new UmbracoBuilder(services, config, typeLoader, loggerFactory);
|
||||
}
|
||||
|
||||
@@ -228,6 +233,7 @@ namespace Umbraco.Extensions
|
||||
builder.Services.ConfigureOptions<UmbracoRequestLocalizationOptions>();
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IApplicationModelProvider, UmbracoApiBehaviorApplicationModelProvider>());
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IApplicationModelProvider, BackOfficeApplicationModelProvider>());
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IApplicationModelProvider, VirtualPageApplicationModelProvider>());
|
||||
builder.Services.AddUmbracoImageSharp(builder.Config);
|
||||
|
||||
// AspNetCore specific services
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Web.Common.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IStartupFilter"/> registered early in DI so that it executes before any user IStartupFilters
|
||||
/// to ensure that all Umbraco service and requirements are started correctly and in order.
|
||||
/// </summary>
|
||||
public sealed class UmbracoStartupFilter : IStartupFilter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) =>
|
||||
app =>
|
||||
{
|
||||
app.UseUmbraco();
|
||||
next(app);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
|
||||
namespace Umbraco.Web.Common.Extensions
|
||||
{
|
||||
public static class ControllerActionEndpointConventionBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows for defining a callback to set the returned <see cref="IPublishedContent"/> for the current request for this route
|
||||
/// </summary>
|
||||
public static void ForUmbracoPage(
|
||||
this ControllerActionEndpointConventionBuilder builder,
|
||||
Func<ActionExecutingContext, IPublishedContent> findContent)
|
||||
=> builder.Add(convention =>
|
||||
{
|
||||
// filter out matched endpoints that are suppressed
|
||||
if (convention.Metadata.OfType<ISuppressMatchingMetadata>().FirstOrDefault()?.SuppressMatching != true)
|
||||
{
|
||||
// Get the controller action descriptor
|
||||
ControllerActionDescriptor actionDescriptor = convention.Metadata.OfType<ControllerActionDescriptor>().FirstOrDefault();
|
||||
if (actionDescriptor != null)
|
||||
{
|
||||
// This is more or less like the IApplicationModelProvider, it allows us to add filters, etc... to the ControllerActionDescriptor
|
||||
// dynamically. Here we will add our custom virtual page filter along with a callback in the endpoint's metadata
|
||||
// to execute in order to find the IPublishedContent for the request.
|
||||
|
||||
var filter = new UmbracoVirtualPageFilterAttribute();
|
||||
|
||||
// Check if this already contains this filter since we don't want it applied twice.
|
||||
// This could occur if the controller being routed is IVirtualPageController AND
|
||||
// is being routed with ForUmbracoPage. In that case, ForUmbracoPage wins
|
||||
// because the UmbracoVirtualPageFilterAttribute will check for the metadata first since
|
||||
// that is more explicit and flexible in case the same controller is routed multiple times.
|
||||
if (!actionDescriptor.FilterDescriptors.Any(x => x.Filter is UmbracoVirtualPageFilterAttribute))
|
||||
{
|
||||
actionDescriptor.FilterDescriptors.Add(new FilterDescriptor(filter, 0));
|
||||
convention.Metadata.Add(filter);
|
||||
}
|
||||
|
||||
convention.Metadata.Add(new CustomRouteContentFinderDelegate(findContent));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using Umbraco.Web.Common.Extensions;
|
||||
using Umbraco.Web.Common.Routing;
|
||||
using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Common.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to set the <see cref="UmbracoRouteValues"/> request feature based on the <see cref="CustomRouteContentFinderDelegate"/> specified (if any)
|
||||
/// for the custom route.
|
||||
/// </summary>
|
||||
public class UmbracoVirtualPageFilterAttribute : Attribute, IAsyncActionFilter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
Endpoint endpoint = context.HttpContext.GetEndpoint();
|
||||
|
||||
// Check if there is any delegate in the metadata of the route, this
|
||||
// will occur when using the ForUmbraco method during routing.
|
||||
CustomRouteContentFinderDelegate contentFinder = endpoint.Metadata.OfType<CustomRouteContentFinderDelegate>().FirstOrDefault();
|
||||
|
||||
if (contentFinder != null)
|
||||
{
|
||||
await SetUmbracoRouteValues(context, contentFinder.FindContent(context));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if the controller is IVirtualPageController and then use that to FindContent
|
||||
if (context.Controller is IVirtualPageController ctrl)
|
||||
{
|
||||
await SetUmbracoRouteValues(context, ctrl.FindContent(context));
|
||||
}
|
||||
}
|
||||
|
||||
// if we've assigned not found, just exit
|
||||
if (!(context.Result is NotFoundResult))
|
||||
{
|
||||
await next();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetUmbracoRouteValues(ActionExecutingContext context, IPublishedContent content)
|
||||
{
|
||||
if (content != null)
|
||||
{
|
||||
IUmbracoContextAccessor umbracoContext = context.HttpContext.RequestServices.GetRequiredService<IUmbracoContextAccessor>();
|
||||
IPublishedRouter router = context.HttpContext.RequestServices.GetRequiredService<IPublishedRouter>();
|
||||
IPublishedRequestBuilder requestBuilder = await router.CreateRequestAsync(umbracoContext.UmbracoContext.CleanedUmbracoUrl);
|
||||
requestBuilder.SetPublishedContent(content);
|
||||
IPublishedRequest publishedRequest = requestBuilder.Build();
|
||||
|
||||
var routeValues = new UmbracoRouteValues(
|
||||
publishedRequest,
|
||||
(ControllerActionDescriptor)context.ActionDescriptor);
|
||||
|
||||
context.HttpContext.Features.Set(routeValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if there is no content then it should be a not found
|
||||
context.Result = new NotFoundResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Web.Common.AspNetCore;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Macros
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Web.Common.Extensions
|
||||
{
|
||||
internal class CustomRouteContentFinderDelegate
|
||||
{
|
||||
private readonly Func<ActionExecutingContext, IPublishedContent> _findContent;
|
||||
|
||||
public CustomRouteContentFinderDelegate(Func<ActionExecutingContext, IPublishedContent> findContent) => _findContent = findContent;
|
||||
|
||||
public IPublishedContent FindContent(ActionExecutingContext actionExecutingContext) => _findContent(actionExecutingContext);
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,19 @@ namespace Umbraco.Cms.Web.Common.Routing
|
||||
// We don't want to include dynamic endpoints in this check since we would have no idea if that
|
||||
// matches since they will probably match everything.
|
||||
bool isDynamic = x.Metadata.OfType<IDynamicEndpointMetadata>().Any(x => x.IsDynamic);
|
||||
return !isDynamic;
|
||||
if (isDynamic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// filter out matched endpoints that are suppressed
|
||||
var isSuppressed = x.Metadata.OfType<ISuppressMatchingMetadata>().FirstOrDefault()?.SuppressMatching == true;
|
||||
if (isSuppressed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
var routeValues = new RouteValueDictionary();
|
||||
|
||||
@@ -21,12 +21,10 @@ namespace Umbraco.Cms.Web.Common.Routing
|
||||
public UmbracoRouteValues(
|
||||
IPublishedRequest publishedRequest,
|
||||
ControllerActionDescriptor controllerActionDescriptor,
|
||||
string templateName = null,
|
||||
bool hasHijackedRoute = false)
|
||||
string templateName = null)
|
||||
{
|
||||
PublishedRequest = publishedRequest;
|
||||
ControllerActionDescriptor = controllerActionDescriptor;
|
||||
HasHijackedRoute = hasHijackedRoute;
|
||||
TemplateName = templateName;
|
||||
}
|
||||
|
||||
@@ -59,10 +57,5 @@ namespace Umbraco.Cms.Web.Common.Routing
|
||||
/// Gets the <see cref="IPublishedRequest"/>
|
||||
/// </summary>
|
||||
public IPublishedRequest PublishedRequest { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current request has a hijacked route/user controller routed for it
|
||||
/// </summary>
|
||||
public bool HasHijackedRoute { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.AspNetCore
|
||||
{
|
||||
// TODO: Should be in Views namespace?
|
||||
|
||||
public abstract class UmbracoViewPage : UmbracoViewPage<IPublishedContent>
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
|
||||
@@ -59,7 +59,6 @@ namespace Umbraco.Cms.Web.UI.NetCore
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseUmbraco();
|
||||
app.UseUmbracoBackOffice();
|
||||
app.UseUmbracoWebsite();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@inherits Umbraco.Cms.Web.Common.AspNetCore.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockListModel>
|
||||
@inherits Umbraco.Cms.Web.Common.AspNetCore.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockListModel>
|
||||
@{
|
||||
if (!Model.Any()) { return; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@using System.Web
|
||||
@using System.Web
|
||||
@using Microsoft.AspNetCore.Html
|
||||
@using Newtonsoft.Json.Linq
|
||||
@inherits Umbraco.Cms.Web.Common.AspNetCore.UmbracoViewPage<dynamic>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@using System.Web
|
||||
@using System.Web
|
||||
@using Microsoft.AspNetCore.Html
|
||||
@using Newtonsoft.Json.Linq
|
||||
@inherits Umbraco.Cms.Web.Common.AspNetCore.UmbracoViewPage<dynamic>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@using Umbraco.Core
|
||||
@using Umbraco.Cms.Core
|
||||
@using Umbraco.Cms.Core
|
||||
@inherits Umbraco.Cms.Web.Common.AspNetCore.UmbracoViewPage<dynamic>
|
||||
|
||||
@{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@inherits Umbraco.Cms.Web.Common.AspNetCore.UmbracoViewPage<dynamic>
|
||||
@inherits Umbraco.Cms.Web.Common.AspNetCore.UmbracoViewPage<dynamic>
|
||||
|
||||
@if (Model.value != null)
|
||||
{
|
||||
|
||||
@@ -93,9 +93,9 @@ namespace Umbraco.Cms.Web.Website.Routing
|
||||
_defaultControllerDescriptor.Value,
|
||||
templateName: customActionName);
|
||||
|
||||
def = CheckHijackedRoute(httpContext, def);
|
||||
def = CheckHijackedRoute(httpContext, def, out bool hasHijackedRoute);
|
||||
|
||||
def = CheckNoTemplate(httpContext, def);
|
||||
def = CheckNoTemplate(httpContext, def, hasHijackedRoute);
|
||||
|
||||
return def;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ namespace Umbraco.Cms.Web.Website.Routing
|
||||
/// <summary>
|
||||
/// Check if the route is hijacked and return new route values
|
||||
/// </summary>
|
||||
private UmbracoRouteValues CheckHijackedRoute(HttpContext httpContext, UmbracoRouteValues def)
|
||||
private UmbracoRouteValues CheckHijackedRoute(HttpContext httpContext, UmbracoRouteValues def, out bool hasHijackedRoute)
|
||||
{
|
||||
IPublishedRequest request = def.PublishedRequest;
|
||||
|
||||
@@ -113,21 +113,23 @@ namespace Umbraco.Cms.Web.Website.Routing
|
||||
ControllerActionDescriptor descriptor = _controllerActionSearcher.Find<IRenderController>(httpContext, customControllerName, def.TemplateName);
|
||||
if (descriptor != null)
|
||||
{
|
||||
hasHijackedRoute = true;
|
||||
|
||||
return new UmbracoRouteValues(
|
||||
request,
|
||||
descriptor,
|
||||
def.TemplateName,
|
||||
true);
|
||||
def.TemplateName);
|
||||
}
|
||||
}
|
||||
|
||||
hasHijackedRoute = false;
|
||||
return def;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Special check for when no template or hijacked route is done which needs to re-run through the routing pipeline again for last chance finders
|
||||
/// </summary>
|
||||
private UmbracoRouteValues CheckNoTemplate(HttpContext httpContext, UmbracoRouteValues def)
|
||||
private UmbracoRouteValues CheckNoTemplate(HttpContext httpContext, UmbracoRouteValues def, bool hasHijackedRoute)
|
||||
{
|
||||
IPublishedRequest request = def.PublishedRequest;
|
||||
|
||||
@@ -138,7 +140,7 @@ namespace Umbraco.Cms.Web.Website.Routing
|
||||
if (request.HasPublishedContent()
|
||||
&& !request.HasTemplate()
|
||||
&& !_umbracoFeatures.Disabled.DisableTemplates
|
||||
&& !def.HasHijackedRoute)
|
||||
&& !hasHijackedRoute)
|
||||
{
|
||||
IPublishedContent content = request.PublishedContent;
|
||||
|
||||
@@ -162,7 +164,7 @@ namespace Umbraco.Cms.Web.Website.Routing
|
||||
// if the content has changed, we must then again check for hijacked routes
|
||||
if (content != request.PublishedContent)
|
||||
{
|
||||
def = CheckHijackedRoute(httpContext, def);
|
||||
def = CheckHijackedRoute(httpContext, def, out _);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
using System;
|
||||
using System.Web.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Core;
|
||||
using Current = Umbraco.Web.Composing.Current;
|
||||
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for custom routed pages that are being integrated with the Umbraco data but are not
|
||||
/// part of the umbraco request pipeline. This allows umbraco macros to be able to execute in this scenario.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is inspired from this discussion:
|
||||
/// https://our.umbraco.com/forum/developers/extending-umbraco/41367-Umbraco-6-MVC-Custom-MVC-Route?p=3
|
||||
///
|
||||
/// which is based on custom routing found here:
|
||||
/// http://shazwazza.com/post/Custom-MVC-routing-in-Umbraco
|
||||
/// </remarks>
|
||||
public class EnsurePublishedContentRequestAttribute : ActionFilterAttribute
|
||||
{
|
||||
// TODO: Need to port this to netcore and figure out if its needed or how this should work (part of a different task)
|
||||
|
||||
//private readonly string _dataTokenName;
|
||||
//private IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
//private readonly int? _contentId;
|
||||
//private IPublishedContentQuery _publishedContentQuery;
|
||||
|
||||
///// <summary>
|
||||
///// Constructor - can be used for testing
|
||||
///// </summary>
|
||||
///// <param name="umbracoContextAccessor"></param>
|
||||
///// <param name="contentId"></param>
|
||||
//public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, int contentId)
|
||||
//{
|
||||
// _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
// _contentId = contentId;
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// A constructor used to set an explicit content Id to the PublishedRequest that will be created
|
||||
///// </summary>
|
||||
///// <param name="contentId"></param>
|
||||
//public EnsurePublishedContentRequestAttribute(int contentId)
|
||||
//{
|
||||
// _contentId = contentId;
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// A constructor used to set the data token key name that contains a reference to a PublishedContent instance
|
||||
///// </summary>
|
||||
///// <param name="dataTokenName"></param>
|
||||
//public EnsurePublishedContentRequestAttribute(string dataTokenName)
|
||||
//{
|
||||
// _dataTokenName = dataTokenName;
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// Constructor - can be used for testing
|
||||
///// </summary>
|
||||
///// <param name="umbracoContextAccessor"></param>
|
||||
///// <param name="dataTokenName"></param>
|
||||
//public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, string dataTokenName)
|
||||
//{
|
||||
// _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
// _dataTokenName = dataTokenName;
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// Exposes the UmbracoContext
|
||||
///// </summary>
|
||||
//protected IUmbracoContext UmbracoContext => _umbracoContextAccessor?.UmbracoContext ?? Current.UmbracoContext;
|
||||
|
||||
//// TODO: try lazy property injection?
|
||||
//private IPublishedRouter PublishedRouter => Current.Factory.GetRequiredService<IPublishedRouter>();
|
||||
|
||||
//private IPublishedContentQuery PublishedContentQuery => _publishedContentQuery ?? (_publishedContentQuery = Current.Factory.GetRequiredService<IPublishedContentQuery>());
|
||||
|
||||
//public override void OnActionExecuted(ActionExecutedContext filterContext)
|
||||
//{
|
||||
// base.OnActionExecuted(filterContext);
|
||||
|
||||
// // First we need to check if the published content request has been set, if it has we're going to ignore this and not actually do anything
|
||||
// if (Current.UmbracoContext.PublishedRequest != null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Current.UmbracoContext.PublishedRequest = PublishedRouter.CreateRequest(Current.UmbracoContext);
|
||||
// ConfigurePublishedContentRequest(Current.UmbracoContext.PublishedRequest, filterContext);
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// This assigns the published content to the request, developers can override this to specify
|
||||
///// any other custom attributes required.
|
||||
///// </summary>
|
||||
///// <param name="request"></param>
|
||||
///// <param name="filterContext"></param>
|
||||
//protected virtual void ConfigurePublishedContentRequest(IPublishedRequest request, ActionExecutedContext filterContext)
|
||||
//{
|
||||
// if (_contentId.HasValue)
|
||||
// {
|
||||
// var content = PublishedContentQuery.Content(_contentId.Value);
|
||||
// if (content == null)
|
||||
// {
|
||||
// throw new InvalidOperationException("Could not resolve content with id " + _contentId);
|
||||
// }
|
||||
// request.PublishedContent = content;
|
||||
// }
|
||||
// else if (_dataTokenName.IsNullOrWhiteSpace() == false)
|
||||
// {
|
||||
// var result = filterContext.RouteData.DataTokens[_dataTokenName];
|
||||
// if (result == null)
|
||||
// {
|
||||
// throw new InvalidOperationException("No data token could be found with the name " + _dataTokenName);
|
||||
// }
|
||||
// if (result is IPublishedContent == false)
|
||||
// {
|
||||
// throw new InvalidOperationException("The data token resolved with name " + _dataTokenName + " was not an instance of " + typeof(IPublishedContent));
|
||||
// }
|
||||
// request.PublishedContent = (IPublishedContent)result;
|
||||
// }
|
||||
|
||||
// PublishedRouter.PrepareRequest(request);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Composing;
|
||||
using System;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
|
||||
namespace Umbraco.Web.Mvc
|
||||
{
|
||||
|
||||
@@ -170,7 +170,6 @@
|
||||
<Compile Include="WebApi\SessionHttpControllerRouteHandler.cs" />
|
||||
<Compile Include="WebApi\UmbracoApiControllerTypeCollectionBuilder.cs" />
|
||||
<Compile Include="Mvc\UmbracoVirtualNodeByIdRouteHandler.cs" />
|
||||
<Compile Include="Mvc\EnsurePublishedContentRequestAttribute.cs" />
|
||||
<Compile Include="Mvc\UmbracoVirtualNodeRouteHandler.cs" />
|
||||
<Compile Include="Security\AuthenticationOptionsExtensions.cs" />
|
||||
<Compile Include="Mvc\ViewDataDictionaryExtensions.cs" />
|
||||
|
||||
Reference in New Issue
Block a user