diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index f0ae3607a6..6069060648 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -269,7 +269,21 @@ 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) { - return new NoTemplateHandler(); + var handler = publishedContentRequest.ProcessNoTemplateInMvc(requestContext.HttpContext); + + // 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 + // (a document was found and its template is WebForms) + + // if it's null it means that a document was found and its template is Mvc + + // if we have a handler, return now + if (handler != null) + return handler; + + // else we are running Mvc + // update the route definition + routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); } //no post values, just route to the controller/action requried (local) diff --git a/src/Umbraco.Web/Routing/NoTemplateHandler.cs b/src/Umbraco.Web/Routing/NoTemplateHandler.cs deleted file mode 100644 index 8131d0f195..0000000000 --- a/src/Umbraco.Web/Routing/NoTemplateHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Web; - -namespace Umbraco.Web.Routing -{ - /// - /// Gets executed when there is no template assigned to a request and there is no hijacked MVC route - /// - internal class NoTemplateHandler : IHttpHandler - { - public void ProcessRequest(HttpContext context) - { - WriteOutput(context); - } - - internal void WriteOutput(HttpContext context) - { - var response = context.Response; - - response.Clear(); - response.Write(""); - response.End(); - } - - public bool IsReusable - { - get { return true; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs index 1259b22fdb..282f118899 100644 --- a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs +++ b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs @@ -7,6 +7,16 @@ namespace Umbraco.Web.Routing /// internal class PublishedContentNotFoundHandler : IHttpHandler { + string _message; + + public PublishedContentNotFoundHandler() + { } + + public PublishedContentNotFoundHandler(string message) + { + _message = message; + } + public void ProcessRequest(HttpContext context) { WriteOutput(context); @@ -28,7 +38,9 @@ namespace Umbraco.Web.Routing response.Write("

Page not found

"); response.Write("

"); response.Write(string.Format(reason, HttpUtility.HtmlEncode(UmbracoContext.Current.OriginalRequestUrl))); - response.Write(""); + if (!string.IsNullOrWhiteSpace(_message)) + response.Write("

" + _message + "

"); response.Write("

This page can be replaced with a custom 404. Check the documentation for \"custom 404\".

"); response.Write("

This page is intentionally left ugly ;-)

