From da7fea47c63c4e4408fe0080f44a4bcd5376e92c Mon Sep 17 00:00:00 2001 From: Stephan Date: Sun, 3 Feb 2013 14:34:04 -0100 Subject: [PATCH] U4-1441 - refactor (fix) legacy NotFoundHandler support --- .../Routing/DefaultLastChanceLookup.cs | 228 ------------------ .../Routing/LastChanceLookupResolver.cs | 10 +- src/Umbraco.Web/Routing/LookupByAlias.cs | 1 - src/Umbraco.Web/Routing/LookupByLegacy404.cs | 52 ++++ .../Routing/LookupByNotFoundHandlers.cs | 143 +++++++++++ .../Routing/NotFoundHandlerHelper.cs | 139 +++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 4 +- src/Umbraco.Web/WebBootManager.cs | 13 +- 8 files changed, 353 insertions(+), 237 deletions(-) delete mode 100644 src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs create mode 100644 src/Umbraco.Web/Routing/LookupByLegacy404.cs create mode 100644 src/Umbraco.Web/Routing/LookupByNotFoundHandlers.cs create mode 100644 src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs diff --git a/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs b/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs deleted file mode 100644 index 674a6e64a4..0000000000 --- a/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Web; -using System.Xml; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using umbraco.IO; -using umbraco.interfaces; - -namespace Umbraco.Web.Routing -{ - /// - /// Provides an implementation of that handles backward compatilibty with legacy INotFoundHandler. - /// - internal class DefaultLastChanceLookup : IDocumentLastChanceLookup - { - // notes - // - // at the moment we load the legacy INotFoundHandler - // excluding those that have been replaced by proper lookups, - // and run them. - // - // when we finaly obsolete INotFoundHandler, we'll have to move - // over here code from legacy requestHandler.hande404, which - // basically uses umbraco.library.GetCurrentNotFoundPageId(); - // which also would need to be refactored / migrated here. - // - // the best way to do this would be to create a DefaultLastChanceLookup2 - // that would do everything by itself, and let ppl use it if they - // want, then make it the default one, then remove this one. - - /// - /// Tries to find and assign an Umbraco document to a PublishedContentRequest. - /// - /// The PublishedContentRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public bool TrySetDocument(PublishedContentRequest docRequest) - { - docRequest.PublishedContent = HandlePageNotFound(docRequest); - return docRequest.HasNode; - } - - #region Copied over from presentation.requestHandler - - //FIXME: this is temporary and should be obsoleted - - string GetLegacyUrlForNotFoundHandlers(PublishedContentRequest docRequest) - { - // that's not backward-compatible because when requesting "/foo.aspx" - // 4.9 : url = "foo.aspx" - // 4.10 : url = "/foo" - //return docRequest.Uri.AbsolutePath; - - // so we have to run the legacy code for url preparation :-( - - // code from requestModule.UmbracoRewrite - string tmp = HttpContext.Current.Request.Path.ToLower(); - - // note: requestModule.UmbracoRewrite also does some confusing stuff - // with stripping &umbPage from the querystring?! ignored. - - // code from requestHandler.cleanUrl - string root = Umbraco.Core.IO.SystemDirectories.Root.ToLower(); - if (!string.IsNullOrEmpty(root) && tmp.StartsWith(root)) - tmp = tmp.Substring(root.Length); - tmp = tmp.TrimEnd('/'); - if (tmp == "/default.aspx") - tmp = string.Empty; - else if (tmp == root) - tmp = string.Empty; - - // code from UmbracoDefault.Page_PreInit - if (tmp != "" && HttpContext.Current.Request["umbPageID"] == null) - { - string tryIntParse = tmp.Replace("/", "").Replace(".aspx", string.Empty); - int result; - if (int.TryParse(tryIntParse, out result)) - tmp = tmp.Replace(".aspx", string.Empty); - } - else if (!string.IsNullOrEmpty(HttpContext.Current.Request["umbPageID"])) - { - int result; - if (int.TryParse(HttpContext.Current.Request["umbPageID"], out result)) - { - tmp = HttpContext.Current.Request["umbPageID"]; - } - } - - // code from requestHandler.ctor - if (tmp != "") - tmp = tmp.Substring(1); - - return tmp; - } - - IPublishedContent HandlePageNotFound(PublishedContentRequest docRequest) - { - LogHelper.Debug("Running for url='{0}'.", () => docRequest.Uri.AbsolutePath); - - //XmlNode currentPage = null; - IPublishedContent currentPage = null; - var url = GetLegacyUrlForNotFoundHandlers(docRequest); - - foreach (var handler in GetNotFoundHandlers()) - { - if (handler.Execute(url) && handler.redirectID > 0) - { - //currentPage = umbracoContent.GetElementById(handler.redirectID.ToString()); - currentPage = docRequest.RoutingContext.PublishedContentStore.GetDocumentById( - docRequest.RoutingContext.UmbracoContext, - handler.redirectID); - - // FIXME - could it be null? - - LogHelper.Debug("Handler '{0}' found node with id={1}.", () => handler.GetType().FullName, () => handler.redirectID); - - //// check for caching - //if (handler.CacheUrl) - //{ - // if (url.StartsWith("/")) - // url = "/" + url; - - // var cacheKey = (currentDomain == null ? "" : currentDomain.Name) + url; - // var culture = currentDomain == null ? null : currentDomain.Language.CultureAlias; - // SetCache(cacheKey, new CacheEntry(handler.redirectID.ToString(), culture)); - - // HttpContext.Current.Trace.Write("NotFoundHandler", - // string.Format("Added to cache '{0}', {1}.", url, handler.redirectID)); - //} - - break; - } - } - - return currentPage; - } - - static IEnumerable _customHandlerTypes = null; - static readonly object CustomHandlerTypesLock = new object(); - - IEnumerable InitializeNotFoundHandlers() - { - // initialize handlers - // create the definition cache - - LogHelper.Debug("Registering custom handlers."); - - var customHandlerTypes = new List(); - - var customHandlers = new XmlDocument(); - customHandlers.Load(Umbraco.Core.IO.IOHelper.MapPath(Umbraco.Core.IO.SystemFiles.NotFoundhandlersConfig)); - - foreach (XmlNode n in customHandlers.DocumentElement.SelectNodes("notFound")) - { - var assemblyName = n.Attributes.GetNamedItem("assembly").Value; - - var typeName = n.Attributes.GetNamedItem("type").Value; - string ns = assemblyName; - var nsAttr = n.Attributes.GetNamedItem("namespace"); - if (nsAttr != null && !string.IsNullOrWhiteSpace(nsAttr.Value)) - ns = nsAttr.Value; - - if (assemblyName == "umbraco" && (ns + "." + typeName) != "umbraco.handle404") - { - // skip those that are in umbraco.dll because we have replaced them with IDocumentLookups - // but do not skip "handle404" as that's the built-in legacy final handler, and for the time - // being people will have it in their config. - continue; - } - - LogHelper.Debug("Registering '{0}.{1},{2}'.", () => ns, () => typeName, () => assemblyName); - - Type type = null; - try - { - //TODO: This isn't a good way to load the assembly, its already in the Domain so we should be getting the type - // this loads the assembly into the wrong assembly load context!! - - var assembly = Assembly.LoadFrom(Umbraco.Core.IO.IOHelper.MapPath(Umbraco.Core.IO.SystemDirectories.Bin + "/" + assemblyName + ".dll")); - type = assembly.GetType(ns + "." + typeName); - } - catch (Exception e) - { - LogHelper.Error("Error registering handler, ignoring.", e); - } - - if (type != null) - customHandlerTypes.Add(type); - } - - return customHandlerTypes; - } - - IEnumerable GetNotFoundHandlers() - { - // instanciate new handlers - // using definition cache - - lock (CustomHandlerTypesLock) - { - if (_customHandlerTypes == null) - _customHandlerTypes = InitializeNotFoundHandlers(); - } - - var handlers = new List(); - - foreach (var type in _customHandlerTypes) - { - try - { - var handler = Activator.CreateInstance(type) as INotFoundHandler; - if (handler != null) - handlers.Add(handler); - } - catch (Exception e) - { - LogHelper.Error(string.Format("Error instanciating handler {0}, ignoring.", type.FullName), e); - } - } - - return handlers; - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/LastChanceLookupResolver.cs b/src/Umbraco.Web/Routing/LastChanceLookupResolver.cs index 0feadc8a4b..fc77661f95 100644 --- a/src/Umbraco.Web/Routing/LastChanceLookupResolver.cs +++ b/src/Umbraco.Web/Routing/LastChanceLookupResolver.cs @@ -8,9 +8,13 @@ namespace Umbraco.Web.Routing /// internal sealed class LastChanceLookupResolver : SingleObjectResolverBase { - - internal LastChanceLookupResolver(IDocumentLastChanceLookup lastChanceLookup) - : base(lastChanceLookup) + internal LastChanceLookupResolver() + : base(true) + { + } + + internal LastChanceLookupResolver(IDocumentLastChanceLookup lastChanceLookup) + : base(lastChanceLookup, true) { } diff --git a/src/Umbraco.Web/Routing/LookupByAlias.cs b/src/Umbraco.Web/Routing/LookupByAlias.cs index 0c0c023d23..530f12fcd6 100644 --- a/src/Umbraco.Web/Routing/LookupByAlias.cs +++ b/src/Umbraco.Web/Routing/LookupByAlias.cs @@ -24,7 +24,6 @@ namespace Umbraco.Web.Routing /// A value indicating whether an Umbraco document was found and assigned. public bool TrySetDocument(PublishedContentRequest docRequest) { - IPublishedContent node = null; if (docRequest.Uri.AbsolutePath != "/") // no alias if "/" diff --git a/src/Umbraco.Web/Routing/LookupByLegacy404.cs b/src/Umbraco.Web/Routing/LookupByLegacy404.cs new file mode 100644 index 0000000000..be73fa81c4 --- /dev/null +++ b/src/Umbraco.Web/Routing/LookupByLegacy404.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Routing +{ + internal class LookupByLegacy404 : IPublishedContentLookup + { + /// + /// Tries to find and assign an Umbraco document to a PublishedContentRequest. + /// + /// The PublishedContentRequest. + /// A value indicating whether an Umbraco document was found and assigned. + public bool TrySetDocument(PublishedContentRequest pcr) + { + LogHelper.Debug("Looking for a page to handle 404."); + + // TODO - replace the whole logic and stop calling into library! + var error404 = global::umbraco.library.GetCurrentNotFoundPageId(); + var id = int.Parse(error404); + + IPublishedContent content = null; + + if (id > 0) + { + LogHelper.Debug("Got id={0}.", () => id); + + content = pcr.RoutingContext.PublishedContentStore.GetDocumentById( + pcr.RoutingContext.UmbracoContext, + id); + + if (content == null) + LogHelper.Debug("Could not find content with that id."); + else + LogHelper.Debug("Found corresponding content."); + } + else + { + LogHelper.Debug("Got nothing."); + } + + pcr.PublishedContent = content; + pcr.Is404 = true; + return content != null; + } + } +} diff --git a/src/Umbraco.Web/Routing/LookupByNotFoundHandlers.cs b/src/Umbraco.Web/Routing/LookupByNotFoundHandlers.cs new file mode 100644 index 0000000000..09ba92d506 --- /dev/null +++ b/src/Umbraco.Web/Routing/LookupByNotFoundHandlers.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Web; +using System.Xml; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using umbraco.IO; +using umbraco.interfaces; + +namespace Umbraco.Web.Routing +{ + /// + /// Provides an implementation of that handles backward compatilibty with legacy INotFoundHandler. + /// + internal class LookupByNotFoundHandlers : IPublishedContentLookup + { + // notes + // + // at the moment we load the legacy INotFoundHandler + // excluding those that have been replaced by proper finders, + // and run them. + + /// + /// Tries to find and assign an Umbraco document to a PublishedContentRequest. + /// + /// The PublishedContentRequest. + /// A value indicating whether an Umbraco document was found and assigned. + public bool TrySetDocument(PublishedContentRequest docRequest) + { + HandlePageNotFound(docRequest); + return docRequest.HasNode; + } + + #region Copied over and adapted from presentation.requestHandler + + //FIXME: this is temporary and should be obsoleted + + void HandlePageNotFound(PublishedContentRequest docRequest) + { + LogHelper.Debug("Running for url='{0}'.", () => docRequest.Uri.AbsolutePath); + + var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers(); + + foreach (var handler in GetNotFoundHandlers()) + { + IPublishedContentLookup lookup = null; + + LogHelper.Debug("Handler '{0}'.", () => handler.GetType().FullName); + + // replace with our own implementation + if (handler is global::umbraco.SearchForAlias) + lookup = new LookupByAlias(); + else if (handler is global::umbraco.SearchForProfile) + lookup = new LookupByProfile(); + else if (handler is global::umbraco.SearchForTemplate) + lookup = new LookupByNiceUrlAndTemplate(); + else if (handler is global::umbraco.handle404) + lookup = new LookupByLegacy404(); + + if (lookup != null) + { + LogHelper.Debug("Replace handler '{0}' by new lookup '{1}'.", () => handler.GetType().FullName, () => lookup.GetType().FullName); + if (lookup.TrySetDocument(docRequest)) + { + // do NOT set docRequest.PublishedContent again here as + // it would clear any template that the finder might have set + LogHelper.Debug("Lookup '{0}' found node with id={1}.", () => lookup.GetType().FullName, () => docRequest.PublishedContent.Id); + if (docRequest.Is404) + LogHelper.Debug("Lookup '{0}' set status to 404.", () => lookup.GetType().FullName); + return; + } + } + + // else it's a legacy handler, run + + if (handler.Execute(url) && handler.redirectID > 0) + { + docRequest.PublishedContent = docRequest.RoutingContext.PublishedContentStore.GetDocumentById( + docRequest.RoutingContext.UmbracoContext, + handler.redirectID); + + if (!docRequest.HasNode) + { + LogHelper.Debug("Handler '{0}' found node with id={1} which is not valid.", () => handler.GetType().FullName, () => handler.redirectID); + break; + } + + LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handler.GetType().FullName, () => handler.redirectID); + + if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404) + { + LogHelper.Debug("Handler '{0}' set status code to 404.", () => handler.GetType().FullName); + docRequest.Is404 = true; + } + + //// check for caching + //if (handler.CacheUrl) + //{ + // if (url.StartsWith("/")) + // url = "/" + url; + + // var cacheKey = (currentDomain == null ? "" : currentDomain.Name) + url; + // var culture = currentDomain == null ? null : currentDomain.Language.CultureAlias; + // SetCache(cacheKey, new CacheEntry(handler.redirectID.ToString(), culture)); + + // HttpContext.Current.Trace.Write("NotFoundHandler", + // string.Format("Added to cache '{0}', {1}.", url, handler.redirectID)); + //} + + break; + } + } + } + + IEnumerable GetNotFoundHandlers() + { + // instanciate new handlers + // using definition cache + + var handlers = new List(); + + foreach (var type in NotFoundHandlerHelper.CustomHandlerTypes) + { + try + { + var handler = Activator.CreateInstance(type) as INotFoundHandler; + if (handler != null) + handlers.Add(handler); + } + catch (Exception e) + { + LogHelper.Error(string.Format("Error instanciating handler {0}, ignoring.", type.FullName), e); + } + } + + return handlers; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs new file mode 100644 index 0000000000..595c8fd7c9 --- /dev/null +++ b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using System.Xml; +using System.Reflection; + +using Umbraco.Core; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.Routing +{ + // provides internal access to legacy url -- should get rid of it eventually + internal static class NotFoundHandlerHelper + { + const string ContextKey = "Umbraco.Web.Routing.NotFoundHandlerHelper.Url"; + + static NotFoundHandlerHelper() + { + InitializeNotFoundHandlers(); + } + + public static string GetLegacyUrlForNotFoundHandlers() + { + // that's not backward-compatible because when requesting "/foo.aspx" + // 4.9 : url = "foo.aspx" + // 4.10 : url = "/foo" + //return pcr.Uri.AbsolutePath; + + // so we have to run the legacy code for url preparation :-( + + var httpContext = HttpContext.Current; + + if (httpContext == null) + return ""; + + var url = httpContext.Items[ContextKey] as string; + if (url != null) + return url; + + // code from requestModule.UmbracoRewrite + string tmp = httpContext.Request.Path.ToLower(); + + // note: requestModule.UmbracoRewrite also did some stripping of &umbPage + // from the querystring... that was in v3.x to fix some issues with pre-forms + // auth. Paul Sterling confirmed in jan. 2013 that we can get rid of it. + + // code from requestHandler.cleanUrl + string root = Umbraco.Core.IO.SystemDirectories.Root.ToLower(); + if (!string.IsNullOrEmpty(root) && tmp.StartsWith(root)) + tmp = tmp.Substring(root.Length); + tmp = tmp.TrimEnd('/'); + if (tmp == "/default.aspx") + tmp = string.Empty; + else if (tmp == root) + tmp = string.Empty; + + // code from UmbracoDefault.Page_PreInit + if (tmp != "" && httpContext.Request["umbPageID"] == null) + { + string tryIntParse = tmp.Replace("/", "").Replace(".aspx", string.Empty); + int result; + if (int.TryParse(tryIntParse, out result)) + tmp = tmp.Replace(".aspx", string.Empty); + } + else if (!string.IsNullOrEmpty(httpContext.Request["umbPageID"])) + { + int result; + if (int.TryParse(httpContext.Request["umbPageID"], out result)) + { + tmp = httpContext.Request["umbPageID"]; + } + } + + // code from requestHandler.ctor + if (tmp != "") + tmp = tmp.Substring(1); + + httpContext.Items[ContextKey] = tmp; + return tmp; + } + + static IEnumerable _customHandlerTypes = null; + + static void InitializeNotFoundHandlers() + { + // initialize handlers + // create the definition cache + + LogHelper.Debug("Registering custom handlers."); + + var customHandlerTypes = new List(); + + var customHandlers = new XmlDocument(); + customHandlers.Load(Umbraco.Core.IO.IOHelper.MapPath(Umbraco.Core.IO.SystemFiles.NotFoundhandlersConfig)); + + foreach (XmlNode n in customHandlers.DocumentElement.SelectNodes("notFound")) + { + var assemblyName = n.Attributes.GetNamedItem("assembly").Value; + var typeName = n.Attributes.GetNamedItem("type").Value; + + string ns = assemblyName; + var nsAttr = n.Attributes.GetNamedItem("namespace"); + if (nsAttr != null && !string.IsNullOrWhiteSpace(nsAttr.Value)) + ns = nsAttr.Value; + + LogHelper.Debug("Registering '{0}.{1},{2}'.", () => ns, () => typeName, () => assemblyName); + + Type type = null; + try + { + //TODO: This isn't a good way to load the assembly, its already in the Domain so we should be getting the type + // this loads the assembly into the wrong assembly load context!! + + var assembly = Assembly.LoadFrom(Umbraco.Core.IO.IOHelper.MapPath(Umbraco.Core.IO.SystemDirectories.Bin + "/" + assemblyName + ".dll")); + type = assembly.GetType(ns + "." + typeName); + } + catch (Exception e) + { + LogHelper.Error("Error registering handler, ignoring.", e); + } + + if (type != null) + customHandlerTypes.Add(type); + } + + _customHandlerTypes = customHandlerTypes; + } + + public static IEnumerable CustomHandlerTypes + { + get + { + return _customHandlerTypes; + } + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0a275a4e38..051977688a 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -315,8 +315,10 @@ + + @@ -398,7 +400,7 @@ - + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index b4aed9339d..a6ead39ab5 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -221,7 +221,9 @@ namespace Umbraco.Web typeof (RenderControllerFactory) }); - LastChanceLookupResolver.Current = new LastChanceLookupResolver(new DefaultLastChanceLookup()); + // the legacy 404 will run from within LookupByNotFoundHandlers below + // so for the time being there is no last chance lookup + LastChanceLookupResolver.Current = new LastChanceLookupResolver(); DocumentLookupsResolver.Current = new DocumentLookupsResolver( //add all known resolvers in the correct order, devs can then modify this list on application startup either by binding to events @@ -231,9 +233,12 @@ namespace Umbraco.Web typeof (LookupByPageIdQuery), typeof (LookupByNiceUrl), typeof (LookupByIdPath), - typeof (LookupByNiceUrlAndTemplate), - typeof (LookupByProfile), - typeof (LookupByAlias) + // these will be handled by LookupByNotFoundHandlers + // so they can be enabled/disabled even though resolvers are not public yet + //typeof (LookupByNiceUrlAndTemplate), + //typeof (LookupByProfile), + //typeof (LookupByAlias), + typeof (LookupByNotFoundHandlers) }); RoutesCacheResolver.Current = new RoutesCacheResolver(new DefaultRoutesCache(_isForTesting == false));