From 9e2733ce69fadeebbb064e828346dae2583680c9 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 26 Aug 2013 15:47:48 +0200 Subject: [PATCH] U4-2549 - fix issue with last chance content finder Conflicts: src/Umbraco.Web/Umbraco.Web.csproj Conflicts: src/Umbraco.Web/Umbraco.Web.csproj --- .../config/404handlers.Release.config | 2 +- src/Umbraco.Web.UI/config/404handlers.config | 2 +- src/Umbraco.Web/Routing/AliasUrlProvider.cs | 21 ++- .../ContentFinderByNotFoundHandlers.cs | 132 +++++++----------- ...ntentLastChanceFinderByNotFoundHandlers.cs | 101 ++++++++++++++ .../Routing/NotFoundHandlerHelper.cs | 101 ++++++++++++-- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/WebBootManager.cs | 38 +++-- 8 files changed, 279 insertions(+), 119 deletions(-) create mode 100644 src/Umbraco.Web/Routing/ContentLastChanceFinderByNotFoundHandlers.cs diff --git a/src/Umbraco.Web.UI/config/404handlers.Release.config b/src/Umbraco.Web.UI/config/404handlers.Release.config index 11bdbaf8ef..770f7ca64b 100644 --- a/src/Umbraco.Web.UI/config/404handlers.Release.config +++ b/src/Umbraco.Web.UI/config/404handlers.Release.config @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/404handlers.config b/src/Umbraco.Web.UI/config/404handlers.config index 69bf6f1dd0..b6dd88fa8b 100644 --- a/src/Umbraco.Web.UI/config/404handlers.config +++ b/src/Umbraco.Web.UI/config/404handlers.config @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index f8e270a16c..d8846afd60 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -88,14 +88,25 @@ namespace Umbraco.Web.Routing #region Utilities - private bool FindByUrlAliasEnabled + private static bool FindByUrlAliasEnabled { get { - var hasFinder = ContentFinderResolver.Current.ContainsType(); - var hasHandler = ContentFinderResolver.Current.ContainsType() - && NotFoundHandlerHelper.CustomHandlerTypes.Contains(typeof(global::umbraco.SearchForAlias)); - return hasFinder || hasHandler; + // finder + if (ContentFinderResolver.Current.ContainsType()) + return true; + + // handler wrapped into a finder + if (ContentFinderResolver.Current.ContainsType>()) + return true; + + // handler wrapped into special finder + if (ContentFinderResolver.Current.ContainsType() + && NotFoundHandlerHelper.IsNotFoundHandlerEnabled()) + return true; + + // anything else, we can't detect + return false; } } diff --git a/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandlers.cs b/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandlers.cs index 6c10a45dd7..cb80429167 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandlers.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByNotFoundHandlers.cs @@ -29,115 +29,77 @@ namespace Umbraco.Web.Routing #region Copied over and adapted from presentation.requestHandler - void HandlePageNotFound(PublishedContentRequest docRequest) + private static void HandlePageNotFound(PublishedContentRequest docRequest) { var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers(); LogHelper.Debug("Running for legacy url='{0}'.", () => url); - foreach (var handler in GetNotFoundHandlers()) + foreach (var handler in NotFoundHandlerHelper.GetNotFoundHandlers()) { - IContentFinder finder = null; var handlerName = handler.GetType().FullName; - LogHelper.Debug("Handler '{0}'.", () => handlerName); - // replace with our own implementation - if (handler is global::umbraco.SearchForAlias) - finder = new ContentFinderByUrlAlias(); - else if (handler is global::umbraco.SearchForProfile) - finder = new ContentFinderByProfile(); - else if (handler is global::umbraco.SearchForTemplate) - finder = new ContentFinderByNiceUrlAndTemplate(); - else if (handler is global::umbraco.handle404) - finder = new ContentFinderByLegacy404(); - + var finder = NotFoundHandlerHelper.SubsituteFinder(handler); if (finder != null) { var finderName = finder.GetType().FullName; LogHelper.Debug("Replace handler '{0}' by new finder '{1}'.", () => handlerName, () => finderName); - if (finder.TryFindContent(docRequest)) - { - // do NOT set docRequest.PublishedContent again here as - // it would clear any template that the finder might have set - LogHelper.Debug("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id); - if (docRequest.Is404) - LogHelper.Debug("Finder '{0}' set status to 404.", () => finderName); - // if we found a document, break, don't look at more handler -- we're done - break; - } + // can't find a document => continue with other handlers + if (finder.TryFindContent(docRequest) == false) + continue; - // if we did not find a document, continue, look at other handlers - continue; - } + // found a document => break, don't run other handlers, we're done - // else it's a legacy handler, run + // in theory an IContentFinder can return true yet set no document + // but none of the substitued finders (see SubstituteFinder) do it. - if (handler.Execute(url) && handler.redirectID > 0) - { - var redirectId = handler.redirectID; - docRequest.PublishedContent = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectId); + // do NOT set docRequest.PublishedContent again here + // as it would clear any template that the finder might have set - if (!docRequest.HasPublishedContent) - { - LogHelper.Debug("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId); - break; - } + LogHelper.Debug("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id); + if (docRequest.Is404) + LogHelper.Debug("Finder '{0}' set status to 404.", () => finderName); - LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId); - - if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404) - { - LogHelper.Debug("Handler '{0}' set status code to 404.", () => handlerName); - 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)); - //} - - // if we found a document, break, don't look at more handler -- we're done + LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => docRequest.PublishedContent.Id); break; } - // if we did not find a document, continue, look at other handlers + // else it's a legacy handler: run + + // can't find a document => continue with other handlers + if (handler.Execute(url) == false || handler.redirectID <= 0) + continue; + + // found a document ID => ensure it's a valid document + var redirectId = handler.redirectID; + docRequest.PublishedContent = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectId); + + if (docRequest.HasPublishedContent == false) + { + // the handler said it could handle the url, and returned a content ID + // yet that content ID is invalid... should we run the other handlers? + // I don't think so, not here, let the "last chance" finder take care. + // so, break. + + LogHelper.Debug("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId); + break; + } + + // found a valid document => break, don't run other handlers, we're done + + LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId); + + if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404) + { + LogHelper.Debug("Handler '{0}' set status code to 404.", () => handlerName); + docRequest.Is404 = true; + } + + 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/ContentLastChanceFinderByNotFoundHandlers.cs b/src/Umbraco.Web/Routing/ContentLastChanceFinderByNotFoundHandlers.cs new file mode 100644 index 0000000000..115e8cabc0 --- /dev/null +++ b/src/Umbraco.Web/Routing/ContentLastChanceFinderByNotFoundHandlers.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using umbraco.interfaces; + +namespace Umbraco.Web.Routing +{ + /// + /// Provides an implementation of that runs legacy INotFoundHandler in "last chance" situation. + /// + public class ContentLastChanceFinderByNotFoundHandlers : IContentFinder + { + // 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 TryFindContent(PublishedContentRequest docRequest) + { + HandlePageNotFound(docRequest); + return docRequest.HasPublishedContent; + } + + #region Copied over and adapted from presentation.requestHandler + + private static void HandlePageNotFound(PublishedContentRequest docRequest) + { + var url = NotFoundHandlerHelper.GetLegacyUrlForNotFoundHandlers(); + LogHelper.Debug("Running for legacy url='{0}'.", () => url); + + var handler = NotFoundHandlerHelper.GetNotFoundLastChanceHandler(); + var handlerName = handler.GetType().FullName; + LogHelper.Debug("Handler '{0}'.", () => handlerName); + + var finder = NotFoundHandlerHelper.SubsituteFinder(handler); + if (finder != null) + { + var finderName = finder.GetType().FullName; + LogHelper.Debug("Replace handler '{0}' by new finder '{1}'.", () => handlerName, () => finderName); + + // can't find a document => exit + if (finder.TryFindContent(docRequest) == false) + return; + + // found a document => we're done + + // in theory an IContentFinder can return true yet set no document + // but none of the substitued finders (see SubstituteFinder) do it. + + // do NOT set docRequest.PublishedContent again here + // as it would clear any template that the finder might have set + + LogHelper.Debug("Finder '{0}' found node with id={1}.", () => finderName, () => docRequest.PublishedContent.Id); + if (docRequest.Is404) + LogHelper.Debug("Finder '{0}' set status to 404.", () => finderName); + + LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => docRequest.PublishedContent.Id); + return; + } + + // else it's a legacy handler, run + + // can't find a document => exit + if (handler.Execute(url) == false || handler.redirectID <= 0) + return; + + // found a document ID => ensure it's a valid document + var redirectId = handler.redirectID; + docRequest.PublishedContent = docRequest.RoutingContext.UmbracoContext.ContentCache.GetById(redirectId); + + if (docRequest.HasPublishedContent == false) + { + // the handler said it could handle the url, and returned a content ID + // yet that content ID is invalid... exit. + + LogHelper.Debug("Handler '{0}' found node with id={1} which is not valid.", () => handlerName, () => redirectId); + return; + } + + // found a valid document => return + + LogHelper.Debug("Handler '{0}' found valid node with id={1}.", () => handlerName, () => redirectId); + + if (docRequest.RoutingContext.UmbracoContext.HttpContext.Response.StatusCode == 404) + { + LogHelper.Debug("Handler '{0}' set status code to 404.", () => handlerName); + docRequest.Is404 = true; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs index 4287b215d3..36d4a55bc9 100644 --- a/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs +++ b/src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Web; using System.Xml; using System.Reflection; +using Umbraco.Core; using Umbraco.Core.Logging; +using umbraco.interfaces; namespace Umbraco.Web.Routing { @@ -36,14 +39,14 @@ namespace Umbraco.Web.Routing return url; // code from requestModule.UmbracoRewrite - string tmp = httpContext.Request.Path.ToLower(); + var 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 = Core.IO.SystemDirectories.Root.ToLower(); + var root = Core.IO.SystemDirectories.Root.ToLower(); if (!string.IsNullOrEmpty(root) && tmp.StartsWith(root)) tmp = tmp.Substring(root.Length); tmp = tmp.TrimEnd('/'); @@ -55,7 +58,7 @@ namespace Umbraco.Web.Routing // code from UmbracoDefault.Page_PreInit if (tmp != "" && httpContext.Request["umbPageID"] == null) { - string tryIntParse = tmp.Replace("/", "").Replace(".aspx", string.Empty); + var tryIntParse = tmp.Replace("/", "").Replace(".aspx", string.Empty); int result; if (int.TryParse(tryIntParse, out result)) tmp = tmp.Replace(".aspx", string.Empty); @@ -77,7 +80,8 @@ namespace Umbraco.Web.Routing return tmp; } - static IEnumerable _customHandlerTypes; + private static IEnumerable _customHandlerTypes; + private static Type _customLastChanceHandlerType; static void InitializeNotFoundHandlers() { @@ -87,6 +91,8 @@ namespace Umbraco.Web.Routing LogHelper.Debug("Registering custom handlers."); var customHandlerTypes = new List(); + Type customLastChanceHandlerType = null; + var hasLast = false; var customHandlers = new XmlDocument(); customHandlers.Load(Core.IO.IOHelper.MapPath(Core.IO.SystemFiles.NotFoundhandlersConfig)); @@ -96,12 +102,23 @@ namespace Umbraco.Web.Routing var assemblyName = n.Attributes.GetNamedItem("assembly").Value; var typeName = n.Attributes.GetNamedItem("type").Value; - string ns = assemblyName; + var ns = assemblyName; var nsAttr = n.Attributes.GetNamedItem("namespace"); - if (nsAttr != null && !string.IsNullOrWhiteSpace(nsAttr.Value)) + if (nsAttr != null && string.IsNullOrWhiteSpace(nsAttr.Value) == false) ns = nsAttr.Value; - LogHelper.Debug("Registering '{0}.{1},{2}'.", () => ns, () => typeName, () => assemblyName); + var lcAttr = n.Attributes.GetNamedItem("last"); + var last = lcAttr != null && lcAttr.Value != null && lcAttr.Value.InvariantEquals("true"); + + if (last) // there can only be one last chance handler in the config file + { + if (hasLast) + throw new Exception(); + hasLast = true; + } + + LogHelper.Debug("Registering '{0}.{1},{2}'{3}", () => ns, () => typeName, () => assemblyName, + () => last ? " (last)." : "."); Type type = null; try @@ -114,19 +131,79 @@ namespace Umbraco.Web.Routing LogHelper.Error("Error registering handler, ignoring.", e); } - if (type != null) + if (type == null) continue; + + if (last) + _customLastChanceHandlerType = type; + else customHandlerTypes.Add(type); } - _customHandlerTypes = customHandlerTypes; + _customHandlerTypes = customHandlerTypes.ToArray(); } - public static IEnumerable CustomHandlerTypes + public static IEnumerable GetNotFoundHandlers() { - get + // instanciate new handlers + // using definition cache + + var handlers = new List(); + + foreach (var type in _customHandlerTypes) { - return _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; } + + public static bool IsNotFoundHandlerEnabled() + { + return _customHandlerTypes.Contains(typeof (T)); + } + + public static INotFoundHandler GetNotFoundLastChanceHandler() + { + if (_customLastChanceHandlerType == null) return null; + + try + { + var handler = Activator.CreateInstance(_customLastChanceHandlerType) as INotFoundHandler; + if (handler != null) + return handler; + } + catch (Exception e) + { + LogHelper.Error(string.Format("Error instanciating handler {0}, ignoring.", _customLastChanceHandlerType.FullName), e); + } + + return null; + } + + public static IContentFinder SubsituteFinder(INotFoundHandler handler) + { + IContentFinder finder = null; + + if (handler is global::umbraco.SearchForAlias) + finder = new ContentFinderByUrlAlias(); + else if (handler is global::umbraco.SearchForProfile) + finder = new ContentFinderByProfile(); + else if (handler is global::umbraco.SearchForTemplate) + finder = new ContentFinderByNiceUrlAndTemplate(); + else if (handler is global::umbraco.handle404) + finder = new ContentFinderByLegacy404(); + + return finder; + } + } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c8ab6c7f95..d65bcae0e6 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -292,6 +292,7 @@ + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index b2507f2174..35869fb76b 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -302,23 +302,31 @@ namespace Umbraco.Web typeof(DefaultUrlProvider) ); - // the legacy 404 will run from within ContentFinderByNotFoundHandlers below - // so for the time being there is no last chance finder - ContentLastChanceFinderResolver.Current = new ContentLastChanceFinderResolver(); + ContentLastChanceFinderResolver.Current = new ContentLastChanceFinderResolver( + // handled by ContentLastChanceFinderByNotFoundHandlers for the time being + // soon as we get rid of INotFoundHandler support, we must enable this + //new ContentFinderByLegacy404() + + // implement INotFoundHandler support... remove once we get rid of it + new ContentLastChanceFinderByNotFoundHandlers()); ContentFinderResolver.Current = new ContentFinderResolver( - // add all known resolvers in the correct order, devs can then modify this list - // on application startup either by binding to events or in their own global.asax - typeof (ContentFinderByPageIdQuery), - typeof (ContentFinderByNiceUrl), - typeof (ContentFinderByIdPath), - // these will be handled by ContentFinderByNotFoundHandlers - // so they can be enabled/disabled even though resolvers are not public yet - //typeof (ContentFinderByNiceUrlAndTemplate), - //typeof (ContentFinderByProfile), - //typeof (ContentFinderByUrlAlias), - typeof (ContentFinderByNotFoundHandlers) - ); + // all built-in finders in the correct order, devs can then modify this list + // on application startup via an application event handler. + typeof (ContentFinderByPageIdQuery), + typeof (ContentFinderByNiceUrl), + typeof (ContentFinderByIdPath), + + // these will be handled by ContentFinderByNotFoundHandlers so they can be enabled/disabled + // via the config file... soon as we get rid of INotFoundHandler support, we must enable + // them here. + //typeof (ContentFinderByNiceUrlAndTemplate), + //typeof (ContentFinderByProfile), + //typeof (ContentFinderByUrlAlias), + + // implement INotFoundHandler support... remove once we get rid of it + typeof (ContentFinderByNotFoundHandlers) + ); SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper());