diff --git a/src/Umbraco.Tests/Routing/FinderByNiceUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/FinderByNiceUrlWithDomainsTests.cs
index ea20439455..6ea32d75f1 100644
--- a/src/Umbraco.Tests/Routing/FinderByNiceUrlWithDomainsTests.cs
+++ b/src/Umbraco.Tests/Routing/FinderByNiceUrlWithDomainsTests.cs
@@ -158,16 +158,15 @@ namespace Umbraco.Tests.Routing
var routingContext = GetRoutingContext(url);
var uri = routingContext.UmbracoContext.CleanedUmbracoUrl; //very important to use the cleaned up umbraco url
- var docreq = new PublishedContentRequest(uri, routingContext);
+ var pcr = new PublishedContentRequest(uri, routingContext);
// must lookup domain else lookup by url fails
- var builder = new PublishedContentRequestBuilder(docreq);
- builder.LookupDomain();
+ pcr.Engine.FindDomain();
var lookup = new FinderByNiceUrl();
- var result = lookup.TryFindDocument(docreq);
+ var result = lookup.TryFindDocument(pcr);
Assert.IsTrue(result);
- Assert.AreEqual(expectedId, docreq.PublishedContentId);
+ Assert.AreEqual(expectedId, pcr.PublishedContentId);
}
[TestCase("http://domain1.com/", 1001, "en-US")]
@@ -197,17 +196,16 @@ namespace Umbraco.Tests.Routing
var routingContext = GetRoutingContext(url);
var uri = routingContext.UmbracoContext.CleanedUmbracoUrl; //very important to use the cleaned up umbraco url
- var docreq = new PublishedContentRequest(uri, routingContext);
+ var pcr = new PublishedContentRequest(uri, routingContext);
// must lookup domain else lookup by url fails
- var builder = new PublishedContentRequestBuilder(docreq);
- builder.LookupDomain();
- Assert.AreEqual(expectedCulture, docreq.Culture.Name);
+ pcr.Engine.FindDomain();
+ Assert.AreEqual(expectedCulture, pcr.Culture.Name);
var lookup = new FinderByNiceUrl();
- var result = lookup.TryFindDocument(docreq);
+ var result = lookup.TryFindDocument(pcr);
Assert.IsTrue(result);
- Assert.AreEqual(expectedId, docreq.PublishedContentId);
+ Assert.AreEqual(expectedId, pcr.PublishedContentId);
}
}
}
diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs
index f8131ec656..f6414ee62b 100644
--- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs
+++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs
@@ -43,16 +43,15 @@ namespace Umbraco.Tests.Routing
// route a rogue url
url = "http://domain1.com/1001-1/1001-1-1";
var uri = routingContext.UmbracoContext.CleanedUmbracoUrl; //very important to use the cleaned up umbraco url
- var docreq = new PublishedContentRequest(uri, routingContext);
- var builder = new PublishedContentRequestBuilder(docreq);
- builder.LookupDomain();
- Assert.IsTrue(docreq.HasDomain);
+ var pcr = new PublishedContentRequest(uri, routingContext);
+ pcr.Engine.FindDomain();
+ Assert.IsTrue(pcr.HasDomain);
// check that it's been routed
var lookup = new FinderByNiceUrl();
- var result = lookup.TryFindDocument(docreq);
+ var result = lookup.TryFindDocument(pcr);
Assert.IsTrue(result);
- Assert.AreEqual(100111, docreq.PublishedContentId);
+ Assert.AreEqual(100111, pcr.PublishedContentId);
// has the cache been polluted?
cachedRoutes = ((DefaultRoutesCache)routingContext.UmbracoContext.RoutesCache).GetCachedRoutes();
diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs
index fab342df7b..f586477154 100644
--- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs
+++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs
@@ -283,6 +283,25 @@ namespace Umbraco.Web.Mvc
return def;
}
+ internal IHttpHandler GetHandlerOnMissingTemplate(PublishedContentRequest pcr)
+ {
+ if (!pcr.HasPublishedContent)
+ // means the builder could not find a proper document to handle 404
+ return new PublishedContentNotFoundHandler();
+
+ if (!pcr.HasTemplate)
+ // 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.");
+
+ if (pcr.RenderingEngine != RenderingEngine.Mvc)
+ // back to webforms
+ return (global::umbraco.UmbracoDefault)System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath("~/default.aspx", typeof(global::umbraco.UmbracoDefault));
+
+ return null;
+ }
+
///
/// this will determine the controller and set the values in the route data
///
@@ -303,13 +322,15 @@ namespace Umbraco.Web.Mvc
//we want to return a blank page, but we'll leave that up to the NoTemplateHandler.
if (!publishedContentRequest.HasTemplate && !routeDef.HasHijackedRoute)
{
- var handler = publishedContentRequest.ProcessNoTemplateInMvc(requestContext.HttpContext);
- //though this code should never execute if the ProcessNoTemplateInMvc method redirects, it seems that we should check it
- //and return null, this could be required for unit testing as well
+ publishedContentRequest.UpdateOnMissingTemplate();
if (publishedContentRequest.IsRedirect)
{
+ requestContext.HttpContext.Response.Redirect(publishedContentRequest.RedirectUrl, true);
return null;
}
+ if (publishedContentRequest.Is404)
+ requestContext.HttpContext.Response.StatusCode = 404;
+ var handler = GetHandlerOnMissingTemplate(publishedContentRequest);
// if it's not null it can be either the PublishedContentNotFoundHandler (no document was
// found to handle 404, or document with no template was found) or the WebForms handler
diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs
index 8f6795a8d5..560753648a 100644
--- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs
+++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs
@@ -29,153 +29,17 @@ namespace Umbraco.Web.Routing
internal class PublishedContentRequest
{
///
- /// Assigns the request to the http context and proceeds to process the request. If everything is successful, invoke the callback.
+ /// Triggers once the published content request has been prepared, but before it is processed.
///
- ///
- ///
- ///
- internal void ProcessRequest(HttpContextBase httpContext, UmbracoContext umbracoContext, Action onSuccess)
- {
- if (umbracoContext == null)
- throw new NullReferenceException("The UmbracoContext.Current is null, ProcessRequest cannot proceed unless there is a current UmbracoContext");
- if (umbracoContext.RoutingContext == null)
- throw new NullReferenceException("The UmbracoContext.RoutingContext has not been assigned, ProcessRequest cannot proceed unless there is a RoutingContext assigned to the UmbracoContext");
+ /// 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;
- //assign back since this is a front-end request
- umbracoContext.PublishedContentRequest = this;
-
- // 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.
-
- //find domain
- _builder.LookupDomain();
- // redirect if it has been flagged
- if (this.IsRedirect)
- httpContext.Response.Redirect(this.RedirectUrl, true);
- //set the culture on the thread - once, so it's set when running document lookups
- Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = this.Culture;
- //find the document, found will be true if the doc request has found BOTH a node and a template
- // though currently we don't use this value.
- var found = _builder.LookupDocument();
- //set the culture on the thread -- again, 'cos it might have changed due to a wildcard domain
- Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = this.Culture;
- //this could be called in the LookupDocument method, but I've just put it here for clarity.
- _builder.DetermineRenderingEngine();
-
- //TODO: here we should launch an event so that people can modify the doc request to do whatever they want.
-
- // redirect if it has been flagged
- if (this.IsRedirect)
- httpContext.Response.Redirect(this.RedirectUrl, true);
-
- // handle 404
- if (this.Is404)
- {
- httpContext.Response.StatusCode = 404;
-
- if (!this.HasPublishedContent)
- {
- httpContext.RemapHandler(new PublishedContentNotFoundHandler());
- return;
- }
-
- // else we have a document to render
- // not having a template is ok here, MVC will take care of it
- }
-
- // just be safe - should never ever happen
- if (!this.HasPublishedContent)
- throw new Exception("No document to render.");
-
- // trigger PublishedContentRequest.Rendering event?
- // with complete access to the content request?
-
- // render even though we might have no template
- // to give MVC a chance to hijack routes
- // pass off to our handlers (mvc or webforms)
-
- // assign the legacy page back to the docrequest
- // handlers like default.aspx will want it and most macros currently need it
- this.UmbracoPage = new page(this);
-
- // these two are used by many legacy objects
- httpContext.Items["pageID"] = this.PublishedContentId;
- httpContext.Items["pageElements"] = this.UmbracoPage.Elements;
-
- if (onSuccess != null)
- onSuccess(this);
- }
-
- ///
- /// After execution is handed off to MVC, we can finally check if the request has: No Template assigned and also the
- /// route is not hijacked. When this occurs, we need to send the routing back through the builder to check for
- /// not found handlers.
- ///
- ///
- ///
- internal IHttpHandler ProcessNoTemplateInMvc(HttpContextBase httpContext)
- {
- var content = this.PublishedContent;
- this.PublishedContent = null;
-
- _builder.LookupDocument2();
- _builder.DetermineRenderingEngine();
-
- // redirect if it has been flagged
- if (this.IsRedirect)
- {
- httpContext.Response.Redirect(this.RedirectUrl, true);
- }
-
-
- // here .Is404 _has_ to be true
- httpContext.Response.StatusCode = 404;
-
- if (!this.HasPublishedContent)
- {
- // means the builder could not find a proper document to handle 404
- // restore the saved content so we know it exists
- this.PublishedContent = content;
- return new PublishedContentNotFoundHandler();
- }
-
- if (!this.HasTemplate)
- {
- // means the builder 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.");
- }
-
- // render even though we might have no template
- // to give MVC a chance to hijack routes
- // pass off to our handlers (mvc or webforms)
-
- // assign the legacy page back to the docrequest
- // handlers like default.aspx will want it and most macros currently need it
- this.UmbracoPage = new page(this);
-
- // these two are used by many legacy objects
- httpContext.Items["pageID"] = this.PublishedContentId;
- httpContext.Items["pageElements"] = this.UmbracoPage.Elements;
-
- switch (this.RenderingEngine)
- {
- case Core.RenderingEngine.Mvc:
- return null;
- case Core.RenderingEngine.WebForms:
- default:
- return (global::umbraco.UmbracoDefault)System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath("~/default.aspx", typeof(global::umbraco.UmbracoDefault));
- }
- }
-
- private PublishedContentRequestBuilder _builder;
+ // the engine that does all the processing
+ // because in order to keep things clean and separated,
+ // the content request is just a data holder
+ private PublishedContentRequestEngine _engine;
///
/// Initializes a new instance of the class with a specific Uri and routing context.
@@ -183,18 +47,47 @@ namespace Umbraco.Web.Routing
/// The request Uri.
/// A routing context.
public PublishedContentRequest(Uri uri, RoutingContext routingContext)
- {
+ {
if (uri == null) throw new ArgumentNullException("uri");
if (routingContext == null) throw new ArgumentNullException("routingContext");
this.Uri = uri;
this.RoutingContext = routingContext;
- _builder = new PublishedContentRequestBuilder(this);
-
- // default is Mvc
- this.RenderingEngine = RenderingEngine.Mvc;
- }
+ _engine = new PublishedContentRequestEngine(this);
+
+ this.RenderingEngine = RenderingEngine.Mvc; // default
+ }
+
+ ///
+ /// Gets the engine associated to the request.
+ ///
+ internal PublishedContentRequestEngine Engine { get { return _engine; } }
+
+ ///
+ /// Prepares the request.
+ ///
+ internal void Prepare()
+ {
+ _engine.PrepareRequest();
+ }
+
+ ///
+ /// Updates the request when there is no template to render the content.
+ ///
+ internal void UpdateOnMissingTemplate()
+ {
+ _engine.UpdateRequestOnMissingTemplate();
+ }
+
+ ///
+ /// Triggers the Prepared event.
+ ///
+ internal void OnPrepared()
+ {
+ if (Prepared != null)
+ Prepared(this, EventArgs.Empty);
+ }
///
/// Gets or sets the cleaned up Uri used for routing.
@@ -214,6 +107,13 @@ namespace Umbraco.Web.Routing
///
private IPublishedContent _publishedContent = null;
+ ///
+ /// The initial requested IPublishedContent, if any, else null.
+ ///
+ /// The initial requested content is the content that was found by the finders,
+ /// before anything such as 404, redirect... took place.
+ private IPublishedContent _initialPublishedContent = null;
+
///
/// Gets or sets the requested content.
///
@@ -225,11 +125,26 @@ namespace Umbraco.Web.Routing
{
_publishedContent = value;
this.Template = null;
- this.AlternateTemplateAlias = null;
_publishedContentId = _publishedContent != null ? _publishedContent.Id : 0;
}
}
+ ///
+ /// Gets the initial requested content.
+ ///
+ /// The initial requested content is the content that was found by the finders,
+ /// before anything such as 404, redirect... took place.
+ public IPublishedContent InitialPublishedContent { get { return _initialPublishedContent; } }
+
+ ///
+ /// Gets or sets a value indicating whether the current published content is the initial one.
+ ///
+ public bool IsInitialPublishedContent
+ {
+ get { return _initialPublishedContent != null && _initialPublishedContent == _publishedContent; }
+ set { _initialPublishedContent = _publishedContent; }
+ }
+
///
/// Gets the identifier of the requested content.
///
@@ -269,16 +184,6 @@ namespace Umbraco.Web.Routing
get { return this.Template != null ; }
}
- ///
- /// Gets or sets the alternate template alias.
- ///
- ///
- /// When null or empty, use the default template.
- /// Alternate template works only when displaying the intended document and should be set
- /// after PublishedContent since setting PublishedContent clears the alternate template.
- ///
- public string AlternateTemplateAlias { get; set; }
-
#endregion
#region Domain and Culture
diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestBuilder.cs b/src/Umbraco.Web/Routing/PublishedContentRequestBuilder.cs
deleted file mode 100644
index 63490a11ee..0000000000
--- a/src/Umbraco.Web/Routing/PublishedContentRequestBuilder.cs
+++ /dev/null
@@ -1,513 +0,0 @@
-using System;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using Umbraco.Core;
-using Umbraco.Core.IO;
-using Umbraco.Core.Logging;
-using umbraco.cms.businesslogic.language;
-using umbraco.cms.businesslogic.member;
-using umbraco.cms.businesslogic.template;
-using umbraco.cms.businesslogic.web;
-
-namespace Umbraco.Web.Routing
-{
- ///
- /// Looks up the document using ILookup's and sets any additional properties required on the PublishedContentRequest object
- ///
- internal class PublishedContentRequestBuilder
- {
- private readonly PublishedContentRequest _publishedContentRequest;
- private readonly UmbracoContext _umbracoContext;
- private readonly RoutingContext _routingContext;
-
- public PublishedContentRequestBuilder(PublishedContentRequest publishedContentRequest)
- {
- if (publishedContentRequest == null) throw new ArgumentNullException("publishedContentRequest");
- _publishedContentRequest = publishedContentRequest;
- _umbracoContext = publishedContentRequest.RoutingContext.UmbracoContext;
- _routingContext = publishedContentRequest.RoutingContext;
- }
-
- ///
- /// Determines the rendering engine to use and sets the flag on the PublishedContentRequest
- ///
- internal void DetermineRenderingEngine()
- {
- //First, if there is no template, we will default to use MVC because MVC supports Hijacking routes which
- //sometimes don't require a template since the developer may want full control over the rendering.
- //Webforms doesn't support this so MVC it is. MVC will also handle what to do if no template or hijacked route
- //is there (i.e. blank page)
- if (!_publishedContentRequest.HasTemplate)
- {
- _publishedContentRequest.RenderingEngine = RenderingEngine.Mvc;
- return;
- }
-
- //NOTE: Not sure how the alias is actually saved with a space as this shouldn't ever be the case?
- // but apparently this happens. I think what should actually be done always is the template alias
- // should be saved using the ToUmbracoAlias method and then we can use this here too, that way it
- // it 100% consistent. I'll leave this here for now until further invenstigation.
- var templateAlias = _publishedContentRequest.Template.Alias.Replace(" ", string.Empty);
- //var templateAlias = _publishedContentRequest.Template.Alias.ToUmbracoAlias(StringAliasCaseType.PascalCase);
-
- Func determineEngine =
- (directory, alias, extensions, renderingEngine) =>
- {
- //so we have a template, now we need to figure out where the template is, this is done just by the Alias field
- //ensure it exists
- if (!directory.Exists) Directory.CreateDirectory(directory.FullName);
- var file = directory.GetFiles()
- .FirstOrDefault(x => extensions.Any(e => x.Name.InvariantEquals(alias + e)));
-
- if (file != null)
- {
- //it is mvc since we have a template there that exists with this alias
- _publishedContentRequest.RenderingEngine = renderingEngine;
- return true;
- }
- return false;
- };
-
- //first determine if it is MVC, we will favor mvc if there is a template with the same name in both
- // folders, if it is then MVC will be selected
- if (!determineEngine(
- new DirectoryInfo(IOHelper.MapPath(SystemDirectories.MvcViews)),
- templateAlias,
- new[]{".cshtml", ".vbhtml"},
- RenderingEngine.Mvc))
- {
- //if not, then determine if it is webforms (this should def match if a template is assigned and its not in the MVC folder)
- // if it doesn't match, then MVC will be used by default anyways.
- determineEngine(
- new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Masterpages)),
- templateAlias,
- new[] {".master"},
- RenderingEngine.WebForms);
- }
-
- }
-
- ///
- /// Determines the site root (if any) matching the http request.
- ///
- /// A value indicating whether a domain was found.
- internal bool LookupDomain()
- {
- const string tracePrefix = "LookupDomain: ";
-
- // note - we are not handling schemes nor ports here.
-
- LogHelper.Debug("{0}Uri=\"{1}\"", () => tracePrefix, () => _publishedContentRequest.Uri);
-
- // try to find a domain matching the current request
- var domainAndUri = DomainHelper.DomainMatch(Domain.GetDomains(), _umbracoContext.CleanedUmbracoUrl, false);
-
- // handle domain
- if (domainAndUri != null)
- {
- // matching an existing domain
- LogHelper.Debug("{0}Matches domain=\"{1}\", rootId={2}, culture=\"{3}\"",
- () => tracePrefix,
- () => domainAndUri.Domain.Name,
- () => domainAndUri.Domain.RootNodeId,
- () => domainAndUri.Domain.Language.CultureAlias);
-
- _publishedContentRequest.Domain = domainAndUri.Domain;
- _publishedContentRequest.DomainUri = domainAndUri.Uri;
- _publishedContentRequest.Culture = new CultureInfo(domainAndUri.Domain.Language.CultureAlias);
-
- // canonical? not implemented at the moment
- // if (...)
- // {
- // this.RedirectUrl = "...";
- // return true;
- // }
- }
- else
- {
- // not matching any existing domain
- LogHelper.Debug("{0}Matches no domain", () => tracePrefix);
-
- var defaultLanguage = Language.GetAllAsList().FirstOrDefault();
- _publishedContentRequest.Culture = defaultLanguage == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultLanguage.CultureAlias);
- }
-
- LogHelper.Debug("{0}Culture=\"{1}\"", () => tracePrefix, () => _publishedContentRequest.Culture.Name);
-
- return _publishedContentRequest.Domain != null;
- }
-
- ///
- /// Determines the Umbraco document (if any) matching the http request.
- ///
- /// A value indicating whether a document and template nave been found.
- internal bool LookupDocument()
- {
- const string tracePrefix = "LookupDocument: ";
- LogHelper.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => _publishedContentRequest.Uri.AbsolutePath);
-
- // run the document lookups
- LookupDocument1();
-
- // not handling umbracoRedirect here but after LookupDocument2
- // so internal redirect, 404, etc has precedence over redirect
-
- // handle not-found, redirects, access, template
- LookupDocument2();
-
- // handle umbracoRedirect (moved from umbraco.page)
- FollowRedirect();
-
- // handle wildcard domains
- HandleWildcardDomains();
-
- bool resolved = _publishedContentRequest.HasPublishedContent && _publishedContentRequest.HasTemplate;
- return resolved;
- }
-
- ///
- /// Performs the document resolution first pass.
- ///
- /// The first past consists in running the document lookups.
- internal void LookupDocument1()
- {
- const string tracePrefix = "LookupDocument: ";
-
- // look for the document
- // the first successful resolver, if any, will set this.Node, and may also set this.Template
- // some lookups may implement caching
-
- using (DisposableTimer.DebugDuration(
- () => string.Format("{0}Begin resolvers", tracePrefix),
- () => string.Format("{0}End resolvers, {1}", tracePrefix, (_publishedContentRequest.HasPublishedContent ? "a document was found" : "no document was found"))))
- {
- _routingContext.PublishedContentFinders.Any(lookup => lookup.TryFindDocument(_publishedContentRequest));
- }
- }
-
- ///
- /// Performs the document resolution second pass.
- ///
- ///
- /// The second pass consists in handling "not found", internal redirects, access validation, and template.
- /// TODO: Rename this method accordingly .... but to what?
- ///
- internal void LookupDocument2()
- {
- const string tracePrefix = "LookupDocument2: ";
-
- // at that point if we have a .PublishedContent then it is the "expected" document
- // time to read the alternate template alias, from querystring, form, cookie or server vars.
- // it will be cleared as soon as .PublishedContent change, because then it's not the
- // expected content anymore and the alternate template does not apply.
- _publishedContentRequest.AlternateTemplateAlias = _umbracoContext.HttpContext.Request["altTemplate"];
-
- _umbracoContext.HttpContext.Trace.Write(string.Format("test {0}", _publishedContentRequest.AlternateTemplateAlias));
-
- // handle "not found", follow internal redirects, validate access, template
- // because these might loop, we have to have some sort of infinite loop detection
- int i = 0, j = 0;
- const int maxLoop = 12;
- do
- {
- LogHelper.Debug("{0}{1}", () => tracePrefix, () => (i == 0 ? "Begin" : "Loop"));
-
- // handle not found
- if (!_publishedContentRequest.HasPublishedContent)
- {
- _publishedContentRequest.Is404 = true;
- LogHelper.Debug("{0}No document, try last chance lookup", () => tracePrefix);
-
- // if it fails then give up, there isn't much more that we can do
- var lastChance = _routingContext.PublishedContentLastChanceFinder;
- if (lastChance == null || !lastChance.TryFindDocument(_publishedContentRequest))
- {
- LogHelper.Debug("{0}Failed to find a document, give up", () => tracePrefix);
- break;
- }
-
- LogHelper.Debug("{0}Found a document", () => tracePrefix);
- }
-
- // follow internal redirects as long as it's not running out of control ie infinite loop of some sort
- j = 0;
- while (FollowInternalRedirects() && j++ < maxLoop) ;
- if (j == maxLoop) // we're running out of control
- break;
-
- // ensure access
- if (_publishedContentRequest.HasPublishedContent)
- EnsureNodeAccess();
-
- // 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 (!_publishedContentRequest.HasPublishedContent && i++ < maxLoop);
-
- if (i == maxLoop || j == maxLoop)
- {
- LogHelper.Debug("{0}Looks like we're running into an infinite loop, abort", () => tracePrefix);
- _publishedContentRequest.PublishedContent = null;
- }
-
- // resolve template - will do nothing if a template is already set
- // moved out of the loop because LookupTemplate does set .PublishedContent to null anymore
- // (see node in LookupTemplate)
- if (_publishedContentRequest.HasPublishedContent)
- LookupTemplate();
-
- LogHelper.Debug("{0}End", () => tracePrefix);
- }
-
- ///
- /// Follows internal redirections through the umbracoInternalRedirectId document property.
- ///
- /// A value indicating whether redirection took place and led to a new published document.
- ///
- /// 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()
- {
- const string tracePrefix = "FollowInternalRedirects: ";
-
- if (_publishedContentRequest.PublishedContent == null)
- throw new InvalidOperationException("There is no node.");
-
- bool redirect = false;
- var internalRedirect = _publishedContentRequest.PublishedContent.GetPropertyValue("umbracoInternalRedirectId");
-
- if (!string.IsNullOrWhiteSpace(internalRedirect))
- {
- LogHelper.Debug("{0}Found umbracoInternalRedirectId={1}", () => tracePrefix, () => internalRedirect);
-
- int internalRedirectId;
- if (!int.TryParse(internalRedirect, out internalRedirectId))
- internalRedirectId = -1;
-
- if (internalRedirectId <= 0)
- {
- // bad redirect - log and display the current page (legacy behavior)
- //_publishedContentRequest.Document = null; // no! that would be to force a 404
- LogHelper.Debug("{0}Failed to redirect to id={1}: invalid value", () => tracePrefix, () => internalRedirect);
- }
- else if (internalRedirectId == _publishedContentRequest.PublishedContentId)
- {
- // redirect to self
- LogHelper.Debug("{0}Redirecting to self, ignore", () => tracePrefix);
- }
- else
- {
- // redirect to another page
- var node = _routingContext.PublishedContentStore.GetDocumentById(
- _umbracoContext,
- internalRedirectId);
-
- _publishedContentRequest.PublishedContent = node;
- if (node != null)
- {
- redirect = true;
- LogHelper.Debug("{0}Redirecting to id={1}", () => tracePrefix, () => internalRedirectId);
- }
- else
- {
- LogHelper.Debug("{0}Failed to redirect to id={1}: no such published document", () => tracePrefix, () => internalRedirectId);
- }
- }
- }
-
- return redirect;
- }
-
- ///
- /// 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 EnsureNodeAccess()
- {
- const string tracePrefix = "EnsurePageAccess: ";
-
- if (_publishedContentRequest.PublishedContent == null)
- throw new InvalidOperationException("There is no node.");
-
- var path = _publishedContentRequest.PublishedContent.Path;
-
- if (Access.IsProtected(_publishedContentRequest.PublishedContentId, path))
- {
- LogHelper.Debug("{0}Page is protected, check for access", () => tracePrefix);
-
- System.Web.Security.MembershipUser user = null;
- try
- {
- user = System.Web.Security.Membership.GetUser();
- }
- catch (ArgumentException)
- {
- LogHelper.Debug("{0}Membership.GetUser returned ArgumentException", () => tracePrefix);
- }
-
- if (user == null || !Member.IsLoggedOn())
- {
- LogHelper.Debug("{0}Not logged in, redirect to login page", () => tracePrefix);
- var loginPageId = Access.GetLoginPage(path);
- if (loginPageId != _publishedContentRequest.PublishedContentId)
- _publishedContentRequest.PublishedContent = _routingContext.PublishedContentStore.GetDocumentById(
- _umbracoContext,
- loginPageId);
- }
- else if (!Access.HasAccces(_publishedContentRequest.PublishedContentId, user.ProviderUserKey))
- {
- LogHelper.Debug("{0}Current member has not access, redirect to error page", () => tracePrefix);
- var errorPageId = Access.GetErrorPage(path);
- if (errorPageId != _publishedContentRequest.PublishedContentId)
- _publishedContentRequest.PublishedContent = _routingContext.PublishedContentStore.GetDocumentById(
- _umbracoContext,
- errorPageId);
- }
- else
- {
- LogHelper.Debug("{0}Current member has access", () => tracePrefix);
- }
- }
- else
- {
- LogHelper.Debug("{0}Page is not protected", () => tracePrefix);
- }
- }
-
- ///
- /// Resolves a template for the current node.
- ///
- private void LookupTemplate()
- {
- // HERE we should let people register their own way of finding a template, same as with documents!!!!
- // do we?
-
- const string tracePrefix = "LookupTemplate: ";
-
- if (_publishedContentRequest.PublishedContent == null)
- throw new InvalidOperationException("There is no node.");
-
- if (_publishedContentRequest.AlternateTemplateAlias.IsNullOrWhiteSpace())
- {
- // 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 (_publishedContentRequest.HasTemplate)
- {
- LogHelper.Debug("{0}Has a template already, and no alternate template.", () => tracePrefix);
- return;
- }
-
- // TODO: When we remove the need for a database for templates, then this id should be irrelavent,
- // not sure how were going to do this nicely.
-
- var templateId = _publishedContentRequest.PublishedContent.TemplateId;
-
- if (templateId > 0)
- {
- LogHelper.Debug("{0}Look for template id={1}", () => tracePrefix, () => templateId);
- // don't use the Template ctor as the result is not cached... instead use this static method
- var template = Template.GetTemplate(templateId);
- if (template == null)
- throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render");
- _publishedContentRequest.Template = template;
- LogHelper.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias);
- }
- else
- {
- LogHelper.Debug("{0}No specified template.", () => tracePrefix);
- }
- }
- else
- {
- // we have an alternate template specified. lookup the template with that alias
- // this means the we override any template that a content lookup might have set
- // so /path/to/page/template1?altTemplate=template2 will use template2
-
- // ignore if the alias does not match - just trace
-
- if (_publishedContentRequest.HasTemplate)
- LogHelper.Debug("{0}Has a template already, but also an alternate template.", () => tracePrefix);
- LogHelper.Debug("{0}Look for alternate template alias=\"{1}\"", () => tracePrefix, () => _publishedContentRequest.AlternateTemplateAlias);
-
- var template = Template.GetByAlias(_publishedContentRequest.AlternateTemplateAlias, true);
- if (template != null)
- {
- _publishedContentRequest.Template = template;
- LogHelper.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias);
- }
- else
- {
- LogHelper.Debug("{0}The template with alias=\"{1}\" does not exist, ignoring.", () => tracePrefix, () => _publishedContentRequest.AlternateTemplateAlias);
- }
- }
-
- if (!_publishedContentRequest.HasTemplate)
- {
- LogHelper.Debug("{0}No template was found.", () => tracePrefix);
-
- // initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true
- // then reset _publishedContentRequest.Document to null to force a 404.
- //
- // but: because we want to let MVC hijack routes even though no template is defined, we decide that
- // a missing template is OK but the request will then be forwarded to MVC, which will need to take
- // care of everything.
- //
- // so, don't set _publishedContentRequest.Document to null here
- }
- else
- {
- LogHelper.Debug("{0}Running with template id={1} alias=\"{2}\"", () => tracePrefix, () => _publishedContentRequest.Template.Id, () => _publishedContentRequest.Template.Alias);
- }
- }
-
- ///
- /// Follows external redirection through umbracoRedirect document property.
- ///
- /// As per legacy, if the redirect does not work, we just ignore it.
- private void FollowRedirect()
- {
- if (_publishedContentRequest.HasPublishedContent)
- {
- var redirectId = _publishedContentRequest.PublishedContent.GetPropertyValue("umbracoRedirect", -1);
-
- string redirectUrl = "#";
- if (redirectId > 0)
- redirectUrl = _routingContext.NiceUrlProvider.GetNiceUrl(redirectId);
- if (redirectUrl != "#")
- _publishedContentRequest.RedirectUrl = redirectUrl;
- }
- }
-
- ///
- /// Looks for wildcard domains in the path and updates Culture accordingly.
- ///
- private void HandleWildcardDomains()
- {
- const string tracePrefix = "HandleWildcardDomains: ";
-
- if (!_publishedContentRequest.HasPublishedContent)
- return;
-
- var nodePath = _publishedContentRequest.PublishedContent.Path;
- LogHelper.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => nodePath);
- var rootNodeId = _publishedContentRequest.HasDomain ? _publishedContentRequest.Domain.RootNodeId : (int?)null;
- var domain = DomainHelper.LookForWildcardDomain(Domain.GetDomains(), nodePath, rootNodeId);
-
- if (domain != null)
- {
- _publishedContentRequest.Culture = new CultureInfo(domain.Language.CultureAlias);
- LogHelper.Debug("{0}Got domain on node {1}, set culture to \"{2}\".", () => tracePrefix,
- () => domain.RootNodeId, () => _publishedContentRequest.Culture.Name);
- }
- else
- {
- LogHelper.Debug("{0}No match.", () => tracePrefix);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs
new file mode 100644
index 0000000000..86ed01278d
--- /dev/null
+++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs
@@ -0,0 +1,642 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Globalization;
+using System.IO;
+
+using Umbraco.Core;
+using Umbraco.Core.IO;
+using Umbraco.Core.Logging;
+
+using umbraco;
+using umbraco.cms.businesslogic.web;
+using umbraco.cms.businesslogic.language;
+using umbraco.cms.businesslogic.template;
+using umbraco.cms.businesslogic.member;
+
+namespace Umbraco.Web.Routing
+{
+ internal class PublishedContentRequestEngine
+ {
+ private PublishedContentRequest _pcr;
+ private RoutingContext _routingContext;
+
+ ///
+ /// Initializes a new instance of the class with a content request.
+ ///
+ /// The content request.
+ public PublishedContentRequestEngine(PublishedContentRequest pcr)
+ {
+ _pcr = pcr;
+ _routingContext = pcr.RoutingContext;
+
+ var umbracoContext = _routingContext.UmbracoContext;
+ if (_routingContext == null) throw new ArgumentException("pcr.RoutingContext is null.");
+ if (umbracoContext == null) throw new ArgumentException("pcr.RoutingContext.UmbracoContext is null.");
+ if (umbracoContext.RoutingContext != _routingContext) throw new ArgumentException("RoutingContext confusion.");
+ // no! not set yet.
+ //if (umbracoContext.PublishedContentRequest != _pcr) throw new ArgumentException("PublishedContentRequest confusion.");
+ }
+
+ #region Public
+
+ ///
+ /// Prepares the request.
+ ///
+ public void PrepareRequest()
+ {
+ // 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.
+
+ //find domain
+ FindDomain();
+
+ // if request has been flagged to redirect then return
+ // whoever called us is in charge of actually redirecting
+ if (_pcr.IsRedirect)
+ return;
+
+ // set the culture on the thread - once, so it's set when running document lookups
+ Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture;
+
+ // find the document & template
+ FindPublishedContentAndTemplate();
+
+ // set the culture on the thread -- again, 'cos it might have changed due to a wildcard domain
+ Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture;
+
+ // find the rendering engine
+ FindRenderingEngine();
+
+ // trigger the Prepared event - at that point it is still possible to change about anything
+ _pcr.OnPrepared();
+
+ // set the culture on the thread -- again, 'cos it might have changed in the event handler
+ Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _pcr.Culture;
+
+ // if request has been flagged to redirect then return
+ // whoever called us is in charge of actually redirecting
+ if (_pcr.IsRedirect)
+ return;
+
+ // safety
+ if (!_pcr.HasPublishedContent)
+ _pcr.Is404 = true;
+
+ // handle 404 : return
+ // whoever called us is in charge of doing what's appropriate
+ if (_pcr.Is404)
+ return;
+
+ // 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
+
+ // assign the legacy page back to the docrequest
+ // handlers like default.aspx will want it and most macros currently need it
+ _pcr.UmbracoPage = new page(_pcr);
+
+ // these two are used by many legacy objects
+ _routingContext.UmbracoContext.HttpContext.Items["pageID"] = _pcr.PublishedContentId;
+ _routingContext.UmbracoContext.HttpContext.Items["pageElements"] = _pcr.UmbracoPage.Elements;
+ }
+
+ ///
+ /// Updates the request when there is no template to render the content.
+ ///
+ /// This is called from Mvc when there's a document to render but no template.
+ public void UpdateRequestOnMissingTemplate()
+ {
+ // clear content
+ var content = _pcr.PublishedContent;
+ _pcr.PublishedContent = null;
+
+ HandlePublishedContent(); // will go 404
+ FindTemplate();
+ FindRenderingEngine();
+
+ // if request has been flagged to redirect then return
+ // whoever called us is in charge of redirecting
+ if (_pcr.IsRedirect)
+ return;
+
+ if (!_pcr.HasPublishedContent)
+ {
+ // means the engine could not find a proper document to handle 404
+ // restore the saved content so we know it exists
+ _pcr.PublishedContent = content;
+ return;
+ }
+
+ if (!_pcr.HasTemplate)
+ {
+ // 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;
+ }
+
+ // assign the legacy page back to the docrequest
+ // handlers like default.aspx will want it and most macros currently need it
+ _pcr.UmbracoPage = new page(_pcr);
+
+ // these two are used by many legacy objects
+ _routingContext.UmbracoContext.HttpContext.Items["pageID"] = _pcr.PublishedContentId;
+ _routingContext.UmbracoContext.HttpContext.Items["pageElements"] = _pcr.UmbracoPage.Elements;
+ }
+
+ #endregion
+
+ #region Domain
+
+ ///
+ /// Finds the site root (if any) matching the http request, and updates the PublishedContentRequest accordingly.
+ ///
+ /// A value indicating whether a domain was found.
+ internal bool FindDomain()
+ {
+ const string tracePrefix = "FindDomain: ";
+
+ // note - we are not handling schemes nor ports here.
+
+ LogHelper.Debug("{0}Uri=\"{1}\"", () => tracePrefix, () => _pcr.Uri);
+
+ // try to find a domain matching the current request
+ var domainAndUri = DomainHelper.DomainMatch(Domain.GetDomains(), _pcr.Uri, false);
+
+ // handle domain
+ if (domainAndUri != null)
+ {
+ // matching an existing domain
+ LogHelper.Debug("{0}Matches domain=\"{1}\", rootId={2}, culture=\"{3}\"",
+ () => tracePrefix,
+ () => domainAndUri.Domain.Name,
+ () => domainAndUri.Domain.RootNodeId,
+ () => domainAndUri.Domain.Language.CultureAlias);
+
+ _pcr.Domain = domainAndUri.Domain;
+ _pcr.DomainUri = domainAndUri.Uri;
+ _pcr.Culture = new CultureInfo(domainAndUri.Domain.Language.CultureAlias);
+
+ // canonical? not implemented at the moment
+ // if (...)
+ // {
+ // _pcr.RedirectUrl = "...";
+ // return true;
+ // }
+ }
+ else
+ {
+ // not matching any existing domain
+ LogHelper.Debug("{0}Matches no domain", () => tracePrefix);
+
+ var defaultLanguage = Language.GetAllAsList().FirstOrDefault();
+ _pcr.Culture = defaultLanguage == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultLanguage.CultureAlias);
+ }
+
+ LogHelper.Debug("{0}Culture=\"{1}\"", () => tracePrefix, () => _pcr.Culture.Name);
+
+ return _pcr.Domain != null;
+ }
+
+ ///
+ /// Looks for wildcard domains in the path and updates Culture accordingly.
+ ///
+ private void HandleWildcardDomains()
+ {
+ const string tracePrefix = "HandleWildcardDomains: ";
+
+ if (!_pcr.HasPublishedContent)
+ return;
+
+ var nodePath = _pcr.PublishedContent.Path;
+ LogHelper.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => nodePath);
+ var rootNodeId = _pcr.HasDomain ? _pcr.Domain.RootNodeId : (int?)null;
+ var domain = DomainHelper.LookForWildcardDomain(Domain.GetDomains(), nodePath, rootNodeId);
+
+ if (domain != null)
+ {
+ _pcr.Culture = new CultureInfo(domain.Language.CultureAlias);
+ LogHelper.Debug("{0}Got domain on node {1}, set culture to \"{2}\".", () => tracePrefix,
+ () => domain.RootNodeId, () => _pcr.Culture.Name);
+ }
+ else
+ {
+ LogHelper.Debug("{0}No match.", () => tracePrefix);
+ }
+ }
+
+ #endregion
+
+ #region Rendering engine
+
+ ///
+ /// Finds the rendering engine to use, and updates the PublishedContentRequest accordingly.
+ ///
+ internal void FindRenderingEngine()
+ {
+ //First, if there is no template, we will default to use MVC because MVC supports Hijacking routes which
+ //sometimes don't require a template since the developer may want full control over the rendering.
+ //Webforms doesn't support this so MVC it is. MVC will also handle what to do if no template or hijacked route
+ //is there (i.e. blank page)
+ if (!_pcr.HasTemplate)
+ {
+ _pcr.RenderingEngine = RenderingEngine.Mvc;
+ return;
+ }
+
+ //NOTE: Not sure how the alias is actually saved with a space as this shouldn't ever be the case?
+ // but apparently this happens. I think what should actually be done always is the template alias
+ // should be saved using the ToUmbracoAlias method and then we can use this here too, that way it
+ // it 100% consistent. I'll leave this here for now until further invenstigation.
+ var templateAlias = _pcr.Template.Alias.Replace(" ", string.Empty);
+ //var templateAlias = _pcr.Template.Alias.ToUmbracoAlias(StringAliasCaseType.PascalCase);
+
+ Func determineEngine =
+ (directory, alias, extensions, renderingEngine) =>
+ {
+ //so we have a template, now we need to figure out where the template is, this is done just by the Alias field
+ //ensure it exists
+ if (!directory.Exists) Directory.CreateDirectory(directory.FullName);
+ var file = directory.GetFiles()
+ .FirstOrDefault(x => extensions.Any(e => x.Name.InvariantEquals(alias + e)));
+
+ if (file != null)
+ {
+ //it is mvc since we have a template there that exists with this alias
+ _pcr.RenderingEngine = renderingEngine;
+ return true;
+ }
+ return false;
+ };
+
+ //first determine if it is MVC, we will favor mvc if there is a template with the same name in both
+ // folders, if it is then MVC will be selected
+ if (!determineEngine(
+ new DirectoryInfo(IOHelper.MapPath(SystemDirectories.MvcViews)),
+ templateAlias,
+ new[] { ".cshtml", ".vbhtml" },
+ RenderingEngine.Mvc))
+ {
+ //if not, then determine if it is webforms (this should def match if a template is assigned and its not in the MVC folder)
+ // if it doesn't match, then MVC will be used by default anyways.
+ determineEngine(
+ new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Masterpages)),
+ templateAlias,
+ new[] { ".master" },
+ RenderingEngine.WebForms);
+ }
+
+ }
+
+ #endregion
+
+ #region Document and template
+
+ ///
+ /// Finds the Umbraco document (if any) matching the request, and updates the PublishedContentRequest accordingly.
+ ///
+ /// A value indicating whether a document and template were found.
+ private bool FindPublishedContentAndTemplate()
+ {
+ const string tracePrefix = "FindPublishedContentAndTemplate: ";
+ LogHelper.Debug("{0}Path=\"{1}\"", () => tracePrefix, () => _pcr.Uri.AbsolutePath);
+
+ // run the document finders
+ FindPublishedContent();
+
+ // not handling umbracoRedirect here but after LookupDocument2
+ // so internal redirect, 404, etc has precedence over redirect
+
+ // handle not-found, redirects, access...
+ HandlePublishedContent();
+
+ // find a template
+ FindTemplate();
+
+ // handle umbracoRedirect
+ FollowExternalRedirect();
+
+ // handle wildcard domains
+ HandleWildcardDomains();
+
+ return _pcr.HasPublishedContent && _pcr.HasTemplate;
+ }
+
+ ///
+ /// Tries to find the document matching the request, by running the IPublishedContentFinder instances.
+ ///
+ internal void FindPublishedContent()
+ {
+ const string tracePrefix = "FindPublishedContent: ";
+
+ // look for the document
+ // the first successful finder, if any, will set this.PublishedContent, and may also set this.Template
+ // some finders may implement caching
+
+ using (DisposableTimer.DebugDuration(
+ () => string.Format("{0}Begin finders", tracePrefix),
+ () => string.Format("{0}End finders, {1}", tracePrefix, (_pcr.HasPublishedContent ? "a document was found" : "no document was found"))))
+ {
+ _routingContext.PublishedContentFinders.Any(lookup => lookup.TryFindDocument(_pcr));
+ }
+
+ // indicate that the published content (if any) we have at the moment is the
+ // one that was found by the standard finders before anything else took place.
+ _pcr.IsInitialPublishedContent = true;
+ }
+
+ ///
+ /// Handles the published content (if any).
+ ///
+ ///
+ /// Handles "not found", internal redirects, access validation...
+ /// things that must be handled in one place because they can create loops
+ ///
+ private void HandlePublishedContent()
+ {
+ const string tracePrefix = "HandlePublishedContent: ";
+
+ // because these might loop, we have to have some sort of infinite loop detection
+ int i = 0, j = 0;
+ const int maxLoop = 8;
+ do
+ {
+ LogHelper.Debug("{0}{1}", () => tracePrefix, () => (i == 0 ? "Begin" : "Loop"));
+
+ // handle not found
+ if (!_pcr.HasPublishedContent)
+ {
+ _pcr.Is404 = true;
+ LogHelper.Debug("{0}No document, try last chance lookup", () => tracePrefix);
+
+ // if it fails then give up, there isn't much more that we can do
+ var lastChance = _routingContext.PublishedContentLastChanceFinder;
+ if (lastChance == null || !lastChance.TryFindDocument(_pcr))
+ {
+ LogHelper.Debug("{0}Failed to find a document, give up", () => tracePrefix);
+ break;
+ }
+
+ LogHelper.Debug("{0}Found a document", () => tracePrefix);
+ }
+
+ // follow internal redirects as long as it's not running out of control ie infinite loop of some sort
+ j = 0;
+ while (FollowInternalRedirects() && j++ < maxLoop) ;
+ if (j == maxLoop) // we're running out of control
+ break;
+
+ // ensure access
+ if (_pcr.HasPublishedContent)
+ EnsurePublishedContentAccess();
+
+ // 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 (!_pcr.HasPublishedContent && i++ < maxLoop);
+
+ if (i == maxLoop || j == maxLoop)
+ {
+ LogHelper.Debug("{0}Looks like we're running into an infinite loop, abort", () => tracePrefix);
+ _pcr.PublishedContent = null;
+ }
+
+ LogHelper.Debug("{0}End", () => tracePrefix);
+ }
+
+ ///
+ /// Follows internal redirections through the umbracoInternalRedirectId document property.
+ ///
+ /// A value indicating whether redirection took place and led to a new published document.
+ ///
+ /// 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()
+ {
+ const string tracePrefix = "FollowInternalRedirects: ";
+
+ if (_pcr.PublishedContent == null)
+ throw new InvalidOperationException("There is no PublishedContent.");
+
+ bool redirect = false;
+ var internalRedirect = _pcr.PublishedContent.GetPropertyValue("umbracoInternalRedirectId");
+
+ if (!string.IsNullOrWhiteSpace(internalRedirect))
+ {
+ LogHelper.Debug("{0}Found umbracoInternalRedirectId={1}", () => tracePrefix, () => internalRedirect);
+
+ int internalRedirectId;
+ if (!int.TryParse(internalRedirect, out internalRedirectId))
+ internalRedirectId = -1;
+
+ if (internalRedirectId <= 0)
+ {
+ // bad redirect - log and display the current page (legacy behavior)
+ //_pcr.Document = null; // no! that would be to force a 404
+ LogHelper.Debug("{0}Failed to redirect to id={1}: invalid value", () => tracePrefix, () => internalRedirect);
+ }
+ else if (internalRedirectId == _pcr.PublishedContentId)
+ {
+ // redirect to self
+ LogHelper.Debug("{0}Redirecting to self, ignore", () => tracePrefix);
+ }
+ else
+ {
+ // redirect to another page
+ var node = _routingContext.PublishedContentStore.GetDocumentById(_routingContext.UmbracoContext, internalRedirectId);
+
+ _pcr.PublishedContent = node;
+ if (node != null)
+ {
+ redirect = true;
+ LogHelper.Debug("{0}Redirecting to id={1}", () => tracePrefix, () => internalRedirectId);
+ }
+ else
+ {
+ LogHelper.Debug("{0}Failed to redirect to id={1}: no such published document", () => tracePrefix, () => internalRedirectId);
+ }
+ }
+ }
+
+ return redirect;
+ }
+
+ ///
+ /// 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()
+ {
+ const string tracePrefix = "EnsurePublishedContentAccess: ";
+
+ if (_pcr.PublishedContent == null)
+ throw new InvalidOperationException("There is no PublishedContent.");
+
+ var path = _pcr.PublishedContent.Path;
+
+ if (Access.IsProtected(_pcr.PublishedContentId, path))
+ {
+ LogHelper.Debug("{0}Page is protected, check for access", () => tracePrefix);
+
+ System.Web.Security.MembershipUser user = null;
+ try
+ {
+ user = System.Web.Security.Membership.GetUser();
+ }
+ catch (ArgumentException)
+ {
+ LogHelper.Debug("{0}Membership.GetUser returned ArgumentException", () => tracePrefix);
+ }
+
+ if (user == null || !Member.IsLoggedOn())
+ {
+ LogHelper.Debug("{0}Not logged in, redirect to login page", () => tracePrefix);
+ var loginPageId = Access.GetLoginPage(path);
+ if (loginPageId != _pcr.PublishedContentId)
+ _pcr.PublishedContent = _routingContext.PublishedContentStore.GetDocumentById(_routingContext.UmbracoContext, loginPageId);
+ }
+ else if (!Access.HasAccces(_pcr.PublishedContentId, user.ProviderUserKey))
+ {
+ LogHelper.Debug("{0}Current member has not access, redirect to error page", () => tracePrefix);
+ var errorPageId = Access.GetErrorPage(path);
+ if (errorPageId != _pcr.PublishedContentId)
+ _pcr.PublishedContent = _routingContext.PublishedContentStore.GetDocumentById(_routingContext.UmbracoContext, errorPageId);
+ }
+ else
+ {
+ LogHelper.Debug("{0}Current member has access", () => tracePrefix);
+ }
+ }
+ else
+ {
+ LogHelper.Debug("{0}Page is not protected", () => tracePrefix);
+ }
+ }
+
+ ///
+ /// Finds a template for the current node.
+ ///
+ private void FindTemplate()
+ {
+ // 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?
+
+ const string tracePrefix = "FindTemplate: ";
+
+ if (_pcr.PublishedContent == null)
+ throw new InvalidOperationException("There is no PublishedContent.");
+
+ // read the alternate template alias, from querystring, form, cookie or server vars,
+ // only if the published content is the initial once, else the alternate template
+ // does not apply
+ string altTemplate = _pcr.IsInitialPublishedContent
+ ? _routingContext.UmbracoContext.HttpContext.Request["altTemplate"]
+ : null;
+
+ if (string.IsNullOrWhiteSpace(altTemplate))
+ {
+ // we don't have an alternate template specified. use the current one if there's one already,
+ // which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...),
+ // else lookup the template id on the document then lookup the template with that id.
+
+ if (_pcr.HasTemplate)
+ {
+ LogHelper.Debug("{0}Has a template already, and no alternate template.", () => tracePrefix);
+ return;
+ }
+
+ // TODO: When we remove the need for a database for templates, then this id should be irrelavent,
+ // not sure how were going to do this nicely.
+
+ var templateId = _pcr.PublishedContent.TemplateId;
+
+ if (templateId > 0)
+ {
+ LogHelper.Debug("{0}Look for template id={1}", () => tracePrefix, () => templateId);
+ // don't use the Template ctor as the result is not cached... instead use this static method
+ var template = Template.GetTemplate(templateId);
+ if (template == null)
+ throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render");
+ _pcr.Template = template;
+ LogHelper.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias);
+ }
+ else
+ {
+ LogHelper.Debug("{0}No specified template.", () => tracePrefix);
+ }
+ }
+ else
+ {
+ // we have an alternate template specified. lookup the template with that alias
+ // this means the we override any template that a content lookup might have set
+ // so /path/to/page/template1?altTemplate=template2 will use template2
+
+ // ignore if the alias does not match - just trace
+
+ if (_pcr.HasTemplate)
+ LogHelper.Debug("{0}Has a template already, but also an alternate template.", () => tracePrefix);
+ LogHelper.Debug("{0}Look for alternate template alias=\"{1}\"", () => tracePrefix, () => altTemplate);
+
+ var template = Template.GetByAlias(altTemplate, true);
+ if (template != null)
+ {
+ _pcr.Template = template;
+ LogHelper.Debug("{0}Got template id={1} alias=\"{2}\"", () => tracePrefix, () => template.Id, () => template.Alias);
+ }
+ else
+ {
+ LogHelper.Debug("{0}The template with alias=\"{1}\" does not exist, ignoring.", () => tracePrefix, () => altTemplate);
+ }
+ }
+
+ if (!_pcr.HasTemplate)
+ {
+ LogHelper.Debug("{0}No template was found.", () => tracePrefix);
+
+ // initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true
+ // then reset _pcr.Document to null to force a 404.
+ //
+ // but: because we want to let MVC hijack routes even though no template is defined, we decide that
+ // a missing template is OK but the request will then be forwarded to MVC, which will need to take
+ // care of everything.
+ //
+ // so, don't set _pcr.Document to null here
+ }
+ else
+ {
+ LogHelper.Debug("{0}Running with template id={1} alias=\"{2}\"", () => tracePrefix, () => _pcr.Template.Id, () => _pcr.Template.Alias);
+ }
+ }
+
+ ///
+ /// Follows external redirection through umbracoRedirect document property.
+ ///
+ /// As per legacy, if the redirect does not work, we just ignore it.
+ private void FollowExternalRedirect()
+ {
+ if (_pcr.HasPublishedContent)
+ {
+ var redirectId = _pcr.PublishedContent.GetPropertyValue("umbracoRedirect", -1);
+
+ string redirectUrl = "#";
+ if (redirectId > 0)
+ redirectUrl = _routingContext.NiceUrlProvider.GetNiceUrl(redirectId);
+ if (redirectUrl != "#")
+ _pcr.RedirectUrl = redirectUrl;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Umbraco.Web/Templates/TemplateRenderer.cs b/src/Umbraco.Web/Templates/TemplateRenderer.cs
index 4f8180fbc7..b12d33c890 100644
--- a/src/Umbraco.Web/Templates/TemplateRenderer.cs
+++ b/src/Umbraco.Web/Templates/TemplateRenderer.cs
@@ -101,9 +101,8 @@ namespace Umbraco.Web.Templates
}
//ok, we have a document and a template assigned, now to do some rendering.
- var builder = new PublishedContentRequestBuilder(contentRequest);
//determine the rendering engine
- builder.DetermineRenderingEngine();
+ contentRequest.Engine.FindRenderingEngine();
//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.
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index 1b3ab1d1af..4e3f0162d5 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -327,6 +327,7 @@
+
@@ -416,7 +417,6 @@
-
diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs
index 378463c1d4..2201ab176b 100644
--- a/src/Umbraco.Web/UmbracoModule.cs
+++ b/src/Umbraco.Web/UmbracoModule.cs
@@ -80,9 +80,9 @@ namespace Umbraco.Web
return;
if (UmbracoContext.Current == null)
- throw new NullReferenceException("The UmbracoContext.Current is null, ProcessRequest cannot proceed unless there is a current UmbracoContext");
+ throw new InvalidOperationException("The UmbracoContext.Current is null, ProcessRequest cannot proceed unless there is a current UmbracoContext");
if (UmbracoContext.Current.RoutingContext == null)
- throw new NullReferenceException("The UmbracoContext.RoutingContext has not been assigned, ProcessRequest cannot proceed unless there is a RoutingContext assigned to the UmbracoContext");
+ throw new InvalidOperationException("The UmbracoContext.RoutingContext has not been assigned, ProcessRequest cannot proceed unless there is a RoutingContext assigned to the UmbracoContext");
var umbracoContext = UmbracoContext.Current;
@@ -101,16 +101,32 @@ namespace Umbraco.Web
// ok, process
- var uri = umbracoContext.OriginalRequestUrl;
-
- // legacy - no idea what this is but does something with the query strings
+ // legacy - if query contains umbPage=something then url is rewritten to /something
+ // because with Umbraco 3.x on .NET 2.x the form auth url would be something like
+ // login.aspx?ReturnUrl=default.aspx&umbPage=solutions.aspx
+ // and then the next request would be default.aspx?umbPage=solutions.aspx
+ // instead of being solutions.aspx -- are we still impacted by that one now that
+ // we rewrite the urls properly internally? I guess not?
+ // there's a 2008 discussion here http://forum.umbraco.org/yaf_postst5377_FormsAuthenticationRedirectToLoginPage-problem.aspx
+ var uri = umbracoContext.CleanedUmbracoUrl;
LegacyCleanUmbPageFromQueryString(ref uri);
- // instanciate a request a process
+ // instanciate, prepare and process the published content request
// important to use CleanedUmbracoUrl - lowercase path-only version of the current url
- var docreq = new PublishedContentRequest(umbracoContext.CleanedUmbracoUrl, umbracoContext.RoutingContext);
- docreq.ProcessRequest(httpContext, umbracoContext,
- docreq2 => RewriteToUmbracoHandler(HttpContext.Current, uri.Query, docreq2.RenderingEngine));
+ var pcr = new PublishedContentRequest(uri /*umbracoContext.CleanedUmbracoUrl*/, umbracoContext.RoutingContext);
+ umbracoContext.PublishedContentRequest = pcr;
+ pcr.Prepare();
+ if (pcr.IsRedirect)
+ {
+ httpContext.Response.Redirect(pcr.RedirectUrl, true);
+ return;
+ }
+ if (pcr.Is404)
+ httpContext.Response.StatusCode = 404;
+ if (!pcr.HasPublishedContent)
+ httpContext.RemapHandler(new PublishedContentNotFoundHandler());
+ else
+ RewriteToUmbracoHandler(httpContext, pcr);
}
///
@@ -307,22 +323,23 @@ namespace Umbraco.Web
///
///
///
- private void RewriteToUmbracoHandler(HttpContext context, string currentQuery, RenderingEngine engine)
+ private void RewriteToUmbracoHandler(HttpContextBase context, PublishedContentRequest pcr)
{
+ // NOTE: we do not want to use TransferRequest even though many docs say it is better with IIS7, turns out this is
+ // not what we need. The purpose of TransferRequest is to ensure that .net processes all of the rules for the newly
+ // rewritten url, but this is not what we want!
+ // read: http://forums.iis.net/t/1146511.aspx
- //NOTE: We do not want to use TransferRequest even though many docs say it is better with IIS7, turns out this is
- //not what we need. The purpose of TransferRequest is to ensure that .net processes all of the rules for the newly
- //rewritten url, but this is not what we want!
- // http://forums.iis.net/t/1146511.aspx
+ string query = pcr.Uri.Query.TrimStart(new[] { '?' });
string rewritePath;
- switch (engine)
+ switch (pcr.RenderingEngine)
{
case RenderingEngine.Mvc:
// GlobalSettings.Path has already been through IOHelper.ResolveUrl() so it begins with / and vdir (if any)
rewritePath = GlobalSettings.Path.TrimEnd(new[] { '/' }) + "/RenderMvc";
- // we rewrite the path to the path of the handler (i.e. default.aspx or /umbraco/RenderMvc )
- context.RewritePath(rewritePath, "", currentQuery.TrimStart(new[] { '?' }), false);
+ // rewrite the path to the path of the handler (i.e. /umbraco/RenderMvc)
+ context.RewritePath(rewritePath, "", query, false);
//if it is MVC we need to do something special, we are not using TransferRequest as this will
//require us to rewrite the path with query strings and then reparse the query strings, this would
@@ -333,17 +350,15 @@ namespace Umbraco.Web
//we also cannot re-create this functionality because the setter for the HttpContext.Request.RequestContext is internal
//so really, this is pretty much the only way without using Server.TransferRequest and if we did that, we'd have to rethink
//a bunch of things!
-
var urlRouting = new UrlRoutingModule();
- urlRouting.PostResolveRequestCache(new HttpContextWrapper(context));
-
+ urlRouting.PostResolveRequestCache(context);
break;
+
case RenderingEngine.WebForms:
default:
rewritePath = "~/default.aspx";
- // rewrite the path to the path of the handler (i.e. default.aspx or /umbraco/RenderMvc )
- context.RewritePath(rewritePath, "", currentQuery.TrimStart(new[] { '?' }), false);
-
+ // rewrite the path to the path of the handler (i.e. default.aspx)
+ context.RewritePath(rewritePath, "", query, false);
break;
}
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Nodes.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Nodes.cs
index 6139abefd3..be350419de 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Nodes.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/uQuery/uQuery-Nodes.cs
@@ -209,11 +209,11 @@ namespace umbraco
uri = uri.MakeAbsolute(Umbraco.Web.UmbracoContext.Current.CleanedUmbracoUrl);
uri = Umbraco.Web.UriUtility.UriToUmbraco(uri);
- var docreq = new Umbraco.Web.Routing.PublishedContentRequest(uri, Umbraco.Web.UmbracoContext.Current.RoutingContext);
- var builder = new Umbraco.Web.Routing.PublishedContentRequestBuilder(docreq);
- builder.LookupDomain();
- builder.LookupDocument1(); // will _not_ follow redirects, handle 404, nothing - just run lookups
- return docreq.HasPublishedContent ? docreq.PublishedContentId : uQuery.RootNodeId;
+ var pcr = new Umbraco.Web.Routing.PublishedContentRequest(uri, Umbraco.Web.UmbracoContext.Current.RoutingContext);
+ // partially prepare the request: do _not_ follow redirects, handle 404, nothing - just find a content
+ pcr.Engine.FindDomain();
+ pcr.Engine.FindPublishedContent();
+ return pcr.HasPublishedContent ? pcr.PublishedContentId : uQuery.RootNodeId;
}
///