diff --git a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs index 856b91e36f..414d1da871 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs @@ -1,11 +1,9 @@ -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Models.PublishedContent; using System.Globalization; -using Umbraco.Core.Configuration.Models; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Core; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Routing { @@ -19,13 +17,22 @@ namespace Umbraco.Web.Routing { private readonly ILogger _logger; private readonly IRequestAccessor _requestAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly WebRoutingSettings _webRoutingSettings; - public ContentFinderByIdPath(IOptions webRoutingSettings, ILogger logger, IRequestAccessor requestAccessor) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByIdPath( + IOptions webRoutingSettings, + ILogger logger, + IRequestAccessor requestAccessor, + IUmbracoContextAccessor umbracoContextAccessor) { _webRoutingSettings = webRoutingSettings.Value ?? throw new System.ArgumentNullException(nameof(webRoutingSettings)); _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); - _requestAccessor = requestAccessor; + _requestAccessor = requestAccessor ?? throw new System.ArgumentNullException(nameof(requestAccessor)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); } /// @@ -33,43 +40,48 @@ namespace Umbraco.Web.Routing /// /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(IPublishedRequest frequest) + public bool TryFindContent(IPublishedRequestBuilder frequest) { - - if (frequest.UmbracoContext != null && frequest.UmbracoContext.InPreviewMode == false - && _webRoutingSettings.DisableFindContentByIdPath) + IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext; + if (umbCtx == null || (umbCtx != null && umbCtx.InPreviewMode == false && _webRoutingSettings.DisableFindContentByIdPath)) + { return false; + } IPublishedContent node = null; var path = frequest.Uri.GetAbsolutePathDecoded(); var nodeId = -1; - if (path != "/") // no id if "/" + + // no id if "/" + if (path != "/") { var noSlashPath = path.Substring(1); if (int.TryParse(noSlashPath, out nodeId) == false) + { nodeId = -1; + } if (nodeId > 0) { _logger.LogDebug("Id={NodeId}", nodeId); - node = frequest.UmbracoContext.Content.GetById(nodeId); + node = umbCtx.Content.GetById(nodeId); if (node != null) { var cultureFromQuerystring = _requestAccessor.GetQueryStringValue("culture"); - //if we have a node, check if we have a culture in the query string + // if we have a node, check if we have a culture in the query string if (!string.IsNullOrEmpty(cultureFromQuerystring)) { - //we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though - frequest.Culture = CultureInfo.GetCultureInfo(cultureFromQuerystring); + // we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though + frequest.SetCulture(CultureInfo.GetCultureInfo(cultureFromQuerystring)); } - frequest.PublishedContent = node; - _logger.LogDebug("Found node with id={PublishedContentId}", frequest.PublishedContent.Id); + frequest.SetPublishedContent(node); + _logger.LogDebug("Found node with id={PublishedContentId}", node.Id); } else { @@ -79,7 +91,9 @@ namespace Umbraco.Web.Routing } if (nodeId == -1) + { _logger.LogDebug("Not a node id"); + } return node != null; } diff --git a/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs index 16d7a0c4cf..15698f9134 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs @@ -1,4 +1,6 @@ - + +using Umbraco.Core.Models.PublishedContent; + namespace Umbraco.Web.Routing { /// @@ -11,25 +13,37 @@ namespace Umbraco.Web.Routing public class ContentFinderByPageIdQuery : IContentFinder { private readonly IRequestAccessor _requestAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public ContentFinderByPageIdQuery(IRequestAccessor requestAccessor) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByPageIdQuery(IRequestAccessor requestAccessor, IUmbracoContextAccessor umbracoContextAccessor) { - _requestAccessor = requestAccessor; + _requestAccessor = requestAccessor ?? throw new System.ArgumentNullException(nameof(requestAccessor)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); } - public bool TryFindContent(IPublishedRequest frequest) + /// + public bool TryFindContent(IPublishedRequestBuilder frequest) { - int pageId; - if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), out pageId)) + IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext; + if (umbCtx == null) { - var doc = frequest.UmbracoContext.Content.GetById(pageId); + return false; + } + + if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), out int pageId)) + { + IPublishedContent doc = umbCtx.Content.GetById(pageId); if (doc != null) { - frequest.PublishedContent = doc; + frequest.SetPublishedContent(doc); return true; } } + return false; } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs index 895917c69d..a35135e5a3 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using Microsoft.Extensions.Logging; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; namespace Umbraco.Web.Routing @@ -17,12 +19,21 @@ namespace Umbraco.Web.Routing private readonly IRedirectUrlService _redirectUrlService; private readonly ILogger _logger; private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public ContentFinderByRedirectUrl(IRedirectUrlService redirectUrlService, ILogger logger, IPublishedUrlProvider publishedUrlProvider) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByRedirectUrl( + IRedirectUrlService redirectUrlService, + ILogger logger, + IPublishedUrlProvider publishedUrlProvider, + IUmbracoContextAccessor umbracoContextAccessor) { _redirectUrlService = redirectUrlService; _logger = logger; _publishedUrlProvider = publishedUrlProvider; + _umbracoContextAccessor = umbracoContextAccessor; } /// @@ -31,15 +42,19 @@ namespace Umbraco.Web.Routing /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. /// Optionally, can also assign the template or anything else on the document request, although that is not required. - public bool TryFindContent(IPublishedRequest frequest) + public bool TryFindContent(IPublishedRequestBuilder frequest) { - var route = frequest.HasDomain + IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext; + if (umbCtx == null) + { + return false; + } + + var route = frequest.Domain != null ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()) : frequest.Uri.GetAbsolutePathDecoded(); - - - var redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture.Name); + IRedirectUrl redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture.Name); if (redirectUrl == null) { @@ -47,7 +62,7 @@ namespace Umbraco.Web.Routing return false; } - var content = frequest.UmbracoContext.Content.GetById(redirectUrl.ContentId); + IPublishedContent content = umbCtx.Content.GetById(redirectUrl.ContentId); var url = content == null ? "#" : content.Url(_publishedUrlProvider, redirectUrl.Culture); if (url.StartsWith("#")) { @@ -59,17 +74,17 @@ namespace Umbraco.Web.Routing url = string.IsNullOrEmpty(frequest.Uri.Query) ? url : url + frequest.Uri.Query; _logger.LogDebug("Route {Route} matches content {ContentId} with URL '{Url}', redirecting.", route, content.Id, url); - frequest.SetRedirectPermanent(url); + frequest + .SetRedirectPermanent(url) - // From: http://stackoverflow.com/a/22468386/5018 - // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 - // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads - // to problems if you rename a page back to it's original name or create a new page with the original name - //frequest.Cacheability = HttpCacheability.NoCache; - frequest.CacheabilityNoCache = true; - frequest.CacheExtensions = new List { "no-store, must-revalidate" }; - frequest.Headers = new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }; + // From: http://stackoverflow.com/a/22468386/5018 + // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 + // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads + // to problems if you rename a page back to it's original name or create a new page with the original name + .SetCacheabilityNoCache(true) + .SetCacheExtensions(new List { "no-store, must-revalidate" }) + .SetHeaders(new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }); return true; } diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs index 653e808dfe..44ae4335e2 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs @@ -14,44 +14,70 @@ namespace Umbraco.Web.Routing { private readonly ILogger _logger; - public ContentFinderByUrl(ILogger logger) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByUrl(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) { - _logger = logger; + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); + UmbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); } + /// + /// Gets the + /// + protected IUmbracoContextAccessor UmbracoContextAccessor { get; } + /// /// Tries to find and assign an Umbraco document to a PublishedRequest. /// /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. - public virtual bool TryFindContent(IPublishedRequest frequest) + public virtual bool TryFindContent(IPublishedRequestBuilder frequest) { - string route; - if (frequest.HasDomain) - route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()); - else - route = frequest.Uri.GetAbsolutePathDecoded(); + IUmbracoContext umbCtx = UmbracoContextAccessor.UmbracoContext; + if (umbCtx == null) + { + return false; + } - var node = FindContent(frequest, route); + string route; + if (frequest.Domain != null) + { + route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()); + } + else + { + route = frequest.Uri.GetAbsolutePathDecoded(); + } + + IPublishedContent node = FindContent(frequest, route); return node != null; } /// /// Tries to find an Umbraco document for a PublishedRequest and a route. /// - /// The document request. - /// The route. /// The document node, or null. - protected IPublishedContent FindContent(IPublishedRequest docreq, string route) + protected IPublishedContent FindContent(IPublishedRequestBuilder docreq, string route) { - if (docreq == null) throw new System.ArgumentNullException(nameof(docreq)); + IUmbracoContext umbCtx = UmbracoContextAccessor.UmbracoContext; + if (umbCtx == null) + { + return null; + } + + if (docreq == null) + { + throw new System.ArgumentNullException(nameof(docreq)); + } _logger.LogDebug("Test route {Route}", route); - var node = docreq.UmbracoContext.Content.GetByRoute(docreq.UmbracoContext.InPreviewMode, route, culture: docreq.Culture?.Name); + IPublishedContent node = umbCtx.Content.GetByRoute(umbCtx.InPreviewMode, route, culture: docreq.Culture?.Name); if (node != null) { - docreq.PublishedContent = node; + docreq.SetPublishedContent(node); _logger.LogDebug("Got content, id={NodeId}", node.Id); } else diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs index 24bfad914d..f7ebd6bbc5 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs @@ -20,12 +20,21 @@ namespace Umbraco.Web.Routing { private readonly IPublishedValueFallback _publishedValueFallback; private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ILogger _logger; - public ContentFinderByUrlAlias(ILogger logger, IPublishedValueFallback publishedValueFallback, IVariationContextAccessor variationContextAccessor) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByUrlAlias( + ILogger logger, + IPublishedValueFallback publishedValueFallback, + IVariationContextAccessor variationContextAccessor, + IUmbracoContextAccessor umbracoContextAccessor) { _publishedValueFallback = publishedValueFallback; _variationContextAccessor = variationContextAccessor; + _umbracoContextAccessor = umbracoContextAccessor; _logger = logger; } @@ -34,21 +43,29 @@ namespace Umbraco.Web.Routing /// /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(IPublishedRequest frequest) + public bool TryFindContent(IPublishedRequestBuilder frequest) { + IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext; + if (umbCtx == null) + { + return false; + } + IPublishedContent node = null; - if (frequest.Uri.AbsolutePath != "/") // no alias if "/" + // no alias if "/" + if (frequest.Uri.AbsolutePath != "/") { - node = FindContentByAlias(frequest.UmbracoContext.Content, - frequest.HasDomain ? frequest.Domain.ContentId : 0, + node = FindContentByAlias( + umbCtx.Content, + frequest.Domain != null ? frequest.Domain.ContentId : 0, frequest.Culture.Name, frequest.Uri.GetAbsolutePathDecoded()); if (node != null) { - frequest.PublishedContent = node; - _logger.LogDebug("Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", frequest.Uri.AbsolutePath, frequest.PublishedContent.Id); + frequest.SetPublishedContent(node); + _logger.LogDebug("Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", frequest.Uri.AbsolutePath, node.Id); } } @@ -57,7 +74,10 @@ namespace Umbraco.Web.Routing private IPublishedContent FindContentByAlias(IPublishedContentCache cache, int rootNodeId, string culture, string alias) { - if (alias == null) throw new ArgumentNullException(nameof(alias)); + if (alias == null) + { + throw new ArgumentNullException(nameof(alias)); + } // the alias may be "foo/bar" or "/foo/bar" // there may be spaces as in "/foo/bar, /foo/nil" @@ -65,7 +85,6 @@ namespace Umbraco.Web.Routing // TODO: can we normalize the values so that they contain no whitespaces, and no leading slashes? // and then the comparisons in IsMatch can be way faster - and allocate way less strings - const string propertyAlias = Constants.Conventions.Content.UrlAlias; var test1 = alias.TrimStart('/') + ","; @@ -80,38 +99,52 @@ namespace Umbraco.Web.Routing // "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" + // " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" + // ")]" + if (!c.HasProperty(propertyAlias)) + { + return false; + } - if (!c.HasProperty(propertyAlias)) return false; - var p = c.GetProperty(propertyAlias); + IPublishedProperty p = c.GetProperty(propertyAlias); var varies = p.PropertyType.VariesByCulture(); string v; if (varies) { - if (!c.HasCulture(culture)) return false; + if (!c.HasCulture(culture)) + { + return false; + } + v = c.Value(_publishedValueFallback, propertyAlias, culture); } else { v = c.Value(_publishedValueFallback, propertyAlias); } - if (string.IsNullOrWhiteSpace(v)) return false; - v = "," + v.Replace(" ", "") + ","; + + if (string.IsNullOrWhiteSpace(v)) + { + return false; + } + + v = "," + v.Replace(" ", string.Empty) + ","; return v.InvariantContains(a1) || v.InvariantContains(a2); } // TODO: even with Linq, what happens below has to be horribly slow // but the only solution is to entirely refactor URL providers to stop being dynamic - if (rootNodeId > 0) { - var rootNode = cache.GetById(rootNodeId); + IPublishedContent rootNode = cache.GetById(rootNodeId); return rootNode?.Descendants(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2)); } - foreach (var rootContent in cache.GetAtRoot()) + foreach (IPublishedContent rootContent in cache.GetAtRoot()) { - var c = rootContent.DescendantsOrSelf(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2)); - if (c != null) return c; + IPublishedContent c = rootContent.DescendantsOrSelf(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2)); + if (c != null) + { + return c; + } } return null; diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs index 8ae4e2aead..c6bd4f383d 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs @@ -1,10 +1,10 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; -using Umbraco.Core.Configuration.Models; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Logging; namespace Umbraco.Web.Routing { @@ -24,8 +24,16 @@ namespace Umbraco.Web.Routing private readonly IContentTypeService _contentTypeService; private readonly WebRoutingSettings _webRoutingSettings; - public ContentFinderByUrlAndTemplate(ILogger logger, IFileService fileService, IContentTypeService contentTypeService, IOptions webRoutingSettings) - : base(logger) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByUrlAndTemplate( + ILogger logger, + IFileService fileService, + IContentTypeService contentTypeService, + IUmbracoContextAccessor umbracoContextAccessor, + IOptions webRoutingSettings) + : base(logger, umbracoContextAccessor) { _logger = logger; _fileService = fileService; @@ -39,13 +47,14 @@ namespace Umbraco.Web.Routing /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. /// If successful, also assigns the template. - public override bool TryFindContent(IPublishedRequest frequest) + public override bool TryFindContent(IPublishedRequestBuilder frequest) { - IPublishedContent node = null; var path = frequest.Uri.GetAbsolutePathDecoded(); - if (frequest.HasDomain) + if (frequest.Domain != null) + { path = DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, path); + } // no template if "/" if (path == "/") @@ -59,7 +68,7 @@ namespace Umbraco.Web.Routing var templateAlias = path.Substring(pos + 1); path = pos == 0 ? "/" : path.Substring(0, pos); - var template = _fileService.GetTemplate(templateAlias); + ITemplate template = _fileService.GetTemplate(templateAlias); if (template == null) { @@ -70,8 +79,8 @@ namespace Umbraco.Web.Routing _logger.LogDebug("Valid template: '{TemplateAlias}'", templateAlias); // look for node corresponding to the rest of the route - var route = frequest.HasDomain ? (frequest.Domain.ContentId + path) : path; - node = FindContent(frequest, route); // also assigns to published request + var route = frequest.Domain != null ? (frequest.Domain.ContentId + path) : path; + IPublishedContent node = FindContent(frequest, route); if (node == null) { @@ -83,12 +92,12 @@ namespace Umbraco.Web.Routing if (!node.IsAllowedTemplate(_contentTypeService, _webRoutingSettings, template.Id)) { _logger.LogWarning("Alternative template '{TemplateAlias}' is not allowed on node {NodeId}.", template.Alias, node.Id); - frequest.PublishedContent = null; // clear + frequest.SetPublishedContent(null); // clear return false; } // got it - frequest.TemplateModel = template; + frequest.SetTemplate(template); return true; } } diff --git a/src/Umbraco.Core/Routing/IContentFinder.cs b/src/Umbraco.Core/Routing/IContentFinder.cs index 39d31741a6..57575b3cf0 100644 --- a/src/Umbraco.Core/Routing/IContentFinder.cs +++ b/src/Umbraco.Core/Routing/IContentFinder.cs @@ -11,6 +11,6 @@ namespace Umbraco.Web.Routing /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. /// Optionally, can also assign the template or anything else on the document request, although that is not required. - bool TryFindContent(IPublishedRequest request); + bool TryFindContent(IPublishedRequestBuilder request); } } diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index 51fc9ccf64..f05df351f3 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -6,29 +6,24 @@ using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Routing { + public interface IPublishedRequest { /// - /// Gets the UmbracoContext. - /// - IUmbracoContext UmbracoContext { get; } // TODO: This should be injected and removed from here - - /// - /// Gets or sets the cleaned up Uri used for routing. + /// Gets the cleaned up inbound Uri used for routing. /// /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - Uri Uri { get; set; } + Uri Uri { get; } /// - /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. + /// Gets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. /// - bool IgnorePublishedContentCollisions { get; set; } + bool IgnorePublishedContentCollisions { get; } /// - /// Gets or sets the requested content. + /// Gets a value indicating the requested content. /// - /// Setting the requested content clears Template. - IPublishedContent PublishedContent { get; set; } + IPublishedContent PublishedContent { get; } /// /// Gets the initial requested content. @@ -38,187 +33,59 @@ namespace Umbraco.Web.Routing IPublishedContent InitialPublishedContent { get; } /// - /// Gets value indicating whether the current published content is the initial one. - /// - bool IsInitialPublishedContent { get; } - - /// - /// Gets or sets a value indicating whether the current published content has been obtained + /// Gets a value indicating whether the current published content has been obtained /// from the initial published content following internal redirections exclusively. /// /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to /// apply the internal redirect or not, when content is not the initial content. - bool IsInternalRedirectPublishedContent { get; } + bool IsInternalRedirectPublishedContent { get; } // TODO: Not sure what thsi is yet /// - /// Gets a value indicating whether the content request has a content. + /// Gets the template assigned to the request (if any) /// - bool HasPublishedContent { get; } - - ITemplate TemplateModel { get; set; } + ITemplate Template { get; } /// - /// Gets the alias of the template to use to display the requested content. - /// - string TemplateAlias { get; } - - /// - /// Gets a value indicating whether the content request has a template. - /// - bool HasTemplate { get; } - - void UpdateToNotFound(); - - /// - /// Gets or sets the content request's domain. + /// Gets the content request's domain. /// /// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example, /// the Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". - DomainAndUri Domain { get; set; } + DomainAndUri Domain { get; } /// - /// Gets a value indicating whether the content request has a domain. + /// Gets the content request's culture. /// - bool HasDomain { get; } + CultureInfo Culture { get; } /// - /// Gets or sets the content request's culture. - /// - CultureInfo Culture { get; set; } - - /// - /// Gets or sets a value indicating whether the requested content could not be found. - /// - /// This is set in the PublishedContentRequestBuilder and can also be used in - /// custom content finders or Prepared event handlers, where we want to allow developers - /// to indicate a request is 404 but not to cancel it. - bool Is404 { get; set; } - - /// - /// Gets a value indicating whether the content request triggers a redirect (permanent or not). - /// - bool IsRedirect { get; } - - /// - /// Gets or sets a value indicating whether the redirect is permanent. - /// - bool IsRedirectPermanent { get; } - - /// - /// Gets or sets the url to redirect to, when the content request triggers a redirect. + /// Gets the url to redirect to, when the content request triggers a redirect. /// string RedirectUrl { get; } /// - /// Gets or sets the content request http response status code. + /// Gets the content request http response status code. /// /// Does not actually set the http response status code, only registers that the response /// should use the specified code. The code will or will not be used, in due time. int ResponseStatusCode { get; } /// - /// Gets or sets the content request http response status description. + /// Gets the content request http response status description. /// /// Does not actually set the http response status description, only registers that the response /// should use the specified description. The description will or will not be used, in due time. string ResponseStatusDescription { get; } /// - /// Gets or sets a list of Extensions to append to the Response.Cache object. + /// Gets a list of Extensions to append to the Response.Cache object. /// - List CacheExtensions { get; set; } + IReadOnlyList CacheExtensions { get; } /// - /// Gets or sets a dictionary of Headers to append to the Response object. + /// Gets a dictionary of Headers to append to the Response object. /// - Dictionary Headers { get; set; } + IReadOnlyDictionary Headers { get; } - bool CacheabilityNoCache { get; set; } - - /// - /// Prepares the request. - /// - void Prepare(); - - /// - /// Triggers the Preparing event. - /// - void OnPreparing(); - - /// - /// Triggers the Prepared event. - /// - void OnPrepared(); - - /// - /// Sets the requested content, following an internal redirect. - /// - /// The requested content. - /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will - /// preserve or reset the template, if any. - void SetInternalRedirectPublishedContent(IPublishedContent content); - - /// - /// Indicates that the current PublishedContent is the initial one. - /// - void SetIsInitialPublishedContent(); - - /// - /// Tries to set the template to use to display the requested content. - /// - /// The alias of the template. - /// A value indicating whether a valid template with the specified alias was found. - /// - /// Successfully setting the template does refresh RenderingEngine. - /// If setting the template fails, then the previous template (if any) remains in place. - /// - bool TrySetTemplate(string alias); - - /// - /// Sets the template to use to display the requested content. - /// - /// The template. - /// Setting the template does refresh RenderingEngine. - void SetTemplate(ITemplate template); - - /// - /// Resets the template. - /// - void ResetTemplate(); - - /// - /// Indicates that the content request should trigger a redirect (302). - /// - /// The url to redirect to. - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - void SetRedirect(string url); - - /// - /// Indicates that the content request should trigger a permanent redirect (301). - /// - /// The url to redirect to. - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - void SetRedirectPermanent(string url); - - /// - /// Indicates that the content request should trigger a redirect, with a specified status code. - /// - /// The url to redirect to. - /// The status code (300-308). - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - void SetRedirect(string url, int status); - - /// - /// Sets the http response status code, along with an optional associated description. - /// - /// The http status code. - /// The description. - /// Does not actually set the http response status code and description, only registers that - /// the response should use the specified code and description. The code and description will or will - /// not be used, in due time. - void SetResponseStatus(int code, string description = null); + bool CacheabilityNoCache { get; } } } diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs new file mode 100644 index 0000000000..fee64fda8d --- /dev/null +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Routing +{ + /// + /// Used by to route inbound requests to Umbraco content + /// + public interface IPublishedRequestBuilder + { + /// + /// Gets the cleaned up inbound Uri used for routing. + /// + /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. + Uri Uri { get; } + + /// + /// Gets the assigned (if any) + /// + DomainAndUri Domain { get; } + + /// + /// Gets the assigned (if any) + /// + CultureInfo Culture { get; } + + /// + /// Gets a value indicating whether the current published content is the initial one. + /// + bool IsInitialPublishedContent { get; } + + /// + /// Gets a value indicating whether the current published content has been obtained + /// from the initial published content following internal redirections exclusively. + /// + /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to + /// apply the internal redirect or not, when content is not the initial content. + bool IsInternalRedirectPublishedContent { get; } + + /// + /// Gets the content request http response status code. + /// + int ResponseStatusCode { get; } + + /// + /// Gets the current assigned (if any) + /// + IPublishedContent PublishedContent { get; } + + /// + /// Gets the template assigned to the request (if any) + /// + ITemplate Template { get; } + + /// + /// Builds the + /// + IPublishedRequest Build(); + + /// + /// Sets the domain for the request + /// + IPublishedRequestBuilder SetDomain(DomainAndUri domain); + + /// + /// Sets the culture for the request + /// + IPublishedRequestBuilder SetCulture(CultureInfo culture); + + /// + /// Sets the found for the request + /// + IPublishedRequestBuilder SetPublishedContent(IPublishedContent content); + + /// + /// Sets the requested content, following an internal redirect. + /// + /// The requested content. + /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will + /// preserve or reset the template, if any. + IPublishedRequestBuilder SetInternalRedirectPublishedContent(IPublishedContent content); + + /// + /// Indicates that the current PublishedContent is the initial one. + /// + IPublishedRequestBuilder SetIsInitialPublishedContent(); // TODO: Required? + + /// + /// Tries to set the template to use to display the requested content. + /// + /// The alias of the template. + /// A value indicating whether a valid template with the specified alias was found. + /// + /// Successfully setting the template does refresh RenderingEngine. + /// If setting the template fails, then the previous template (if any) remains in place. + /// + bool TrySetTemplate(string alias); + + /// + /// Sets the template to use to display the requested content. + /// + /// The template. + /// Setting the template does refresh RenderingEngine. + IPublishedRequestBuilder SetTemplate(ITemplate template); + + /// + /// Resets the template. + /// + IPublishedRequestBuilder ResetTemplate(); + + /// + /// Indicates that the content request should trigger a permanent redirect (301). + /// + /// The url to redirect to. + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + IPublishedRequestBuilder SetRedirectPermanent(string url); + + /// + /// Indicates that the content request should trigger a redirect, with a specified status code. + /// + /// The url to redirect to. + /// The status code (300-308). + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + IPublishedRequestBuilder SetRedirect(string url, int status = (int)HttpStatusCode.Redirect); + + /// + /// Sets the http response status code, along with an optional associated description. + /// + /// The http status code. + /// The description. + /// Does not actually set the http response status code and description, only registers that + /// the response should use the specified code and description. The code and description will or will + /// not be used, in due time. + IPublishedRequestBuilder SetResponseStatus(int code, string description = null); + + IPublishedRequestBuilder SetCacheabilityNoCache(bool cacheability); + + /// + /// Sets a list of Extensions to append to the Response.Cache object. + /// + IPublishedRequestBuilder SetCacheExtensions(IEnumerable cacheExtensions); + + /// + /// Sets a dictionary of Headers to append to the Response object. + /// + IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers); + + /// + /// Sets a value indicating that the requested content could not be found. + /// + /// This is set in the PublishedContentRequestBuilder and can also be used in + /// custom content finders or Prepared event handlers, where we want to allow developers + /// to indicate a request is 404 but not to cancel it. + IPublishedRequestBuilder SetIs404(bool is404); + + // TODO: This seems to be the same as is404? + //IPublishedRequestBuilder UpdateToNotFound(); + } +} diff --git a/src/Umbraco.Core/Routing/IPublishedRouter.cs b/src/Umbraco.Core/Routing/IPublishedRouter.cs index db9d69df20..aaccb4b4d2 100644 --- a/src/Umbraco.Core/Routing/IPublishedRouter.cs +++ b/src/Umbraco.Core/Routing/IPublishedRouter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Umbraco.Core.Models; @@ -9,46 +9,39 @@ namespace Umbraco.Web.Routing /// public interface IPublishedRouter { - // TODO: consider this and RenderRouteHandler - move some code around? + // TODO: consider this and UmbracoRouteValueTransformer - move some code around? /// /// Creates a published request. /// - /// The current Umbraco context. - /// The (optional) request Uri. - /// A published request. - IPublishedRequest CreateRequest(IUmbracoContext umbracoContext, Uri uri = null); + /// The current request Uri. + /// A published request builder. + IPublishedRequestBuilder CreateRequest(Uri uri); /// /// Prepares a request for rendering. /// /// The request. /// A value indicating whether the request was successfully prepared and can be rendered. - bool PrepareRequest(IPublishedRequest request); + IPublishedRequest RouteRequest(IPublishedRequestBuilder request); /// /// Tries to route a request. /// /// The request. /// A value indicating whether the request can be routed to a document. - bool TryRouteRequest(IPublishedRequest request); + bool TryRouteRequest(IPublishedRequestBuilder request); - /// - /// Gets a template. - /// - /// The template alias - /// The template. - ITemplate GetTemplate(string alias); - - /// - /// Updates the request to "not found". - /// - /// The request. - /// - /// This method is invoked when the pipeline decides it cannot render - /// the request, for whatever reason, and wants to force it to be re-routed - /// and rendered as if no document were found (404). - /// - void UpdateRequestToNotFound(IPublishedRequest request); + // TODO: This shouldn't be required and should be handled differently during route building + ///// + ///// Updates the request to "not found". + ///// + ///// The request. + ///// + ///// This method is invoked when the pipeline decides it cannot render + ///// the request, for whatever reason, and wants to force it to be re-routed + ///// and rendered as if no document were found (404). + ///// + //void UpdateRequestToNotFound(IPublishedRequest request); } } diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index bab60e49f6..b3aa37d31e 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -2,27 +2,90 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Threading; +using Microsoft.Extensions.Options; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Configuration.Models; -using Microsoft.Extensions.Options; namespace Umbraco.Web.Routing { + public class PublishedRequest : IPublishedRequest + { + /// + /// Initializes a new instance of the class. + /// + public PublishedRequest(Uri uri, /*bool ignorePublishedContentCollisions, */IPublishedContent publishedContent, bool isInternalRedirectPublishedContent, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int responseStatusCode, string responseStatusDescription, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache) + { + Uri = uri ?? throw new ArgumentNullException(nameof(uri)); + //IgnorePublishedContentCollisions = ignorePublishedContentCollisions; + PublishedContent = publishedContent ?? throw new ArgumentNullException(nameof(publishedContent)); + IsInternalRedirectPublishedContent = isInternalRedirectPublishedContent; + Template = template ?? throw new ArgumentNullException(nameof(template)); + Domain = domain ?? throw new ArgumentNullException(nameof(domain)); + Culture = culture ?? throw new ArgumentNullException(nameof(culture)); + RedirectUrl = redirectUrl ?? throw new ArgumentNullException(nameof(redirectUrl)); + ResponseStatusCode = responseStatusCode; + ResponseStatusDescription = responseStatusDescription ?? throw new ArgumentNullException(nameof(responseStatusDescription)); + CacheExtensions = cacheExtensions ?? throw new ArgumentNullException(nameof(cacheExtensions)); + Headers = headers ?? throw new ArgumentNullException(nameof(headers)); + CacheabilityNoCache = cacheabilityNoCache; + } + + /// + public Uri Uri { get; } + + /// + public bool IgnorePublishedContentCollisions { get; } + + /// + public IPublishedContent PublishedContent { get; } + + /// + public IPublishedContent InitialPublishedContent { get; } + + /// + public bool IsInternalRedirectPublishedContent { get; } + + /// + public ITemplate Template { get; } + + /// + public DomainAndUri Domain { get; } + + /// + public CultureInfo Culture { get; } + + /// + public string RedirectUrl { get; } + + /// + public int ResponseStatusCode { get; } + + /// + public string ResponseStatusDescription { get; } + + /// + public IReadOnlyList CacheExtensions { get; } + + /// + public IReadOnlyDictionary Headers { get; } + + /// + public bool CacheabilityNoCache { get; } + } + /// /// Represents a request for one specified Umbraco IPublishedContent to be rendered /// by one specified template, using one specified Culture and RenderingEngine. /// - public class PublishedRequest : IPublishedRequest + public class PublishedRequestOld // : IPublishedRequest { private readonly IPublishedRouter _publishedRouter; private readonly WebRoutingSettings _webRoutingSettings; private bool _readonly; // after prepared - private bool _readonlyUri; // after preparing - private Uri _uri; // clean uri, no virtual dir, no trailing slash nor .aspx, nothing private bool _is404; private DomainAndUri _domain; private CultureInfo _culture; @@ -30,12 +93,9 @@ namespace Umbraco.Web.Routing private IPublishedContent _initialPublishedContent; // found by finders before 404, redirects, etc /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The published router. - /// The Umbraco context. - /// The request Uri. - public PublishedRequest(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IOptions webRoutingSettings, Uri uri = null) + public PublishedRequestOld(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IOptions webRoutingSettings, Uri uri = null) { UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter)); @@ -52,98 +112,90 @@ namespace Umbraco.Web.Routing /// Gets or sets the cleaned up Uri used for routing. /// /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - public Uri Uri - { - get => _uri; - set - { - if (_readonlyUri) - throw new InvalidOperationException("Cannot modify Uri after Preparing has triggered."); - _uri = value; - } - } + public Uri Uri { get; } // utility for ensuring it is ok to set some properties public void EnsureWriteable() { if (_readonly) + { throw new InvalidOperationException("Cannot modify a PublishedRequest once it is read-only."); + } } public bool CacheabilityNoCache { get; set; } - /// - /// Prepares the request. - /// - public void Prepare() - { - _publishedRouter.PrepareRequest(this); - } + ///// + ///// Prepares the request. + ///// + //public void Prepare() + //{ + // _publishedRouter.PrepareRequest(this); + //} /// /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. /// public bool IgnorePublishedContentCollisions { get; set; } - #region Events + //#region Events - /// - /// Triggers before the published content request is prepared. - /// - /// When the event triggers, no preparation has been done. It is still possible to - /// modify the request's Uri property, for example to restore its original, public-facing value - /// that might have been modified by an in-between equipment such as a load-balancer. - public static event EventHandler Preparing; + ///// + ///// Triggers before the published content request is prepared. + ///// + ///// When the event triggers, no preparation has been done. It is still possible to + ///// modify the request's Uri property, for example to restore its original, public-facing value + ///// that might have been modified by an in-between equipment such as a load-balancer. + //public static event EventHandler Preparing; - /// - /// Triggers once the published content request has been prepared, but before it is processed. - /// - /// When the event triggers, preparation is done ie domain, culture, document, template, - /// rendering engine, etc. have been setup. It is then possible to change anything, before - /// the request is actually processed and rendered by Umbraco. - public static event EventHandler Prepared; + ///// + ///// Triggers once the published content request has been prepared, but before it is processed. + ///// + ///// When the event triggers, preparation is done ie domain, culture, document, template, + ///// rendering engine, etc. have been setup. It is then possible to change anything, before + ///// the request is actually processed and rendered by Umbraco. + //public static event EventHandler Prepared; - /// - /// Triggers the Preparing event. - /// - public void OnPreparing() - { - Preparing?.Invoke(this, EventArgs.Empty); - _readonlyUri = true; - } + ///// + ///// Triggers the Preparing event. + ///// + //public void OnPreparing() + //{ + // Preparing?.Invoke(this, EventArgs.Empty); + //} - /// - /// Triggers the Prepared event. - /// - public void OnPrepared() - { - Prepared?.Invoke(this, EventArgs.Empty); + ///// + ///// Triggers the Prepared event. + ///// + //public void OnPrepared() + //{ + // Prepared?.Invoke(this, EventArgs.Empty); - if (HasPublishedContent == false) - Is404 = true; // safety + // if (HasPublishedContent == false) + // Is404 = true; // safety - _readonly = true; - } + // _readonly = true; + //} - #endregion + //#endregion #region PublishedContent - /// - /// Gets or sets the requested content. - /// - /// Setting the requested content clears Template. - public IPublishedContent PublishedContent - { - get { return _publishedContent; } - set - { - EnsureWriteable(); - _publishedContent = value; - IsInternalRedirectPublishedContent = false; - TemplateModel = null; - } - } + ///// + ///// Gets or sets the requested content. + ///// + ///// Setting the requested content clears Template. + //public IPublishedContent PublishedContent + //{ + // get { return _publishedContent; } + // set + // { + // EnsureWriteable(); + // _publishedContent = value; + // IsInternalRedirectPublishedContent = false; + // TemplateModel = null; + // } + //} /// /// Sets the requested content, following an internal redirect. @@ -153,38 +205,39 @@ namespace Umbraco.Web.Routing /// preserve or reset the template, if any. public void SetInternalRedirectPublishedContent(IPublishedContent content) { - if (content == null) throw new ArgumentNullException(nameof(content)); - EnsureWriteable(); + //if (content == null) + // throw new ArgumentNullException(nameof(content)); + //EnsureWriteable(); - // unless a template has been set already by the finder, - // template should be null at that point. + //// unless a template has been set already by the finder, + //// template should be null at that point. - // IsInternalRedirect if IsInitial, or already IsInternalRedirect - var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; + //// IsInternalRedirect if IsInitial, or already IsInternalRedirect + //var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; - // redirecting to self - if (content.Id == PublishedContent.Id) // neither can be null - { - // no need to set PublishedContent, we're done - IsInternalRedirectPublishedContent = isInternalRedirect; - return; - } + //// redirecting to self + //if (content.Id == PublishedContent.Id) // neither can be null + //{ + // // no need to set PublishedContent, we're done + // IsInternalRedirectPublishedContent = isInternalRedirect; + // return; + //} - // else + //// else - // save - var template = TemplateModel; + //// save + //var template = Template; - // set published content - this resets the template, and sets IsInternalRedirect to false - PublishedContent = content; - IsInternalRedirectPublishedContent = isInternalRedirect; + //// set published content - this resets the template, and sets IsInternalRedirect to false + //PublishedContent = content; + //IsInternalRedirectPublishedContent = isInternalRedirect; - // must restore the template if it's an internal redirect & the config option is set - if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) - { - // restore - TemplateModel = template; - } + //// must restore the template if it's an internal redirect & the config option is set + //if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) + //{ + // // restore + // TemplateModel = template; + //} } /// @@ -219,91 +272,19 @@ namespace Umbraco.Web.Routing /// apply the internal redirect or not, when content is not the initial content. public bool IsInternalRedirectPublishedContent { get; private set; } - /// - /// Gets a value indicating whether the content request has a content. - /// - public bool HasPublishedContent => PublishedContent != null; #endregion - #region Template - /// /// Gets or sets the template model to use to display the requested content. /// - public ITemplate TemplateModel { get; set; } + public ITemplate Template { get; } /// /// Gets the alias of the template to use to display the requested content. /// - public string TemplateAlias => TemplateModel?.Alias; + public string TemplateAlias => Template?.Alias; - /// - /// Tries to set the template to use to display the requested content. - /// - /// The alias of the template. - /// A value indicating whether a valid template with the specified alias was found. - /// - /// Successfully setting the template does refresh RenderingEngine. - /// If setting the template fails, then the previous template (if any) remains in place. - /// - public bool TrySetTemplate(string alias) - { - EnsureWriteable(); - - if (string.IsNullOrWhiteSpace(alias)) - { - TemplateModel = null; - return true; - } - - // NOTE - can we still get it with whitespaces in it due to old legacy bugs? - alias = alias.Replace(" ", ""); - - var model = _publishedRouter.GetTemplate(alias); - if (model == null) - return false; - - TemplateModel = model; - return true; - } - - /// - /// Sets the template to use to display the requested content. - /// - /// The template. - /// Setting the template does refresh RenderingEngine. - public void SetTemplate(ITemplate template) - { - EnsureWriteable(); - TemplateModel = template; - } - - /// - /// Resets the template. - /// - public void ResetTemplate() - { - EnsureWriteable(); - TemplateModel = null; - } - - /// - /// Gets a value indicating whether the content request has a template. - /// - public bool HasTemplate => TemplateModel != null; - - public void UpdateToNotFound() - { - var __readonly = _readonly; - _readonly = false; - _publishedRouter.UpdateRequestToNotFound(this); - _readonly = __readonly; - } - - #endregion - - #region Domain and Culture /// /// Gets or sets the content request's domain. @@ -341,7 +322,6 @@ namespace Umbraco.Web.Routing // note: do we want to have an ordered list of alternate cultures, // to allow for fallbacks when doing dictionary lookup and such? - #endregion #region Status diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs new file mode 100644 index 0000000000..8167e83e6a --- /dev/null +++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Routing +{ + public class PublishedRequestBuilder : IPublishedRequestBuilder + { + private readonly IFileService _fileService; + private IReadOnlyDictionary _headers; + private bool _cacheability; + private IReadOnlyList _cacheExtensions; + private IPublishedContent _internalRedirectContent; + private bool _isInitContent; + private string _redirectUrl; + private HttpStatusCode _responseStatus = HttpStatusCode.NotFound; + private string _responseDesc; + + /// + /// Initializes a new instance of the class. + /// + public PublishedRequestBuilder(IFileService fileService) + { + _fileService = fileService; + } + + /// + public Uri Uri { get; private set; } + + /// + public DomainAndUri Domain { get; private set; } + + /// + public CultureInfo Culture { get; private set; } + + /// + public ITemplate Template { get; private set; } + + /// + public bool IsInitialPublishedContent { get; private set; } + + /// + public bool IsInternalRedirectPublishedContent { get; private set; } // TODO: Not sure what this is yet + + /// + public int ResponseStatusCode => (int)_responseStatus; + + /// + public IPublishedContent PublishedContent { get; private set; } + + /// + public IPublishedRequest Build() => new PublishedRequest( + Uri, + PublishedContent, + IsInternalRedirectPublishedContent, + Template, + Domain, + Culture, + _redirectUrl, + (int)_responseStatus, + _responseDesc, + _cacheExtensions, + _headers, + _cacheability); + + /// + public IPublishedRequestBuilder ResetTemplate() + { + Template = null; + return this; + } + + /// + public IPublishedRequestBuilder SetCacheabilityNoCache(bool cacheability) + { + _cacheability = cacheability; + return this; + } + + /// + public IPublishedRequestBuilder SetCacheExtensions(IEnumerable cacheExtensions) + { + _cacheExtensions = cacheExtensions.ToList(); + return this; + } + + /// + public IPublishedRequestBuilder SetCulture(CultureInfo culture) + { + Culture = culture; + return this; + } + + /// + public IPublishedRequestBuilder SetDomain(DomainAndUri domain) + { + Domain = domain; + return this; + } + + /// + public IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers) + { + _headers = headers; + return this; + } + + /// + public IPublishedRequestBuilder SetInternalRedirectPublishedContent(IPublishedContent content) + { + _internalRedirectContent = content; + return this; + } + + /// + public IPublishedRequestBuilder SetIs404(bool is404) + { + _responseStatus = HttpStatusCode.NotFound; + return this; + } + + /// + public IPublishedRequestBuilder SetIsInitialPublishedContent() + { + _isInitContent = true; + return this; + } + + /// + public IPublishedRequestBuilder SetPublishedContent(IPublishedContent content) + { + PublishedContent = content; + return this; + } + + /// + public IPublishedRequestBuilder SetRedirect(string url, int status = (int)HttpStatusCode.Redirect) + { + _redirectUrl = url; + _responseStatus = (HttpStatusCode)status; + return this; + } + + /// + public IPublishedRequestBuilder SetRedirectPermanent(string url) + { + _redirectUrl = url; + _responseStatus = HttpStatusCode.Moved; + return this; + } + + /// + public IPublishedRequestBuilder SetResponseStatus(int code, string description = null) + { + _responseStatus = (HttpStatusCode)code; + _responseDesc = description; + return this; + } + + /// + public IPublishedRequestBuilder SetTemplate(ITemplate template) + { + Template = template; + return this; + } + + /// + public bool TrySetTemplate(string alias) + { + if (string.IsNullOrWhiteSpace(alias)) + { + Template = null; + return true; + } + + // NOTE - can we still get it with whitespaces in it due to old legacy bugs? + alias = alias.Replace(" ", ""); + + ITemplate model = _fileService.GetTemplate(alias); + if (model == null) + { + return false; + } + + Template = model; + return true; + } + } +} diff --git a/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs new file mode 100644 index 0000000000..f9c9d8b294 --- /dev/null +++ b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs @@ -0,0 +1,74 @@ +using System.Net; + +namespace Umbraco.Web.Routing +{ + public static class PublishedRequestExtensions + { + /// + /// Gets a value indicating whether the request was successfully routed + /// + public static bool Success(this IPublishedRequest publishedRequest) + => !publishedRequest.IsRedirect() && publishedRequest.HasPublishedContent(); + + /// + /// Gets a value indicating whether the content request has a content. + /// + public static bool HasPublishedContent(this IPublishedRequestBuilder publishedRequest) => publishedRequest.PublishedContent != null; + + /// + /// Gets a value indicating whether the content request has a content. + /// + public static bool HasPublishedContent(this IPublishedRequest publishedRequest) => publishedRequest.PublishedContent != null; + + /// + /// Gets a value indicating whether the content request has a template. + /// + public static bool HasTemplate(this IPublishedRequestBuilder publishedRequest) => publishedRequest.Template != null; + + /// + /// Gets a value indicating whether the content request has a template. + /// + public static bool HasTemplate(this IPublishedRequest publishedRequest) => publishedRequest.Template != null; + + /// + /// Gets the alias of the template to use to display the requested content. + /// + public static string GetTemplateAlias(this IPublishedRequest publishedRequest) => publishedRequest.Template?.Alias; + + /// + /// Gets a value indicating whether the requested content could not be found. + /// + public static bool Is404(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.NotFound; + + /// + /// Gets a value indicating whether the content request triggers a redirect (permanent or not). + /// + public static bool IsRedirect(this IPublishedRequestBuilder publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Redirect || publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; + + /// + /// Gets indicating whether the content request triggers a redirect (permanent or not). + /// + public static bool IsRedirect(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Redirect || publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; + + /// + /// Gets a value indicating whether the redirect is permanent. + /// + public static bool IsRedirectPermanent(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; + + /// + /// Gets a value indicating whether the current published content is the initial one. + /// + public static bool IsInitialPublishedContent(this IPublishedRequest publishedRequest) => publishedRequest.InitialPublishedContent != null; + + /// + /// Gets a value indicating whether the content request has a domain. + /// + public static bool HasDomain(this IPublishedRequestBuilder publishedRequest) => publishedRequest.Domain != null; + + /// + /// Gets a value indicating whether the content request has a domain. + /// + public static bool HasDomain(this IPublishedRequest publishedRequest) => publishedRequest.Domain != null; + + } +} diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 10986b941a..a7b20b84ba 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -1,18 +1,18 @@ using System; -using System.Linq; -using System.Threading; using System.Globalization; using System.IO; +using System.Linq; +using System.Threading; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Configuration.Models; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; +using Umbraco.Web.PublishedCache; using Umbraco.Web.Security; -using Umbraco.Core.Configuration.Models; -using Microsoft.Extensions.Options; namespace Umbraco.Web.Routing { @@ -34,6 +34,7 @@ namespace Umbraco.Web.Routing private readonly IFileService _fileService; private readonly IContentTypeService _contentTypeService; private readonly IPublicAccessService _publicAccessService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; /// /// Initializes a new instance of the class. @@ -51,7 +52,8 @@ namespace Umbraco.Web.Routing IPublicAccessChecker publicAccessChecker, IFileService fileService, IContentTypeService contentTypeService, - IPublicAccessService publicAccessService) + IPublicAccessService publicAccessService, + IUmbracoContextAccessor umbracoContextAccessor) { _webRoutingSettings = webRoutingSettings.Value ?? throw new ArgumentNullException(nameof(webRoutingSettings)); _contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders)); @@ -66,38 +68,32 @@ namespace Umbraco.Web.Routing _fileService = fileService; _contentTypeService = contentTypeService; _publicAccessService = publicAccessService; + _umbracoContextAccessor = umbracoContextAccessor; } /// - public IPublishedRequest CreateRequest(IUmbracoContext umbracoContext, Uri uri = null) - { - return new PublishedRequest(this, umbracoContext, Options.Create(_webRoutingSettings), uri ?? umbracoContext.CleanedUmbracoUrl); - } - - #region Request + public IPublishedRequestBuilder CreateRequest(Uri uri) => new PublishedRequestBuilder(_fileService); /// - public bool TryRouteRequest(IPublishedRequest request) + public bool TryRouteRequest(IPublishedRequestBuilder request) { - // disabled - is it going to change the routing? - //_pcr.OnPreparing(); - FindDomain(request); - if (request.IsRedirect) return false; - if (request.HasPublishedContent) return true; + + // TODO: This was ported from v8 but how could it possibly have a redirect here? + if (request.IsRedirect()) + { + return false; + } + + // TODO: This was ported from v8 but how could it possibly have content here? + if (request.HasPublishedContent()) + { + return true; + } + FindPublishedContent(request); - if (request.IsRedirect) return false; - // don't handle anything - we just want to ensure that we find the content - //HandlePublishedContent(); - //FindTemplate(); - //FollowExternalRedirect(); - //HandleWildcardDomains(); - - // disabled - we just want to ensure that we find the content - //_pcr.OnPrepared(); - - return request.HasPublishedContent; + return request.Build().Success(); } private void SetVariationContext(string culture) @@ -108,33 +104,20 @@ namespace Umbraco.Web.Routing } /// - public bool PrepareRequest(IPublishedRequest request) + public IPublishedRequest RouteRequest(IPublishedRequestBuilder request) { - // note - at that point the original legacy module did something do handle IIS custom 404 errors - // ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support - // "directory URLs" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain - // to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk. - // - // to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors - // so that they point to a non-existing page eg /redirect-404.aspx - // TODO: SD: We need more information on this for when we release 4.10.0 as I'm not sure what this means. - - // trigger the Preparing event - at that point anything can still be changed - // the idea is that it is possible to change the uri - // - request.OnPreparing(); - - //find domain + // find domain FindDomain(request); // if request has been flagged to redirect then return // whoever called us is in charge of actually redirecting - if (request.IsRedirect) + if (request.IsRedirect()) { - return false; + return request.Build(); } // set the culture on the thread - once, so it's set when running document lookups + // TODO: Set this on HttpContext! Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; SetVariationContext(request.Culture.Name); @@ -154,10 +137,10 @@ namespace Umbraco.Web.Routing Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; SetVariationContext(request.Culture.Name); - // trigger the Prepared event - at that point it is still possible to change about anything - // even though the request might be flagged for redirection - we'll redirect _after_ the event - // also, OnPrepared() will make the PublishedRequest readonly, so nothing can change - request.OnPrepared(); + //// trigger the Prepared event - at that point it is still possible to change about anything + //// even though the request might be flagged for redirection - we'll redirect _after_ the event + //// also, OnPrepared() will make the PublishedRequest readonly, so nothing can change + //request.OnPrepared(); // we don't take care of anything so if the content has changed, it's up to the user // to find out the appropriate template @@ -177,80 +160,68 @@ namespace Umbraco.Web.Routing /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values /// but need to finalize it themselves. /// - public bool ConfigureRequest(IPublishedRequest frequest) + internal IPublishedRequest ConfigureRequest(IPublishedRequestBuilder frequest) { - if (frequest.HasPublishedContent == false) + if (!frequest.HasPublishedContent()) { - return false; + return frequest.Build(); } // set the culture on the thread -- again, 'cos it might have changed in the event handler + // TODO: Set this on HttpContext! Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = frequest.Culture; SetVariationContext(frequest.Culture.Name); - // if request has been flagged to redirect, or has no content to display, - // then return - whoever called us is in charge of actually redirecting - if (frequest.IsRedirect || frequest.HasPublishedContent == false) - { - return false; - } - - // we may be 404 _and_ have a content - - // can't go beyond that point without a PublishedContent to render - // it's ok not to have a template, in order to give MVC a chance to hijack routes - return true; + return frequest.Build(); } - /// - public void UpdateRequestToNotFound(IPublishedRequest request) - { - // clear content - var content = request.PublishedContent; - request.PublishedContent = null; + // TODO: This shouldn't be required and should be handled differently during route building + ///// + //public void UpdateRequestToNotFound(IPublishedRequest request) + //{ + // // clear content + // var content = request.PublishedContent; + // request.PublishedContent = null; - HandlePublishedContent(request); // will go 404 - FindTemplate(request); + // HandlePublishedContent(request); // will go 404 + // FindTemplate(request); - // if request has been flagged to redirect then return - // whoever called us is in charge of redirecting - if (request.IsRedirect) - return; + // // if request has been flagged to redirect then return + // // whoever called us is in charge of redirecting + // if (request.IsRedirect) + // { + // return; + // } - if (request.HasPublishedContent == false) - { - // means the engine could not find a proper document to handle 404 - // restore the saved content so we know it exists - request.PublishedContent = content; - return; - } + // if (request.HasPublishedContent == false) + // { + // // means the engine could not find a proper document to handle 404 + // // restore the saved content so we know it exists + // request.PublishedContent = content; + // return; + // } - if (request.HasTemplate == false) - { - // means we may have a document, but we have no template - // at that point there isn't much we can do and there is no point returning - // to Mvc since Mvc can't do much either - return; - } - } - - #endregion - - #region Domain + // if (request.HasTemplate == false) + // { + // // means we may have a document, but we have no template + // // at that point there isn't much we can do and there is no point returning + // // to Mvc since Mvc can't do much either + // return; + // } + //} /// /// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly. /// /// A value indicating whether a domain was found. - internal bool FindDomain(IPublishedRequest request) + internal bool FindDomain(IPublishedRequestBuilder request) { const string tracePrefix = "FindDomain: "; // note - we are not handling schemes nor ports here. - _logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri); - var domainsCache = request.UmbracoContext.PublishedSnapshot.Domains; + IDomainCache domainsCache = _umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Domains; var domains = domainsCache.GetAll(includeWildcards: false).ToList(); // determines whether a domain corresponds to a published document, since some @@ -260,15 +231,19 @@ namespace Umbraco.Web.Routing bool IsPublishedContentDomain(Domain domain) { // just get it from content cache - optimize there, not here - var domainDocument = request.UmbracoContext.PublishedSnapshot.Content.GetById(domain.ContentId); + IPublishedContent domainDocument = _umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Content.GetById(domain.ContentId); // not published - at all if (domainDocument == null) + { return false; + } // invariant - always published if (!domainDocument.ContentType.VariesByCulture()) + { return true; + } // variant, ensure that the culture corresponding to the domain's language is published return domainDocument.Cultures.ContainsKey(domain.Culture.Name); @@ -279,7 +254,7 @@ namespace Umbraco.Web.Routing var defaultCulture = domainsCache.DefaultCulture; // try to find a domain matching the current request - var domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture); + DomainAndUri domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture); // handle domain - always has a contentId and a culture if (domainAndUri != null) @@ -287,8 +262,9 @@ namespace Umbraco.Web.Routing // matching an existing domain _logger.LogDebug("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture); - request.Domain = domainAndUri; - request.Culture = domainAndUri.Culture; + request + .SetDomain(domainAndUri) + .SetCulture(domainAndUri.Culture); // canonical? not implemented at the moment // if (...) @@ -302,7 +278,7 @@ namespace Umbraco.Web.Routing // not matching any existing domain _logger.LogDebug("{TracePrefix}Matches no domain", tracePrefix); - request.Culture = defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture); + request.SetCulture(defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture)); } _logger.LogDebug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture.Name); @@ -313,22 +289,24 @@ namespace Umbraco.Web.Routing /// /// Looks for wildcard domains in the path and updates Culture accordingly. /// - internal void HandleWildcardDomains(IPublishedRequest request) + internal void HandleWildcardDomains(IPublishedRequestBuilder request) { const string tracePrefix = "HandleWildcardDomains: "; - if (request.HasPublishedContent == false) + if (request.PublishedContent == null) + { return; + } var nodePath = request.PublishedContent.Path; _logger.LogDebug("{TracePrefix}Path={NodePath}", tracePrefix, nodePath); - var rootNodeId = request.HasDomain ? request.Domain.ContentId : (int?)null; - var domain = DomainUtilities.FindWildcardDomainInPath(request.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId); + var rootNodeId = request.Domain != null ? request.Domain.ContentId : (int?)null; + Domain domain = DomainUtilities.FindWildcardDomainInPath(_umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId); // always has a contentId and a culture if (domain != null) { - request.Culture = domain.Culture; + request.SetCulture(domain.Culture); _logger.LogDebug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture.Name); } else @@ -337,10 +315,6 @@ namespace Umbraco.Web.Routing } } - #endregion - - #region Rendering engine - internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions) { if (directory == null || directory.Exists == false) @@ -359,21 +333,10 @@ namespace Umbraco.Web.Routing return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e))); } - #endregion - - #region Document and template - - /// - public ITemplate GetTemplate(string alias) - { - return _fileService.GetTemplate(alias); - } - /// /// Finds the Umbraco document (if any) matching the request, and updates the PublishedRequest accordingly. /// - /// A value indicating whether a document and template were found. - private void FindPublishedContentAndTemplate(IPublishedRequest request) + private void FindPublishedContentAndTemplate(IPublishedRequestBuilder request) { _logger.LogDebug("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", request.Uri.AbsolutePath); @@ -383,8 +346,10 @@ namespace Umbraco.Web.Routing // if request has been flagged to redirect then return // whoever called us is in charge of actually redirecting // -- do not process anything any further -- - if (request.IsRedirect) + if (request.IsRedirect()) + { return; + } // not handling umbracoRedirect here but after LookupDocument2 // so internal redirect, 404, etc has precedence over redirect @@ -403,7 +368,7 @@ namespace Umbraco.Web.Routing /// Tries to find the document matching the request, by running the IPublishedContentFinder instances. /// /// There is no finder collection. - internal void FindPublishedContent(IPublishedRequest request) + internal void FindPublishedContent(IPublishedRequestBuilder request) { const string tracePrefix = "FindPublishedContent: "; @@ -413,9 +378,9 @@ namespace Umbraco.Web.Routing using (_profilingLogger.DebugDuration( $"{tracePrefix}Begin finders", - $"{tracePrefix}End finders, {(request.HasPublishedContent ? "a document was found" : "no document was found")}")) + $"{tracePrefix}End finders")) { - //iterate but return on first one that finds it + // iterate but return on first one that finds it var found = _contentFinders.Any(finder => { _logger.LogDebug("Finder {ContentFinderType}", finder.GetType().FullName); @@ -435,7 +400,7 @@ namespace Umbraco.Web.Routing /// Handles "not found", internal redirects, access validation... /// things that must be handled in one place because they can create loops /// - private void HandlePublishedContent(IPublishedRequest request) + private void HandlePublishedContent(IPublishedRequestBuilder request) { // because these might loop, we have to have some sort of infinite loop detection int i = 0, j = 0; @@ -445,9 +410,9 @@ namespace Umbraco.Web.Routing _logger.LogDebug("HandlePublishedContent: Loop {LoopCounter}", i); // handle not found - if (request.HasPublishedContent == false) + if (request.PublishedContent == null) { - request.Is404 = true; + request.SetIs404(true); _logger.LogDebug("HandlePublishedContent: No document, try last chance lookup"); // if it fails then give up, there isn't much more that we can do @@ -464,23 +429,28 @@ namespace Umbraco.Web.Routing j = 0; while (FollowInternalRedirects(request) && j++ < maxLoop) { } - if (j == maxLoop) // we're running out of control + + // we're running out of control + if (j == maxLoop) + { break; + } // ensure access - if (request.HasPublishedContent) + if (request.PublishedContent != null) + { EnsurePublishedContentAccess(request); + } // loop while we don't have page, ie the redirect or access // got us to nowhere and now we need to run the notFoundLookup again // as long as it's not running out of control ie infinite loop of some sort - - } while (request.HasPublishedContent == false && i++ < maxLoop); + } while (request.PublishedContent == null && i++ < maxLoop); if (i == maxLoop || j == maxLoop) { _logger.LogDebug("HandlePublishedContent: Looks like we are running into an infinite loop, abort"); - request.PublishedContent = null; + request.SetPublishedContent(null); } _logger.LogDebug("HandlePublishedContent: End"); @@ -494,14 +464,18 @@ namespace Umbraco.Web.Routing /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. /// As per legacy, if the redirect does not work, we just ignore it. /// - private bool FollowInternalRedirects(IPublishedRequest request) + private bool FollowInternalRedirects(IPublishedRequestBuilder request) { if (request.PublishedContent == null) + { throw new InvalidOperationException("There is no PublishedContent."); + } // don't try to find a redirect if the property doesn't exist if (request.PublishedContent.HasProperty(Constants.Conventions.Content.InternalRedirectId) == false) + { return false; + } var redirect = false; var valid = false; @@ -512,29 +486,31 @@ namespace Umbraco.Web.Routing { // try and get the redirect node from a legacy integer ID valid = true; - internalRedirectNode = request.UmbracoContext.Content.GetById(internalRedirectId); + internalRedirectNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(internalRedirectId); } else { - var udiInternalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId); + GuidUdi udiInternalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId); if (udiInternalRedirectId != null) { // try and get the redirect node from a UDI Guid valid = true; - internalRedirectNode = request.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid); + internalRedirectNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid); } } if (valid == false) { // bad redirect - log and display the current page (legacy behavior) - _logger.LogDebug("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.", + _logger.LogDebug( + "FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.", request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue()); } if (internalRedirectNode == null) { - _logger.LogDebug("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.", + _logger.LogDebug( + "FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.", request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue()); } else if (internalRedirectId == request.PublishedContent.Id) @@ -556,20 +532,22 @@ namespace Umbraco.Web.Routing /// Ensures that access to current node is permitted. /// /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. - private void EnsurePublishedContentAccess(IPublishedRequest request) + private void EnsurePublishedContentAccess(IPublishedRequestBuilder request) { if (request.PublishedContent == null) + { throw new InvalidOperationException("There is no PublishedContent."); + } var path = request.PublishedContent.Path; - var publicAccessAttempt = _publicAccessService.IsProtected(path); + Attempt publicAccessAttempt = _publicAccessService.IsProtected(path); if (publicAccessAttempt) { _logger.LogDebug("EnsurePublishedContentAccess: Page is protected, check for access"); - var status = _publicAccessChecker.HasMemberAccessToContent(request.PublishedContent.Id); + PublicAccessStatus status = _publicAccessChecker.HasMemberAccessToContent(request.PublishedContent.Id); switch (status) { case PublicAccessStatus.NotLoggedIn: @@ -599,24 +577,26 @@ namespace Umbraco.Web.Routing } } - private static void SetPublishedContentAsOtherPage(IPublishedRequest request, int errorPageId) + private void SetPublishedContentAsOtherPage(IPublishedRequestBuilder request, int errorPageId) { if (errorPageId != request.PublishedContent.Id) - request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); + { + request.SetPublishedContent(_umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId)); + } } /// /// Finds a template for the current node, if any. /// - private void FindTemplate(IPublishedRequest request) + private void FindTemplate(IPublishedRequestBuilder request) { + // TODO: We've removed the event, might need to re-add? // NOTE: at the moment there is only 1 way to find a template, and then ppl must // use the Prepared event to change the template if they wish. Should we also // implement an ITemplateFinder logic? - if (request.PublishedContent == null) { - request.TemplateModel = null; + request.SetTemplate(null); return; } @@ -626,6 +606,7 @@ namespace Umbraco.Web.Routing // + optionally, apply the alternate template on internal redirects var useAltTemplate = request.IsInitialPublishedContent || (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent); + var altTemplate = useAltTemplate ? _requestAccessor.GetRequestValue(Constants.Conventions.Url.AltTemplate) : null; @@ -635,21 +616,18 @@ namespace Umbraco.Web.Routing // we don't have an alternate template specified. use the current one if there's one already, // which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...), // else lookup the template id on the document then lookup the template with that id. - - if (request.HasTemplate) + if (request.HasTemplate()) { _logger.LogDebug("FindTemplate: Has a template already, and no alternate template."); return; } - // TODO: When we remove the need for a database for templates, then this id should be irrelevant, - // not sure how were going to do this nicely. - // TODO: We need to limit altTemplate to only allow templates that are assigned to the current document type! // if the template isn't assigned to the document type we should log a warning and return 404 - var templateId = request.PublishedContent.TemplateId; - request.TemplateModel = GetTemplateModel(templateId); + ITemplate template = GetTemplate(templateId); + request.SetTemplate(template); + _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } else { @@ -658,9 +636,11 @@ namespace Umbraco.Web.Routing // so /path/to/page/template1?altTemplate=template2 will use template2 // ignore if the alias does not match - just trace - - if (request.HasTemplate) + if (request.HasTemplate()) + { _logger.LogDebug("FindTemplate: Has a template already, but also an alternative template."); + } + _logger.LogDebug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate); // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings @@ -672,11 +652,11 @@ namespace Umbraco.Web.Routing altTemplate)) { // allowed, use - var template = _fileService.GetTemplate(altTemplate); + ITemplate template = _fileService.GetTemplate(altTemplate); if (template != null) { - request.TemplateModel = template; + request.SetTemplate(template); _logger.LogDebug("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } else @@ -690,11 +670,13 @@ namespace Umbraco.Web.Routing // no allowed, back to default var templateId = request.PublishedContent.TemplateId; - request.TemplateModel = GetTemplateModel(templateId); + ITemplate template = GetTemplate(templateId); + request.SetTemplate(template); + _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } } - if (request.HasTemplate == false) + if (!request.HasTemplate()) { _logger.LogDebug("FindTemplate: No template was found."); @@ -707,13 +689,9 @@ namespace Umbraco.Web.Routing // // so, don't set _pcr.Document to null here } - else - { - _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", request.TemplateModel.Id, request.TemplateModel.Alias); - } } - private ITemplate GetTemplateModel(int? templateId) + private ITemplate GetTemplate(int? templateId) { if (templateId.HasValue == false || templateId.Value == default) { @@ -724,11 +702,16 @@ namespace Umbraco.Web.Routing _logger.LogDebug("GetTemplateModel: Get template id={TemplateId}", templateId); if (templateId == null) + { throw new InvalidOperationException("The template is not set, the page cannot render."); + } - var template = _fileService.GetTemplate(templateId.Value); + ITemplate template = _fileService.GetTemplate(templateId.Value); if (template == null) + { throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render."); + } + _logger.LogDebug("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); return template; } @@ -737,13 +720,18 @@ namespace Umbraco.Web.Routing /// Follows external redirection through umbracoRedirect document property. /// /// As per legacy, if the redirect does not work, we just ignore it. - private void FollowExternalRedirect(IPublishedRequest request) + private void FollowExternalRedirect(IPublishedRequestBuilder request) { - if (request.HasPublishedContent == false) return; + if (request.PublishedContent == null) + { + return; + } // don't try to find a redirect if the property doesn't exist if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false) + { return; + } var redirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect, defaultValue: -1); var redirectUrl = "#"; @@ -754,14 +742,17 @@ namespace Umbraco.Web.Routing else { // might be a UDI instead of an int Id - var redirectUdi = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect); + GuidUdi redirectUdi = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect); if (redirectUdi != null) + { redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid); + } } - if (redirectUrl != "#") - request.SetRedirect(redirectUrl); - } - #endregion + if (redirectUrl != "#") + { + request.SetRedirect(redirectUrl); + } + } } } diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs index 5c00b14af3..19d65b8f3a 100644 --- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -175,35 +175,43 @@ namespace Umbraco.Web.Routing { // test for collisions on the 'main' URL var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); - if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl); + if (uri.IsAbsoluteUri == false) + { + uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl); + } + uri = uriUtility.UriToUmbraco(uri); - var pcr = publishedRouter.CreateRequest(umbracoContext, uri); + IPublishedRequestBuilder pcr = publishedRouter.CreateRequest(uri); publishedRouter.TryRouteRequest(pcr); urlInfo = null; - if (pcr.HasPublishedContent == false) + if (pcr.PublishedContent == null) { urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture); return true; } - if (pcr.IgnorePublishedContentCollisions) - return false; + // TODO: What is this? + //if (pcr.IgnorePublishedContentCollisions) + //{ + // return false; + //} if (pcr.PublishedContent.Id != content.Id) { - var o = pcr.PublishedContent; + IPublishedContent o = pcr.PublishedContent; var l = new List(); while (o != null) { l.Add(o.Name(variationContextAccessor)); o = o.Parent; } + l.Reverse(); var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")"; - urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture); + urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture); return true; } diff --git a/src/Umbraco.Core/Web/IUmbracoContext.cs b/src/Umbraco.Core/Web/IUmbracoContext.cs index c034997d7e..3bbcb43dca 100644 --- a/src/Umbraco.Core/Web/IUmbracoContext.cs +++ b/src/Umbraco.Core/Web/IUmbracoContext.cs @@ -9,10 +9,13 @@ namespace Umbraco.Web public interface IUmbracoContext : IDisposable { /// - /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this + /// Gets the DateTime this instance was created. + /// + /// + /// Used internally for performance calculations, the ObjectCreated DateTime is set as soon as this /// object is instantiated which in the web site is created during the BeginRequest phase. /// We can then determine complete rendering time from that. - /// + /// DateTime ObjectCreated { get; } /// @@ -46,16 +49,16 @@ namespace Umbraco.Web /// IDomainCache Domains { get; } - /// - /// Gets/sets the PublishedRequest object - /// - // TODO: Can we refactor this and not expose this mutable object here? Instead just expose IPublishedContent? A bunch of stuff would need to change but would be better + ///// + ///// Gets or sets the PublishedRequest object + ///// + //// TODO: Can we refactor this? The only nicer way would be to have a RouteRequest method directly on IUmbracoContext but that means adding another dep to the ctx IPublishedRequest PublishedRequest { get; set; } /// /// Gets the variation context accessor. /// - IVariationContextAccessor VariationContextAccessor { get; } + IVariationContextAccessor VariationContextAccessor { get; } // TODO: Does this need to be a property, it can be injected when needed /// /// Gets a value indicating whether the request has debugging enabled @@ -64,10 +67,14 @@ namespace Umbraco.Web bool IsDebug { get; } /// - /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) + /// Gets a value indicating whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) /// bool InPreviewMode { get; } + /// + /// Forces the context into preview + /// + /// A instance to be disposed to exit the preview context IDisposable ForcedPreview(bool preview); } } diff --git a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs index 8f68ec0a64..75fc80015a 100644 --- a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs +++ b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs @@ -1,8 +1,8 @@ using System.Globalization; using System.Linq; using Examine; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.PublishedContent; @@ -19,17 +19,23 @@ namespace Umbraco.Web.Routing private readonly IEntityService _entityService; private readonly ContentSettings _contentSettings; private readonly IExamineManager _examineManager; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + /// + /// Initializes a new instance of the class. + /// public ContentFinderByConfigured404( ILogger logger, IEntityService entityService, IOptions contentConfigSettings, - IExamineManager examineManager) + IExamineManager examineManager, + IUmbracoContextAccessor umbracoContextAccessor) { _logger = logger; _entityService = entityService; _contentSettings = contentConfigSettings.Value; _examineManager = examineManager; + _umbracoContextAccessor = umbracoContextAccessor; } /// @@ -37,13 +43,19 @@ namespace Umbraco.Web.Routing /// /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(IPublishedRequest frequest) + public bool TryFindContent(IPublishedRequestBuilder frequest) { + IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext; + if (umbCtx == null) + { + return false; + } + _logger.LogDebug("Looking for a page to handle 404."); // try to find a culture as best as we can - var errorCulture = CultureInfo.CurrentUICulture; - if (frequest.HasDomain) + CultureInfo errorCulture = CultureInfo.CurrentUICulture; + if (frequest.Domain != null) { errorCulture = frequest.Domain.Culture; } @@ -55,22 +67,29 @@ namespace Umbraco.Web.Routing while (pos > 1) { route = route.Substring(0, pos); - node = frequest.UmbracoContext.Content.GetByRoute(route, culture: frequest?.Culture?.Name); - if (node != null) break; + node = umbCtx.Content.GetByRoute(route, culture: frequest?.Culture?.Name); + if (node != null) + { + break; + } + pos = route.LastIndexOf('/'); } + if (node != null) { - var d = DomainUtilities.FindWildcardDomainInPath(frequest.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), node.Path, null); + Domain d = DomainUtilities.FindWildcardDomainInPath(umbCtx.PublishedSnapshot.Domains.GetAll(true), node.Path, null); if (d != null) + { errorCulture = d.Culture; + } } } var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId( _contentSettings.Error404Collection.ToArray(), _entityService, - new PublishedContentQuery(frequest.UmbracoContext.PublishedSnapshot, frequest.UmbracoContext.VariationContextAccessor, _examineManager), + new PublishedContentQuery(umbCtx.PublishedSnapshot, umbCtx.VariationContextAccessor, _examineManager), errorCulture); IPublishedContent content = null; @@ -79,7 +98,7 @@ namespace Umbraco.Web.Routing { _logger.LogDebug("Got id={ErrorNodeId}.", error404.Value); - content = frequest.UmbracoContext.Content.GetById(error404.Value); + content = umbCtx.Content.GetById(error404.Value); _logger.LogDebug(content == null ? "Could not find content with that id." @@ -90,8 +109,10 @@ namespace Umbraco.Web.Routing _logger.LogDebug("Got nothing."); } - frequest.PublishedContent = content; - frequest.Is404 = true; + frequest + .SetPublishedContent(content) + .SetIs404(true); + return content != null; } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index 83924f3315..3621580dcb 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.ObjectModel; using System.Globalization; @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Routing; namespace Umbraco.Tests.PublishedContent { @@ -18,27 +19,28 @@ namespace Umbraco.Tests.PublishedContent public void ConfigureRequest_Returns_False_Without_HasPublishedContent() { var umbracoContext = GetUmbracoContext("/test"); - var publishedRouter = CreatePublishedRouter(); - var request = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var request = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var result = publishedRouter.ConfigureRequest(request); - Assert.IsFalse(result); + Assert.IsFalse(result.Success()); } [Test] public void ConfigureRequest_Returns_False_When_IsRedirect() { var umbracoContext = GetUmbracoContext("/test"); - var publishedRouter = CreatePublishedRouter(); - var request = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var request = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var content = GetPublishedContentMock(); - request.PublishedContent = content.Object; - request.Culture = new CultureInfo("en-AU"); + request.SetPublishedContent(content.Object); + request.SetCulture(new CultureInfo("en-AU")); request.SetRedirect("/hello"); var result = publishedRouter.ConfigureRequest(request); - Assert.IsFalse(result); + Assert.IsFalse(result.Success()); } + private Mock GetPublishedContentMock() { var pc = new Mock(); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs index 3e3f6163bf..34051c96bd 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Moq; using NUnit.Framework; @@ -48,10 +48,10 @@ namespace Umbraco.Tests.Routing public void Lookup_By_Url_Alias(string urlAsString, int nodeMatch) { var umbracoContext = GetUmbracoContext(urlAsString); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var lookup = - new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor); + new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor, GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs index 9af04cfb18..5a390f667c 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.Extensions.Logging; using Moq; @@ -57,15 +57,15 @@ namespace Umbraco.Tests.Routing //SetDomains1(); var umbracoContext = GetUmbracoContext(inputUrl); - var publishedRouter = CreatePublishedRouter(); - var request = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var request = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // must lookup domain publishedRouter.FindDomain(request); if (expectedNode > 0) Assert.AreEqual(expectedCulture, request.Culture.Name); - var finder = new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor); + var finder = new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor, GetUmbracoContextAccessor(umbracoContext)); var result = finder.TryFindContent(request); if (expectedNode > 0) diff --git a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs index deb5ac30bf..0ed3161caf 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Microsoft.Extensions.Logging; using Umbraco.Core; @@ -18,10 +18,10 @@ namespace Umbraco.Tests.Routing public void Lookup_By_Id(string urlAsString, int nodeMatch) { var umbracoContext = GetUmbracoContext(urlAsString); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var webRoutingSettings = new WebRoutingSettings(); - var lookup = new ContentFinderByIdPath(Microsoft.Extensions.Options.Options.Create(webRoutingSettings), LoggerFactory.CreateLogger(), Factory.GetRequiredService()); + var lookup = new ContentFinderByIdPath(Microsoft.Extensions.Options.Options.Create(webRoutingSettings), LoggerFactory.CreateLogger(), Factory.GetRequiredService(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs index 405572334c..24872a128e 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs @@ -1,4 +1,4 @@ -using Moq; +using Moq; using NUnit.Framework; using Umbraco.Tests.TestHelpers; using Umbraco.Web; @@ -18,12 +18,12 @@ namespace Umbraco.Tests.Routing { var umbracoContext = GetUmbracoContext(urlAsString); var httpContext = GetHttpContextFactory(urlAsString).HttpContext; - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var mockRequestAccessor = new Mock(); mockRequestAccessor.Setup(x => x.GetRequestValue("umbPageID")).Returns(httpContext.Request.QueryString["umbPageID"]); - var lookup = new ContentFinderByPageIdQuery(mockRequestAccessor.Object); + var lookup = new ContentFinderByPageIdQuery(mockRequestAccessor.Object, GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs index b849b100ea..82b433a1a0 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using Microsoft.Extensions.Logging; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; @@ -32,17 +32,24 @@ namespace Umbraco.Tests.Routing var template1 = CreateTemplate("test"); var template2 = CreateTemplate("blah"); var umbracoContext = GetUmbracoContext(urlAsString, template1.Id, globalSettings: globalSettings); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var reqBuilder = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var webRoutingSettings = new WebRoutingSettings(); - var lookup = new ContentFinderByUrlAndTemplate(LoggerFactory.CreateLogger(), ServiceContext.FileService, ServiceContext.ContentTypeService, Microsoft.Extensions.Options.Options.Create(webRoutingSettings)); + var lookup = new ContentFinderByUrlAndTemplate( + LoggerFactory.CreateLogger(), + ServiceContext.FileService, + ServiceContext.ContentTypeService, + GetUmbracoContextAccessor(umbracoContext), + Microsoft.Extensions.Options.Options.Create(webRoutingSettings)); - var result = lookup.TryFindContent(frequest); + var result = lookup.TryFindContent(reqBuilder); + + IPublishedRequest frequest = reqBuilder.Build(); Assert.IsTrue(result); Assert.IsNotNull(frequest.PublishedContent); - Assert.IsNotNull(frequest.TemplateAlias); - Assert.AreEqual("blah".ToUpperInvariant(), frequest.TemplateAlias.ToUpperInvariant()); + Assert.IsNotNull(frequest.GetTemplateAlias()); + Assert.AreEqual("blah".ToUpperInvariant(), frequest.GetTemplateAlias().ToUpperInvariant()); } } } diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs index 6a0b8c3f3b..807cf729ef 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using NUnit.Framework; using Umbraco.Core.Configuration.Models; @@ -6,6 +6,8 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; using Umbraco.Web.Routing; using Microsoft.Extensions.Logging; +using Umbraco.Web; +using Moq; namespace Umbraco.Tests.Routing { @@ -30,9 +32,9 @@ namespace Umbraco.Tests.Routing var snapshotService = CreatePublishedSnapshotService(globalSettings); var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings, snapshotService: snapshotService); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); Assert.IsTrue(globalSettings.HideTopLevelNodeFromPath); @@ -64,9 +66,9 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); Assert.IsFalse(globalSettings.HideTopLevelNodeFromPath); @@ -88,9 +90,9 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); @@ -114,10 +116,10 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.Domain = new DomainAndUri(new Domain(1, "mysite", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/")); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/"))); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); @@ -142,10 +144,10 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.Domain = new DomainAndUri(new Domain(1, "mysite/æøå", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/æøå")); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite/æøå", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/æøå"))); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs index 8428f44a8b..84f86f1e09 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs @@ -1,4 +1,4 @@ -using Moq; +using Moq; using NUnit.Framework; using Microsoft.Extensions.Logging; using Umbraco.Core; @@ -133,13 +133,13 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = true }; var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // must lookup domain else lookup by URL fails publishedRouter.FindDomain(frequest); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); Assert.AreEqual(expectedId, frequest.PublishedContent.Id); @@ -174,14 +174,14 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = true }; var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // must lookup domain else lookup by URL fails publishedRouter.FindDomain(frequest); Assert.AreEqual(expectedCulture, frequest.Culture.Name); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); Assert.AreEqual(expectedId, frequest.PublishedContent.Id); diff --git a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs index c51ca27b8c..4d5111df08 100644 --- a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs +++ b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Microsoft.Extensions.Logging; using Umbraco.Core.Models; @@ -268,15 +268,15 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); Assert.AreEqual(expectedCulture, frequest.Culture.Name); - var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = finder.TryFindContent(frequest); Assert.IsTrue(result); @@ -316,14 +316,14 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); // find document - var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = finder.TryFindContent(frequest); // apply wildcard domain @@ -369,8 +369,8 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); @@ -378,7 +378,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(expectedCulture, frequest.Culture.Name); - var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = finder.TryFindContent(frequest); Assert.IsTrue(result); diff --git a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs index e08873ac04..31bbd06ade 100644 --- a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs +++ b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.Linq; using Moq; @@ -63,8 +63,10 @@ namespace Umbraco.Tests.Routing content.Path = "-1,1046"; var umbContext = GetUmbracoContext("http://localhost:8000"); - var publishedRouter = CreatePublishedRouter(Factory, - contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger()) })); + var publishedRouter = CreatePublishedRouter( + GetUmbracoContextAccessor(umbContext), + Factory, + contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbContext)) })); var urls = content.GetContentUrls(publishedRouter, umbContext, GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, @@ -88,7 +90,7 @@ namespace Umbraco.Tests.Routing content.Published = true; var umbContext = GetUmbracoContext("http://localhost:8000"); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbContext); + var umbracoContextAccessor = GetUmbracoContextAccessor(umbContext); var urlProvider = new DefaultUrlProvider( Microsoft.Extensions.Options.Options.Create(_requestHandlerSettings), LoggerFactory.CreateLogger(), @@ -102,8 +104,10 @@ namespace Umbraco.Tests.Routing Mock.Of() ); - var publishedRouter = CreatePublishedRouter(Factory, - contentFinders:new ContentFinderCollection(new[]{new ContentFinderByUrl(LoggerFactory.CreateLogger()) })); + var publishedRouter = CreatePublishedRouter( + umbracoContextAccessor, + Factory, + contentFinders:new ContentFinderCollection(new[]{new ContentFinderByUrl(LoggerFactory.CreateLogger(), umbracoContextAccessor) })); var urls = content.GetContentUrls(publishedRouter, umbContext, GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, @@ -134,7 +138,7 @@ namespace Umbraco.Tests.Routing child.Published = true; var umbContext = GetUmbracoContext("http://localhost:8000"); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbContext); + var umbracoContextAccessor = GetUmbracoContextAccessor(umbContext); var urlProvider = new DefaultUrlProvider( Microsoft.Extensions.Options.Options.Create(_requestHandlerSettings), LoggerFactory.CreateLogger(), @@ -147,8 +151,10 @@ namespace Umbraco.Tests.Routing Mock.Of() ); - var publishedRouter = CreatePublishedRouter(Factory, - contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger()) })); + var publishedRouter = CreatePublishedRouter( + umbracoContextAccessor, + Factory, + contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger(), umbracoContextAccessor) })); var urls = child.GetContentUrls(publishedRouter, umbContext, GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index a8d017e3cb..63e8180aff 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Web.Mvc; using System.Web.Routing; @@ -91,15 +91,15 @@ namespace Umbraco.Tests.Routing var routeData = new RouteData { Route = route }; var umbracoContext = GetUmbracoContext(url, template.Id, routeData); var httpContext = GetHttpContextFactory(url, routeData).HttpContext; - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.PublishedContent = umbracoContext.Content.GetById(1174); - frequest.TemplateModel = template; + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + frequest.SetPublishedContent(umbracoContext.Content.GetById(1174)); + frequest.SetTemplate(template); var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContextAccessor, Mock.Of>()), ShortStringHelper); - handler.GetHandlerForRoute(httpContext.Request.RequestContext, frequest); + handler.GetHandlerForRoute(httpContext.Request.RequestContext, frequest.Build()); Assert.AreEqual("RenderMvc", routeData.Values["controller"].ToString()); //the route action will still be the one we've asked for because our RenderActionInvoker is the thing that decides // if the action matches. @@ -129,10 +129,10 @@ namespace Umbraco.Tests.Routing var routeData = new RouteData() { Route = route }; var umbracoContext = GetUmbracoContext("~/dummy-page", template.Id, routeData, true); var httpContext = GetHttpContextFactory(url, routeData).HttpContext; - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.PublishedContent = umbracoContext.Content.GetById(1172); - frequest.TemplateModel = template; + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + frequest.SetPublishedContent(umbracoContext.Content.GetById(1172)); + frequest.SetTemplate(template); var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); var type = new AutoPublishedContentType(Guid.NewGuid(), 22, "CustomDocument", new PublishedPropertyType[] { }); @@ -149,7 +149,7 @@ namespace Umbraco.Tests.Routing Factory.GetRequiredService()); }), ShortStringHelper); - handler.GetHandlerForRoute(httpContext.Request.RequestContext, frequest); + handler.GetHandlerForRoute(httpContext.Request.RequestContext, frequest.Build()); Assert.AreEqual("CustomDocument", routeData.Values["controller"].ToString()); Assert.AreEqual( //global::umbraco.cms.helpers.Casing.SafeAlias(template.Alias), diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index 46d67eb9bd..cdc62b1a35 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -43,7 +43,7 @@ namespace Umbraco.Tests.Routing // get the nice URL for 100111 var umbracoContext = GetUmbracoContext(url, 9999, globalSettings: globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); + var umbracoContextAccessor = GetUmbracoContextAccessor(umbracoContext); var urlProvider = new DefaultUrlProvider( Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), LoggerFactory.CreateLogger(), @@ -59,14 +59,14 @@ namespace Umbraco.Tests.Routing Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]); // route a rogue URL - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); publishedRouter.FindDomain(frequest); - Assert.IsTrue(frequest.HasDomain); + Assert.IsTrue(frequest.HasDomain()); // check that it's been routed - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); Assert.AreEqual(100111, frequest.PublishedContent.Id); diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index 103d361fc5..e7ccc01acb 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading; using Microsoft.Extensions.DependencyInjection; @@ -93,15 +93,14 @@ namespace Umbraco.Tests.TestHelpers "; } - internal PublishedRouter CreatePublishedRouter(IServiceProvider container = null, ContentFinderCollection contentFinders = null) + internal PublishedRouter CreatePublishedRouter(IUmbracoContextAccessor umbracoContextAccessor, IServiceProvider container = null, ContentFinderCollection contentFinders = null) { var webRoutingSettings = new WebRoutingSettings(); - return CreatePublishedRouter(webRoutingSettings, container ?? Factory, contentFinders); + return CreatePublishedRouter(umbracoContextAccessor, webRoutingSettings, container ?? Factory, contentFinders); } - internal static PublishedRouter CreatePublishedRouter(WebRoutingSettings webRoutingSettings, IServiceProvider container = null, ContentFinderCollection contentFinders = null) - { - return new PublishedRouter( + internal static PublishedRouter CreatePublishedRouter(IUmbracoContextAccessor umbracoContextAccessor, WebRoutingSettings webRoutingSettings, IServiceProvider container = null, ContentFinderCollection contentFinders = null) + => new PublishedRouter( Microsoft.Extensions.Options.Options.Create(webRoutingSettings), contentFinders ?? new ContentFinderCollection(Enumerable.Empty()), new TestLastChanceFinder(), @@ -111,11 +110,11 @@ namespace Umbraco.Tests.TestHelpers Mock.Of(), Mock.Of(), container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), - container?.GetRequiredService()?? Current.Factory.GetRequiredService(), - container?.GetRequiredService()?? Current.Factory.GetRequiredService(), + container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), + container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), - container?.GetRequiredService() ?? Current.Factory.GetRequiredService() + container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), + umbracoContextAccessor ); - } } } diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestLastChanceFinder.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestLastChanceFinder.cs index 48517f85dd..dfbca2763c 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestLastChanceFinder.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestLastChanceFinder.cs @@ -1,12 +1,9 @@ -using Umbraco.Web.Routing; +using Umbraco.Web.Routing; namespace Umbraco.Tests.TestHelpers.Stubs { internal class TestLastChanceFinder : IContentLastChanceFinder { - public bool TryFindContent(IPublishedRequest frequest) - { - return false; - } + public bool TryFindContent(IPublishedRequestBuilder frequest) => false; } } diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 302e1198a8..b47da98709 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -350,6 +350,8 @@ namespace Umbraco.Tests.TestHelpers } } + protected IUmbracoContextAccessor GetUmbracoContextAccessor(IUmbracoContext ctx) => new TestUmbracoContextAccessor(ctx); + protected IUmbracoContext GetUmbracoContext(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, GlobalSettings globalSettings = null, IPublishedSnapshotService snapshotService = null) { // ensure we have a PublishedCachesService diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index 6afc75e931..3e5e799dba 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -151,13 +151,13 @@ namespace Umbraco.Tests.Web.Mvc var content = Mock.Of(publishedContent => publishedContent.Id == 12345); var webRoutingSettings = new WebRoutingSettings(); - var publishedRouter = BaseWebTest.CreatePublishedRouter(webRoutingSettings); - var frequest = publishedRouter.CreateRequest(umbracoContext, new Uri("http://localhost/test")); - frequest.PublishedContent = content; + var publishedRouter = BaseWebTest.CreatePublishedRouter(umbracoContextAccessor, webRoutingSettings); + var frequest = publishedRouter.CreateRequest(new Uri("http://localhost/test")); + frequest.SetPublishedContent(content); var routeDefinition = new RouteDefinition { - PublishedRequest = frequest + PublishedRequest = frequest.Build() }; var routeData = new RouteData(); diff --git a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs index 6cd514033f..654eb8f8d7 100644 --- a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs @@ -65,12 +65,13 @@ namespace Umbraco.Web.Common.Templates if (writer == null) throw new ArgumentNullException(nameof(writer)); var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + // instantiate a request and process // important to use CleanedUmbracoUrl - lowercase path-only version of the current URL, though this isn't going to matter // terribly much for this implementation since we are just creating a doc content request to modify it's properties manually. - var contentRequest = _publishedRouter.CreateRequest(umbracoContext); + var requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); - var doc = contentRequest.UmbracoContext.Content.GetById(pageId); + var doc = umbracoContext.Content.GetById(pageId); if (doc == null) { @@ -78,32 +79,37 @@ namespace Umbraco.Web.Common.Templates return; } - //in some cases the UmbracoContext will not have a PublishedRequest assigned to it if we are not in the - //execution of a front-end rendered page. In this case set the culture to the default. - //set the culture to the same as is currently rendering + // in some cases the UmbracoContext will not have a PublishedRequest assigned to it if we are not in the + // execution of a front-end rendered page. In this case set the culture to the default. + // set the culture to the same as is currently rendering if (umbracoContext.PublishedRequest == null) { var defaultLanguage = _languageService.GetAllLanguages().FirstOrDefault(); - contentRequest.Culture = defaultLanguage == null + + requestBuilder.SetCulture(defaultLanguage == null ? CultureInfo.CurrentUICulture - : defaultLanguage.CultureInfo; + : defaultLanguage.CultureInfo); } else { - contentRequest.Culture = umbracoContext.PublishedRequest.Culture; + requestBuilder.SetCulture(umbracoContext.PublishedRequest.Culture); } - //set the doc that was found by id - contentRequest.PublishedContent = doc; - //set the template, either based on the AltTemplate found or the standard template of the doc + // set the doc that was found by id + requestBuilder.SetPublishedContent(doc); + + // set the template, either based on the AltTemplate found or the standard template of the doc var templateId = _webRoutingSettings.DisableAlternativeTemplates || !altTemplateId.HasValue ? doc.TemplateId : altTemplateId.Value; - if (templateId.HasValue) - contentRequest.TemplateModel = _fileService.GetTemplate(templateId.Value); - //if there is not template then exit - if (contentRequest.HasTemplate == false) + if (templateId.HasValue) + { + requestBuilder.SetTemplate(_fileService.GetTemplate(templateId.Value)); + } + + // if there is not template then exit + if (requestBuilder.HasTemplate() == false) { if (altTemplateId.HasValue == false) { @@ -113,24 +119,27 @@ namespace Umbraco.Web.Common.Templates { writer.Write("", altTemplateId); } + return; } - //First, save all of the items locally that we know are used in the chain of execution, we'll need to restore these - //after this page has rendered. - SaveExistingItems(out var oldPublishedRequest); + // First, save all of the items locally that we know are used in the chain of execution, we'll need to restore these + // after this page has rendered. + SaveExistingItems(out IPublishedRequest oldPublishedRequest); + + IPublishedRequest contentRequest = requestBuilder.Build(); try { - //set the new items on context objects for this templates execution + // set the new items on context objects for this templates execution SetNewItemsOnContextObjects(contentRequest); - //Render the template + // Render the template ExecuteTemplateRendering(writer, contentRequest); } finally { - //restore items on context objects to continuing rendering the parent template + // restore items on context objects to continuing rendering the parent template RestoreItems(oldPublishedRequest); } @@ -140,11 +149,11 @@ namespace Umbraco.Web.Common.Templates { var httpContext = _httpContextAccessor.GetRequiredHttpContext(); - var viewResult = _viewEngine.GetView(null, $"~/Views/{request.TemplateAlias}.cshtml", false); + var viewResult = _viewEngine.GetView(null, $"~/Views/{request.GetTemplateAlias()}.cshtml", false); if (viewResult.Success == false) { - throw new InvalidOperationException($"A view with the name {request.TemplateAlias} could not be found"); + throw new InvalidOperationException($"A view with the name {request.GetTemplateAlias()} could not be found"); } var modelMetadataProvider = httpContext.RequestServices.GetRequiredService(); @@ -175,7 +184,7 @@ namespace Umbraco.Web.Common.Templates private void SetNewItemsOnContextObjects(IPublishedRequest request) { - //now, set the new ones for this page execution + // now, set the new ones for this page execution _umbracoContextAccessor.UmbracoContext.PublishedRequest = request; } @@ -184,8 +193,8 @@ namespace Umbraco.Web.Common.Templates /// private void SaveExistingItems(out IPublishedRequest oldPublishedRequest) { - //Many objects require that these legacy items are in the http context items... before we render this template we need to first - //save the values in them so that we can re-set them after we render so the rest of the execution works as per normal + // Many objects require that these legacy items are in the http context items... before we render this template we need to first + // save the values in them so that we can re-set them after we render so the rest of the execution works as per normal oldPublishedRequest = _umbracoContextAccessor.UmbracoContext.PublishedRequest; } diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs index 2d22bc5a90..76a5823801 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs @@ -62,105 +62,83 @@ namespace Umbraco.Web // 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get // the current domain during application startup. // see: http://issues.umbraco.org/issue/U4-1890 - // OriginalRequestUrl = _requestAccessor.GetRequestUrl() ?? new Uri("http://localhost"); CleanedUmbracoUrl = uriUtility.UriToUmbraco(OriginalRequestUrl); } - /// - /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this - /// object is instantiated which in the web site is created during the BeginRequest phase. - /// We can then determine complete rendering time from that. - /// + /// public DateTime ObjectCreated { get; } /// - /// This is used internally for debugging and also used to define anything required to distinguish this request from another. + /// Gets the context Id /// - public Guid UmbracoRequestId { get; } + /// + /// Used internally for debugging and also used to define anything required to distinguish this request from another. + /// + internal Guid UmbracoRequestId { get; } - /// - /// Gets the uri that is handled by ASP.NET after server-side rewriting took place. - /// + /// public Uri OriginalRequestUrl { get; } - /// - /// Gets the cleaned up url that is handled by Umbraco. - /// - /// That is, lowercase, no trailing slash after path, no .aspx... + /// public Uri CleanedUmbracoUrl { get; } - /// - /// Gets the published snapshot. - /// + /// public IPublishedSnapshot PublishedSnapshot => _publishedSnapshot.Value; - /// - /// Gets the published content cache. - /// + /// public IPublishedContentCache Content => PublishedSnapshot.Content; - /// - /// Gets the published media cache. - /// + /// public IPublishedMediaCache Media => PublishedSnapshot.Media; - /// - /// Gets the domains cache. - /// + /// public IDomainCache Domains => PublishedSnapshot.Domains; - /// - /// Gets/sets the PublishedRequest object - /// + /// public IPublishedRequest PublishedRequest { get; set; } - /// - /// Gets the variation context accessor. - /// + /// public IVariationContextAccessor VariationContextAccessor { get; } - /// - /// Gets a value indicating whether the request has debugging enabled - /// - /// true if this instance is debug; otherwise, false. - public bool IsDebug - { - get - { - //NOTE: the request can be null during app startup! - return _hostingEnvironment.IsDebugMode + /// + public bool IsDebug => // NOTE: the request can be null during app startup! + _hostingEnvironment.IsDebugMode && (string.IsNullOrEmpty(_requestAccessor.GetRequestValue("umbdebugshowtrace")) == false || string.IsNullOrEmpty(_requestAccessor.GetRequestValue("umbdebug")) == false || string.IsNullOrEmpty(_cookieManager.GetCookieValue("UMB-DEBUG")) == false); - } - } - /// - /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) - /// + /// public bool InPreviewMode { get { - if (_previewing.HasValue == false) DetectPreviewMode(); + if (_previewing.HasValue == false) + { + DetectPreviewMode(); + } + return _previewing ?? false; } private set => _previewing = value; } - public string PreviewToken + internal string PreviewToken { get { - if (_previewing.HasValue == false) DetectPreviewMode(); + if (_previewing.HasValue == false) + { + DetectPreviewMode(); + } + return _previewToken; } } private void DetectPreviewMode() { - var requestUrl = _requestAccessor.GetRequestUrl(); + Uri requestUrl = _requestAccessor.GetRequestUrl(); if (requestUrl != null && requestUrl.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) == false && _backofficeSecurity.CurrentUser != null) @@ -172,15 +150,17 @@ namespace Umbraco.Web _previewing = _previewToken.IsNullOrWhiteSpace() == false; } - // say we render a macro or RTE in a give 'preview' mode that might not be the 'current' one, - // then due to the way it all works at the moment, the 'current' published snapshot need to be in the proper - // default 'preview' mode - somehow we have to force it. and that could be recursive. + /// public IDisposable ForcedPreview(bool preview) { + // say we render a macro or RTE in a give 'preview' mode that might not be the 'current' one, + // then due to the way it all works at the moment, the 'current' published snapshot need to be in the proper + // default 'preview' mode - somehow we have to force it. and that could be recursive. InPreviewMode = preview; return PublishedSnapshot.ForcedPreview(preview, orig => InPreviewMode = orig); } + /// protected override void DisposeResources() { // DisposableObject ensures that this runs only once @@ -189,7 +169,9 @@ namespace Umbraco.Web // (but don't create caches just to dispose them) // context is not multi-threaded if (_publishedSnapshot.IsValueCreated) + { _publishedSnapshot.Value.Dispose(); + } } } } diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 731c0320d6..7b0fed7991 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -16,13 +14,10 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; -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; @@ -143,12 +138,12 @@ namespace Umbraco.Web.Website.Routing // check that a template is defined), if it doesn't and there is a hijacked route it will just route // to the index Action - if (request.HasTemplate) + if (request.HasTemplate()) { // 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. - customActionName = request.TemplateAlias.Split('.')[0].ToSafeAlias(_shortStringHelper); + customActionName = request.GetTemplateAlias().Split('.')[0].ToSafeAlias(_shortStringHelper); } // creates the default route definition which maps to the 'UmbracoController' controller @@ -229,12 +224,12 @@ 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); + IPublishedRequestBuilder requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); - // 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; + // TODO: This is ugly with the re-assignment to umbraco context + publishedRequest = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder); + + return publishedRequest.Success() && publishedRequest.HasPublishedContent(); // // HandleHttpResponseStatus returns a value indicating that the request should // // not be processed any further, eg because it has been redirect. then, exit. diff --git a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs index 3ca7e861ac..8d044ea8dd 100644 --- a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs +++ b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs @@ -22,107 +22,109 @@ namespace Umbraco.Web.Mvc /// public class EnsurePublishedContentRequestAttribute : ActionFilterAttribute { - private readonly string _dataTokenName; - private IUmbracoContextAccessor _umbracoContextAccessor; - private readonly int? _contentId; - private IPublishedContentQuery _publishedContentQuery; + // TODO: Need to port this to netcore and figure out if its needed or how this should work (part of a different task) - /// - /// Constructor - can be used for testing - /// - /// - /// - public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, int contentId) - { - _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); - _contentId = contentId; - } + //private readonly string _dataTokenName; + //private IUmbracoContextAccessor _umbracoContextAccessor; + //private readonly int? _contentId; + //private IPublishedContentQuery _publishedContentQuery; - /// - /// A constructor used to set an explicit content Id to the PublishedRequest that will be created - /// - /// - public EnsurePublishedContentRequestAttribute(int contentId) - { - _contentId = contentId; - } + ///// + ///// Constructor - can be used for testing + ///// + ///// + ///// + //public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, int contentId) + //{ + // _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + // _contentId = contentId; + //} - /// - /// A constructor used to set the data token key name that contains a reference to a PublishedContent instance - /// - /// - public EnsurePublishedContentRequestAttribute(string dataTokenName) - { - _dataTokenName = dataTokenName; - } + ///// + ///// A constructor used to set an explicit content Id to the PublishedRequest that will be created + ///// + ///// + //public EnsurePublishedContentRequestAttribute(int contentId) + //{ + // _contentId = contentId; + //} - /// - /// Constructor - can be used for testing - /// - /// - /// - public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, string dataTokenName) - { - _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); - _dataTokenName = dataTokenName; - } + ///// + ///// A constructor used to set the data token key name that contains a reference to a PublishedContent instance + ///// + ///// + //public EnsurePublishedContentRequestAttribute(string dataTokenName) + //{ + // _dataTokenName = dataTokenName; + //} - /// - /// Exposes the UmbracoContext - /// - protected IUmbracoContext UmbracoContext => _umbracoContextAccessor?.UmbracoContext ?? Current.UmbracoContext; + ///// + ///// Constructor - can be used for testing + ///// + ///// + ///// + //public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, string dataTokenName) + //{ + // _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + // _dataTokenName = dataTokenName; + //} - // TODO: try lazy property injection? - private IPublishedRouter PublishedRouter => Current.Factory.GetRequiredService(); + ///// + ///// Exposes the UmbracoContext + ///// + //protected IUmbracoContext UmbracoContext => _umbracoContextAccessor?.UmbracoContext ?? Current.UmbracoContext; - private IPublishedContentQuery PublishedContentQuery => _publishedContentQuery ?? (_publishedContentQuery = Current.Factory.GetRequiredService()); + //// TODO: try lazy property injection? + //private IPublishedRouter PublishedRouter => Current.Factory.GetRequiredService(); - public override void OnActionExecuted(ActionExecutedContext filterContext) - { - base.OnActionExecuted(filterContext); + //private IPublishedContentQuery PublishedContentQuery => _publishedContentQuery ?? (_publishedContentQuery = Current.Factory.GetRequiredService()); - // First we need to check if the published content request has been set, if it has we're going to ignore this and not actually do anything - if (Current.UmbracoContext.PublishedRequest != null) - { - return; - } + //public override void OnActionExecuted(ActionExecutedContext filterContext) + //{ + // base.OnActionExecuted(filterContext); - Current.UmbracoContext.PublishedRequest = PublishedRouter.CreateRequest(Current.UmbracoContext); - ConfigurePublishedContentRequest(Current.UmbracoContext.PublishedRequest, filterContext); - } + // // First we need to check if the published content request has been set, if it has we're going to ignore this and not actually do anything + // if (Current.UmbracoContext.PublishedRequest != null) + // { + // return; + // } - /// - /// This assigns the published content to the request, developers can override this to specify - /// any other custom attributes required. - /// - /// - /// - protected virtual void ConfigurePublishedContentRequest(IPublishedRequest request, ActionExecutedContext filterContext) - { - if (_contentId.HasValue) - { - var content = PublishedContentQuery.Content(_contentId.Value); - if (content == null) - { - throw new InvalidOperationException("Could not resolve content with id " + _contentId); - } - request.PublishedContent = content; - } - else if (_dataTokenName.IsNullOrWhiteSpace() == false) - { - var result = filterContext.RouteData.DataTokens[_dataTokenName]; - if (result == null) - { - throw new InvalidOperationException("No data token could be found with the name " + _dataTokenName); - } - if (result is IPublishedContent == false) - { - throw new InvalidOperationException("The data token resolved with name " + _dataTokenName + " was not an instance of " + typeof(IPublishedContent)); - } - request.PublishedContent = (IPublishedContent)result; - } + // Current.UmbracoContext.PublishedRequest = PublishedRouter.CreateRequest(Current.UmbracoContext); + // ConfigurePublishedContentRequest(Current.UmbracoContext.PublishedRequest, filterContext); + //} - PublishedRouter.PrepareRequest(request); - } + ///// + ///// This assigns the published content to the request, developers can override this to specify + ///// any other custom attributes required. + ///// + ///// + ///// + //protected virtual void ConfigurePublishedContentRequest(IPublishedRequest request, ActionExecutedContext filterContext) + //{ + // if (_contentId.HasValue) + // { + // var content = PublishedContentQuery.Content(_contentId.Value); + // if (content == null) + // { + // throw new InvalidOperationException("Could not resolve content with id " + _contentId); + // } + // request.PublishedContent = content; + // } + // else if (_dataTokenName.IsNullOrWhiteSpace() == false) + // { + // var result = filterContext.RouteData.DataTokens[_dataTokenName]; + // if (result == null) + // { + // throw new InvalidOperationException("No data token could be found with the name " + _dataTokenName); + // } + // if (result is IPublishedContent == false) + // { + // throw new InvalidOperationException("The data token resolved with name " + _dataTokenName + " was not an instance of " + typeof(IPublishedContent)); + // } + // request.PublishedContent = (IPublishedContent)result; + // } + + // PublishedRouter.PrepareRequest(request); + //} } } diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 3ca0931585..d690fb579b 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -219,7 +219,7 @@ namespace Umbraco.Web.Mvc var defaultControllerType = Current.DefaultRenderMvcControllerType; var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType); - //creates the default route definition which maps to the 'UmbracoController' controller + // creates the default route definition which maps to the 'UmbracoController' controller var def = new RouteDefinition { ControllerName = defaultControllerName, @@ -229,28 +229,28 @@ namespace Umbraco.Web.Mvc HasHijackedRoute = false }; - //check that a template is defined), if it doesn't and there is a hijacked route it will just route + // check that a template is defined), if it doesn't and there is a hijacked route it will just route // to the index Action - if (request.HasTemplate) + if (request.HasTemplate()) { - //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 + // 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); + var templateName = request.GetTemplateAlias().Split('.')[0].ToSafeAlias(_shortStringHelper); def.ActionName = templateName; } - //check if there's a custom controller assigned, base on the document type alias. + // check if there's a custom controller assigned, base on the document type alias. var controllerType = _controllerFactory.GetControllerTypeInternal(requestContext, request.PublishedContent.ContentType.Alias); - //check if that controller exists + // check if that controller exists if (controllerType != null) { - //ensure the controller is of type IRenderMvcController and ControllerBase + // ensure the controller is of type IRenderMvcController and ControllerBase if (TypeHelper.IsTypeAssignableFrom(controllerType) && TypeHelper.IsTypeAssignableFrom(controllerType)) { - //set the controller and name to the custom one + // set the controller and name to the custom one def.ControllerType = controllerType; def.ControllerName = ControllerExtensions.GetControllerName(controllerType); if (def.ControllerName != defaultControllerName) @@ -266,12 +266,12 @@ namespace Umbraco.Web.Mvc typeof(IRenderController).FullName, typeof(ControllerBase).FullName); - //we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults + // we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults // that have already been set above. } } - //store the route definition + // store the route definition requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; return def; @@ -284,15 +284,19 @@ namespace Umbraco.Web.Mvc // missing template, so we're in a 404 here // so the content, if any, is a custom 404 page of some sort - if (request.HasPublishedContent == false) + if (request.HasPublishedContent() == false) + { // means the builder could not find a proper document to handle 404 return new PublishedContentNotFoundHandler(); + } - if (request.HasTemplate == false) + if (request.HasTemplate() == false) + { // means the engine could find a proper document, but the document has no template // at that point there isn't much we can do and there is no point returning // to Mvc since Mvc can't do much return new PublishedContentNotFoundHandler("In addition, no template exists to render the custom 404."); + } return null; } @@ -300,8 +304,6 @@ namespace Umbraco.Web.Mvc /// /// this will determine the controller and set the values in the route data /// - /// - /// internal IHttpHandler GetHandlerForRoute(RequestContext requestContext, IPublishedRequest request) { if (requestContext == null) throw new ArgumentNullException(nameof(requestContext)); @@ -309,7 +311,7 @@ namespace Umbraco.Web.Mvc var routeDef = GetUmbracoRouteDefinition(requestContext, request); - //Need to check for a special case if there is form data being posted back to an Umbraco URL + // Need to check for a special case if there is form data being posted back to an Umbraco URL var postedInfo = GetFormInfo(requestContext); if (postedInfo != null) { @@ -321,10 +323,11 @@ namespace Umbraco.Web.Mvc // if this is the case we want to return a blank page, but we'll leave that up to the NoTemplateHandler. // We also check if templates have been disabled since if they are then we're allowed to render even though there's no template, // for example for json rendering in headless. - if ((request.HasTemplate == false && Features.Disabled.DisableTemplates == false) - && routeDef.HasHijackedRoute == false) + if (request.HasTemplate() == false && Features.Disabled.DisableTemplates == false && routeDef.HasHijackedRoute == false) { - request.UpdateToNotFound(); // request will go 404 + + // TODO: Handle this differently + // request.UpdateToNotFound(); // request will go 404 // HandleHttpResponseStatus returns a value indicating that the request should // not be processed any further, eg because it has been redirect. then, exit. diff --git a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs index dc922d9fd2..9d5449f340 100644 --- a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs @@ -7,11 +7,14 @@ using Umbraco.Web.Models; using Umbraco.Web.Routing; using Umbraco.Core; using Umbraco.Web.Composing; +using System; namespace Umbraco.Web.Mvc { public abstract class UmbracoVirtualNodeRouteHandler : IRouteHandler { + // TODO: Need to port this to netcore and figure out if its needed or how this should work (part of a different task) + // TODO: try lazy property injection? private IPublishedRouter PublishedRouter => Current.Factory.GetRequiredService(); @@ -46,54 +49,56 @@ namespace Umbraco.Web.Mvc public IHttpHandler GetHttpHandler(RequestContext requestContext) { - var umbracoContext = GetUmbracoContext(requestContext); + throw new NotImplementedException(); - var found = FindContent(requestContext, umbracoContext); - if (found == null) return new NotFoundHandler(); + // var umbracoContext = GetUmbracoContext(requestContext); - var request = PublishedRouter.CreateRequest(umbracoContext); - request.PublishedContent = found; - umbracoContext.PublishedRequest = request; + // var found = FindContent(requestContext, umbracoContext); + // if (found == null) return new NotFoundHandler(); - // allows inheritors to change the published content request - PreparePublishedContentRequest(umbracoContext.PublishedRequest); + // var request = PublishedRouter.CreateRequest(umbracoContext); + // request.PublishedContent = found; + // umbracoContext.PublishedRequest = request; - // create the render model - var renderModel = new ContentModel(umbracoContext.PublishedRequest.PublishedContent); + // // allows inheritors to change the published content request + // PreparePublishedContentRequest(umbracoContext.PublishedRequest); - // 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); + // // create the render model + // var renderModel = new ContentModel(umbracoContext.PublishedRequest.PublishedContent); - //// this is used just for a flag that this is an umbraco custom route - //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.CustomRouteDataToken, true); + // // 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); - // Here we need to detect if a SurfaceController has posted - var formInfo = RenderRouteHandler.GetFormInfo(requestContext); - if (formInfo != null) - { - var def = new RouteDefinition - { - ActionName = requestContext.RouteData.GetRequiredString("action"), - ControllerName = requestContext.RouteData.GetRequiredString("controller"), - PublishedRequest = umbracoContext.PublishedRequest - }; + // //// this is used just for a flag that this is an umbraco custom route + // //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.CustomRouteDataToken, true); - // set the special data token to the current route definition - requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; + // // Here we need to detect if a SurfaceController has posted + // var formInfo = RenderRouteHandler.GetFormInfo(requestContext); + // if (formInfo != null) + // { + // var def = new RouteDefinition + // { + // ActionName = requestContext.RouteData.GetRequiredString("action"), + // ControllerName = requestContext.RouteData.GetRequiredString("controller"), + // PublishedRequest = umbracoContext.PublishedRequest + // }; - return RenderRouteHandler.HandlePostedValues(requestContext, formInfo); - } + // // set the special data token to the current route definition + // requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; - return new MvcHandler(requestContext); + // return RenderRouteHandler.HandlePostedValues(requestContext, formInfo); + // } + + // return new MvcHandler(requestContext); } protected abstract IPublishedContent FindContent(RequestContext requestContext, IUmbracoContext umbracoContext); - protected virtual void PreparePublishedContentRequest(IPublishedRequest request) - { - PublishedRouter.PrepareRequest(request); - } + //protected virtual void PreparePublishedContentRequest(IPublishedRequest request) + //{ + // PublishedRouter.PrepareRequest(request); + //} } } diff --git a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs index 0045cf33dc..28bae7bced 100644 --- a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs +++ b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs @@ -1,4 +1,4 @@ -using System.Web; +using System.Web; using Umbraco.Web.Composing; namespace Umbraco.Web.Routing @@ -31,9 +31,9 @@ namespace Umbraco.Web.Routing var frequest = Current.UmbracoContext.PublishedRequest; var reason = "Cannot render the page at URL '{0}'."; - if (frequest.HasPublishedContent == false) + if (frequest.HasPublishedContent() == false) reason = "No umbraco document matches the URL '{0}'."; - else if (frequest.HasTemplate == false) + else if (frequest.HasTemplate() == false) reason = "No template exists to render the document at URL '{0}'."; response.Write("

Page not found

"); diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 5c7468ce95..d0c2fd5de7 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -135,16 +135,15 @@ namespace Umbraco.Web // instantiate, prepare and process the published content request // important to use CleanedUmbracoUrl - lowercase path-only version of the current URL - var request = _publishedRouter.CreateRequest(umbracoContext); - umbracoContext.PublishedRequest = request; - _publishedRouter.PrepareRequest(request); + var requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder); // HandleHttpResponseStatus returns a value indicating that the request should // not be processed any further, eg because it has been redirect. then, exit. if (UmbracoModule.HandleHttpResponseStatus(httpContext, request, _logger)) return; - if (request.HasPublishedContent == false) + if (request.HasPublishedContent() == false) httpContext.RemapHandler(new PublishedContentNotFoundHandler()); else RewriteToUmbracoHandler(httpContext, request); diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 932861a89c..b2b3d4c5a8 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web; using Microsoft.Extensions.Logging; using Umbraco.Core; @@ -51,8 +51,8 @@ namespace Umbraco.Web var response = context.Response; logger.LogDebug("Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}", - pcr.IsRedirect ? (pcr.IsRedirectPermanent ? "permanent" : "redirect") : "none", - pcr.Is404 ? "true" : "false", + pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none", + pcr.Is404() ? "true" : "false", pcr.ResponseStatusCode); if(pcr.CacheabilityNoCache) @@ -64,15 +64,15 @@ namespace Umbraco.Web foreach (var header in pcr.Headers) response.AppendHeader(header.Key, header.Value); - if (pcr.IsRedirect) + if (pcr.IsRedirect()) { - if (pcr.IsRedirectPermanent) + if (pcr.IsRedirectPermanent()) response.RedirectPermanent(pcr.RedirectUrl, false); // do not end response else response.Redirect(pcr.RedirectUrl, false); // do not end response end = true; } - else if (pcr.Is404) + else if (pcr.Is404()) { response.StatusCode = 404; response.TrySkipIisCustomErrors = /*Current.Configs.WebRouting().TrySkipIisCustomErrors; TODO introduce from config*/ false; @@ -90,7 +90,7 @@ namespace Umbraco.Web //if (pcr.IsRedirect) // response.End(); // end response -- kills the thread and does not return! - if (pcr.IsRedirect == false) return end; + if (pcr.IsRedirect() == false) return end; response.Flush(); // bypass everything and directly execute EndRequest event -- but returns