diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 5d059d8a23..e29d793909 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core +namespace Umbraco.Core { public static partial class Constants { @@ -7,10 +7,11 @@ /// public static class Web { - public const string UmbracoContextDataToken = "umbraco-context"; - public const string UmbracoDataToken = "umbraco"; - public const string PublishedDocumentRequestDataToken = "umbraco-doc-request"; - public const string CustomRouteDataToken = "umbraco-custom-route"; + // TODO: Need to review these... + //public const string UmbracoContextDataToken = "umbraco-context"; + //public const string UmbracoDataToken = "umbraco"; + //public const string PublishedDocumentRequestDataToken = "umbraco-doc-request"; + //public const string CustomRouteDataToken = "umbraco-custom-route"; public const string UmbracoRouteDefinitionDataToken = "umbraco-route-def"; /// diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index 73ce858b52..cc526ffe6e 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -23,6 +23,13 @@ namespace Umbraco.Web.PublishedCache * */ + /// + /// Loads the caches on startup - called once during startup + /// TODO: Temporary, this is temporal coupling, we cannot use IUmbracoApplicationLifetime.ApplicationInit (which we want to delete) + /// handler because that is executed with netcore's IHostApplicationLifetime.ApplicationStarted mechanism which fires async + /// which we don't want since this will not have initialized before our endpoints execute. So for now this is explicitly + /// called on UseUmbracoContentCaching on startup. + /// void LoadCachesOnStartup(); /// diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index f357108a4e..51fc9ccf64 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Routing /// /// Gets the UmbracoContext. /// - IUmbracoContext UmbracoContext { get; } + IUmbracoContext UmbracoContext { get; } // TODO: This should be injected and removed from here /// /// Gets or sets the cleaned up Uri used for routing. diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 65e8e343f7..84945c78d4 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using Microsoft.Extensions.Logging; using Umbraco.Core.Cache; @@ -20,7 +20,6 @@ namespace Umbraco.Core.Scoping private readonly CoreDebugSettings _coreDebugSettings; private readonly IMediaFileSystem _mediaFileSystem; private readonly ILogger _logger; - private readonly ITypeFinder _typeFinder; private readonly IsolationLevel _isolationLevel; private readonly RepositoryCacheMode _repositoryCacheMode; @@ -38,10 +37,15 @@ namespace Umbraco.Core.Scoping private IEventDispatcher _eventDispatcher; // initializes a new scope - private Scope(ScopeProvider scopeProvider, + private Scope( + ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, - ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, IScopeContext scopeContext, bool detachable, + ILogger logger, + FileSystems fileSystems, + Scope parent, + IScopeContext scopeContext, + bool detachable, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, @@ -53,7 +57,6 @@ namespace Umbraco.Core.Scoping _coreDebugSettings = coreDebugSettings; _mediaFileSystem = mediaFileSystem; _logger = logger; - _typeFinder = typeFinder; Context = scopeContext; @@ -117,31 +120,38 @@ namespace Umbraco.Core.Scoping } // initializes a new scope - public Scope(ScopeProvider scopeProvider, + public Scope( + ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, - ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, bool detachable, IScopeContext scopeContext, + ILogger logger, + FileSystems fileSystems, + bool detachable, + IScopeContext scopeContext, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, typeFinder, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } // initializes a new scope in a nested scopes chain, with its parent - public Scope(ScopeProvider scopeProvider, + public Scope( + ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, - ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, + ILogger logger, + FileSystems fileSystems, + Scope parent, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, typeFinder, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } public Guid InstanceId { get; } = Guid.NewGuid(); diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index 52c096b224..151c4cfb3c 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -23,13 +23,12 @@ namespace Umbraco.Core.Scoping { private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; - private readonly ITypeFinder _typeFinder; private readonly IRequestCache _requestCache; private readonly FileSystems _fileSystems; private readonly CoreDebugSettings _coreDebugSettings; private readonly IMediaFileSystem _mediaFileSystem; - public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ILoggerFactory loggerFactory, ITypeFinder typeFinder, IRequestCache requestCache) + public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ILoggerFactory loggerFactory, IRequestCache requestCache) { DatabaseFactory = databaseFactory; _fileSystems = fileSystems; @@ -37,7 +36,6 @@ namespace Umbraco.Core.Scoping _mediaFileSystem = mediaFileSystem; _logger = logger; _loggerFactory = loggerFactory; - _typeFinder = typeFinder; _requestCache = requestCache; // take control of the FileSystems _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; @@ -256,7 +254,7 @@ namespace Umbraco.Core.Scoping IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null) { - return new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _typeFinder, _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); + return new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); } /// @@ -312,13 +310,13 @@ namespace Umbraco.Core.Scoping { var ambientContext = AmbientContext; var newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _typeFinder, _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); // assign only if scope creation did not throw! SetAmbient(scope, newContext ?? ambientContext); return scope; } - var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _typeFinder, _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); SetAmbient(nested, AmbientContext); return nested; } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index 44aacab944..3bc29c9a9d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -39,14 +39,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components var mock = new Mock(); var loggerFactory = NullLoggerFactory.Instance; var logger = loggerFactory.CreateLogger("GenericLogger"); - var typeFinder = TestHelper.GetTypeFinder(); var globalSettings = new GlobalSettings(); var connectionStrings = new ConnectionStrings(); var f = new UmbracoDatabaseFactory(loggerFactory.CreateLogger(), loggerFactory, Options.Create(globalSettings), Options.Create(connectionStrings), new Lazy(() => new MapperCollection(Enumerable.Empty())), TestHelper.DbProviderFactoryCreator); var fs = new FileSystems(mock.Object, loggerFactory.CreateLogger(), loggerFactory, IOHelper, Options.Create(globalSettings), Mock.Of()); var coreDebug = new CoreDebugSettings(); var mediaFileSystem = Mock.Of(); - var p = new ScopeProvider(f, fs, Options.Create(coreDebug), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, typeFinder, NoAppCache.Instance); + var p = new ScopeProvider(f, fs, Options.Create(coreDebug), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(logger); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(loggerFactory.CreateLogger); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs index b414e49e95..ba5910da29 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -9,7 +9,9 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Common.ModelBinders; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; +using Umbraco.Web.Website.Routing; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { @@ -20,7 +22,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Does_Not_Bind_Model_When_UmbracoDataToken_Not_In_Route_Data() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), withUmbracoDataToken: false); + IPublishedContent pc = CreatePublishedContent(); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, withUmbracoDataToken: false); var binder = new ContentModelBinder(); // Act @@ -34,7 +37,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Does_Not_Bind_Model_When_Source_Not_Of_Expected_Type() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), source: new NonContentModel()); + IPublishedContent pc = CreatePublishedContent(); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: new NonContentModel()); var binder = new ContentModelBinder(); // Act @@ -48,8 +52,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void BindModel_Returns_If_Same_Type() { // Arrange - var content = new ContentModel(CreatePublishedContent()); - var bindingContext = CreateBindingContext(typeof(ContentModel), source: content); + IPublishedContent pc = CreatePublishedContent(); + var content = new ContentModel(pc); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: content); var binder = new ContentModelBinder(); // Act @@ -63,7 +68,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Binds_From_IPublishedContent_To_Content_Model() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), source: CreatePublishedContent()); + IPublishedContent pc = CreatePublishedContent(); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: pc); var binder = new ContentModelBinder(); // Act @@ -77,7 +83,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Binds_From_IPublishedContent_To_Content_Model_Of_T() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), source: new ContentModel(new ContentType2(CreatePublishedContent()))); + IPublishedContent pc = CreatePublishedContent(); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: new ContentModel(new ContentType2(pc))); var binder = new ContentModelBinder(); // Act @@ -87,12 +94,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders Assert.True(bindingContext.Result.IsModelSet); } - private ModelBindingContext CreateBindingContext(Type modelType, bool withUmbracoDataToken = true, object source = null) + private ModelBindingContext CreateBindingContext(Type modelType, IPublishedContent publishedContent, bool withUmbracoDataToken = true, object source = null) { var httpContext = new DefaultHttpContext(); var routeData = new RouteData(); if (withUmbracoDataToken) - routeData.DataTokens.Add(Constants.Web.UmbracoDataToken, source); + { + routeData.Values.Add(Constants.Web.UmbracoRouteDefinitionDataToken, new UmbracoRouteValues(publishedContent)); + } var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor()); var metadataProvider = new EmptyModelMetadataProvider(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs index 501c10551d..660a9b7bd1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -9,7 +9,9 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Common.ModelBinders; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; +using Umbraco.Web.Website.Routing; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { @@ -106,8 +108,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders [Test] public void No_DataToken_Returns_Null() { - var content = new MyContent(Mock.Of()); - var bindingContext = CreateBindingContext(typeof(ContentModel), false, content); + IPublishedContent pc = Mock.Of(); + var content = new MyContent(pc); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, false, content); _contentModelBinder.BindModelAsync(bindingContext); @@ -117,7 +120,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders [Test] public void Invalid_DataToken_Model_Type_Returns_Null() { - var bindingContext = CreateBindingContext(typeof(IPublishedContent), source: "Hello"); + IPublishedContent pc = Mock.Of(); + var bindingContext = CreateBindingContext(typeof(IPublishedContent), pc, source: "Hello"); _contentModelBinder.BindModelAsync(bindingContext); Assert.IsNull(bindingContext.Result.Model); } @@ -125,20 +129,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders [Test] public void IPublishedContent_DataToken_Model_Type_Uses_DefaultImplementation() { - var content = new MyContent(Mock.Of()); - var bindingContext = CreateBindingContext(typeof(MyContent), source: content); + IPublishedContent pc = Mock.Of(); + var content = new MyContent(pc); + var bindingContext = CreateBindingContext(typeof(MyContent), pc, source: content); _contentModelBinder.BindModelAsync(bindingContext); Assert.AreEqual(content, bindingContext.Result.Model); } - private ModelBindingContext CreateBindingContext(Type modelType, bool withUmbracoDataToken = true, object source = null) + private ModelBindingContext CreateBindingContext(Type modelType, IPublishedContent publishedContent, bool withUmbracoDataToken = true, object source = null) { var httpContext = new DefaultHttpContext(); var routeData = new RouteData(); if (withUmbracoDataToken) - routeData.DataTokens.Add(Constants.Web.UmbracoDataToken, source); + { + routeData.Values.Add(Constants.Web.UmbracoRouteDefinitionDataToken, new UmbracoRouteValues(publishedContent)); + } var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor()); var metadataProvider = new EmptyModelMetadataProvider(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs deleted file mode 100644 index bf5c422bd8..0000000000 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -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.Infrastructure; -using Microsoft.AspNetCore.Mvc.ViewEngines; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using NUnit.Framework; -using Umbraco.Web.Models; -using Umbraco.Web.Mvc; -using Umbraco.Web.Website.Controllers; - -namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers -{ - [TestFixture] - public class RenderIndexActionSelectorAttributeTests - { - [Test] - public void IsValidForRequest__ensure_caching_works() - { - var sut = new RenderIndexActionSelectorAttribute(); - - var actionDescriptor = - GetRenderMvcControllerIndexMethodFromCurrentType(typeof(MatchesDefaultIndexController)).First(); - var actionDescriptorCollectionProviderMock = new Mock(); - actionDescriptorCollectionProviderMock.Setup(x => x.ActionDescriptors) - .Returns(new ActionDescriptorCollection(Array.Empty(), 1)); - - var routeContext = CreateRouteContext(actionDescriptorCollectionProviderMock.Object); - - // Call the method multiple times - for (var i = 0; i < 1; i++) - { - sut.IsValidForRequest(routeContext, actionDescriptor); - } - - //Ensure the underlying ActionDescriptors is only called once. - actionDescriptorCollectionProviderMock.Verify(x=>x.ActionDescriptors, Times.Once); - } - - [Test] - [TestCase(typeof(MatchesDefaultIndexController), - "Index", new[] { typeof(ContentModel) }, typeof(IActionResult), ExpectedResult = true)] - [TestCase(typeof(MatchesOverriddenIndexController), - "Index", new[] { typeof(ContentModel) }, typeof(IActionResult), ExpectedResult = true)] - [TestCase(typeof(MatchesCustomIndexController), - "Index", new[] { typeof(ContentModel), typeof(int) }, typeof(IActionResult), ExpectedResult = false)] - [TestCase(typeof(MatchesAsyncIndexController), - "Index", new[] { typeof(ContentModel) }, typeof(Task), ExpectedResult = false)] - public bool IsValidForRequest__must_return_the_expected_result(Type controllerType, string actionName, Type[] parameterTypes, Type returnType) - { - //Fake all IActionDescriptor's that will be returned by IActionDescriptorCollectionProvider - var actionDescriptors = GetRenderMvcControllerIndexMethodFromCurrentType(controllerType); - - // Find the one that match the current request - var actualActionDescriptor = actionDescriptors.Single(x => x.ActionName == actionName - && x.ControllerTypeInfo.Name == controllerType.Name - && x.MethodInfo.ReturnType == returnType - && x.MethodInfo.GetParameters().Select(m => m.ParameterType).SequenceEqual(parameterTypes)); - - //Fake the IActionDescriptorCollectionProvider and add it to the service collection on httpcontext - var sut = new RenderIndexActionSelectorAttribute(); - - var routeContext = CreateRouteContext(new TestActionDescriptorCollectionProvider(actionDescriptors)); - - //Act - var result = sut.IsValidForRequest(routeContext, actualActionDescriptor); - return result; - } - - private ControllerActionDescriptor[] GetRenderMvcControllerIndexMethodFromCurrentType(Type controllerType) - { - var actions = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance) - .Where(m => !m.IsSpecialName - && m.GetCustomAttribute() is null - && m.Module.Name.Contains("Umbraco")); - - var actionDescriptors = actions - .Select(x => new ControllerActionDescriptor() - { - ControllerTypeInfo = controllerType.GetTypeInfo(), - ActionName = x.Name, - MethodInfo = x - }).ToArray(); - - return actionDescriptors; - } - - private static RouteContext CreateRouteContext(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) - { - //Fake the IActionDescriptorCollectionProvider and add it to the service collection on httpcontext - var httpContext = new DefaultHttpContext(); - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(actionDescriptorCollectionProvider); - httpContext.RequestServices = - new DefaultServiceProviderFactory(new ServiceProviderOptions()) - .CreateServiceProvider(serviceCollection); - - // Put the fake httpcontext on the route context. - var routeContext = new RouteContext(httpContext); - return routeContext; - } - - private class TestActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider - { - public TestActionDescriptorCollectionProvider(IReadOnlyList items) - { - ActionDescriptors = new ActionDescriptorCollection(items, 1); - } - - public ActionDescriptorCollection ActionDescriptors { get; } - } - - private class MatchesDefaultIndexController : RenderController - { - public MatchesDefaultIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) - { - } - } - - private class MatchesOverriddenIndexController : RenderController - { - public override IActionResult Index(ContentModel model) - { - return base.Index(model); - } - - public MatchesOverriddenIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) - { - } - } - - private class MatchesCustomIndexController : RenderController - { - public IActionResult Index(ContentModel model, int page) - { - return base.Index(model); - } - - public MatchesCustomIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) - { - } - } - - private class MatchesAsyncIndexController : RenderController - { - public new async Task Index(ContentModel model) - { - return await Task.FromResult(base.Index(model)); - } - - public MatchesAsyncIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) - { - } - } - } -} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs index d1ffc2044e..5db2d435c9 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Services; using Umbraco.Tests.Common; using Umbraco.Tests.Testing; using Umbraco.Web; +using Umbraco.Web.Common.Routing; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; @@ -164,16 +165,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers var content = Mock.Of(publishedContent => publishedContent.Id == 12345); - var publishedRequestMock = new Mock(); - publishedRequestMock.Setup(x => x.PublishedContent).Returns(content); - - var routeDefinition = new RouteDefinition - { - PublishedRequest = publishedRequestMock.Object - }; + var routeDefinition = new UmbracoRouteValues(content); var routeData = new RouteData(); - routeData.DataTokens.Add(CoreConstants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); + routeData.Values.Add(CoreConstants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); var ctrl = new TestSurfaceController(umbracoContextAccessor, Mock.Of(), Mock.Of()); ctrl.ControllerContext = new ControllerContext() diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs index 2bc89e0842..9c9e2d1da2 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using Umbraco.Core; @@ -161,7 +161,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var domainCache = new DomainCache(_domainService, _defaultCultureAccessor); return new PublishedSnapshot( - new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _contentTypeCache, _routesCache,_variationContextAccessor, previewToken), + new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _contentTypeCache, _routesCache, _variationContextAccessor, previewToken), new PublishedMediaCache(_xmlStore, _mediaService, _userService, _requestCache, _contentTypeCache, _entitySerializer, _umbracoContextAccessor, _variationContextAccessor), new PublishedMemberCache(_xmlStore, _requestCache, _memberService, _contentTypeCache, _userService, _variationContextAccessor), domainCache); @@ -278,5 +278,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache { return "Test status"; } + + public override void LoadCachesOnStartup() { } } } diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 4e1540a525..e663996d60 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; @@ -35,11 +35,11 @@ namespace Umbraco.Tests.Scoping DoThing2 = null; DoThing3 = null; - var services = TestHelper.GetRegister(); + var services = TestHelper.GetRegister(); var composition = new UmbracoBuilder(services, Mock.Of(), TestHelper.GetMockedTypeLoader()); - _testObjects = new TestObjects(services); + _testObjects = new TestObjects(); var globalSettings = new GlobalSettings(); composition.Services.AddUnique(factory => new FileSystems(factory, factory.GetService>(), factory.GetService(), TestHelper.IOHelper, Microsoft.Extensions.Options.Options.Create(globalSettings), TestHelper.GetHostingEnvironment())); @@ -47,7 +47,7 @@ namespace Umbraco.Tests.Scoping Current.Factory = composition.CreateServiceProvider(); } - + [TestCase(false, true, true)] [TestCase(false, true, false)] [TestCase(false, false, true)] @@ -140,7 +140,7 @@ namespace Umbraco.Tests.Scoping { //content1 will be filtered from the args - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(new[]{ content1 , content3})); + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(new[] { content1, content3 })); scope.Events.Dispatch(DoDeleteForContent, this, new DeleteEventArgs(content1), "DoDeleteForContent"); scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); //this entire event will be filtered @@ -156,7 +156,7 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(content3.Id, ((SaveEventArgs)events[0].Args).SavedEntities.First().Id); Assert.AreEqual(typeof(DeleteEventArgs), events[1].Args.GetType()); - Assert.AreEqual(content1.Id, ((DeleteEventArgs) events[1].Args).DeletedEntities.First().Id); + Assert.AreEqual(content1.Id, ((DeleteEventArgs)events[1].Args).DeletedEntities.First().Id); Assert.AreEqual(typeof(SaveEventArgs), events[2].Args.GetType()); Assert.AreEqual(content2.Id, ((SaveEventArgs)events[2].Args).SavedEntities.First().Id); @@ -177,8 +177,8 @@ namespace Umbraco.Tests.Scoping var scopeProvider = _testObjects.GetScopeProvider(NullLoggerFactory.Instance); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { - scope.Events.Dispatch(Test_Unpublished, contentService, new PublishEventArgs(new [] { content }), "Unpublished"); - scope.Events.Dispatch(Test_Deleted, contentService, new DeleteEventArgs(new [] { content }), "Deleted"); + scope.Events.Dispatch(Test_Unpublished, contentService, new PublishEventArgs(new[] { content }), "Unpublished"); + scope.Events.Dispatch(Test_Deleted, contentService, new DeleteEventArgs(new[] { content }), "Deleted"); // see U4-10764 var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); @@ -258,9 +258,9 @@ namespace Umbraco.Tests.Scoping // events have been queued var events = scope.Events.GetEvents(EventDefinitionFilter.FirstIn).ToArray(); Assert.AreEqual(1, events.Length); - Assert.AreEqual(content1, ((SaveEventArgs) events[0].Args).SavedEntities.First()); + Assert.AreEqual(content1, ((SaveEventArgs)events[0].Args).SavedEntities.First()); Assert.IsTrue(object.ReferenceEquals(content1, ((SaveEventArgs)events[0].Args).SavedEntities.First())); - Assert.AreEqual(content1.UpdateDate, ((SaveEventArgs) events[0].Args).SavedEntities.First().UpdateDate); + Assert.AreEqual(content1.UpdateDate, ((SaveEventArgs)events[0].Args).SavedEntities.First().UpdateDate); } } diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index 6e27bdd07c..fc3b0d6dba 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -27,7 +27,7 @@ namespace Umbraco.Tests.TestHelpers protected ISqlContext SqlContext { get; private set; } - internal TestObjects TestObjects = new TestObjects(null); + internal TestObjects TestObjects = new TestObjects(); protected Sql Sql() { diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 91b82caccb..3b17861fb3 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -1,7 +1,5 @@ -using System; +using System; using System.Configuration; -using System.IO; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; @@ -9,20 +7,14 @@ using Moq; using NPoco; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Events; -using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; using Umbraco.Persistance.SqlCe; -using Umbraco.Tests.Common.Builders; -using Umbraco.Tests.TestHelpers.Stubs; using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.TestHelpers @@ -32,11 +24,9 @@ namespace Umbraco.Tests.TestHelpers /// internal partial class TestObjects { - private readonly IServiceCollection _register; - public TestObjects(IServiceCollection register) + public TestObjects() { - _register = register; } /// @@ -69,19 +59,7 @@ namespace Umbraco.Tests.TestHelpers return new UmbracoDatabase(connection, sqlContext, logger, TestHelper.BulkSqlInsertProvider); } - private Lazy GetLazyService(IServiceProvider container, Func ctor) - where T : class - { - return new Lazy(() => container?.GetService() ?? ctor(container)); - } - - private T GetRepo(IServiceProvider container) - where T : class, IRepository - { - return container?.GetService() ?? Mock.Of(); - } - - public IScopeProvider GetScopeProvider(ILoggerFactory loggerFactory, ITypeFinder typeFinder = null, FileSystems fileSystems = null, IUmbracoDatabaseFactory databaseFactory = null) + public IScopeProvider GetScopeProvider(ILoggerFactory loggerFactory, FileSystems fileSystems = null, IUmbracoDatabaseFactory databaseFactory = null) { var globalSettings = new GlobalSettings(); var connectionString = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ConnectionString; @@ -103,11 +81,10 @@ namespace Umbraco.Tests.TestHelpers TestHelper.DbProviderFactoryCreator); } - typeFinder ??= new TypeFinder(loggerFactory.CreateLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash()); fileSystems ??= new FileSystems(Current.Factory, loggerFactory.CreateLogger(), loggerFactory, TestHelper.IOHelper, Options.Create(globalSettings), TestHelper.GetHostingEnvironment()); var coreDebug = TestHelper.CoreDebugSettings; var mediaFileSystem = Mock.Of(); - return new ScopeProvider(databaseFactory, fileSystems, Microsoft.Extensions.Options.Options.Create(coreDebugSettings), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, typeFinder, NoAppCache.Instance); + return new ScopeProvider(databaseFactory, fileSystems, Options.Create(coreDebugSettings), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance); } } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 533966f5a9..a3d48e3592 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -1,4 +1,4 @@ -using Examine; +using Examine; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -235,10 +235,7 @@ namespace Umbraco.Tests.Testing services.AddUnique(membershipHelper); - - - - TestObjects = new TestObjects(services); + TestObjects = new TestObjects(); Compose(); Current.Factory = Factory = Builder.CreateServiceProvider(); Initialize(); @@ -494,7 +491,7 @@ namespace Umbraco.Tests.Testing Builder.WithCollectionBuilder(); // empty Builder.Services.AddUnique(factory - => TestObjects.GetScopeProvider(_loggerFactory, factory.GetService(), factory.GetService(), factory.GetService())); + => TestObjects.GetScopeProvider(_loggerFactory, factory.GetService(), factory.GetService())); Builder.Services.AddUnique(factory => (IScopeAccessor)factory.GetRequiredService()); Builder.ComposeServices(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 74eed99143..2f9ab25d2a 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -275,7 +275,6 @@ - diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index a2bcb4928a..6afc75e931 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web; using System.Web.Mvc; using System.Web.Routing; @@ -161,7 +161,7 @@ namespace Umbraco.Tests.Web.Mvc }; var routeData = new RouteData(); - routeData.DataTokens.Add(Core.Constants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); + routeData.Values.Add(Core.Constants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); var ctrl = new TestSurfaceController(umbracoContextAccessor, Mock.Of()); ctrl.ControllerContext = new ControllerContext(Mock.Of(), routeData, ctrl); diff --git a/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs b/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs deleted file mode 100644 index 4e52617e6c..0000000000 --- a/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System.IO; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; -using Moq; -using NUnit.Framework; -using Umbraco.Core.Security; -using Umbraco.Tests.Common; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; -using Umbraco.Web; -using Umbraco.Web.Mvc; -using Umbraco.Web.PublishedCache; -using Current = Umbraco.Web.Composing.Current; - -namespace Umbraco.Tests.Web -{ - [TestFixture] - [UmbracoTest(WithApplication = true)] - public class WebExtensionMethodTests : UmbracoTestBase - { - [Test] - public void RouteDataExtensions_GetUmbracoContext() - { - var httpContextAccessor = TestHelper.GetHttpContextAccessor(); - - var umbCtx = new UmbracoContext( - httpContextAccessor, - Mock.Of(), - Mock.Of(), - TestObjects.GetGlobalSettings(), - HostingEnvironment, - new TestVariationContextAccessor(), - UriUtility, - new AspNetCookieManager(httpContextAccessor)); - var r1 = new RouteData(); - r1.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx); - - Assert.IsTrue(r1.DataTokens.ContainsKey(Core.Constants.Web.UmbracoContextDataToken)); - Assert.AreSame(umbCtx, r1.DataTokens[Core.Constants.Web.UmbracoContextDataToken]); - } - - [Test] - public void ControllerContextExtensions_GetUmbracoContext_From_RouteValues() - { - var httpContextAccessor = TestHelper.GetHttpContextAccessor(); - - var umbCtx = new UmbracoContext( - httpContextAccessor, - Mock.Of(), - Mock.Of(), - TestObjects.GetGlobalSettings(), - HostingEnvironment, - new TestVariationContextAccessor(), - UriUtility, - new AspNetCookieManager(httpContextAccessor)); - - var r1 = new RouteData(); - r1.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx); - var ctx1 = CreateViewContext(new ControllerContext(Mock.Of(), r1, new MyController())); - var r2 = new RouteData(); - r2.DataTokens.Add("ParentActionViewContext", ctx1); - var ctx2 = CreateViewContext(new ControllerContext(Mock.Of(), r2, new MyController())); - var r3 = new RouteData(); - r3.DataTokens.Add("ParentActionViewContext", ctx2); - var ctx3 = CreateViewContext(new ControllerContext(Mock.Of(), r3, new MyController())); - - var result = ctx3.GetUmbracoContext(); - - Assert.IsNotNull(result); - Assert.AreSame(umbCtx, result); - } - - [Test] - public void ControllerContextExtensions_GetUmbracoContext_From_Current() - { - var httpContextAccessor = TestHelper.GetHttpContextAccessor(); - - var umbCtx = new UmbracoContext( - httpContextAccessor, - Mock.Of(), - Mock.Of(), - TestObjects.GetGlobalSettings(), - HostingEnvironment, - new TestVariationContextAccessor(), - UriUtility, - new AspNetCookieManager(httpContextAccessor)); - - var httpContext = Mock.Of(); - - var r1 = new RouteData(); - var ctx1 = CreateViewContext(new ControllerContext(httpContext, r1, new MyController())); - var r2 = new RouteData(); - r2.DataTokens.Add("ParentActionViewContext", ctx1); - var ctx2 = CreateViewContext(new ControllerContext(httpContext, r2, new MyController())); - var r3 = new RouteData(); - r3.DataTokens.Add("ParentActionViewContext", ctx2); - var ctx3 = CreateViewContext(new ControllerContext(httpContext, r3, new MyController())); - - Current.UmbracoContextAccessor = new TestUmbracoContextAccessor(); - Current.UmbracoContextAccessor.UmbracoContext = umbCtx; - - var result = ctx3.GetUmbracoContext(); - - Assert.IsNotNull(result); - Assert.AreSame(umbCtx, result); - } - - [Test] - public void ControllerContextExtensions_GetDataTokenInViewContextHierarchy() - { - var r1 = new RouteData(); - r1.DataTokens.Add("hello", "world"); - r1.DataTokens.Add("r", "1"); - var ctx1 = CreateViewContext(new ControllerContext(Mock.Of(), r1, new MyController())); - var r2 = new RouteData(); - r2.DataTokens.Add("ParentActionViewContext", ctx1); - r2.DataTokens.Add("r", "2"); - var ctx2 = CreateViewContext(new ControllerContext(Mock.Of(), r2, new MyController())); - var r3 = new RouteData(); - r3.DataTokens.Add("ParentActionViewContext", ctx2); - r3.DataTokens.Add("r", "3"); - var ctx3 = CreateViewContext(new ControllerContext(Mock.Of(), r3, new MyController())); - - var result = ctx3.GetDataTokenInViewContextHierarchy("hello"); - - Assert.IsNotNull(result as string); - Assert.AreEqual((string) result, "world"); - } - - private static ViewContext CreateViewContext(ControllerContext ctx) - { - return new ViewContext(ctx, Mock.Of(), new ViewDataDictionary(), new TempDataDictionary(), new StringWriter()); - } - - private class MyController : Controller - { } - } -} diff --git a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs index 4b8f730e45..a97b67a900 100644 --- a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Html; @@ -19,6 +19,7 @@ using Umbraco.Web.Common.ModelBinders; namespace Umbraco.Web.Common.AspNetCore { + // TODO: Should be in Views namespace? public abstract class UmbracoViewPage : UmbracoViewPage { @@ -92,6 +93,8 @@ namespace Umbraco.Web.Common.AspNetCore base.WriteLiteral(value); } + // TODO: This trick doesn't work anymore, this method used to be an override. + // Now the model is bound in a different place // maps model protected async Task SetViewDataAsync(ViewDataDictionary viewData) { @@ -111,8 +114,6 @@ namespace Umbraco.Web.Common.AspNetCore ViewData = (ViewDataDictionary) viewData; } - - // viewData is the ViewDataDictionary (maybe ) that we have // modelType is the type of the model that we need to bind to // diff --git a/src/Umbraco.Web.Website/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs similarity index 63% rename from src/Umbraco.Web.Website/Controllers/RenderController.cs rename to src/Umbraco.Web.Common/Controllers/RenderController.cs index 071560d860..ec73c061e2 100644 --- a/src/Umbraco.Web.Website/Controllers/RenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs @@ -2,13 +2,14 @@ using System; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.Extensions.Logging; +using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.Filters; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; using Umbraco.Web.Routing; -namespace Umbraco.Web.Website.Controllers +namespace Umbraco.Web.Common.Controllers { /// @@ -17,9 +18,9 @@ namespace Umbraco.Web.Website.Controllers [TypeFilter(typeof(ModelBindingExceptionFilter))] public class RenderController : UmbracoController, IRenderController { - private IPublishedRequest _publishedRequest; private readonly ILogger _logger; private readonly ICompositeViewEngine _compositeViewEngine; + private UmbracoRouteValues _routeValues; /// /// Initializes a new instance of the class. @@ -33,27 +34,27 @@ namespace Umbraco.Web.Website.Controllers /// /// Gets the current content item. /// - protected IPublishedContent CurrentPage => PublishedRequest.PublishedContent; + protected IPublishedContent CurrentPage => UmbracoRouteValues.PublishedContent; /// - /// Gets the current published content request. + /// Gets the /// - protected internal virtual IPublishedRequest PublishedRequest + protected UmbracoRouteValues UmbracoRouteValues { get { - if (_publishedRequest != null) + if (_routeValues != null) { - return _publishedRequest; + return _routeValues; } - if (RouteData.DataTokens.ContainsKey(Core.Constants.Web.PublishedDocumentRequestDataToken) == false) + if (!ControllerContext.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var def)) { - throw new InvalidOperationException("DataTokens must contain an 'umbraco-doc-request' key with a PublishedRequest object"); + throw new InvalidOperationException($"No route value found with key {Core.Constants.Web.UmbracoRouteDefinitionDataToken}"); } - _publishedRequest = (IPublishedRequest)RouteData.DataTokens[Core.Constants.Web.PublishedDocumentRequestDataToken]; - return _publishedRequest; + _routeValues = (UmbracoRouteValues)def; + return _routeValues; } } @@ -79,22 +80,20 @@ namespace Umbraco.Web.Website.Controllers /// The type of the model. /// The model. /// The action result. - /// If the template found in the route values doesn't physically exist, then an empty ContentResult will be returned. + /// If the template found in the route values doesn't physically exist and exception is thrown protected IActionResult CurrentTemplate(T model) { - var template = ControllerContext.RouteData.Values["action"].ToString(); - if (EnsurePhsyicalViewExists(template) == false) + if (EnsurePhsyicalViewExists(UmbracoRouteValues.TemplateName) == false) { - throw new InvalidOperationException("No physical template file was found for template " + template); + throw new InvalidOperationException("No physical template file was found for template " + UmbracoRouteValues.TemplateName); } - return View(template, model); + return View(UmbracoRouteValues.TemplateName, model); } /// /// The default action to render the front-end view. /// - [RenderIndexActionSelector] - public virtual IActionResult Index(ContentModel model) => CurrentTemplate(model); + public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage)); } } diff --git a/src/Umbraco.Web.Common/Macros/MacroRenderer.cs b/src/Umbraco.Web.Common/Macros/MacroRenderer.cs index c79c5229fc..9a9e46eefc 100644 --- a/src/Umbraco.Web.Common/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web.Common/Macros/MacroRenderer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -38,7 +38,7 @@ namespace Umbraco.Web.Macros private readonly IHttpContextAccessor _httpContextAccessor; public MacroRenderer( - IProfilingLogger profilingLogger , + IProfilingLogger profilingLogger, ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, @@ -53,7 +53,7 @@ namespace Umbraco.Web.Macros IRequestAccessor requestAccessor, IHttpContextAccessor httpContextAccessor) { - _profilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger )); + _profilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger)); _logger = logger; _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -111,12 +111,14 @@ namespace Umbraco.Web.Macros private MacroContent GetMacroContentFromCache(MacroModel model) { // only if cache is enabled - if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0) return null; + if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0) + return null; var cache = _appCaches.RuntimeCache; var macroContent = cache.GetCacheItem(CacheKeys.MacroContentCacheKey + model.CacheIdentifier); - if (macroContent == null) return null; + if (macroContent == null) + return null; _logger.LogDebug("Macro content loaded from cache '{MacroCacheId}'", model.CacheIdentifier); @@ -145,16 +147,19 @@ namespace Umbraco.Web.Macros private void AddMacroContentToCache(MacroModel model, MacroContent macroContent) { // only if cache is enabled - if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0) return; + if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0) + return; // just make sure... - if (macroContent == null) return; + if (macroContent == null) + return; // do not cache if it should cache by member and there's not member if (model.CacheByMember) { var key = _memberUserKeyProvider.GetMemberProviderUserKey(); - if (key is null) return; + if (key is null) + return; } // remember when we cache the content @@ -184,10 +189,12 @@ namespace Umbraco.Web.Macros private FileInfo GetMacroFile(MacroModel model) { var filename = GetMacroFileName(model); - if (filename == null) return null; + if (filename == null) + return null; var mapped = _hostingEnvironment.MapPathContentRoot(filename); - if (mapped == null) return null; + if (mapped == null) + return null; var file = new FileInfo(mapped); return file.Exists ? file : null; @@ -223,7 +230,8 @@ namespace Umbraco.Web.Macros private MacroContent Render(MacroModel macro, IPublishedContent content) { - if (content == null) throw new ArgumentNullException(nameof(content)); + if (content == null) + throw new ArgumentNullException(nameof(content)); var macroInfo = $"Render Macro: {macro.Name}, cache: {macro.CacheDuration}"; using (_profilingLogger.DebugDuration(macroInfo, "Rendered Macro.")) @@ -328,7 +336,8 @@ namespace Umbraco.Web.Macros /// should not be cached. In that case the attempt may also contain an exception. private Attempt ExecuteMacroOfType(MacroModel model, IPublishedContent content) { - if (model == null) throw new ArgumentNullException(nameof(model)); + if (model == null) + throw new ArgumentNullException(nameof(model)); // ensure that we are running against a published node (ie available in XML) // that may not be the case if the macro is embedded in a RTE of an unpublished document @@ -356,7 +365,7 @@ namespace Umbraco.Web.Macros /// The text output of the macro execution. private MacroContent ExecutePartialView(MacroModel macro, IPublishedContent content) { - var engine = new PartialViewMacroEngine(_umbracoContextAccessor, _httpContextAccessor, _hostingEnvironment); + var engine = new PartialViewMacroEngine(_httpContextAccessor, _hostingEnvironment); return engine.Execute(macro, content); } diff --git a/src/Umbraco.Web.Common/Macros/PartialViewMacroEngine.cs b/src/Umbraco.Web.Common/Macros/PartialViewMacroEngine.cs index db1658e962..790e437148 100644 --- a/src/Umbraco.Web.Common/Macros/PartialViewMacroEngine.cs +++ b/src/Umbraco.Web.Common/Macros/PartialViewMacroEngine.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Text.Encodings.Web; @@ -27,56 +27,71 @@ namespace Umbraco.Web.Common.Macros { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHostingEnvironment _hostingEnvironment; - private readonly Func _getUmbracoContext; + //private readonly Func _getUmbracoContext; public PartialViewMacroEngine( - IUmbracoContextAccessor umbracoContextAccessor, + //IUmbracoContextAccessor umbracoContextAccessor, IHttpContextAccessor httpContextAccessor, IHostingEnvironment hostingEnvironment) { _httpContextAccessor = httpContextAccessor; _hostingEnvironment = hostingEnvironment; - _getUmbracoContext = () => - { - var context = umbracoContextAccessor.UmbracoContext; - if (context == null) - throw new InvalidOperationException( - $"The {GetType()} cannot execute with a null UmbracoContext.Current reference."); - return context; - }; + //_getUmbracoContext = () => + //{ + // var context = umbracoContextAccessor.UmbracoContext; + // if (context == null) + // { + // throw new InvalidOperationException( + // $"The {GetType()} cannot execute with a null UmbracoContext.Current reference."); + // } + + // return context; + //}; } - public bool Validate(string code, string tempFileName, IPublishedContent currentPage, out string errorMessage) - { - var temp = GetVirtualPathFromPhysicalPath(tempFileName); - try - { - CompileAndInstantiate(temp); - } - catch (Exception exception) - { - errorMessage = exception.Message; - return false; - } + //public bool Validate(string code, string tempFileName, IPublishedContent currentPage, out string errorMessage) + //{ + // var temp = GetVirtualPathFromPhysicalPath(tempFileName); + // try + // { + // CompileAndInstantiate(temp); + // } + // catch (Exception exception) + // { + // errorMessage = exception.Message; + // return false; + // } - errorMessage = string.Empty; - return true; - } + // errorMessage = string.Empty; + // return true; + //} public MacroContent Execute(MacroModel macro, IPublishedContent content) { - if (macro == null) throw new ArgumentNullException(nameof(macro)); - if (content == null) throw new ArgumentNullException(nameof(content)); + if (macro == null) + { + throw new ArgumentNullException(nameof(macro)); + } + + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + if (string.IsNullOrWhiteSpace(macro.MacroSource)) + { throw new ArgumentException("The MacroSource property of the macro object cannot be null or empty"); + } var httpContext = _httpContextAccessor.GetRequiredHttpContext(); - var umbCtx = _getUmbracoContext(); + //var umbCtx = _getUmbracoContext(); var routeVals = new RouteData(); routeVals.Values.Add("controller", "PartialViewMacro"); routeVals.Values.Add("action", "Index"); - routeVals.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx); //required for UmbracoViewPage + + //TODO: Was required for UmbracoViewPage need to figure out if we still need that, i really don't think this is necessary + //routeVals.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx); var modelMetadataProvider = httpContext.RequestServices.GetRequiredService(); var tempDataProvider = httpContext.RequestServices.GetRequiredService(); @@ -109,6 +124,7 @@ namespace Umbraco.Web.Common.Macros return new MacroContent { Text = output }; } + private class FakeView : IView { /// @@ -120,29 +136,30 @@ namespace Umbraco.Web.Common.Macros /// public string Path { get; } = "View"; } - private string GetVirtualPathFromPhysicalPath(string physicalPath) - { - var rootpath = _hostingEnvironment.MapPathContentRoot("~/"); - physicalPath = physicalPath.Replace(rootpath, ""); - physicalPath = physicalPath.Replace("\\", "/"); - return "~/" + physicalPath; - } - private static PartialViewMacroPage CompileAndInstantiate(string virtualPath) - { - // //Compile Razor - We Will Leave This To ASP.NET Compilation Engine & ASP.NET WebPages - // //Security in medium trust is strict around here, so we can only pass a virtual file path - // //ASP.NET Compilation Engine caches returned types - // //Changed From BuildManager As Other Properties Are Attached Like Context Path/ - // var webPageBase = WebPageBase.CreateInstanceFromVirtualPath(virtualPath); - // var webPage = webPageBase as PartialViewMacroPage; - // if (webPage == null) - // throw new InvalidCastException("All Partial View Macro views must inherit from " + typeof(PartialViewMacroPage).FullName); - // return webPage; + //private string GetVirtualPathFromPhysicalPath(string physicalPath) + //{ + // var rootpath = _hostingEnvironment.MapPathContentRoot("~/"); + // physicalPath = physicalPath.Replace(rootpath, ""); + // physicalPath = physicalPath.Replace("\\", "/"); + // return "~/" + physicalPath; + //} - //TODO? How to check this - return null; - } + //private static PartialViewMacroPage CompileAndInstantiate(string virtualPath) + //{ + // // //Compile Razor - We Will Leave This To ASP.NET Compilation Engine & ASP.NET WebPages + // // //Security in medium trust is strict around here, so we can only pass a virtual file path + // // //ASP.NET Compilation Engine caches returned types + // // //Changed From BuildManager As Other Properties Are Attached Like Context Path/ + // // var webPageBase = WebPageBase.CreateInstanceFromVirtualPath(virtualPath); + // // var webPage = webPageBase as PartialViewMacroPage; + // // if (webPage == null) + // // throw new InvalidCastException("All Partial View Macro views must inherit from " + typeof(PartialViewMacroPage).FullName); + // // return webPage; + + // //TODO? How to check this + // return null; + //} } } diff --git a/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs index 113f411c6f..d8178033c9 100644 --- a/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs +++ b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; namespace Umbraco.Web.Common.ModelBinders @@ -15,26 +16,15 @@ namespace Umbraco.Web.Common.ModelBinders { public Task BindModelAsync(ModelBindingContext bindingContext) { - if (bindingContext.ActionContext.RouteData.DataTokens.TryGetValue(Core.Constants.Web.UmbracoDataToken, out var source) == false) + // Although this model binder is built to work both ways between IPublishedContent and IContentModel in reality + // only IPublishedContent will ever exist in the request. + if (!bindingContext.ActionContext.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var source) + || !(source is UmbracoRouteValues umbracoRouteValues)) { return Task.CompletedTask; } - // This model binder deals with IContentModel and IPublishedContent by extracting the model from the route's - // datatokens. This data token is set in 2 places: RenderRouteHandler, UmbracoVirtualNodeRouteHandler - // and both always set the model to an instance of `ContentModel`. - - // No need for type checks to ensure we have the appropriate binder, as in .NET Core this is handled in the provider, - // in this case ContentModelBinderProvider. - - // Being defensive though.... if for any reason the model is not either IContentModel or IPublishedContent, - // then we return since those are the only types this binder is dealing with. - if (source is IContentModel == false && source is IPublishedContent == false) - { - return Task.CompletedTask; - } - - BindModelAsync(bindingContext, source, bindingContext.ModelType); + BindModelAsync(bindingContext, umbracoRouteValues.PublishedContent, bindingContext.ModelType); return Task.CompletedTask; } @@ -56,7 +46,7 @@ namespace Umbraco.Web.Common.ModelBinders // If types already match, return var sourceType = source.GetType(); - if (sourceType. Inherits(modelType)) // includes == + if (sourceType.Inherits(modelType)) // includes == { bindingContext.Result = ModelBindingResult.Success(source); return Task.CompletedTask; @@ -71,7 +61,8 @@ namespace Umbraco.Web.Common.ModelBinders { // else check if we can convert it to a content var attempt1 = source.TryConvertTo(); - if (attempt1.Success) sourceContent = attempt1.Result; + if (attempt1.Success) + sourceContent = attempt1.Result; } // If we have a content @@ -129,11 +120,13 @@ namespace Umbraco.Web.Common.ModelBinders // prepare message msg.Append("Cannot bind source"); - if (sourceContent) msg.Append(" content"); + if (sourceContent) + msg.Append(" content"); msg.Append(" type "); msg.Append(sourceType.FullName); msg.Append(" to model"); - if (modelContent) msg.Append(" content"); + if (modelContent) + msg.Append(" content"); msg.Append(" type "); msg.Append(modelType.FullName); msg.Append("."); diff --git a/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs b/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs new file mode 100644 index 0000000000..2ab047a757 --- /dev/null +++ b/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs @@ -0,0 +1,68 @@ +using System; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Extensions; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Common.Routing +{ + /// + /// Represents the data required to route to a specific controller/action during an Umbraco request + /// + public class UmbracoRouteValues + { + /// + /// The default action name + /// + public const string DefaultActionName = nameof(RenderController.Index); + + /// + /// Initializes a new instance of the class. + /// + public UmbracoRouteValues( + IPublishedContent publishedContent, + string controllerName = null, + Type controllerType = null, + string actionName = DefaultActionName, + string templateName = null, + bool hasHijackedRoute = false) + { + ControllerName = controllerName ?? ControllerExtensions.GetControllerName(); + ControllerType = controllerType ?? typeof(RenderController); + PublishedContent = publishedContent; + HasHijackedRoute = hasHijackedRoute; + ActionName = actionName; + TemplateName = templateName; + } + + /// + /// Gets the controller name + /// + public string ControllerName { get; } + + /// + /// Gets the action name + /// + public string ActionName { get; } + + /// + /// Gets the template name + /// + public string TemplateName { get; } + + /// + /// Gets the Controller type found for routing to + /// + public Type ControllerType { get; } + + /// + /// Gets the + /// + public IPublishedContent PublishedContent { get; } + + /// + /// Gets a value indicating whether the current request has a hijacked route/user controller routed for it + /// + public bool HasHijackedRoute { get; } + } +} diff --git a/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs b/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs index 62942541e9..abf269e062 100644 --- a/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs +++ b/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; @@ -99,7 +99,7 @@ namespace Umbraco.Web.Website.ActionResults /// private static void ValidateRouteData(RouteData routeData) { - if (routeData.DataTokens.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken) == false) + if (routeData.Values.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken) == false) { throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name + " in the context of an Http POST when using a SurfaceController form"); diff --git a/src/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttribute.cs b/src/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttribute.cs deleted file mode 100644 index 0027132c23..0000000000 --- a/src/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttribute.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.ActionConstraints; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; - -namespace Umbraco.Web.Website.Controllers -{ - /// - /// A custom ActionMethodSelector which will ensure that the RenderMvcController.Index(ContentModel model) action will be executed - /// if the - /// - internal class RenderIndexActionSelectorAttribute : ActionMethodSelectorAttribute - { - private static readonly ConcurrentDictionary> _controllerActionsCache = new ConcurrentDictionary>(); - - /// - /// Determines whether the action method selection is valid for the specified controller context. - /// - /// - /// true if the action method selection is valid for the specified controller context; otherwise, false. - /// - /// The route context. - /// Information about the action method. - public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action) - { - if (action is ControllerActionDescriptor controllerAction) - { - var currType = controllerAction.ControllerTypeInfo.UnderlyingSystemType; - var baseType = controllerAction.ControllerTypeInfo.BaseType; - - //It's the same type, so this must be the Index action to use - if (currType == baseType) - return true; - - var actions = _controllerActionsCache.GetOrAdd(currType, type => - { - var actionDescriptors = routeContext.HttpContext.RequestServices - .GetRequiredService().ActionDescriptors.Items - .Where(x => x is ControllerActionDescriptor).Cast() - .Where(x => x.ControllerTypeInfo == controllerAction.ControllerTypeInfo); - - return actionDescriptors; - }); - - //If there are more than one Index action for this controller, then - // this base class one should not be matched - return actions.Count(x => x.ActionName == "Index") <= 1; - } - - return false; - - } - } -} diff --git a/src/Umbraco.Web.Website/Controllers/SurfaceController.cs b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs index 1d3e4c5626..390da69453 100644 --- a/src/Umbraco.Web.Website/Controllers/SurfaceController.cs +++ b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs @@ -8,9 +8,9 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Routing; using Umbraco.Web.Website.ActionResults; -using Umbraco.Web.Website.Routing; namespace Umbraco.Web.Website.Controllers { @@ -39,18 +39,18 @@ namespace Umbraco.Web.Website.Controllers { var routeDefAttempt = TryGetRouteDefinitionFromAncestorViewContexts(); if (routeDefAttempt.Success == false) + { throw routeDefAttempt.Exception; + } var routeDef = routeDefAttempt.Result; - return routeDef.PublishedRequest.PublishedContent; + return routeDef.PublishedContent; } } /// /// Redirects to the Umbraco page with the given id /// - /// - /// protected RedirectToUmbracoPageResult RedirectToUmbracoPage(Guid contentKey) { return new RedirectToUmbracoPageResult(contentKey, PublishedUrlProvider, UmbracoContextAccessor); @@ -59,9 +59,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the Umbraco page with the given id and passes provided querystring /// - /// - /// - /// protected RedirectToUmbracoPageResult RedirectToUmbracoPage(Guid contentKey, QueryString queryString) { return new RedirectToUmbracoPageResult(contentKey, queryString, PublishedUrlProvider, UmbracoContextAccessor); @@ -70,8 +67,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the Umbraco page with the given published content /// - /// - /// protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent) { return new RedirectToUmbracoPageResult(publishedContent, PublishedUrlProvider, UmbracoContextAccessor); @@ -80,9 +75,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the Umbraco page with the given published content and passes provided querystring /// - /// - /// - /// protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent, QueryString queryString) { return new RedirectToUmbracoPageResult(publishedContent, queryString, PublishedUrlProvider, UmbracoContextAccessor); @@ -91,7 +83,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the currently rendered Umbraco page /// - /// protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage() { return new RedirectToUmbracoPageResult(CurrentPage, PublishedUrlProvider, UmbracoContextAccessor); @@ -100,8 +91,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the currently rendered Umbraco page and passes provided querystring /// - /// - /// protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage(QueryString queryString) { return new RedirectToUmbracoPageResult(CurrentPage, queryString, PublishedUrlProvider, UmbracoContextAccessor); @@ -110,7 +99,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the currently rendered Umbraco URL /// - /// /// /// This is useful if you need to redirect /// to the current page but the current page is actually a rewritten URL normally done with something like @@ -124,7 +112,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Returns the currently rendered Umbraco page /// - /// protected UmbracoPageResult CurrentUmbracoPage() { return new UmbracoPageResult(ProfilingLogger); @@ -133,18 +120,19 @@ namespace Umbraco.Web.Website.Controllers /// /// we need to recursively find the route definition based on the parent view context /// - /// - private Attempt TryGetRouteDefinitionFromAncestorViewContexts() + private Attempt TryGetRouteDefinitionFromAncestorViewContexts() { var currentContext = ControllerContext; while (!(currentContext is null)) { var currentRouteData = currentContext.RouteData; - if (currentRouteData.DataTokens.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken)) - return Attempt.Succeed((RouteDefinition)currentRouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken]); + if (currentRouteData.Values.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken)) + { + return Attempt.Succeed((UmbracoRouteValues)currentRouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken]); + } } - return Attempt.Fail( + return Attempt.Fail( new InvalidOperationException("Cannot find the Umbraco route definition in the route values, the request must be made in the context of an Umbraco request")); } } diff --git a/src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs b/src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs index 669e1835d4..65c27a3269 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Web.Common.Controllers; namespace Umbraco.Web.Website.Controllers { diff --git a/src/Umbraco.Web.Website/Routing/RouteDefinition.cs b/src/Umbraco.Web.Website/Routing/RouteDefinition.cs deleted file mode 100644 index 47206bd0c3..0000000000 --- a/src/Umbraco.Web.Website/Routing/RouteDefinition.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using Umbraco.Web.Routing; - -namespace Umbraco.Web.Website.Routing -{ - /// - /// Represents the data required to route to a specific controller/action during an Umbraco request - /// - public class RouteDefinition - { - public string ControllerName { get; set; } - public string ActionName { get; set; } - - /// - /// The Controller type found for routing to - /// - public Type ControllerType { get; set; } - - /// - /// Everything related to the current content request including the requested content - /// - public IPublishedRequest PublishedRequest { get; set; } - - /// - /// Gets/sets whether the current request has a hijacked route/user controller routed for it - /// - public bool HasHijackedRoute { get; set; } - } -} diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 328b7cc1d4..2c1debc3ee 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -10,10 +12,12 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Strings; using Umbraco.Extensions; using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.Middleware; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; using Umbraco.Web.Routing; using Umbraco.Web.Website.Controllers; @@ -23,6 +27,12 @@ namespace Umbraco.Web.Website.Routing /// /// The route value transformer for Umbraco front-end routes /// + /// + /// NOTE: In aspnet 5 DynamicRouteValueTransformer has been improved, see https://github.com/dotnet/aspnetcore/issues/21471 + /// It seems as though with the "State" parameter we could more easily assign the IPublishedRequest or IPublishedContent + /// or UmbracoContext more easily that way. In the meantime we will rely on assigning the IPublishedRequest to the + /// route values along with the IPublishedContent to the umbraco context + /// public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer { private readonly ILogger _logger; @@ -59,20 +69,13 @@ namespace Umbraco.Web.Website.Routing throw new InvalidOperationException($"There is no current UmbracoContext, it must be initialized before the {nameof(UmbracoRouteValueTransformer)} executes, ensure that {nameof(UmbracoRequestMiddleware)} is registered prior to 'UseRouting'"); } - bool routed = RouteRequest(_umbracoContextAccessor.UmbracoContext); + bool routed = RouteRequest(_umbracoContextAccessor.UmbracoContext, out IPublishedRequest publishedRequest); if (!routed) { // TODO: Deal with it not being routable, perhaps this should be an enum result? } - IPublishedRequest request = _umbracoContextAccessor.UmbracoContext.PublishedRequest; // This cannot be null here - - SetupRouteDataForRequest( - new ContentModel(request.PublishedContent), - request, - values); - - RouteDefinition routeDef = GetUmbracoRouteDefinition(httpContext, values, request); + UmbracoRouteValues routeDef = GetUmbracoRouteDefinition(httpContext, values, publishedRequest); values["controller"] = routeDef.ControllerName; if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false) { @@ -83,29 +86,9 @@ namespace Umbraco.Web.Website.Routing } /// - /// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views + /// Returns a object based on the current content request /// - private void SetupRouteDataForRequest(ContentModel contentModel, IPublishedRequest frequest, RouteValueDictionary values) - { - // put essential data into the data tokens, the 'umbraco' key is required to be there for the view engine - - // required for the ContentModelBinder and view engine. - // TODO: Are we sure, seems strange to need this in netcore - values.TryAdd(Constants.Web.UmbracoDataToken, contentModel); - - // required for RenderMvcController - // TODO: Are we sure, seems strange to need this in netcore - values.TryAdd(Constants.Web.PublishedDocumentRequestDataToken, frequest); - - // required for UmbracoViewPage - // TODO: Are we sure, seems strange to need this in netcore - values.TryAdd(Constants.Web.UmbracoContextDataToken, _umbracoContextAccessor.UmbracoContext); - } - - /// - /// Returns a object based on the current content request - /// - private RouteDefinition GetUmbracoRouteDefinition(HttpContext httpContext, RouteValueDictionary values, IPublishedRequest request) + private UmbracoRouteValues GetUmbracoRouteDefinition(HttpContext httpContext, RouteValueDictionary values, IPublishedRequest request) { if (httpContext is null) { @@ -125,17 +108,8 @@ namespace Umbraco.Web.Website.Routing Type defaultControllerType = _renderingDefaults.DefaultControllerType; var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType); - // creates the default route definition which maps to the 'UmbracoController' controller - var def = new RouteDefinition - { - ControllerName = defaultControllerName, - ControllerType = defaultControllerType, - PublishedRequest = request, - - // ActionName = ((Route)requestContext.RouteData.Route).Defaults["action"].ToString(), - ActionName = "Index", - HasHijackedRoute = false - }; + string customActionName = null; + var customControllerName = request.PublishedContent.ContentType.Alias; // never null // check that a template is defined), if it doesn't and there is a hijacked route it will just route // to the index Action @@ -144,34 +118,47 @@ namespace Umbraco.Web.Website.Routing // the template Alias should always be already saved with a safe name. // if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed // with the action name attribute. - var templateName = request.TemplateAlias.Split('.')[0].ToSafeAlias(_shortStringHelper); - def.ActionName = templateName; + customActionName = request.TemplateAlias.Split('.')[0].ToSafeAlias(_shortStringHelper); } - // check if there's a custom controller assigned, base on the document type alias. - Type controllerType = FindControllerType(request.PublishedContent.ContentType.Alias); + // creates the default route definition which maps to the 'UmbracoController' controller + var def = new UmbracoRouteValues( + request.PublishedContent, + defaultControllerName, + defaultControllerType, + templateName: customActionName); - // check if that controller exists - if (controllerType != null) + IReadOnlyList candidates = FindControllerCandidates(customControllerName, customActionName, def.ActionName); + + // check if there's a custom controller assigned, base on the document type alias. + var customControllerCandidates = candidates.Where(x => x.ControllerName.InvariantEquals(customControllerName)).ToList(); + + // check if that custom controller exists + if (customControllerCandidates.Count > 0) { + ControllerActionDescriptor controllerDescriptor = customControllerCandidates[0]; + // ensure the controller is of type IRenderController and ControllerBase - if (TypeHelper.IsTypeAssignableFrom(controllerType) - && TypeHelper.IsTypeAssignableFrom(controllerType)) + if (TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo) + && TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo)) { - // set the controller and name to the custom one - def.ControllerType = controllerType; - def.ControllerName = ControllerExtensions.GetControllerName(controllerType); - if (def.ControllerName != defaultControllerName) - { - def.HasHijackedRoute = true; - } + // now check if the custom action matches + var customActionExists = customActionName != null && customControllerCandidates.Any(x => x.ActionName.InvariantEquals(customActionName)); + + def = new UmbracoRouteValues( + request.PublishedContent, + controllerDescriptor.ControllerName, + controllerDescriptor.ControllerTypeInfo, + customActionExists ? customActionName : def.ActionName, + customActionName, + true); // Hijacked = true } else { _logger.LogWarning( "The current Document Type {ContentTypeAlias} matches a locally declared controller of type {ControllerName}. Custom Controllers for Umbraco routing must implement '{UmbracoRenderController}' and inherit from '{UmbracoControllerBase}'.", request.PublishedContent.ContentType.Alias, - controllerType.FullName, + controllerDescriptor.ControllerTypeInfo.FullName, typeof(IRenderController).FullName, typeof(ControllerBase).FullName); @@ -186,17 +173,21 @@ namespace Umbraco.Web.Website.Routing return def; } - private Type FindControllerType(string controllerName) + /// + /// Return a list of controller candidates that match the custom controller and action names + /// + private IReadOnlyList FindControllerCandidates(string customControllerName, string customActionName, string defaultActionName) { - ControllerActionDescriptor descriptor = _actionDescriptorCollectionProvider.ActionDescriptors.Items + var descriptors = _actionDescriptorCollectionProvider.ActionDescriptors.Items .Cast() - .FirstOrDefault(x => - x.ControllerName.Equals(controllerName)); + .Where(x => x.ControllerName.InvariantEquals(customControllerName) + && (x.ActionName.InvariantEquals(defaultActionName) || (customActionName != null && x.ActionName.InvariantEquals(customActionName)))) + .ToList(); - return descriptor?.ControllerTypeInfo; + return descriptors; } - private bool RouteRequest(IUmbracoContext umbracoContext) + private bool RouteRequest(IUmbracoContext umbracoContext, out IPublishedRequest publishedRequest) { // TODO: I suspect one day this will be async @@ -209,7 +200,9 @@ namespace Umbraco.Web.Website.Routing // instantiate, prepare and process the published content request // important to use CleanedUmbracoUrl - lowercase path-only version of the current url IPublishedRequest request = _publishedRouter.CreateRequest(umbracoContext); - umbracoContext.PublishedRequest = request; // TODO: This is ugly + + // TODO: This is ugly with the re-assignment to umbraco context also because IPublishedRequest is mutable + publishedRequest = umbracoContext.PublishedRequest = request; bool prepared = _publishedRouter.PrepareRequest(request); return prepared && request.HasPublishedContent; diff --git a/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs index ac16be417a..e0b16a351e 100644 --- a/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs +++ b/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.Logging; @@ -6,6 +6,10 @@ using Microsoft.Extensions.Options; namespace Umbraco.Web.Website.ViewEngines { + // TODO: We don't really need to have different view engines simply to search additional places, + // we can just do ConfigureOptions on startup to add more to the + // default list so this can be totally removed/replaced with configure options logic. + /// /// A view engine to look into the App_Plugins folder for views for packaged controllers /// @@ -21,28 +25,28 @@ namespace Umbraco.Web.Website.ViewEngines { } - private static IOptions OverrideViewLocations() + private static IOptions OverrideViewLocations() => Options.Create(new RazorViewEngineOptions() { - return Options.Create(new RazorViewEngineOptions() - { - AreaViewLocationFormats = + // This is definitely not doing what it used to do :P see: + // https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/src/Umbraco.Web/Mvc/PluginViewEngine.cs#L23 + + AreaViewLocationFormats = { - //set all of the area view locations to the plugin folder + // set all of the area view locations to the plugin folder string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.cshtml"), - //will be used when we have partial view and child action macros + // will be used when we have partial view and child action macros string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/Partials/{0}.cshtml"), string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/MacroPartials/{0}.cshtml"), - //for partialsCurrent. + // for partialsCurrent. string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.cshtml"), }, - ViewLocationFormats = + ViewLocationFormats = { string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), } - }); - } + }); } } diff --git a/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs index ea727a07f1..8c53255928 100644 --- a/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs +++ b/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -16,6 +16,10 @@ using Umbraco.Web.Models; namespace Umbraco.Web.Website.ViewEngines { + // TODO: We don't really need to have different view engines simply to search additional places, + // we can just do ConfigureOptions on startup to add more to the + // default list so this can be totally removed/replaced with configure options logic. + /// /// 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. @@ -23,6 +27,9 @@ namespace Umbraco.Web.Website.ViewEngines public class RenderViewEngine : RazorViewEngine, IRenderViewEngine { + /// + /// Initializes a new instance of the class. + /// public RenderViewEngine( IRazorPageFactoryProvider pageFactory, IRazorPageActivator pageActivator, @@ -33,27 +40,24 @@ namespace Umbraco.Web.Website.ViewEngines { } - private static IOptions OverrideViewLocations() + private static IOptions OverrideViewLocations() => Options.Create(new RazorViewEngineOptions() { - return Options.Create(new RazorViewEngineOptions() - { - //NOTE: we will make the main view location the last to be searched since if it is the first to be searched and there is both a view and a partial - // view in both locations and the main view is rendering a partial view with the same name, we will get a stack overflow exception. - // http://issues.umbraco.org/issue/U4-1287, http://issues.umbraco.org/issue/U4-1215 - ViewLocationFormats = + // NOTE: we will make the main view location the last to be searched since if it is the first to be searched and there is both a view and a partial + // view in both locations and the main view is rendering a partial view with the same name, we will get a stack overflow exception. + // http://issues.umbraco.org/issue/U4-1287, http://issues.umbraco.org/issue/U4-1215 + ViewLocationFormats = { - "/Partials/{0}.cshtml", - "/MacroPartials/{0}.cshtml", - "/{0}.cshtml" + "/Views/Partials/{0}.cshtml", + "/Views/MacroPartials/{0}.cshtml", + "/Views/{0}.cshtml" }, - AreaViewLocationFormats = + AreaViewLocationFormats = { - "/Partials/{0}.cshtml", - "/MacroPartials/{0}.cshtml", - "/{0}.cshtml" + "/Views/Partials/{0}.cshtml", + "/Views/MacroPartials/{0}.cshtml", + "/Views/{0}.cshtml" } - }); - } + }); public new ViewEngineResult FindView(ActionContext context, string viewName, bool isMainPage) { @@ -72,16 +76,17 @@ namespace Umbraco.Web.Website.ViewEngines /// private static bool ShouldFindView(ActionContext context, bool isMainPage) { - //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 (!isMainPage && !(umbracoToken is ContentModel)) - return true; - - // only find views if we're rendering the umbraco front end - return umbracoToken is ContentModel; + return true; + // TODO: Determine if this is required, i don't think it is + ////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 (!isMainPage && !(umbracoToken is ContentModel)) + // return true; + //// only find views if we're rendering the umbraco front end + //return umbracoToken is ContentModel; } diff --git a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs index 1c2be3f713..c59c701d42 100644 --- a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs +++ b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Web.Http; using System.Web.Mvc; @@ -127,7 +127,9 @@ namespace Umbraco.Web.Mvc //match this area controllerPluginRoute.DataTokens.Add("area", area.AreaName); - controllerPluginRoute.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, umbracoTokenValue); //ensure the umbraco token is set + + // TODO: No longer needed, remove + //controllerPluginRoute.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, umbracoTokenValue); //ensure the umbraco token is set return controllerPluginRoute; } diff --git a/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs b/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs deleted file mode 100644 index 4baaaac4fc..0000000000 --- a/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Web.Mvc; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.Mvc -{ - public static class ControllerContextExtensions - { - /// - /// Gets the Umbraco context from a controller context hierarchy, if any, else the 'current' Umbraco context. - /// - /// The controller context. - /// The Umbraco context. - public static IUmbracoContext GetUmbracoContext(this ControllerContext controllerContext) - { - var o = controllerContext.GetDataTokenInViewContextHierarchy(Core.Constants.Web.UmbracoContextDataToken); - return o != null ? o as IUmbracoContext : Current.UmbracoContext; - } - - /// - /// Recursively gets a data token from a controller context hierarchy. - /// - /// The controller context. - /// The name of the data token. - /// The data token, or null. - internal static object GetDataTokenInViewContextHierarchy(this ControllerContext controllerContext, string dataTokenName) - { - var context = controllerContext; - while (context != null) - { - object token; - if (context.RouteData.DataTokens.TryGetValue(dataTokenName, out token)) - return token; - context = context.ParentActionViewContext; - } - return null; - } - } -} diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 19e1b79c89..3ca0931585 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -56,8 +56,6 @@ namespace Umbraco.Web.Mvc /// Assigns the correct controller based on the Umbraco request and returns a standard MvcHandler to process the response, /// this also stores the render model into the data tokens for the current RouteData. /// - /// - /// public IHttpHandler GetHttpHandler(RequestContext requestContext) { if (UmbracoContext == null) @@ -70,37 +68,18 @@ namespace Umbraco.Web.Mvc throw new NullReferenceException("There is no current PublishedRequest, it must be initialized before the RenderRouteHandler executes"); } - SetupRouteDataForRequest( - new ContentModel(request.PublishedContent), - requestContext, - request); - return GetHandlerForRoute(requestContext, request); } #endregion - /// - /// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views - /// - /// - /// - /// - internal void SetupRouteDataForRequest(ContentModel contentModel, RequestContext requestContext, IPublishedRequest frequest) - { - //put essential data into the data tokens, the 'umbraco' key is required to be there for the view engine - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, contentModel); //required for the ContentModelBinder and view engine - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, frequest); //required for RenderMvcController - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, UmbracoContext); //required for UmbracoViewPage - } - private void UpdateRouteDataForRequest(ContentModel contentModel, RequestContext requestContext) { if (contentModel == null) throw new ArgumentNullException(nameof(contentModel)); if (requestContext == null) throw new ArgumentNullException(nameof(requestContext)); - requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoDataToken] = contentModel; + // requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoDataToken] = contentModel; // the rest should not change -- it's only the published content that has changed } @@ -293,7 +272,7 @@ namespace Umbraco.Web.Mvc } //store the route definition - requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; + requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; return def; } diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs index cd344ea261..fa67248e7d 100644 --- a/src/Umbraco.Web/Mvc/SurfaceController.cs +++ b/src/Umbraco.Web/Mvc/SurfaceController.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Core; using System.Collections.Specialized; using Umbraco.Core.Cache; @@ -204,8 +204,10 @@ namespace Umbraco.Web.Mvc while (currentContext != null) { var currentRouteData = currentContext.RouteData; - if (currentRouteData.DataTokens.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken)) - return Attempt.Succeed((RouteDefinition)currentRouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken]); + if (currentRouteData.Values.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken)) + { + return Attempt.Succeed((RouteDefinition)currentRouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken]); + } currentContext = currentContext.IsChildAction ? currentContext.ParentActionViewContext diff --git a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs index 30c990a981..580924b909 100644 --- a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Web.Mvc; using System.Web.Routing; @@ -26,7 +26,7 @@ namespace Umbraco.Web.Mvc ValidateRouteData(context.RouteData); - var routeDef = (RouteDefinition)context.RouteData.DataTokens[Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken]; + var routeDef = (RouteDefinition)context.RouteData.Values[Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken]; var factory = ControllerBuilder.Current.GetControllerFactory(); context.RouteData.Values["action"] = routeDef.ActionName; @@ -72,7 +72,7 @@ namespace Umbraco.Web.Mvc /// private static void ValidateRouteData(RouteData routeData) { - if (routeData.DataTokens.ContainsKey(Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken) == false) + if (routeData.Values.ContainsKey(Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken) == false) { throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name + " in the context of an Http POST when using a SurfaceController form"); diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs index 18e1fb8a1a..43dc341655 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text; using System.Web; using System.Web.Mvc; @@ -20,9 +20,7 @@ using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Web.Mvc { - /// - /// Represents the properties and methods that are needed in order to render an Umbraco view. - /// + // TODO: This has been ported to netcore, just needs testing public abstract class UmbracoViewPage : WebViewPage { private readonly GlobalSettings _globalSettings; @@ -50,11 +48,9 @@ namespace Umbraco.Web.Mvc // like the Services & ApplicationCache properties, and have a setter for those special weird // cases. - /// - /// Gets the Umbraco context. - /// - public IUmbracoContext UmbracoContext => _umbracoContext - ?? (_umbracoContext = ViewContext.GetUmbracoContext() ?? Current.UmbracoContext); + // TODO: Can be injected to the view in netcore, else injected to the base model + // public IUmbracoContext UmbracoContext => _umbracoContext + // ?? (_umbracoContext = ViewContext.GetUmbracoContext() ?? Current.UmbracoContext); /// /// Gets the public content request. @@ -63,21 +59,27 @@ namespace Umbraco.Web.Mvc { get { - const string token = Core.Constants.Web.PublishedDocumentRequestDataToken; + // TODO: we only have one data token for a route now: Constants.Web.UmbracoRouteDefinitionDataToken - // we should always try to return the object from the data tokens just in case its a custom object and not - // the one from UmbracoContext. Fallback to UmbracoContext if necessary. + throw new NotImplementedException("Probably needs to be ported to netcore"); - // try view context - if (ViewContext.RouteData.DataTokens.ContainsKey(token)) - return (IPublishedRequest) ViewContext.RouteData.DataTokens.GetRequiredObject(token); + //// we should always try to return the object from the data tokens just in case its a custom object and not + //// the one from UmbracoContext. Fallback to UmbracoContext if necessary. - // child action, try parent view context - if (ViewContext.IsChildAction && ViewContext.ParentActionViewContext.RouteData.DataTokens.ContainsKey(token)) - return (IPublishedRequest) ViewContext.ParentActionViewContext.RouteData.DataTokens.GetRequiredObject(token); + //// try view context + //if (ViewContext.RouteData.DataTokens.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken)) + //{ + // return (IPublishedRequest) ViewContext.RouteData.DataTokens.GetRequiredObject(Constants.Web.UmbracoRouteDefinitionDataToken); + //} - // fallback to UmbracoContext - return UmbracoContext.PublishedRequest; + //// child action, try parent view context + //if (ViewContext.IsChildAction && ViewContext.ParentActionViewContext.RouteData.DataTokens.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken)) + //{ + // return (IPublishedRequest) ViewContext.ParentActionViewContext.RouteData.DataTokens.GetRequiredObject(Constants.Web.UmbracoRouteDefinitionDataToken); + //} + + //// fallback to UmbracoContext + //return UmbracoContext.PublishedRequest; } } diff --git a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs index c00eb24cca..dc922d9fd2 100644 --- a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs @@ -1,4 +1,4 @@ -using System.Web; +using System.Web; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Extensions.DependencyInjection; @@ -62,12 +62,12 @@ namespace Umbraco.Web.Mvc var renderModel = new ContentModel(umbracoContext.PublishedRequest.PublishedContent); // assigns the required tokens to the request - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, renderModel); - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, umbracoContext.PublishedRequest); - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbracoContext); + //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, renderModel); + //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, umbracoContext.PublishedRequest); + //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbracoContext); - // this is used just for a flag that this is an umbraco custom route - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.CustomRouteDataToken, true); + //// this is used just for a flag that this is an umbraco custom route + //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.CustomRouteDataToken, true); // Here we need to detect if a SurfaceController has posted var formInfo = RenderRouteHandler.GetFormInfo(requestContext); @@ -81,7 +81,7 @@ namespace Umbraco.Web.Mvc }; // set the special data token to the current route definition - requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; + requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; return RenderRouteHandler.HandlePostedValues(requestContext, formInfo); } diff --git a/src/Umbraco.Web/Runtime/WebInitialComponent.cs b/src/Umbraco.Web/Runtime/WebInitialComponent.cs index 4fb1754946..c11d648b37 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComponent.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComponent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Web.Http; @@ -171,7 +171,9 @@ namespace Umbraco.Web.Runtime new[] { meta.ControllerNamespace }); if (route.DataTokens == null) // web api routes don't set the data tokens object route.DataTokens = new RouteValueDictionary(); - route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "api"); //ensure the umbraco token is set + + // TODO: Pretty sure this is not necessary, we'll see + //route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "api"); //ensure the umbraco token is set } private static void RouteLocalSurfaceController(Type controller, string umbracoPath) @@ -183,7 +185,10 @@ namespace Umbraco.Web.Runtime url, // URL to match new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, new[] { meta.ControllerNamespace }); // look in this namespace to create the controller - route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "surface"); // ensure the umbraco token is set + + // TODO: Pretty sure this is not necessary, we'll see + //route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "surface"); // ensure the umbraco token is set + route.DataTokens.Add("UseNamespaceFallback", false); // don't look anywhere else except this namespace! // make it use our custom/special SurfaceMvcHandler route.RouteHandler = new SurfaceRouteHandler(); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 8867971657..758839314b 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -184,7 +184,6 @@ -