diff --git a/src/Umbraco.Tests/UmbracoModuleTests.cs b/src/Umbraco.Tests/UmbracoModuleTests.cs index aab8759929..d12fbd80e8 100644 --- a/src/Umbraco.Tests/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/UmbracoModuleTests.cs @@ -133,12 +133,21 @@ namespace Umbraco.Tests [TestCase("/default.aspx?path=/", true)] [TestCase("/default.aspx?path=/home.aspx", true)] [TestCase("/default.aspx?path=/home.aspx?altTemplate=blah", true)] + [TestCase("/default.aspx?p=/home.aspx", false)] //missing path + [TestCase("/defaul.aspx?path=/home.aspx", false)] //not default path public void Process_Front_End_Document_Request(string url, bool assert) { var httpContextFactory = new FakeHttpContextFactory(url); var httpContext = httpContextFactory.HttpContext; var umbracoContext = new UmbracoContext(httpContext, ApplicationContext.Current, new DefaultRoutesCache(false)); - + var contentStore = new ContentStore(umbracoContext); + var niceUrls = new NiceUrlProvider(contentStore, umbracoContext); + umbracoContext.RoutingContext = new RoutingContext( + new IDocumentLookup[] {new LookupByNiceUrl()}, + new DefaultLastChanceLookup(), + contentStore, + niceUrls); + StateHelper.HttpContext = httpContext; //because of so much dependency on the db, we need to create som stuff here, i originally abstracted out stuff but @@ -152,9 +161,7 @@ namespace Umbraco.Tests var result = _module.ProcessFrontEndDocumentRequest( httpContext, - umbracoContext, - new IDocumentLookup[] {new LookupByNiceUrl()}, - new DefaultLastChanceLookup()); + umbracoContext); Assert.AreEqual(assert, result); } diff --git a/src/Umbraco.Web/Routing/DefaultRoutesCache.cs b/src/Umbraco.Web/Routing/DefaultRoutesCache.cs index d2b37f2a3a..a5a7bf8d02 100644 --- a/src/Umbraco.Web/Routing/DefaultRoutesCache.cs +++ b/src/Umbraco.Web/Routing/DefaultRoutesCache.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using Umbraco.Core; @@ -10,9 +11,8 @@ namespace Umbraco.Web.Routing /// internal class DefaultRoutesCache : IRoutesCache { - private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - private Dictionary _routes; - private Dictionary _nodeIds; + private ConcurrentDictionary _routes; + private ConcurrentDictionary _nodeIds; /// /// Initializes a new instance of the class. @@ -50,12 +50,9 @@ namespace Umbraco.Web.Routing /// The node identified. /// The route. public void Store(int nodeId, string route) - { - using (new WriteLock(_lock)) - { - _routes[nodeId] = route; - _nodeIds[route] = nodeId; - } + { + _routes.AddOrUpdate(nodeId, i => route, (i, s) => route); + _nodeIds.AddOrUpdate(route, i => nodeId, (i, s) => nodeId); } /// @@ -64,11 +61,10 @@ namespace Umbraco.Web.Routing /// The node identifier. /// The route for the node, else null. public string GetRoute(int nodeId) - { - lock (new ReadLock(_lock)) - { - return _routes.ContainsKey(nodeId) ? _routes[nodeId] : null; - } + { + string val; + _routes.TryGetValue(nodeId, out val); + return val; } /// @@ -78,10 +74,9 @@ namespace Umbraco.Web.Routing /// The node identified for the route, else zero. public int GetNodeId(string route) { - using (new ReadLock(_lock)) - { - return _nodeIds.ContainsKey(route) ? _nodeIds[route] : 0; - } + int val; + _nodeIds.TryGetValue(route, out val); + return val; } /// @@ -90,15 +85,17 @@ namespace Umbraco.Web.Routing /// The node identifier. public void ClearNode(int nodeId) { - using (var lck = new UpgradeableReadLock(_lock)) - { - if (_routes.ContainsKey(nodeId)) - { - lck.UpgradeToWriteLock(); - _nodeIds.Remove(_routes[nodeId]); - _routes.Remove(nodeId); - } - } + if (_routes.ContainsKey(nodeId)) + { + string key; + if (_routes.TryGetValue(nodeId, out key)) + { + int val; + _nodeIds.TryRemove(key, out val); + string val2; + _routes.TryRemove(nodeId, out val2); + } + } } /// @@ -106,11 +103,8 @@ namespace Umbraco.Web.Routing /// public void Clear() { - using (new WriteLock(_lock)) - { - _routes = new Dictionary(); - _nodeIds = new Dictionary(); - } + _routes = new ConcurrentDictionary(); + _nodeIds = new ConcurrentDictionary(); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/DocumentRequest.cs b/src/Umbraco.Web/Routing/DocumentRequest.cs index 9b36dd0eec..6096f0c567 100644 --- a/src/Umbraco.Web/Routing/DocumentRequest.cs +++ b/src/Umbraco.Web/Routing/DocumentRequest.cs @@ -24,10 +24,11 @@ namespace Umbraco.Web.Routing /// internal class DocumentRequest { - public DocumentRequest(Uri uri, RoutingContext routingContext) + public DocumentRequest(Uri uri, UmbracoContext umbracoContext) { this.Uri = uri; - RoutingContext = routingContext; + RoutingContext = umbracoContext.RoutingContext; + UmbracoContext = umbracoContext; } /// @@ -47,6 +48,8 @@ namespace Umbraco.Web.Routing /// public RoutingContext RoutingContext { get; private set; } + public UmbracoContext UmbracoContext { get; private set; } + public Uri Uri { get; private set; } /// @@ -159,7 +162,7 @@ namespace Umbraco.Web.Routing LogHelper.Debug("{0}Uri=\"{1}\"", () => tracePrefix, () => this.Uri); // try to find a domain matching the current request - var domainAndUri = DomainHelper.DomainMatch(Domain.GetDomains(), RoutingContext.UmbracoContext.UmbracoUrl, false); + var domainAndUri = DomainHelper.DomainMatch(Domain.GetDomains(), UmbracoContext.UmbracoUrl, false); // handle domain if (domainAndUri != null) @@ -401,9 +404,9 @@ namespace Umbraco.Web.Routing if (this.Node == null) throw new InvalidOperationException("There is no node."); - var templateAlias = RoutingContext.UmbracoContext.HttpContext.Request.QueryString["altTemplate"]; + var templateAlias = UmbracoContext.HttpContext.Request.QueryString["altTemplate"]; if (string.IsNullOrWhiteSpace(templateAlias)) - templateAlias = RoutingContext.UmbracoContext.HttpContext.Request.Form["altTemplate"]; + templateAlias = UmbracoContext.HttpContext.Request.Form["altTemplate"]; // fixme - we might want to support cookies?!? NO but provide a hook to change the template diff --git a/src/Umbraco.Web/Routing/LookupByNiceUrl.cs b/src/Umbraco.Web/Routing/LookupByNiceUrl.cs index 2b7c2d2d14..17f0e8007a 100644 --- a/src/Umbraco.Web/Routing/LookupByNiceUrl.cs +++ b/src/Umbraco.Web/Routing/LookupByNiceUrl.cs @@ -28,6 +28,9 @@ namespace Umbraco.Web.Routing else route = docreq.Uri.AbsolutePath; + //format the path + route = route.Replace(".aspx", ""); + var node = LookupDocumentNode(docreq, route); return node != null; } @@ -43,8 +46,8 @@ namespace Umbraco.Web.Routing LogHelper.Debug("Test route \"{0}\"", () => route); //return '0' if in preview mode! - var nodeId = !docreq.RoutingContext.UmbracoContext.InPreviewMode - ? docreq.RoutingContext.UmbracoContext.RoutesCache.GetNodeId(route) + var nodeId = !docreq.UmbracoContext.InPreviewMode + ? docreq.UmbracoContext.RoutesCache.GetNodeId(route) : 0; @@ -59,7 +62,7 @@ namespace Umbraco.Web.Routing } else { - docreq.RoutingContext.UmbracoContext.RoutesCache.ClearNode(nodeId); + docreq.UmbracoContext.RoutesCache.ClearNode(nodeId); } } @@ -72,9 +75,9 @@ namespace Umbraco.Web.Routing docreq.Node = node; LogHelper.Debug("Query matches, id={0}", () => docreq.NodeId); - if (!docreq.RoutingContext.UmbracoContext.InPreviewMode) + if (!docreq.UmbracoContext.InPreviewMode) { - docreq.RoutingContext.UmbracoContext.RoutesCache.Store(docreq.NodeId, route); // will not write if previewing + docreq.UmbracoContext.RoutesCache.Store(docreq.NodeId, route); // will not write if previewing } } diff --git a/src/Umbraco.Web/Routing/LookupByProfile.cs b/src/Umbraco.Web/Routing/LookupByProfile.cs index 29547b9b97..5e5b225313 100644 --- a/src/Umbraco.Web/Routing/LookupByProfile.cs +++ b/src/Umbraco.Web/Routing/LookupByProfile.cs @@ -45,7 +45,7 @@ namespace Umbraco.Web.Routing if (node != null) { //TODO: Should be handled by Context Items class manager (http://issues.umbraco.org/issue/U4-61) - docreq.RoutingContext.UmbracoContext.HttpContext.Items["umbMemberLogin"] = memberLogin; + docreq.UmbracoContext.HttpContext.Items["umbMemberLogin"] = memberLogin; } else { diff --git a/src/Umbraco.Web/Routing/NiceUrlProvider.cs b/src/Umbraco.Web/Routing/NiceUrlProvider.cs index a48f38b719..dd3c370332 100644 --- a/src/Umbraco.Web/Routing/NiceUrlProvider.cs +++ b/src/Umbraco.Web/Routing/NiceUrlProvider.cs @@ -184,7 +184,7 @@ namespace Umbraco.Web.Routing uri = new Uri(domainUri.GetLeftPart(UriPartial.Path).TrimEnd('/') + path); // absolute } - return UriFromUmbraco(uri); + return UriUtility.UriFromUmbraco(uri); } IEnumerable AssembleUrls(IEnumerable domainUris, string path, Uri current) @@ -222,39 +222,5 @@ namespace Umbraco.Web.Routing } #endregion - - #region Map public urls to/from umbraco urls - - // fixme - what about vdir? - // path = path.Substring(UriUtility.AppVirtualPathPrefix.Length); // remove virtual directory - - public static Uri UriFromUmbraco(Uri uri) - { - var path = uri.GetSafeAbsolutePath(); - if (path == "/") - return uri; - - if (!global::umbraco.GlobalSettings.UseDirectoryUrls) - path += ".aspx"; - else if (global::umbraco.UmbracoSettings.AddTrailingSlash) - path += "/"; - - return uri.Rewrite(path); - } - - public static Uri UriToUmbraco(Uri uri) - { - var path = uri.GetSafeAbsolutePath(); - - path = path.ToLower(); - if (path != "/") - path = path.TrimEnd('/'); - if (path.EndsWith(".aspx")) - path = path.Substring(0, path.Length - ".aspx".Length); - - return uri.Rewrite(path); - } - - #endregion - } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/RoutingContext.cs b/src/Umbraco.Web/Routing/RoutingContext.cs index a4fed55ab2..74b2bb51d1 100644 --- a/src/Umbraco.Web/Routing/RoutingContext.cs +++ b/src/Umbraco.Web/Routing/RoutingContext.cs @@ -18,23 +18,23 @@ namespace Umbraco.Web.Routing /// The content store. /// The nice urls resolver. internal RoutingContext( - UmbracoContext umbracoContext, + //UmbracoContext umbracoContext, IEnumerable documentLookups, IDocumentLastChanceLookup documentLastChanceLookup, ContentStore contentStore, NiceUrlProvider niceUrlResolver) { - this.UmbracoContext = umbracoContext; + //this.UmbracoContext = umbracoContext; this.DocumentLookups = documentLookups; DocumentLastChanceLookup = documentLastChanceLookup; this.ContentStore = contentStore; this.NiceUrlProvider = niceUrlResolver; } - /// - /// Gets the Umbraco context. - /// - public UmbracoContext UmbracoContext { get; private set; } + ///// + ///// Gets the Umbraco context. + ///// + //public UmbracoContext UmbracoContext { get; private set; } /// /// Gets the document lookups resolver. diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 6d50265d4a..95423584d1 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -52,18 +52,19 @@ namespace Umbraco.Web // set the urls this.RequestUrl = httpContext.Request.Url; - this.ClientUrl = httpContext.Request.Url; + //RawUrl gets preserved across rewrites + this.ClientUrl = UriUtility.ToFullUrl(httpContext.Request.RawUrl, httpContext); - // from IIS Rewrite Module documentation: - // "The URL Rewrite Module preserves the original requested URL path in the following server variables: - // - HTTP_X_ORIGINAL_URL – this server variable contains the original URL in decoded format; - // - UNENCODED_URL – this server variable contains the original URL exactly as it was requested by a Web client, with all original encoding preserved." - // - // see also http://forums.iis.net/t/1152581.aspx - // - var iisOrig = httpContext.Request.ServerVariables["HTTP_X_ORIGINAL_URL"]; - if (!string.IsNullOrWhiteSpace(iisOrig)) - this.ClientUrl = new Uri(iisOrig); + //// from IIS Rewrite Module documentation: + //// "The URL Rewrite Module preserves the original requested URL path in the following server variables: + //// - HTTP_X_ORIGINAL_URL – this server variable contains the original URL in decoded format; + //// - UNENCODED_URL – this server variable contains the original URL exactly as it was requested by a Web client, with all original encoding preserved." + //// + //// see also http://forums.iis.net/t/1152581.aspx + //// + //var iisOrig = httpContext.Request.ServerVariables["HTTP_X_ORIGINAL_URL"]; + //if (!string.IsNullOrWhiteSpace(iisOrig)) + // this.ClientUrl = new Uri(iisOrig); } /// @@ -132,7 +133,7 @@ namespace Umbraco.Web set { _requestUrl = value; - this.UmbracoUrl = NiceUrlProvider.UriToUmbraco(_requestUrl); + this.UmbracoUrl = UriUtility.UriToUmbraco(_requestUrl); } } @@ -140,7 +141,7 @@ namespace Umbraco.Web /// Gets the cleaned up url that is handled by Umbraco. /// /// That is, lowercase, no trailing slash after path, no .aspx... - internal Uri UmbracoUrl { get; set; } + internal Uri UmbracoUrl { get; private set; } private Func _xmlDelegate; @@ -197,6 +198,11 @@ namespace Umbraco.Web get { return DocumentRequest != null; } } + /// + /// Gets/sets the RoutingContext object + /// + internal RoutingContext RoutingContext { get; set; } + /// /// Gets/sets the DocumentRequest object /// diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 88b5eed2e2..065e669c74 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -32,16 +32,13 @@ namespace Umbraco.Web /// /// /// - /// - /// internal bool ProcessFrontEndDocumentRequest( HttpContextBase httpContext, - UmbracoContext umbracoContext, - IEnumerable docLookups, - IDocumentLastChanceLookup lastChanceLookup) + UmbracoContext umbracoContext) { if (httpContext == null) throw new ArgumentNullException("httpContext"); if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + if (umbracoContext.RoutingContext == null) throw new ArgumentNullException("umbracoContext.RoutingContext"); if (httpContext.Request.Url.LocalPath.InvariantStartsWith("/default.aspx") && !string.IsNullOrWhiteSpace(httpContext.Request.QueryString["path"])) @@ -62,22 +59,11 @@ namespace Umbraco.Web return false; } - //create request based objects (one per http request)... - - //create a content store - var contentStore = new ContentStore(umbracoContext); - //create the nice urls - var niceUrls = new NiceUrlProvider(contentStore, umbracoContext); - //create the RoutingContext - var routingContext = new RoutingContext( - umbracoContext, - docLookups, - lastChanceLookup, - contentStore, - niceUrls); + //Create a document request since we are rendering a document on the front-end + // create the new document request which will cleanup the uri once and for all - var docreq = new DocumentRequest(uri, routingContext); - // initialize the DocumentRequest on the UmbracoContext (this is circular dependency but i think in this case is ok) + var docreq = new DocumentRequest(uri, umbracoContext); + //assign the routing context to the umbraco context umbracoContext.DocumentRequest = docreq; // note - at that point the original legacy module did something do handle IIS custom 404 errors @@ -178,12 +164,23 @@ namespace Umbraco.Web RoutesCacheResolver.Current.RoutesCache); UmbracoContext.Current = umbracoContext; + //create a content store + var contentStore = new ContentStore(umbracoContext); + //create the nice urls + var niceUrls = new NiceUrlProvider(contentStore, umbracoContext); + //create the RoutingContext + var routingContext = new RoutingContext( + DocumentLookupsResolver.Current.DocumentLookups, + LastChanceLookupResolver.Current.LastChanceLookup, + contentStore, + niceUrls); + //assign the routing context back to the umbraco context + umbracoContext.RoutingContext = routingContext; + //Does a check to see if this current request contains the information in order to process the //request as a front-end request. If not, then its because the rewrite hasn't taken place yet. //if we need to rewrite, then we'll cleanup the query strings and rewrite to the front end handler. - if (!ProcessFrontEndDocumentRequest(httpContext, umbracoContext, - DocumentLookupsResolver.Current.DocumentLookups, - LastChanceLookupResolver.Current.LastChanceLookup)) + if (!ProcessFrontEndDocumentRequest(httpContext, umbracoContext)) { var uri = httpContext.Request.Url; var lpath = uri.AbsolutePath.ToLower(); @@ -469,9 +466,7 @@ namespace Umbraco.Web // to rewriting happening too early in the chain (Alex Norcliffe 2010-02). //app.PostResolveRequestCache += (sender, e) => - //SD: changed to post map request handler so we can know what the handler actually is, this is a better fit for - //when we handle the routing - app.PostMapRequestHandler += (sender, e) => + app.PostResolveRequestCache += (sender, e) => { var httpContext = ((HttpApplication)sender).Context; ProcessRequest(new HttpContextWrapper(httpContext)); diff --git a/src/Umbraco.Web/UriUtility.cs b/src/Umbraco.Web/UriUtility.cs index b88948666a..abd3d0e910 100644 --- a/src/Umbraco.Web/UriUtility.cs +++ b/src/Umbraco.Web/UriUtility.cs @@ -3,6 +3,7 @@ using System.Text; using System.Web; using Umbraco.Core; +using umbraco; namespace Umbraco.Web { @@ -17,7 +18,7 @@ namespace Umbraco.Web _appVirtualPath = HttpRuntime.AppDomainAppVirtualPath ?? "/"; _appVirtualPathPrefix = _appVirtualPath; if (_appVirtualPathPrefix == "/") - _appVirtualPathPrefix = string.Empty; + _appVirtualPathPrefix = String.Empty; } // will be "/" or "/foo" @@ -44,7 +45,37 @@ namespace Umbraco.Web return url; } - #region ResolveUrl + // fixme - what about vdir? + // path = path.Substring(UriUtility.AppVirtualPathPrefix.Length); // remove virtual directory + + public static Uri UriFromUmbraco(Uri uri) + { + var path = uri.GetSafeAbsolutePath(); + if (path == "/") + return uri; + + if (!GlobalSettings.UseDirectoryUrls) + path += ".aspx"; + else if (UmbracoSettings.AddTrailingSlash) + path += "/"; + + return uri.Rewrite(path); + } + + public static Uri UriToUmbraco(Uri uri) + { + var path = uri.GetSafeAbsolutePath(); + + path = path.ToLower(); + if (path != "/") + path = path.TrimEnd('/'); + if (path.EndsWith(".aspx")) + path = path.Substring(0, path.Length - ".aspx".Length); + + return uri.Rewrite(path); + } + + #region ResolveUrl // http://www.codeproject.com/Articles/53460/ResolveUrl-in-ASP-NET-The-Perfect-Solution // note @@ -123,7 +154,7 @@ namespace Umbraco.Web public static string StartWithScheme(string uri, string scheme) { - return HasScheme(uri) ? uri : string.Format("{0}://{1}", scheme ?? Uri.UriSchemeHttp, uri); + return HasScheme(uri) ? uri : String.Format("{0}://{1}", scheme ?? Uri.UriSchemeHttp, uri); } public static string EndPathWithSlash(string uri) @@ -170,7 +201,7 @@ namespace Umbraco.Web internal static Uri ToFullUrl(string absolutePath, HttpContextBase httpContext) { if (httpContext == null) throw new ArgumentNullException("httpContext"); - if (string.IsNullOrEmpty(absolutePath)) + if (String.IsNullOrEmpty(absolutePath)) throw new ArgumentNullException("absolutePath"); if (!absolutePath.StartsWith("/")) @@ -180,7 +211,7 @@ namespace Umbraco.Web var url = httpContext.Request.Url; var port = url.Port != 80 ? (":" + url.Port) : String.Empty; - return new Uri(string.Format("{0}://{1}{2}{3}", url.Scheme, url.Host, port, absolutePath)); + return new Uri(String.Format("{0}://{1}{2}{3}", url.Scheme, url.Host, port, absolutePath)); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/default.aspx.cs b/src/Umbraco.Web/umbraco.presentation/default.aspx.cs index 38fdc5f2f7..5d0d4c5859 100644 --- a/src/Umbraco.Web/umbraco.presentation/default.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/default.aspx.cs @@ -218,7 +218,7 @@ namespace umbraco // UmbracoModule has already set Response.Status to 404 Response.Write("

Page not found

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

No umbraco document matches the url '" + HttpUtility.HtmlEncode(Request.Url.ToString()) + "'.

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

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

"); // fixme - should try to get infos from the DocumentRequest? diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index e899524309..006920a46f 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -355,7 +355,7 @@ namespace umbraco /// String with a friendly url from a node public static string NiceUrl(int nodeID) { - var niceUrlsProvider = Umbraco.Web.UmbracoContext.Current.DocumentRequest.RoutingContext.NiceUrlProvider; + var niceUrlsProvider = Umbraco.Web.UmbracoContext.Current.RoutingContext.NiceUrlProvider; return niceUrlsProvider.GetNiceUrl(nodeID); } @@ -378,7 +378,7 @@ namespace umbraco /// String with a friendly url with full domain from a node public static string NiceUrlWithDomain(int nodeID) { - var niceUrlsProvider = Umbraco.Web.UmbracoContext.Current.DocumentRequest.RoutingContext.NiceUrlProvider; + var niceUrlsProvider = Umbraco.Web.UmbracoContext.Current.RoutingContext.NiceUrlProvider; return niceUrlsProvider.GetNiceUrl(nodeID, Umbraco.Web.UmbracoContext.Current.UmbracoUrl, true); }