From 343c14db48babb075b471d551d2b034e5f62f366 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 24 Sep 2012 11:36:25 -0200 Subject: [PATCH] fixing routing, 404, domains + add NiceUrl and UriUtility tests --- .../Configuration/UmbracoSettings.cs | 26 ++ .../Routing/NiceUrlProviderTests.cs | 134 +++++--- .../NiceUrlsProviderWithDomainsTests.cs | 299 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Tests/UriUtilityTests.cs | 88 +++++- .../DefaultPublishedContentStore.cs | 25 +- .../Routing/DefaultLastChanceLookup.cs | 36 ++- .../Routing/DocumentNotFoundHandler.cs | 27 +- .../Routing/DocumentRequestBuilder.cs | 12 + src/Umbraco.Web/Routing/LookupByNiceUrl.cs | 12 +- src/Umbraco.Web/Routing/NiceUrlProvider.cs | 89 +++--- src/Umbraco.Web/Routing/NoTemplateHandler.cs | 14 +- src/Umbraco.Web/UmbracoModule.cs | 33 +- src/Umbraco.Web/UriUtility.cs | 11 +- 14 files changed, 637 insertions(+), 170 deletions(-) create mode 100644 src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings.cs b/src/Umbraco.Core/Configuration/UmbracoSettings.cs index d7a7d4723c..1304b284de 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings.cs @@ -334,6 +334,8 @@ namespace Umbraco.Core.Configuration } } + static bool? _addTrailingSlash = null; + /// /// This will add a trailing slash (/) to urls when in directory url mode /// NOTICE: This will always return false if Directory Urls in not active @@ -346,6 +348,8 @@ namespace Umbraco.Core.Configuration { if (GlobalSettings.UseDirectoryUrls) { + if (_addTrailingSlash.HasValue) + return _addTrailingSlash.Value; bool result; if (bool.TryParse(GetKey("/settings/requestHandler/addTrailingSlash"), out result)) return result; @@ -361,6 +365,10 @@ namespace Umbraco.Core.Configuration return false; } } + internal set + { + _addTrailingSlash = value; + } } /// @@ -385,6 +393,24 @@ namespace Umbraco.Core.Configuration } } + public static bool HandleMissingTemplateAs404 + { + get + { + try + { + bool result; + if (bool.TryParse(GetKey("/settings/templates/handleMissingTemplateAs404"), out result)) + return result; + return false; + } + catch + { + return false; + } + } + } + /// /// Gets a value indicating whether umbraco will attempt to load any skins to override default template files diff --git a/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs b/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs index fd3c20471e..59d9ee3e14 100644 --- a/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs @@ -1,5 +1,6 @@ using System; using System.Configuration; +using System.Collections.Generic; using NUnit.Framework; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; @@ -30,46 +31,71 @@ namespace Umbraco.Tests.Routing [Test] public void Ensure_Cache_Is_Correct() { - var routingContext = GetRoutingContext("/test", 1111); ConfigurationManager.AppSettings.Set("umbracoUseDirectoryUrls", "true"); - var ids = new[] - { - new Tuple(1046, "/home"), - new Tuple(1173, "/home/sub1"), - new Tuple(1174, "/home/sub1/sub2"), - new Tuple(1176, "/home/sub1/sub-3"), - new Tuple(1177, "/home/sub1/custom-sub-1"), - new Tuple(1178, "/home/sub1/custom-sub-2"), - new Tuple(1175, "/home/sub-2"), - new Tuple(1172, "/test-page") - }; - foreach(var i in ids) + + var samples = new Dictionary { + { 1046, "/home" }, + { 1173, "/home/sub1" }, + { 1174, "/home/sub1/sub2" }, + { 1176, "/home/sub1/sub-3" }, + { 1177, "/home/sub1/custom-sub-1" }, + { 1178, "/home/sub1/custom-sub-2" }, + { 1175, "/home/sub-2" }, + { 1172, "/test-page" } + }; + + foreach (var sample in samples) { - var result = routingContext.NiceUrlProvider.GetNiceUrl(i.Item1); - Assert.AreEqual(i.Item2, result); + var result = routingContext.NiceUrlProvider.GetNiceUrl(sample.Key); + Assert.AreEqual(sample.Value, result); + } + + var randomSample = new KeyValuePair(1177, "/home/sub1/custom-sub-1"); + for (int i = 0; i < 5; i++) + { + var result = routingContext.NiceUrlProvider.GetNiceUrl(randomSample.Key); + Assert.AreEqual(randomSample.Value, result); + } + + var cachedRoutes = ((DefaultRoutesCache)routingContext.UmbracoContext.RoutesCache).GetCachedRoutes(); + Assert.AreEqual(8, cachedRoutes.Count); + + foreach (var sample in samples) + { + Assert.IsTrue(cachedRoutes.ContainsKey(sample.Key)); + Assert.AreEqual(sample.Value, cachedRoutes[sample.Key]); + } + + var cachedIds = ((DefaultRoutesCache)routingContext.UmbracoContext.RoutesCache).GetCachedIds(); + Assert.AreEqual(8, cachedIds.Count); + + foreach (var sample in samples) + { + var key = sample.Value; + Assert.IsTrue(cachedIds.ContainsKey(key)); + Assert.AreEqual(sample.Key, cachedIds[key]); } - Assert.AreEqual(8, ((DefaultRoutesCache)routingContext.UmbracoContext.RoutesCache).GetCachedRoutes().Count); - Assert.AreEqual(8, ((DefaultRoutesCache)routingContext.UmbracoContext.RoutesCache).GetCachedIds().Count); } - [TestCase(1046, "/home.aspx")] - [TestCase(1173, "/home/sub1.aspx")] - [TestCase(1174, "/home/sub1/sub2.aspx")] - [TestCase(1176, "/home/sub1/sub-3.aspx")] - [TestCase(1177, "/home/sub1/custom-sub-1.aspx")] - [TestCase(1178, "/home/sub1/custom-sub-2.aspx")] - [TestCase(1175, "/home/sub-2.aspx")] - [TestCase(1172, "/test-page.aspx")] - public void Get_Nice_Url_Not_Hiding_Top_Level_No_Directory_Urls(int nodeId, string niceUrlMatch) - { - var routingContext = GetRoutingContext("/test", 1111); + //[TestCase(1046, "/home.aspx")] + //[TestCase(1173, "/home/sub1.aspx")] + //[TestCase(1174, "/home/sub1/sub2.aspx")] + //[TestCase(1176, "/home/sub1/sub-3.aspx")] + //[TestCase(1177, "/home/sub1/custom-sub-1.aspx")] + //[TestCase(1178, "/home/sub1/custom-sub-2.aspx")] + //[TestCase(1175, "/home/sub-2.aspx")] + //[TestCase(1172, "/test-page.aspx")] + //public void Get_Nice_Url_Not_Hiding_Top_Level_No_Directory_Urls(int nodeId, string niceUrlMatch) + //{ + // var routingContext = GetRoutingContext("/test", 1111); - var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId); + // var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId); - Assert.AreEqual(niceUrlMatch, result); - } + // Assert.AreEqual(niceUrlMatch, result); + //} + // test hideTopLevelNodeFromPath false [TestCase(1046, "/home")] [TestCase(1173, "/home/sub1")] [TestCase(1174, "/home/sub1/sub2")] @@ -78,36 +104,40 @@ namespace Umbraco.Tests.Routing [TestCase(1178, "/home/sub1/custom-sub-2")] [TestCase(1175, "/home/sub-2")] [TestCase(1172, "/test-page")] - public void Get_Nice_Url_Not_Hiding_Top_Level_With_Directory_Urls(int nodeId, string niceUrlMatch) + + public void Get_Nice_Url_Not_Hiding_Top_Level(int nodeId, string niceUrlMatch) { var routingContext = GetRoutingContext("/test", 1111); ConfigurationManager.AppSettings.Set("umbracoUseDirectoryUrls", "true"); + ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", "false"); var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId); - Assert.AreEqual(niceUrlMatch, result); } - [TestCase(1046, "/")] - [TestCase(1173, "/sub1.aspx")] - [TestCase(1174, "/sub1/sub2.aspx")] - [TestCase(1176, "/sub1/sub-3.aspx")] - [TestCase(1177, "/sub1/custom-sub-1.aspx")] - [TestCase(1178, "/sub1/custom-sub-2.aspx")] - [TestCase(1175, "/sub-2.aspx")] - [TestCase(1172, "/test-page.aspx")] - public void Get_Nice_Url_Hiding_Top_Level_No_Directory_Urls(int nodeId, string niceUrlMatch) - { - var routingContext = GetRoutingContext("/test", 1111); + // no need for umbracoUseDirectoryUrls test = should be handled by UriUtilityTests - ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", "true"); + //[TestCase(1046, "/")] + //[TestCase(1173, "/sub1.aspx")] + //[TestCase(1174, "/sub1/sub2.aspx")] + //[TestCase(1176, "/sub1/sub-3.aspx")] + //[TestCase(1177, "/sub1/custom-sub-1.aspx")] + //[TestCase(1178, "/sub1/custom-sub-2.aspx")] + //[TestCase(1175, "/sub-2.aspx")] + //[TestCase(1172, "/test-page.aspx")] + //public void Get_Nice_Url_Hiding_Top_Level_No_Directory_Urls(int nodeId, string niceUrlMatch) + //{ + // var routingContext = GetRoutingContext("/test", 1111); - var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId); + // ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", "true"); - Assert.AreEqual(niceUrlMatch, result); - } + // var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId); + // Assert.AreEqual(niceUrlMatch, result); + //} + + // test hideTopLevelNodeFromPath true [TestCase(1046, "/")] [TestCase(1173, "/sub1")] [TestCase(1174, "/sub1/sub2")] @@ -115,16 +145,16 @@ namespace Umbraco.Tests.Routing [TestCase(1177, "/sub1/custom-sub-1")] [TestCase(1178, "/sub1/custom-sub-2")] [TestCase(1175, "/sub-2")] - [TestCase(1172, "/test-page")] - public void Get_Nice_Url_Hiding_Top_Level_With_Directory_Urls(int nodeId, string niceUrlMatch) + [TestCase(1172, "/test-page")] // not hidden because not first root + + public void Get_Nice_Url_Hiding_Top_Level(int nodeId, string niceUrlMatch) { var routingContext = GetRoutingContext("/test", 1111); - ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", "true"); ConfigurationManager.AppSettings.Set("umbracoUseDirectoryUrls", "true"); + ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", "true"); var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId); - Assert.AreEqual(niceUrlMatch, result); } } diff --git a/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs new file mode 100644 index 0000000000..f83f040c01 --- /dev/null +++ b/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs @@ -0,0 +1,299 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Configuration; +using NUnit.Framework; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Routing; +using umbraco.cms.businesslogic.web; +using umbraco.cms.businesslogic.language; + +namespace Umbraco.Tests.Routing +{ + [TestFixture] + public class NiceUrlsProviderWithDomainsTests : BaseRoutingTest + { + public override void TearDown() + { + base.TearDown(); + + ConfigurationManager.AppSettings.Set("umbracoUseDirectoryUrls", ""); + ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", ""); + + ClearLanguagesAndDomains(); + } + + internal override IRoutesCache GetRoutesCache() + { + return new DefaultRoutesCache(false); + } + + void ClearLanguagesAndDomains() + { + var domains = Domain.GetDomains(); + foreach (var d in domains) + d.Delete(); + + var langs = Language.GetAllAsList(); + foreach (var l in langs.Skip(1)) + l.Delete(); + } + + void InitializeLanguagesAndDomains() + { + var domains = Domain.GetDomains(); + foreach (var d in domains) + d.Delete(); + + var langs = Language.GetAllAsList(); + foreach (var l in langs.Skip(1)) + l.Delete(); + + Language.MakeNew("fr-FR"); + } + + void SetDomains1() + { + var langEn = Language.GetByCultureCode("en-US"); + var langFr = Language.GetByCultureCode("fr-FR"); + + Domain.MakeNew("domain1.com", 1001, langFr.id); + } + + void SetDomains2() + { + var langEn = Language.GetByCultureCode("en-US"); + var langFr = Language.GetByCultureCode("fr-FR"); + + Domain.MakeNew("http://domain1.com/foo", 1001, langFr.id); + } + + void SetDomains3() + { + var langEn = Language.GetByCultureCode("en-US"); + var langFr = Language.GetByCultureCode("fr-FR"); + + Domain.MakeNew("http://domain1.com/", 10011, langFr.id); + } + + void SetDomains4() + { + var langEn = Language.GetByCultureCode("en-US"); + var langFr = Language.GetByCultureCode("fr-FR"); + + Domain.MakeNew("http://domain1.com/", 1001, langFr.id); + Domain.MakeNew("http://domain1.com/en", 10011, langEn.id); + Domain.MakeNew("http://domain1.com/fr", 10012, langEn.id); + } + + protected override string GetXmlContent(int templateId) + { + return @" + + +]> + + + + + This is some content]]> + + + + + + + + + + + + + + + This is some content]]> + + + + + + + + + + + + + + + + + + +"; + } + + // with one simple domain "domain1.com" + // basic tests + [TestCase(1001, "http://domain1.com", false, "/")] + [TestCase(10011, "http://domain1.com", false, "/1001-1")] + [TestCase(1002, "http://domain1.com", false, "/1002")] + // absolute tests + [TestCase(1001, "http://domain1.com", true, "http://domain1.com/")] + [TestCase(10011, "http://domain1.com", true, "http://domain1.com/1001-1")] + // different current tests + [TestCase(1001, "http://domain2.com", false, "http://domain1.com/")] + [TestCase(10011, "http://domain2.com", false, "http://domain1.com/1001-1")] + [TestCase(1001, "https://domain1.com", false, "/")] + [TestCase(10011, "https://domain1.com", false, "/1001-1")] + + public void Get_Nice_Url_SimpleDomain(int nodeId, string currentUrl, bool absolute, string expected) + { + var routingContext = GetRoutingContext("/test", 1111); + + ConfigurationManager.AppSettings.Set("umbracoUseDirectoryUrls", "true"); + ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", "false"); // ignored w/domains + + InitializeLanguagesAndDomains(); + SetDomains1(); + + var currentUri = new Uri(currentUrl); + var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId, currentUri, absolute); + Assert.AreEqual(expected, result); + } + + // with one complete domain "http://domain1.com/foo" + // basic tests + [TestCase(1001, "http://domain1.com", false, "/foo")] + [TestCase(10011, "http://domain1.com", false, "/foo/1001-1")] + [TestCase(1002, "http://domain1.com", false, "/1002")] + // absolute tests + [TestCase(1001, "http://domain1.com", true, "http://domain1.com/foo")] + [TestCase(10011, "http://domain1.com", true, "http://domain1.com/foo/1001-1")] + // different current tests + [TestCase(1001, "http://domain2.com", false, "http://domain1.com/foo")] + [TestCase(10011, "http://domain2.com", false, "http://domain1.com/foo/1001-1")] + [TestCase(1001, "https://domain1.com", false, "http://domain1.com/foo")] + [TestCase(10011, "https://domain1.com", false, "http://domain1.com/foo/1001-1")] + + public void Get_Nice_Url_SimpleWithSchemeAndPath(int nodeId, string currentUrl, bool absolute, string expected) + { + var routingContext = GetRoutingContext("/test", 1111); + + ConfigurationManager.AppSettings.Set("umbracoUseDirectoryUrls", "true"); + ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", "false"); // ignored w/domains + + InitializeLanguagesAndDomains(); + SetDomains2(); + + var currentUri = new Uri(currentUrl); + var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId, currentUri, absolute); + Assert.AreEqual(expected, result); + } + + // with one domain, not at root + [TestCase(1001, "http://domain1.com", false, "/1001")] + [TestCase(10011, "http://domain1.com", false, "/")] + [TestCase(100111, "http://domain1.com", false, "/1001-1-1")] + [TestCase(1002, "http://domain1.com", false, "/1002")] + + public void Get_Nice_Url_DeepDomain(int nodeId, string currentUrl, bool absolute, string expected) + { + var routingContext = GetRoutingContext("/test", 1111); + + ConfigurationManager.AppSettings.Set("umbracoUseDirectoryUrls", "true"); + ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", "false"); // ignored w/domains + + InitializeLanguagesAndDomains(); + SetDomains3(); + + var currentUri = new Uri(currentUrl); + var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId, currentUri, absolute); + Assert.AreEqual(expected, result); + } + + // with nested domains + [TestCase(1001, "http://domain1.com", false, "/")] + [TestCase(10011, "http://domain1.com", false, "/en")] + [TestCase(100111, "http://domain1.com", false, "/en/1001-1-1")] + [TestCase(10012, "http://domain1.com", false, "/fr")] + [TestCase(100121, "http://domain1.com", false, "/fr/1001-2-1")] + [TestCase(10013, "http://domain1.com", false, "/1001-3")] + [TestCase(1002, "http://domain1.com", false, "/1002")] + + public void Get_Nice_Url_NestedDomains(int nodeId, string currentUrl, bool absolute, string expected) + { + var routingContext = GetRoutingContext("/test", 1111); + + ConfigurationManager.AppSettings.Set("umbracoUseDirectoryUrls", "true"); + ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", "false"); // ignored w/domains + + InitializeLanguagesAndDomains(); + SetDomains4(); + + var currentUri = new Uri(currentUrl); + var result = routingContext.NiceUrlProvider.GetNiceUrl(nodeId, currentUri, absolute); + Assert.AreEqual(expected, result); + } + + [Test] + public void Get_Nice_Url_DomainsAndCache() + { + var routingContext = GetRoutingContext("/test", 1111); + + ConfigurationManager.AppSettings.Set("umbracoUseDirectoryUrls", "true"); + ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", "false"); // ignored w/domains + + InitializeLanguagesAndDomains(); + SetDomains4(); + + string ignore; + ignore = routingContext.NiceUrlProvider.GetNiceUrl(1001, new Uri("http://domain1.com"), false); + ignore = routingContext.NiceUrlProvider.GetNiceUrl(10011, new Uri("http://domain1.com"), false); + ignore = routingContext.NiceUrlProvider.GetNiceUrl(100111, new Uri("http://domain1.com"), false); + ignore = routingContext.NiceUrlProvider.GetNiceUrl(10012, new Uri("http://domain1.com"), false); + ignore = routingContext.NiceUrlProvider.GetNiceUrl(100121, new Uri("http://domain1.com"), false); + ignore = routingContext.NiceUrlProvider.GetNiceUrl(10013, new Uri("http://domain1.com"), false); + ignore = routingContext.NiceUrlProvider.GetNiceUrl(1002, new Uri("http://domain1.com"), false); + ignore = routingContext.NiceUrlProvider.GetNiceUrl(1001, new Uri("http://domain2.com"), false); + ignore = routingContext.NiceUrlProvider.GetNiceUrl(10011, new Uri("http://domain2.com"), false); + ignore = routingContext.NiceUrlProvider.GetNiceUrl(100111, new Uri("http://domain2.com"), false); + ignore = routingContext.NiceUrlProvider.GetNiceUrl(1002, new Uri("http://domain2.com"), false); + + var cachedRoutes = ((DefaultRoutesCache)routingContext.UmbracoContext.RoutesCache).GetCachedRoutes(); + Assert.AreEqual(7, cachedRoutes.Count); + + var cachedIds = ((DefaultRoutesCache)routingContext.UmbracoContext.RoutesCache).GetCachedIds(); + Assert.AreEqual(7, cachedIds.Count); + + CheckRoute(cachedRoutes, cachedIds, 1001, "1001/"); + CheckRoute(cachedRoutes, cachedIds, 10011, "10011/"); + CheckRoute(cachedRoutes, cachedIds, 100111, "10011/1001-1-1"); + CheckRoute(cachedRoutes, cachedIds, 10012, "10012/"); + CheckRoute(cachedRoutes, cachedIds, 100121, "10012/1001-2-1"); + CheckRoute(cachedRoutes, cachedIds, 10013, "1001/1001-3"); + CheckRoute(cachedRoutes, cachedIds, 1002, "/1002"); + + // use the cache + Assert.AreEqual("/", routingContext.NiceUrlProvider.GetNiceUrl(1001, new Uri("http://domain1.com"), false)); + Assert.AreEqual("/en", routingContext.NiceUrlProvider.GetNiceUrl(10011, new Uri("http://domain1.com"), false)); + Assert.AreEqual("/en/1001-1-1", routingContext.NiceUrlProvider.GetNiceUrl(100111, new Uri("http://domain1.com"), false)); + Assert.AreEqual("/fr", routingContext.NiceUrlProvider.GetNiceUrl(10012, new Uri("http://domain1.com"), false)); + Assert.AreEqual("/fr/1001-2-1", routingContext.NiceUrlProvider.GetNiceUrl(100121, new Uri("http://domain1.com"), false)); + Assert.AreEqual("/1001-3", routingContext.NiceUrlProvider.GetNiceUrl(10013, new Uri("http://domain1.com"), false)); + Assert.AreEqual("/1002", routingContext.NiceUrlProvider.GetNiceUrl(1002, new Uri("http://domain1.com"), false)); + + Assert.AreEqual("http://domain1.com/fr/1001-2-1", routingContext.NiceUrlProvider.GetNiceUrl(100121, new Uri("http://domain2.com"), false)); + } + + void CheckRoute(IDictionary routes, IDictionary ids, int id, string route) + { + Assert.IsTrue(routes.ContainsKey(id)); + Assert.AreEqual(route, routes[id]); + Assert.IsTrue(ids.ContainsKey(route)); + Assert.AreEqual(id, ids[route]); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index f35cdee1e5..81d32c037a 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -68,6 +68,7 @@ + diff --git a/src/Umbraco.Tests/UriUtilityTests.cs b/src/Umbraco.Tests/UriUtilityTests.cs index eed7df2baa..f01e065e86 100644 --- a/src/Umbraco.Tests/UriUtilityTests.cs +++ b/src/Umbraco.Tests/UriUtilityTests.cs @@ -1,28 +1,90 @@ using System; +using System.Configuration; using NUnit.Framework; using Umbraco.Web; namespace Umbraco.Tests { + // fixme - not testing virtual directory! + [TestFixture] public class UriUtilityTests { + // test normal urls + [TestCase("http://LocalHost/", "http://localhost/")] + [TestCase("http://LocalHost/?x=y", "http://localhost/?x=y")] + [TestCase("http://LocalHost/Home", "http://localhost/home")] + [TestCase("http://LocalHost/Home?x=y", "http://localhost/home?x=y")] + [TestCase("http://LocalHost/Home/Sub1", "http://localhost/home/sub1")] + [TestCase("http://LocalHost/Home/Sub1?x=y", "http://localhost/home/sub1?x=y")] - [TestCase("http://Localhost/", "http://localhost/")] - [TestCase("http://localhost/default.aspx", "http://localhost/")] - [TestCase("http://localhost/default.aspx?test=blah", "http://localhost/?test=blah")] - [TestCase("http://localhost/home/Sub1", "http://localhost/home/sub1")] - [TestCase("http://localhost/home/Sub1.aspx", "http://localhost/home/sub1")] - [TestCase("http://localhost/home/Sub1.aspx?test=blah", "http://localhost/home/sub1?test=blah")] - [TestCase("http://Localhost/home/sub1.aspx/blah", "http://localhost/home/sub1/blah")] - [TestCase("http://Localhost/home/sub1.aspx/blah?test=asdf", "http://localhost/home/sub1/blah?test=asdf")] - public void Uri_To_Umbraco(string url, string expected) + // same with .aspx + [TestCase("http://LocalHost/Home.aspx", "http://localhost/home")] + [TestCase("http://LocalHost/Home.aspx?x=y", "http://localhost/home?x=y")] + [TestCase("http://LocalHost/Home/Sub1.aspx", "http://localhost/home/sub1")] + [TestCase("http://LocalHost/Home/Sub1.aspx?x=y", "http://localhost/home/sub1?x=y")] + + // test that the trailing slash goes but not on hostname + [TestCase("http://LocalHost/", "http://localhost/")] + [TestCase("http://LocalHost/Home/", "http://localhost/home")] + [TestCase("http://LocalHost/Home/?x=y", "http://localhost/home?x=y")] + [TestCase("http://LocalHost/Home/Sub1/", "http://localhost/home/sub1")] + [TestCase("http://LocalHost/Home/Sub1/?x=y", "http://localhost/home/sub1?x=y")] + + // test that default.aspx goes, even with parameters + [TestCase("http://LocalHost/deFault.aspx", "http://localhost/")] + [TestCase("http://LocalHost/deFault.aspx?x=y", "http://localhost/?x=y")] + + // test with inner .aspx + [TestCase("http://Localhost/Home/Sub1.aspx/Sub2", "http://localhost/home/sub1/sub2")] + [TestCase("http://Localhost/Home/Sub1.aspx/Sub2?x=y", "http://localhost/home/sub1/sub2?x=y")] + [TestCase("http://Localhost/Home.aspx/Sub1.aspx/Sub2?x=y", "http://localhost/home/sub1/sub2?x=y")] + [TestCase("http://Localhost/deFault.aspx/Home.aspx/deFault.aspx/Sub1.aspx", "http://localhost/home/default/sub1")] + + public void Uri_To_Umbraco(string sourceUrl, string expectedUrl) { - var uri = new Uri(url); - var expectedUri = new Uri(expected); - var result = UriUtility.UriToUmbraco(uri); + var expectedUri = new Uri(expectedUrl); + var sourceUri = new Uri(sourceUrl); + var resultUri = UriUtility.UriToUmbraco(sourceUri); - Assert.AreEqual(expectedUri.ToString(), result.ToString()); + Assert.AreEqual(expectedUri.ToString(), resultUri.ToString()); + } + + // test directoryUrl false, trailingSlash false + [TestCase("/", "/", false, false)] + [TestCase("/home", "/home.aspx", false, false)] + [TestCase("/home/sub1", "/home/sub1.aspx", false, false)] + + // test directoryUrl false, trailingSlash true + [TestCase("/", "/", false, true)] + [TestCase("/home", "/home.aspx", false, true)] + [TestCase("/home/sub1", "/home/sub1.aspx", false, true)] + + // test directoryUrl true, trailingSlash false + [TestCase("/", "/", true, false)] + [TestCase("/home", "/home", true, false)] + [TestCase("/home/sub1", "/home/sub1", true, false)] + + // test directoryUrl true, trailingSlash true + [TestCase("/", "/", true, true)] + [TestCase("/home", "/home/", true, true)] + [TestCase("/home/sub1", "/home/sub1/", true, true)] + + public void Uri_From_Umbraco(string sourceUrl, string expectedUrl, bool directoryUrls, bool trailingSlash) + { + ConfigurationManager.AppSettings.Set("umbracoUseDirectoryUrls", directoryUrls ? "true" : "false"); + Umbraco.Core.Configuration.UmbracoSettings.AddTrailingSlash = trailingSlash; + + var expectedUri = NewUri(expectedUrl); + var sourceUri = NewUri(sourceUrl); + var resultUri = UriUtility.UriFromUmbraco(sourceUri); + + Assert.AreEqual(expectedUri.ToString(), resultUri.ToString()); + } + + Uri NewUri(string url) + { + return new Uri(url, url.StartsWith("http:") ? UriKind.Absolute : UriKind.Relative); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/DefaultPublishedContentStore.cs b/src/Umbraco.Web/DefaultPublishedContentStore.cs index 82cf091e03..69df99713e 100644 --- a/src/Umbraco.Web/DefaultPublishedContentStore.cs +++ b/src/Umbraco.Web/DefaultPublishedContentStore.cs @@ -38,33 +38,24 @@ namespace Umbraco.Web if (route == null) throw new ArgumentNullException("route"); //set the default to be what is in the settings - if (hideTopLevelNode == null) - { - hideTopLevelNode = GlobalSettings.HideTopLevelNodeFromPath; - } + hideTopLevelNode = hideTopLevelNode ?? GlobalSettings.HideTopLevelNodeFromPath; //the route always needs to be lower case because we only store the urlName attribute in lower case route = route.ToLowerInvariant(); - string startNodeIdString = "0"; - string path = route; - if (!route.StartsWith("/")) - { - int pos = route.IndexOf('/'); - startNodeIdString = route.Substring(0, pos); - path = route.Substring(pos + 1); - } - int startNodeId = int.Parse(startNodeIdString); + int pos = route.IndexOf('/'); + string path = pos == 0 ? route : route.Substring(pos); + int startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos)); var xpath = CreateXpathQuery(startNodeId, path, hideTopLevelNode.Value); //check if we can find the node in our xml cache var found = GetXml(umbracoContext).SelectSingleNode(xpath); - //this check is because we allow root nodes that don't have a domain assigned, if - //the previous check fails, we will check if this is a root node (based purely on it having a path with only 1 '/' which it is prefixed with) - //if it is a root node, we'll try to find it again without hiding top level nodes - if (found == null && path.ToCharArray().Count(x => x == '/') == 1) + // if hideTopLevelNodePath is true then for url /foo we looked for /*/foo + // but maybe that was the url of a non-default top-level node, so we also + // have to look for /foo (see note in NiceUrlProvider). + if (found == null && hideTopLevelNode.Value && path.Length > 1 && path.IndexOf('/', 1) < 0) { xpath = CreateXpathQuery(startNodeId, path, false); found = GetXml(umbracoContext).SelectSingleNode(xpath); diff --git a/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs b/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs index 038233f4e6..76c1ee22cb 100644 --- a/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs +++ b/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs @@ -16,6 +16,20 @@ namespace Umbraco.Web.Routing /// 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 DocumentRequest. @@ -98,22 +112,24 @@ namespace Umbraco.Web.Routing { var assemblyName = n.Attributes.GetNamedItem("assembly").Value; - // skip those that are in umbraco.dll because we have - // replaced them with ILookups already -- so we just - // want to load user-defined NotFound handlers... - if (assemblyName == "umbraco") - continue; - 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; - Type type = null; - LogHelper.Debug("Registering '{0}.{1},{2}'.", () => ns, () => typeName, () => assemblyName); - - try + 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!! diff --git a/src/Umbraco.Web/Routing/DocumentNotFoundHandler.cs b/src/Umbraco.Web/Routing/DocumentNotFoundHandler.cs index e578e8d5a7..51a508397f 100644 --- a/src/Umbraco.Web/Routing/DocumentNotFoundHandler.cs +++ b/src/Umbraco.Web/Routing/DocumentNotFoundHandler.cs @@ -14,21 +14,32 @@ namespace Umbraco.Web.Routing internal void WriteOutput(HttpContext context) { - context.Response.StatusCode = 404; + var response = context.Response; - context.Response.Write("

