diff --git a/src/Umbraco.Core/Constants-SystemDirectories.cs b/src/Umbraco.Core/Constants-SystemDirectories.cs index 53e14bb6ec..d4ca2a3c57 100644 --- a/src/Umbraco.Core/Constants-SystemDirectories.cs +++ b/src/Umbraco.Core/Constants-SystemDirectories.cs @@ -20,7 +20,7 @@ public const string AppCode = "~/App_Code"; - public const string AppPlugins = "~/App_Plugins"; + public const string AppPlugins = "/App_Plugins"; public const string MvcViews = "~/Views"; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetupTests.cs new file mode 100644 index 0000000000..a74431b705 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetupTests.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Logging; +using Umbraco.Web.Website.ViewEngines; + +namespace Umbraco.Tests.Runtimes +{ + [TestFixture] + public class ProfilingViewEngineWrapperMvcViewOptionsSetupTests + { + private ProfilingViewEngineWrapperMvcViewOptionsSetup Sut => + new ProfilingViewEngineWrapperMvcViewOptionsSetup(Mock.Of()); + [Test] + public void WrapViewEngines_HasEngines_WrapsAll() + { + var options = new MvcViewOptions() + { + ViewEngines = + { + Mock.Of(), + Mock.Of(), + } + }; + + Sut.Configure(options); + + Assert.That(options.ViewEngines.Count, Is.EqualTo(2)); + Assert.That(options.ViewEngines[0], Is.InstanceOf()); + Assert.That(options.ViewEngines[1], Is.InstanceOf()); + } + + [Test] + public void WrapViewEngines_HasEngines_KeepsSortOrder() + { + var options = new MvcViewOptions() + { + ViewEngines = + { + Mock.Of(), + Mock.Of(), + } + }; + + Sut.Configure(options); + + Assert.That(options.ViewEngines.Count, Is.EqualTo(2)); + Assert.That(((ProfilingViewEngine)options.ViewEngines[0]).Inner, Is.InstanceOf()); + Assert.That(((ProfilingViewEngine)options.ViewEngines[1]).Inner, Is.InstanceOf()); + } + + [Test] + public void WrapViewEngines_HasProfiledEngine_AddsSameInstance() + { + var profiledEngine = new ProfilingViewEngine(Mock.Of(), Mock.Of()); + var options = new MvcViewOptions() + { + ViewEngines = + { + profiledEngine + } + }; + + Sut.Configure(options); + + Assert.That(options.ViewEngines[0], Is.SameAs(profiledEngine)); + } + + [Test] + public void WrapViewEngines_CollectionIsNull_DoesNotThrow() + { + Assert.DoesNotThrow(() => Sut.Configure(new MvcViewOptions())); + } + } +} diff --git a/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs deleted file mode 100644 index 42a0eeba54..0000000000 --- a/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Collections.Generic; -using System.Web.Mvc; -using NUnit.Framework; -using NUnit.Framework.Internal; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web.Mvc; -using Umbraco.Web.Runtime; - -namespace Umbraco.Tests.Runtimes -{ - [TestFixture] - public class WebRuntimeComponentTests - { - [Test] - public void WrapViewEngines_HasEngines_WrapsAll() - { - IList engines = new List - { - new RenderViewEngine(TestHelper.GetHostingEnvironment()), - new PluginViewEngine() - }; - - WebInitialComponent.WrapViewEngines(engines); - - Assert.That(engines.Count, Is.EqualTo(2)); - Assert.That(engines[0], Is.InstanceOf()); - Assert.That(engines[1], Is.InstanceOf()); - } - - [Test] - public void WrapViewEngines_HasEngines_KeepsSortOrder() - { - IList engines = new List - { - new RenderViewEngine(TestHelper.GetHostingEnvironment()), - new PluginViewEngine() - }; - - WebInitialComponent.WrapViewEngines(engines); - - Assert.That(engines.Count, Is.EqualTo(2)); - Assert.That(((ProfilingViewEngine)engines[0]).Inner, Is.InstanceOf()); - Assert.That(((ProfilingViewEngine)engines[1]).Inner, Is.InstanceOf()); - } - - [Test] - public void WrapViewEngines_HasProfiledEngine_AddsSameInstance() - { - var profiledEngine = new ProfilingViewEngine(new PluginViewEngine()); - IList engines = new List - { - profiledEngine - }; - - WebInitialComponent.WrapViewEngines(engines); - - Assert.That(engines[0], Is.SameAs(profiledEngine)); - } - - [Test] - public void WrapViewEngines_CollectionIsNull_DoesNotThrow() - { - Assert.DoesNotThrow(() => WebInitialComponent.WrapViewEngines(null)); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 13e9cbeef1..58aa4ac7ec 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -217,7 +217,6 @@ - diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.json b/src/Umbraco.Web.UI.NetCore/appsettings.json index 0d068bbd43..98dd77d9a1 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.json @@ -38,7 +38,7 @@ "ConvertUrlsToAscii": "try" }, "RuntimeMinification": { - "dataFolder": "App_Data/TEMP/Smidge", + "dataFolder": "App_Data/TEMP/Smidge", "version": "1" }, "Security": { diff --git a/src/Umbraco.Web.Website/Extensions/ActionContextExtensions.cs b/src/Umbraco.Web.Website/Extensions/ActionContextExtensions.cs index 5a79fcf188..acb2ccbffa 100644 --- a/src/Umbraco.Web.Website/Extensions/ActionContextExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/ActionContextExtensions.cs @@ -11,19 +11,9 @@ namespace Umbraco.Extensions /// The action context. /// The name of the data token. /// The data token, or null. - /// MIGRAGED TO NETCORE AS EXTENSION ON HTTPCONTEXT internal static object GetDataTokenInViewContextHierarchy(this ActionContext actionContext, string dataTokenName) { - - var controllerActionDescriptor = actionContext.ActionDescriptor as ControllerActionDescriptor; - while (!(controllerActionDescriptor is null)) - { - object token; - if (actionContext.RouteData.DataTokens.TryGetValue(dataTokenName, out token)) - return token; - controllerActionDescriptor = controllerActionDescriptor.ParentActionViewContext; - } - return null; + return actionContext.RouteData.DataTokens.TryGetValue(dataTokenName, out var token) ? token : null; } } } diff --git a/src/Umbraco.Web.Website/Extensions/UmbracoWebstiteServiceCollectionExtensions.cs b/src/Umbraco.Web.Website/Extensions/UmbracoWebstiteServiceCollectionExtensions.cs index bdc9ee840c..b47679a1bf 100644 --- a/src/Umbraco.Web.Website/Extensions/UmbracoWebstiteServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/UmbracoWebstiteServiceCollectionExtensions.cs @@ -1,5 +1,11 @@ -using Microsoft.AspNetCore.Mvc.Controllers; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Umbraco.Web.Website.ViewEngines; namespace Umbraco.Extensions { @@ -8,6 +14,18 @@ namespace Umbraco.Extensions public static void AddUmbracoWebsite(this IServiceCollection services) { services.AddSingleton(); + + // Set the render & plugin view engines (Super complicated, but this allows us to use the IServiceCollection + // to inject dependencies into the viewEngines) + services.AddTransient, RenderMvcViewOptionsSetup>(); + services.AddSingleton(); + services.AddTransient, PluginMvcViewOptionsSetup>(); + services.AddSingleton(); + + // Wraps all existing view engines in a ProfilerViewEngine + services.AddTransient, ProfilingViewEngineWrapperMvcViewOptionsSetup>(); + } + } } diff --git a/src/Umbraco.Web.Website/ViewEngines/IPluginViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/IPluginViewEngine.cs new file mode 100644 index 0000000000..4bcb7b5dc2 --- /dev/null +++ b/src/Umbraco.Web.Website/ViewEngines/IPluginViewEngine.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Mvc.Razor; + +namespace Umbraco.Web.Website.ViewEngines +{ + public interface IPluginViewEngine : IRazorViewEngine + { + } +} diff --git a/src/Umbraco.Web.Website/ViewEngines/IRenderViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/IRenderViewEngine.cs new file mode 100644 index 0000000000..6f21d21494 --- /dev/null +++ b/src/Umbraco.Web.Website/ViewEngines/IRenderViewEngine.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Mvc.Razor; + +namespace Umbraco.Web.Website.ViewEngines +{ + public interface IRenderViewEngine : IRazorViewEngine + { + } +} diff --git a/src/Umbraco.Web.Website/ViewEngines/PluginMvcViewOptionsSetup.cs b/src/Umbraco.Web.Website/ViewEngines/PluginMvcViewOptionsSetup.cs new file mode 100644 index 0000000000..cf703ddaca --- /dev/null +++ b/src/Umbraco.Web.Website/ViewEngines/PluginMvcViewOptionsSetup.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace Umbraco.Web.Website.ViewEngines +{ + public class PluginMvcViewOptionsSetup : IConfigureOptions + { + private readonly IPluginViewEngine _pluginViewEngine; + + public PluginMvcViewOptionsSetup(IPluginViewEngine pluginViewEngine) + { + _pluginViewEngine = pluginViewEngine ?? throw new ArgumentNullException(nameof(pluginViewEngine)); + } + + public void Configure(MvcViewOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.ViewEngines.Add(_pluginViewEngine); + } + } +} diff --git a/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs index 7c8deb9dd7..ac16be417a 100644 --- a/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs +++ b/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs @@ -9,7 +9,7 @@ namespace Umbraco.Web.Website.ViewEngines /// /// A view engine to look into the App_Plugins folder for views for packaged controllers /// - public class PluginViewEngine : RazorViewEngine + public class PluginViewEngine : RazorViewEngine, IPluginViewEngine { public PluginViewEngine( IRazorPageFactoryProvider pageFactory, diff --git a/src/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetup.cs b/src/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetup.cs new file mode 100644 index 0000000000..b8fbe1e527 --- /dev/null +++ b/src/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetup.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Options; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.Website.ViewEngines +{ + public class ProfilingViewEngineWrapperMvcViewOptionsSetup : IConfigureOptions + { + private readonly IProfiler _profiler; + + public ProfilingViewEngineWrapperMvcViewOptionsSetup(IProfiler profiler) + { + _profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); + } + + public void Configure(MvcViewOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + WrapViewEngines(options.ViewEngines); + } + + private void WrapViewEngines(IList viewEngines) + { + if (viewEngines == null || viewEngines.Count == 0) return; + + var originalEngines = viewEngines.ToList(); + viewEngines.Clear(); + foreach (var engine in originalEngines) + { + var wrappedEngine = engine is ProfilingViewEngine ? engine : new ProfilingViewEngine(engine, _profiler); + viewEngines.Add(wrappedEngine); + } + } + } +} diff --git a/src/Umbraco.Web.Website/ViewEngines/RenderMvcViewOptionsSetup.cs b/src/Umbraco.Web.Website/ViewEngines/RenderMvcViewOptionsSetup.cs new file mode 100644 index 0000000000..d8ad58cc91 --- /dev/null +++ b/src/Umbraco.Web.Website/ViewEngines/RenderMvcViewOptionsSetup.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace Umbraco.Web.Website.ViewEngines +{ + public class RenderMvcViewOptionsSetup : IConfigureOptions + { + private readonly IRenderViewEngine _renderViewEngine; + + public RenderMvcViewOptionsSetup(IRenderViewEngine renderViewEngine) + { + _renderViewEngine = renderViewEngine ?? throw new ArgumentNullException(nameof(renderViewEngine)); + } + + public void Configure(MvcViewOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.ViewEngines.Add(_renderViewEngine); + } + } +} diff --git a/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs index 50e70b68a7..ea727a07f1 100644 --- a/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs +++ b/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Website.ViewEngines /// A view engine to look into the template location specified in the config for the front-end/Rendering part of the cms, /// this includes paths to render partial macros and media item templates. /// - public class RenderViewEngine : RazorViewEngine + public class RenderViewEngine : RazorViewEngine, IRenderViewEngine { public RenderViewEngine( @@ -46,50 +46,22 @@ namespace Umbraco.Web.Website.ViewEngines "/MacroPartials/{0}.cshtml", "/{0}.cshtml" }, + AreaViewLocationFormats = + { + "/Partials/{0}.cshtml", + "/MacroPartials/{0}.cshtml", + "/{0}.cshtml" + } }); } public new ViewEngineResult FindView(ActionContext context, string viewName, bool isMainPage) { - return ShouldFindView(context, viewName, isMainPage) + return ShouldFindView(context, isMainPage) ? base.FindView(context, viewName, isMainPage) : ViewEngineResult.NotFound(viewName, Array.Empty()); } - // /// - // /// Constructor - // /// - // public RenderViewEngine(IHostingEnvironment hostingEnvironment) - // { - // _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); - // - // const string templateFolder = Constants.ViewLocation; - // - // // the Render view engine doesn't support Area's so make those blank - // ViewLocationFormats = _supplementedViewLocations.Select(x => templateFolder + x).ToArray(); - // PartialViewLocationFormats = _supplementedPartialViewLocations.Select(x => templateFolder + x).ToArray(); - // - // AreaPartialViewLocationFormats = Array.Empty(); - // AreaViewLocationFormats = Array.Empty(); - // - // EnsureFoldersAndFiles(); - // } - - - // public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) - // { - // return ShouldFindView(controllerContext, false) - // ? base.FindView(controllerContext, viewName, masterName, useCache) - // : new ViewEngineResult(new string[] { }); - // } - // - // public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) - // { - // return ShouldFindView(controllerContext, true) - // ? base.FindPartialView(controllerContext, partialViewName, useCache) - // : new ViewEngineResult(new string[] { }); - // } - /// /// Determines if the view should be found, this is used for view lookup performance and also to ensure /// less overlap with other user's view engines. This will return true if the Umbraco back office is rendering @@ -98,14 +70,14 @@ namespace Umbraco.Web.Website.ViewEngines /// /// /// - private static bool ShouldFindView(ActionContext context, string viewName, bool isMainPage) + private static bool ShouldFindView(ActionContext context, bool isMainPage) { - var umbracoToken = context.GetDataTokenInViewContextHierarchy(Core.Constants.Web.UmbracoDataToken); - - context.ActionDescriptor. + //In v8, this was testing recursively into if it was a child action, but child action do not exist anymore, + //And my best guess is that it + context.RouteData.DataTokens.TryGetValue(Core.Constants.Web.UmbracoDataToken, out var umbracoToken); // first check if we're rendering a partial view for the back office, or surface controller, etc... // anything that is not ContentModel as this should only pertain to Umbraco views. - if (isPartial && !(umbracoToken is ContentModel)) + if (!isMainPage && !(umbracoToken is ContentModel)) return true; // only find views if we're rendering the umbraco front end diff --git a/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs b/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs index 7179f54c87..6442e1eb2a 100644 --- a/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.Mvc /// The controller context. /// The name of the data token. /// The data token, or null. - /// MIGRAGED TO NETCORE AS EXTENSION ON ActionContext + /// MIGRATED TO NETCORE AS EXTENSION ON ActionContext internal static object GetDataTokenInViewContextHierarchy(this ControllerContext controllerContext, string dataTokenName) { var context = controllerContext; diff --git a/src/Umbraco.Web/Runtime/WebInitialComponent.cs b/src/Umbraco.Web/Runtime/WebInitialComponent.cs index 10623a1ed8..cddb04eac3 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComponent.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComponent.cs @@ -93,8 +93,8 @@ namespace Umbraco.Web.Runtime // ControllerBuilder.Current.SetControllerFactory(controllerFactory); // set the render & plugin view engines - ViewEngines.Engines.Add(new RenderViewEngine(_hostingEnvironment)); - ViewEngines.Engines.Add(new PluginViewEngine()); + // ViewEngines.Engines.Add(new RenderViewEngine(_hostingEnvironment)); + // ViewEngines.Engines.Add(new PluginViewEngine()); ////add the profiling action filter //GlobalFilters.Filters.Add(new ProfilingActionFilter());