"); response.Write(""); @@ -38,7 +50,7 @@ namespace Umbraco.Web.Routing public bool IsReusable { - get { return true; } + get { return false; } } } } diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs index 897623e4e9..3d513cd06f 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs @@ -28,27 +28,22 @@ namespace Umbraco.Web.Routing /// internal class PublishedContentRequest { - /// - /// This creates a PublishedContentRequest and assigns it to the current HttpContext and then proceeds to - /// process the request using the PublishedContentRequestBuilder. If everything is successful, the callback - /// method will be called. + /// Assigns the request to the http context and proceeds to process the request. If everything is successful, invoke the callback. /// /// /// /// /// - internal static void ProcessRequest(HttpContextBase httpContext, UmbracoContext umbracoContext, Uri uri, Action onSuccess) + 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 (uri == null) throw new ArgumentNullException("uri"); 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"); - var docreq = new PublishedContentRequest(uri, umbracoContext.RoutingContext); //assign back since this is a front-end request - umbracoContext.PublishedContentRequest = docreq; + 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 @@ -59,33 +54,31 @@ namespace Umbraco.Web.Routing // 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. - //create the searcher - var searcher = new PublishedContentRequestBuilder(docreq); //find domain - searcher.LookupDomain(); + _builder.LookupDomain(); // redirect if it has been flagged - if (docreq.IsRedirect) - httpContext.Response.Redirect(docreq.RedirectUrl, true); + if (this.IsRedirect) + httpContext.Response.Redirect(this.RedirectUrl, true); //set the culture on the thread - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = docreq.Culture; + 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 = searcher.LookupDocument(); + var found = _builder.LookupDocument(); //this could be called in the LookupDocument method, but I've just put it here for clarity. - searcher.DetermineRenderingEngine(); + _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 (docreq.IsRedirect) - httpContext.Response.Redirect(docreq.RedirectUrl, true); + if (this.IsRedirect) + httpContext.Response.Redirect(this.RedirectUrl, true); // handle 404 - if (docreq.Is404) + if (this.Is404) { httpContext.Response.StatusCode = 404; - if (!docreq.HasNode) + if (!this.HasNode) { httpContext.RemapHandler(new PublishedContentNotFoundHandler()); return; @@ -96,7 +89,7 @@ namespace Umbraco.Web.Routing } // just be safe - should never ever happen - if (!docreq.HasNode) + if (!this.HasNode) throw new Exception("No document to render."); // render even though we might have no template @@ -105,67 +98,135 @@ namespace Umbraco.Web.Routing // assign the legacy page back to the docrequest // handlers like default.aspx will want it and most macros currently need it - docreq.UmbracoPage = new page(docreq); + this.UmbracoPage = new page(this); // these two are used by many legacy objects - httpContext.Items["pageID"] = docreq.DocumentId; - httpContext.Items["pageElements"] = docreq.UmbracoPage.Elements; + httpContext.Items["pageID"] = this.DocumentId; + httpContext.Items["pageElements"] = this.UmbracoPage.Elements; if (onSuccess != null) - onSuccess(docreq); + onSuccess(this); } + 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.HasNode) + { + // 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.DocumentId; + 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; /// - /// Create a content request for a specific URL + /// Initializes a new instance of the class with a specific Uri and routing context. /// - /// - /// + /// 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; - RoutingContext = routingContext; - - //set default - RenderingEngine = RenderingEngine.Mvc; - } + this.RoutingContext = routingContext; - /// - /// the id of the requested node, if any, else zero. - /// - int _nodeId = 0; - - private IPublishedContent _publishedContent = null; + _builder = new PublishedContentRequestBuilder(this); + + // set default + this.RenderingEngine = RenderingEngine.Mvc; + } #region Properties /// - /// Returns the current RoutingContext + /// The identifier of the requested node, if any, else zero. + /// + int _nodeId = 0; + + /// + /// The requested node, if any, else null. + /// + private IPublishedContent _publishedContent = null; + + /// + /// The "umbraco page" object. + /// + private page _umbracoPage; + + /// + /// Gets or sets the current RoutingContext. /// public RoutingContext RoutingContext { get; private set; } /// - /// The cleaned up Uri used for routing + /// Gets or sets the cleaned up Uri used for routing. /// public Uri Uri { get; private set; } /// - /// Gets or sets the document request's domain. + /// Gets or sets the content request's domain. /// public Domain Domain { get; internal set; } + /// + /// Gets or sets the content request's domain Uri. + /// + /// The Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". public Uri DomainUri { get; internal set; } /// - /// Gets or sets whether the rendering engine is MVC or WebForms + /// Gets or sets whether the rendering engine is MVC or WebForms. /// public RenderingEngine RenderingEngine { get; internal set; } /// - /// Gets a value indicating whether the document request has a domain. + /// Gets a value indicating whether the content request has a domain. /// public bool HasDomain { @@ -173,14 +234,12 @@ namespace Umbraco.Web.Routing } /// - /// Gets or sets the document request's culture + /// Gets or sets the content request's culture. /// public CultureInfo Culture { get; set; } - private page _umbracoPage; - /// - /// Returns the Umbraco page object + /// Gets or sets the "umbraco page" object. /// /// /// This value is only used for legacy/webforms code. @@ -190,9 +249,8 @@ namespace Umbraco.Web.Routing get { if (_umbracoPage == null) - { throw new InvalidOperationException("The umbraco page object is only available once Finalize()"); - } + return _umbracoPage; } set { _umbracoPage = value; } @@ -201,6 +259,9 @@ namespace Umbraco.Web.Routing // TODO: fixme - do we want to have an ordered list of alternate cultures, // to allow for fallbacks when doing dictionnary lookup and such? + /// + /// Gets or sets the requested content. + /// public IPublishedContent PublishedContent { get { return _publishedContent; } @@ -213,12 +274,12 @@ namespace Umbraco.Web.Routing } /// - /// Gets or sets the document request's template lookup + /// Gets or sets the template to use to display the requested content. /// public Template Template { get; set; } /// - /// Gets a value indicating whether the document request has a template. + /// Gets a value indicating whether the content request has a template. /// public bool HasTemplate { @@ -226,9 +287,9 @@ namespace Umbraco.Web.Routing } /// - /// Gets the id of the document. + /// Gets the identifier of the requested content. /// - /// Thrown when the document request has no document. + /// Thrown when the content request has no content. public int DocumentId { get @@ -240,7 +301,7 @@ namespace Umbraco.Web.Routing } /// - /// Gets a value indicating whether the document request has a document. + /// Gets a value indicating whether the content request has a content. /// public bool HasNode { @@ -248,21 +309,21 @@ namespace Umbraco.Web.Routing } /// - /// Gets or sets a value indicating whether the requested document could not be found. This is set in the PublishedContentRequestBuilder. + /// Gets or sets a value indicating whether the requested content could not be found. /// + /// This is set in the PublishedContentRequestBuilder. internal bool Is404 { get; set; } /// - /// Gets a value indicating whether the document request triggers a redirect. + /// Gets a value indicating whether the content request triggers a redirect. /// public bool IsRedirect { get { return !string.IsNullOrWhiteSpace(this.RedirectUrl); } } /// - /// Gets the url to redirect to, when the document request triggers a redirect. + /// Gets or sets the url to redirect to, when the content request triggers a redirect. /// public string RedirectUrl { get; set; } - #endregion - + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestBuilder.cs b/src/Umbraco.Web/Routing/PublishedContentRequestBuilder.cs index 4c0b61214d..6b8025cd29 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestBuilder.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestBuilder.cs @@ -175,7 +175,7 @@ namespace Umbraco.Web.Routing /// Performs the document resolution second pass. /// /// The second pass consists in handling "not found", internal redirects, access validation, and template. - private void LookupDocument2() + internal void LookupDocument2() { const string tracePrefix = "LookupDocument2: "; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 528e295273..9f3080b756 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -313,7 +313,6 @@ - diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 0a84dbf9f0..4b3367ef07 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -101,12 +101,10 @@ namespace Umbraco.Web // legacy - no idea what this is but does something with the query strings LegacyCleanUmbPageFromQueryString(ref uri); - //process the request and on success, call the callback method: RewriteToUmbracoHandler - PublishedContentRequest.ProcessRequest( - httpContext, - umbracoContext, - umbracoContext.CleanedUmbracoUrl, //very important to use this url! it is the path only lowercased version of the current URL. - docreq => RewriteToUmbracoHandler(HttpContext.Current, uri.Query, docreq.RenderingEngine)); + // instanciate a request a process + // 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)); } /// @@ -320,7 +318,7 @@ namespace Umbraco.Web rewritePath = "~/" + GlobalSettings.Path.TrimStart(new[] { '~', '/' }).TrimEnd(new[] { '/' }) + "/RenderMvc"; - //First we rewrite the path to the path of the handler (i.e. default.aspx or /umbraco/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); //if it is MVC we need to do something special, we are not using TransferRequest as this will @@ -340,12 +338,9 @@ namespace Umbraco.Web case RenderingEngine.WebForms: default: rewritePath = "~/default.aspx"; - //First we rewrite the path to the path of the handler (i.e. default.aspx or /umbraco/RenderMvc ) + // rewrite the path to the path of the handler (i.e. default.aspx or /umbraco/RenderMvc ) context.RewritePath(rewritePath, "", currentQuery.TrimStart(new[] { '?' }), false); - //now, execute the handler - var webFormshandler = (global::umbraco.UmbracoDefault)BuildManager.CreateInstanceFromVirtualPath("~/default.aspx", typeof(global::umbraco.UmbracoDefault)); - context.RemapHandler(webFormshandler); break; }