Page not found

"); - UmbracoContext.Current.HttpContext.Response.Write("

No umbraco document matches the url '" + HttpUtility.HtmlEncode(UmbracoContext.Current.ClientUrl) + "'.

"); + response.Clear(); + response.StatusCode = 404; - // fixme - should try to get infos from the DocumentRequest? + var docreq = UmbracoContext.Current.DocumentRequest; + var reason = "Cannot render the page at url '{0}'."; + if (!docreq.HasNode) + reason = "No umbraco document matches the url '{0}'."; + else if (!docreq.HasTemplate) + reason = "No template exists to render the document at url '{0}'."; - context.Response.Write("

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

"); - context.Response.Write("

This page is intentionally left ugly ;-)

"); - context.Response.Write(""); + response.Write("

Page not found

"); + response.Write("

"); + response.Write(string.Format(reason, HttpUtility.HtmlEncode(UmbracoContext.Current.ClientUrl))); + 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(""); + + response.End(); } public bool IsReusable { - get { return false; } + get { return true; } } } } diff --git a/src/Umbraco.Web/Routing/DocumentRequestBuilder.cs b/src/Umbraco.Web/Routing/DocumentRequestBuilder.cs index 4c53886d3b..6ab2df3e38 100644 --- a/src/Umbraco.Web/Routing/DocumentRequestBuilder.cs +++ b/src/Umbraco.Web/Routing/DocumentRequestBuilder.cs @@ -185,6 +185,7 @@ namespace Umbraco.Web.Routing // handle not found if (!_documentRequest.HasNode) { + _documentRequest.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 @@ -370,6 +371,17 @@ namespace Umbraco.Web.Routing var template = Template.GetByAlias(templateAlias); _documentRequest.Template = template; } + + if (!_documentRequest.HasTemplate) + { + LogHelper.Debug("{0}No template was found."); + // do not do it if we're already 404 else it creates an infinite loop + if (Umbraco.Core.Configuration.UmbracoSettings.HandleMissingTemplateAs404 && !_documentRequest.Is404) + { + LogHelper.Debug("{0}Assume page not found (404)."); + _documentRequest.Document = null; + } + } } /// diff --git a/src/Umbraco.Web/Routing/LookupByNiceUrl.cs b/src/Umbraco.Web/Routing/LookupByNiceUrl.cs index c5ed09e770..4e584b167c 100644 --- a/src/Umbraco.Web/Routing/LookupByNiceUrl.cs +++ b/src/Umbraco.Web/Routing/LookupByNiceUrl.cs @@ -42,12 +42,14 @@ namespace Umbraco.Web.Routing { LogHelper.Debug("Test route \"{0}\"", () => route); - //return '0' if in preview mode! + // first ask the cache for a node + // return '0' if in preview mode var nodeId = !docreq.RoutingContext.UmbracoContext.InPreviewMode ? docreq.RoutingContext.UmbracoContext.RoutesCache.GetNodeId(route) : 0; - + // if a node was found, get it by id and ensure it exists + // else clear the cache IDocument node = null; if (nodeId > 0) { @@ -66,6 +68,7 @@ namespace Umbraco.Web.Routing } } + // if we still have no node, get it by route if (node == null) { LogHelper.Debug("Cache miss, query"); @@ -78,10 +81,9 @@ namespace Umbraco.Web.Routing docreq.Document = node; LogHelper.Debug("Query matches, id={0}", () => docreq.DocumentId); + // do not store if previewing if (!docreq.RoutingContext.UmbracoContext.InPreviewMode) - { - docreq.RoutingContext.UmbracoContext.RoutesCache.Store(docreq.DocumentId, route); // will not write if previewing - } + docreq.RoutingContext.UmbracoContext.RoutesCache.Store(docreq.DocumentId, route); } else diff --git a/src/Umbraco.Web/Routing/NiceUrlProvider.cs b/src/Umbraco.Web/Routing/NiceUrlProvider.cs index 41addb9c5c..21fb08e5bb 100644 --- a/src/Umbraco.Web/Routing/NiceUrlProvider.cs +++ b/src/Umbraco.Web/Routing/NiceUrlProvider.cs @@ -4,11 +4,11 @@ using System.Linq; using System.Web; using Umbraco.Core; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Web.Routing; using umbraco; -using umbraco.IO; using umbraco.cms.businesslogic.web; namespace Umbraco.Web.Routing @@ -55,63 +55,67 @@ namespace Umbraco.Web.Routing /// The url is absolute or relative depending on the current url, unless absolute is true, and then it is always absolute. public string GetNiceUrl(int nodeId, Uri current, bool absolute) { - Uri domainUri = null; + Uri domainUri; + string path; - // will not read cache if previewing! + // do not read cache if previewing var route = _umbracoContext.InPreviewMode ? null : _umbracoContext.RoutesCache.GetRoute(nodeId); if (!string.IsNullOrEmpty(route)) { - domainUri = nodeId > 0 ? DomainUriAtNode(nodeId, current) : null; + // there was a route in the cache - extract domainUri and path + // route is / or / + int pos = route.IndexOf('/'); + path = pos == 0 ? route : route.Substring(pos); + domainUri = pos == 0 ? null : DomainUriAtNode(int.Parse(route.Substring(0, pos)), current); } else { - var originalNode = _publishedContentStore.GetDocumentById(_umbracoContext, nodeId); - if (originalNode == null) + // there was no route in the cache - create a route + var node = _publishedContentStore.GetDocumentById(_umbracoContext, nodeId); + if (node == null) { LogHelper.Warn( - "Couldn't find any page with the nodeId = {0}. This is most likely caused by the page isn't published!", + "Couldn't find any page with nodeId={0}. This is most likely caused by the page not being published.", nodeId); return "#"; } - - + + // walk up from that node until we hit a node with a domain, + // or we reach the content root, collecting urls in the way var pathParts = new List(); - int id = nodeId; - domainUri = DomainUriAtNode(id, current); - var recursiveNode = originalNode; - while (domainUri == null && id > 0) + var n = node; + domainUri = DomainUriAtNode(n.Id, current); + while (domainUri == null && n != null) // n is null at root { - var urlName = recursiveNode.UrlName; + // get the url + var urlName = n.UrlName; pathParts.Add(urlName); - recursiveNode = recursiveNode.Parent; // set to parent node - if (recursiveNode == null) - { - id = -1; - } - else - { - id = recursiveNode.Id; - } - domainUri = id > 0 ? DomainUriAtNode(id, current) : null; + + // move to parent node + n = n.Parent; + domainUri = n == null ? null : DomainUriAtNode(n.Id, current); } // no domain, respect HideTopLevelNodeFromPath for legacy purposes if (domainUri == null && global::umbraco.GlobalSettings.HideTopLevelNodeFromPath) { - //here we need to determine if this node is another root node (next sibling(s) to the first) because - // in that case, we do not remove the path part. In theory, like in v5, this node would require - // a domain assigned but to maintain compatibility we'll add this check - if (originalNode.Parent == null) + // in theory if hideTopLevelNodeFromPath is true, then there should be only once + // top-level node, or else domains should be assigned. but for backward compatibility + // we add this check - we look for the document matching "/" and if it's not us, then + // we do not hide the top level path + // it has to be taken care of in IPublishedContentStore.GetDocumentByRoute too so if + // "/foo" fails (looking for "/*/foo") we try also "/foo". + // this does not make much sense anyway esp. if both "/foo/" and "/bar/foo" exist, but + // that's the way it works pre-4.10 and we try to be backward compat for the time being + if (node.Parent == null) { var rootNode = _publishedContentStore.GetDocumentByRoute(_umbracoContext, "/", true); - if (rootNode.Id == originalNode.Id) - { + if (rootNode.Id == node.Id) // remove only if we're the default node pathParts.RemoveAt(pathParts.Count - 1); - } } else { @@ -119,15 +123,18 @@ namespace Umbraco.Web.Routing } } - + // assemble the route pathParts.Reverse(); - route = "/" + string.Join("/", pathParts); + path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc + route = (n == null ? "" : n.Id.ToString()) + path; + // do not store if previewing if (!_umbracoContext.InPreviewMode) _umbracoContext.RoutesCache.Store(nodeId, route); } - return AssembleUrl(domainUri, route, current, absolute).ToString(); + // assemble the url from domainUri (maybe null) and path + return AssembleUrl(domainUri, path, current, absolute).ToString(); } /// @@ -198,21 +205,27 @@ namespace Umbraco.Web.Routing if (domainUri == null) { // no domain was found : return a relative url, add vdir if any - uri = new Uri(global::umbraco.IO.SystemDirectories.Root + path, UriKind.Relative); + uri = new Uri(SystemDirectories.Root + path, UriKind.Relative); } else { // a domain was found : return an absolute or relative url // cannot handle vdir, has to be in domain uri if (!absolute && current != null && domainUri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority)) - uri = new Uri(domainUri.AbsolutePath.TrimEnd('/') + path, UriKind.Relative); // relative + uri = new Uri(CombinePaths(domainUri.AbsolutePath, path), UriKind.Relative); // relative else - uri = new Uri(domainUri.GetLeftPart(UriPartial.Path).TrimEnd('/') + path); // absolute + uri = new Uri(CombinePaths(domainUri.GetLeftPart(UriPartial.Path), path)); // absolute } return UriUtility.UriFromUmbraco(uri); } + string CombinePaths(string path1, string path2) + { + string path = path1.TrimEnd('/') + path2; + return path == "/" ? path : path.TrimEnd('/'); + } + IEnumerable AssembleUrls(IEnumerable domainUris, string path, Uri current) { if (domainUris.Any()) @@ -222,7 +235,7 @@ namespace Umbraco.Web.Routing else { // no domain was found : return a relative url, add vdir if any - return new Uri[] { new Uri(global::umbraco.IO.SystemDirectories.Root + path, UriKind.Relative) }; + return new Uri[] { new Uri(SystemDirectories.Root + path, UriKind.Relative) }; } } diff --git a/src/Umbraco.Web/Routing/NoTemplateHandler.cs b/src/Umbraco.Web/Routing/NoTemplateHandler.cs index 43ba2cd6c8..8131d0f195 100644 --- a/src/Umbraco.Web/Routing/NoTemplateHandler.cs +++ b/src/Umbraco.Web/Routing/NoTemplateHandler.cs @@ -14,18 +14,16 @@ namespace Umbraco.Web.Routing internal void WriteOutput(HttpContext context) { - context.Response.Clear(); - context.Response.Write(""); - context.Response.End(); + var response = context.Response; + + response.Clear(); + response.Write(""); + response.End(); } public bool IsReusable { - get - { - //yes this is reusable since it always returns the same thing - return true; - } + get { return true; } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index d5b400e3bd..a351bd4541 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -103,7 +103,7 @@ namespace Umbraco.Web var searcher = new DocumentRequestBuilder(docreq); //find domain searcher.LookupDomain(); - //redirect if it has been flagged + // redirect if it has been flagged if (docreq.IsRedirect) httpContext.Response.Redirect(docreq.RedirectUrl, true); //set the culture on the thread @@ -116,32 +116,37 @@ namespace Umbraco.Web //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 + // redirect if it has been flagged if (docreq.IsRedirect) httpContext.Response.Redirect(docreq.RedirectUrl, true); - //if no doc is found, send to our not found handler + // handle 404 if (docreq.Is404) { - httpContext.RemapHandler(new DocumentNotFoundHandler()); + httpContext.Response.StatusCode = 404; + + if (!docreq.HasNode) + httpContext.RemapHandler(new DocumentNotFoundHandler()); + else if (!docreq.HasTemplate) + httpContext.RemapHandler(new NoTemplateHandler()); + + // else we have a document to render } - else + + if (docreq.HasNode && docreq.HasTemplate) { + // everything is ready to pass off to our handlers (mvc or webforms) + // still need to setup a few things to deal with legacy code - //ok everything is ready to pass off to our handlers (mvc or webforms) but we need to setup a few things - //mostly to do with legacy code,etc... - - //we need to complete the request which assigns the page back to the docrequest to make it available for legacy handlers like default.aspx + // assign the legagcy page back to the docrequest + // handlers like default.aspx will want it docreq.UmbracoPage = new page(docreq); - //this is required for many legacy things in umbraco to work + // these two are used by many legacy objects httpContext.Items["pageID"] = docreq.DocumentId; - - //this is again required by many legacy objects - httpContext.Items.Add("pageElements", docreq.UmbracoPage.Elements); + httpContext.Items["pageElements"] = docreq.UmbracoPage.Elements; RewriteToUmbracoHandler(HttpContext.Current, uri.Query, docreq.RenderingEngine); - } } } diff --git a/src/Umbraco.Web/UriUtility.cs b/src/Umbraco.Web/UriUtility.cs index 596228e10d..34d7cead12 100644 --- a/src/Umbraco.Web/UriUtility.cs +++ b/src/Umbraco.Web/UriUtility.cs @@ -78,16 +78,17 @@ namespace Umbraco.Web //if this is the case we need to change it to '/' if (path.StartsWith("/default.aspx", StringComparison.InvariantCultureIgnoreCase)) { - path = "/" + path.Substring("/default.aspx".Length, path.Length - "/default.aspx".Length); + string rempath = path.Substring("/default.aspx".Length, path.Length - "/default.aspx".Length); + path = rempath.StartsWith("/") ? rempath : "/" + rempath; + } + if (path != "/") + { + path = path.TrimEnd('/'); } - if (path != "/") - path = path.TrimEnd('/'); //if any part of the path contains .aspx, replace it with nothing. //sometimes .aspx is not at the end since we might have /home/sub1.aspx/customtemplate path = path.Replace(".aspx", ""); - //if (path.EndsWith(".aspx")) - // path = path.Substring(0, path.Length - ".aspx".Length); return uri.Rewrite(path); }