Web.Routing - refactor domains management

This commit is contained in:
Stephan
2013-02-19 06:37:25 -01:00
parent 7da60e9937
commit ac3bec5d9e
13 changed files with 967 additions and 185 deletions

View File

@@ -0,0 +1,309 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web.Routing;
using umbraco.cms.businesslogic.web;
using System.Reflection;
namespace Umbraco.Tests.Routing
{
[TestFixture]
public class SiteDomainHelperTests
{
[SetUp]
public void SetUp()
{
SiteDomainHelper.Clear(); // assuming this works!
}
[Test]
public void AddSites()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
var sites = SiteDomainHelper.Sites;
Assert.AreEqual(2, sites.Count);
Assert.Contains("site1", sites.Keys);
Assert.Contains("site2", sites.Keys);
var domains = sites["site1"];
Assert.AreEqual(3, domains.Count());
Assert.Contains("domain1.com", domains);
Assert.Contains("domain1.net", domains);
Assert.Contains("domain1.org", domains);
domains = sites["site2"];
Assert.AreEqual(3, domains.Count());
Assert.Contains("domain2.com", domains);
Assert.Contains("domain2.net", domains);
Assert.Contains("domain2.org", domains);
}
[TestCase("foo")] // that one is suspect
[TestCase("domain.com")]
[TestCase("domain.com/")]
[TestCase("domain.com:12")]
[TestCase("domain.com:12/")]
[TestCase("http://www.domain.com")]
[TestCase("http://www.domain.com:12")]
[TestCase("http://www.domain.com:12/")]
[TestCase("https://foo.www.domain.com")]
[TestCase("https://foo.www.domain.com:5478/")]
public void AddValidSite(string domain)
{
SiteDomainHelper.AddSite("site1", domain);
}
[TestCase("domain.com/foo")]
[TestCase("http:/domain.com")]
[TestCase("*")]
public void AddInvalidSite(string domain)
{
Assert.Throws<ArgumentOutOfRangeException>(() => SiteDomainHelper.AddSite("site1", domain));
}
[Test]
public void AddRemoveSites()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
var sites = SiteDomainHelper.Sites;
SiteDomainHelper.RemoveSite("site1");
SiteDomainHelper.RemoveSite("site3");
Assert.AreEqual(1, sites.Count);
Assert.Contains("site2", sites.Keys);
}
[Test]
public void AddSiteAgain()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site1", "domain2.com", "domain1.net");
var sites = SiteDomainHelper.Sites;
Assert.AreEqual(1, sites.Count);
Assert.Contains("site1", sites.Keys);
var domains = sites["site1"];
Assert.AreEqual(2, domains.Count());
Assert.Contains("domain2.com", domains);
Assert.Contains("domain1.net", domains);
}
[Test]
public void BindSitesOnce()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org");
SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org");
SiteDomainHelper.BindSites("site1", "site2");
var bindings = SiteDomainHelper.Bindings;
Assert.AreEqual(2, bindings.Count);
Assert.Contains("site1", bindings.Keys);
Assert.Contains("site2", bindings.Keys);
var others = bindings["site1"];
Assert.AreEqual(1, others.Count);
Assert.Contains("site2", others);
others = bindings["site2"];
Assert.AreEqual(1, others.Count);
Assert.Contains("site1", others);
}
[Test]
public void BindMoreSites()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org");
SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org");
SiteDomainHelper.BindSites("site1", "site2");
SiteDomainHelper.BindSites("site1", "site3");
var bindings = SiteDomainHelper.Bindings;
Assert.AreEqual(3, bindings.Count);
Assert.Contains("site1", bindings.Keys);
Assert.Contains("site2", bindings.Keys);
Assert.Contains("site3", bindings.Keys);
var others = bindings["site1"];
Assert.AreEqual(2, others.Count);
Assert.Contains("site2", others);
Assert.Contains("site3", others);
others = bindings["site2"];
Assert.AreEqual(2, others.Count);
Assert.Contains("site1", others);
Assert.Contains("site3", others);
others = bindings["site3"];
Assert.AreEqual(2, others.Count);
Assert.Contains("site1", others);
Assert.Contains("site2", others);
}
[Test]
public void MapDomain()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org");
SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org");
//SiteDomainHelper.BindSites("site1", "site3");
//SiteDomainHelper.BindSites("site2", "site4");
// map methods are not static because we can override them
var helper = new SiteDomainHelper();
// current is a site1 uri, domains contain current
// so we'll get current
//
var current = new Uri("http://domain1.com/foo/bar");
var output = helper.MapDomain(current, new[]
{
new DomainAndUri(new MockDomain("domain1.com"), Uri.UriSchemeHttp),
new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp),
}).Uri.ToString();
Assert.AreEqual("http://domain1.com/", output);
// current is a site1 uri, domains do not contain current
// so we'll get the corresponding site1 domain
//
current = new Uri("http://domain1.com/foo/bar");
output = helper.MapDomain(current, new[]
{
new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp),
new DomainAndUri(new MockDomain("domain2.net"), Uri.UriSchemeHttp)
}).Uri.ToString();
Assert.AreEqual("http://domain1.net/", output);
// current is a site1 uri, domains do not contain current
// so we'll get the corresponding site1 domain
// order does not matter
//
current = new Uri("http://domain1.com/foo/bar");
output = helper.MapDomain(current, new[]
{
new DomainAndUri(new MockDomain("domain2.net"), Uri.UriSchemeHttp),
new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp)
}).Uri.ToString();
Assert.AreEqual("http://domain1.net/", output);
}
[Test]
public void MapDomains()
{
SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org");
SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org");
SiteDomainHelper.AddSite("site3", "domain3.com", "domain3.net", "domain3.org");
SiteDomainHelper.AddSite("site4", "domain4.com", "domain4.net", "domain4.org");
// map methods are not static because we can override them
var helper = new SiteDomainHelper();
// the rule is:
// - exclude the current domain
// - exclude what MapDomain would return
// - return all domains from same site, or bound sites
// current is a site1 uri, domains contains current
//
var current = new Uri("http://domain1.com/foo/bar");
var output = helper.MapDomains(current, new[]
{
new DomainAndUri(new MockDomain("domain1.com"), Uri.UriSchemeHttp), // no: current + what MapDomain would pick
new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup)
}).ToArray();
Assert.AreEqual(1, output.Count());
Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray());
// current is a site1 uri, domains does not contain current
//
current = new Uri("http://domain1.com/foo/bar");
output = helper.MapDomains(current, new[]
{
new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp), // no: what MapDomain would pick
new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup)
}).ToArray();
Assert.AreEqual(1, output.Count());
Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray());
SiteDomainHelper.BindSites("site1", "site3");
SiteDomainHelper.BindSites("site2", "site4");
// current is a site1 uri, domains contains current
//
current = new Uri("http://domain1.com/foo/bar");
output = helper.MapDomains(current, new[]
{
new DomainAndUri(new MockDomain("domain1.com"), Uri.UriSchemeHttp), // no: current + what MapDomain would pick
new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // yes: bound site
new DomainAndUri(new MockDomain("domain3.org"), Uri.UriSchemeHttp), // yes: bound site
new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup)
}).ToArray();
Assert.AreEqual(3, output.Count());
Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray());
Assert.Contains("http://domain3.com/", output.Select(d => d.Uri.ToString()).ToArray());
Assert.Contains("http://domain3.org/", output.Select(d => d.Uri.ToString()).ToArray());
// current is a site1 uri, domains does not contain current
//
current = new Uri("http://domain1.com/foo/bar");
output = helper.MapDomains(current, new[]
{
new DomainAndUri(new MockDomain("domain1.net"), Uri.UriSchemeHttp), // no: what MapDomain would pick
new DomainAndUri(new MockDomain("domain2.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain3.com"), Uri.UriSchemeHttp), // yes: bound site
new DomainAndUri(new MockDomain("domain3.org"), Uri.UriSchemeHttp), // yes: bound site
new DomainAndUri(new MockDomain("domain4.com"), Uri.UriSchemeHttp), // no: not same site
new DomainAndUri(new MockDomain("domain1.org"), Uri.UriSchemeHttp), // yes: same site (though bogus setup)
}).ToArray();
Assert.AreEqual(3, output.Count());
Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray());
Assert.Contains("http://domain3.com/", output.Select(d => d.Uri.ToString()).ToArray());
Assert.Contains("http://domain3.org/", output.Select(d => d.Uri.ToString()).ToArray());
}
class MockDomain : Domain
{
private static readonly FieldInfo NameField = typeof (Domain).GetField("_name", BindingFlags.Instance | BindingFlags.NonPublic);
public MockDomain(string name)
{
NameField.SetValue(this, name);
}
}
}
}

View File

@@ -285,6 +285,7 @@
<Compile Include="Routing\ContentFinderByNiceUrlWithDomainsTests.cs" />
<Compile Include="Routing\DomainsAndCulturesTests.cs" />
<Compile Include="Routing\NiceUrlsProviderWithDomainsTests.cs" />
<Compile Include="Routing\SiteDomainHelperTests.cs" />
<Compile Include="Routing\uQueryGetNodeIdByUrlTests.cs" />
<Compile Include="Routing\UrlsWithNestedDomains.cs" />
<Compile Include="Services\BaseServiceTest.cs" />

View File

@@ -10,6 +10,12 @@ namespace Umbraco.Web.Routing
/// </summary>
internal class AliasUrlProvider : IUrlProvider
{
// note - at the moment we seem to accept pretty much anything as an alias
// without any form of validation ... could even prob. kill the XPath ...
// ok, this is somewhat experimental and is NOT enabled by default
#region GetUrl
/// <summary>
/// Gets the nice url of a published content.
/// </summary>
@@ -29,18 +35,9 @@ namespace Umbraco.Web.Routing
return null; // we have nothing to say
}
const string UmbracoUrlAlias = "umbracoUrlAlias";
#endregion
private bool FindByUrlAliasEnabled
{
get
{
var hasFinder = ContentFinderResolver.Current.ContainsType<ContentFinderByUrlAlias>();
var hasHandler = ContentFinderResolver.Current.ContainsType<ContentFinderByNotFoundHandlers>()
&& NotFoundHandlerHelper.CustomHandlerTypes.Contains(typeof(global::umbraco.SearchForAlias));
return hasFinder || hasHandler;
}
}
#region GetOtherUrls
/// <summary>
/// Gets the other urls of a published content.
@@ -66,32 +63,51 @@ namespace Umbraco.Web.Routing
if (string.IsNullOrWhiteSpace(umbracoUrlName))
return Enumerable.Empty<string>();
/*
// 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 n = node;
Uri domainUri = DomainUriAtNode(n.Id, current);
while (domainUri == null && n != null) // n is null at root
var domainUris = DomainHelper.DomainsForNode(n.Id, current);
while (domainUris == null && n != null) // n is null at root
{
// get the url
var urlName = n.UrlName;
// move to parent node
n = n.Parent;
domainUri = n == null ? null : DomainUriAtNode(n.Id, current);
domainUris = n == null ? null : DomainHelper.DomainsForNode(n.Id, current);
}
if (domainUri == null)
return new string[] { "/" + umbracoUrlName };
else
// fuck - there may be MANY domains actually!!
return null;
*
*/
var path = "/" + umbracoUrlName;
// just for fun
return new[] { "/" + umbracoUrlName };
if (domainUris == null)
{
var uri = new Uri(path, UriKind.Relative);
return new[] { UriUtility.UriFromUmbraco(uri).ToString() };
}
return domainUris
.Select(domainUri => new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)))
.Select(uri => UriUtility.UriFromUmbraco(uri).ToString());
}
#endregion
#region Utilities
const string UmbracoUrlAlias = "umbracoUrlAlias";
private bool FindByUrlAliasEnabled
{
get
{
var hasFinder = ContentFinderResolver.Current.ContainsType<ContentFinderByUrlAlias>();
var hasHandler = ContentFinderResolver.Current.ContainsType<ContentFinderByNotFoundHandlers>()
&& NotFoundHandlerHelper.CustomHandlerTypes.Contains(typeof(global::umbraco.SearchForAlias));
return hasFinder || hasHandler;
}
}
string CombinePaths(string path1, string path2)
{
string path = path1.TrimEnd('/') + path2;
return path == "/" ? path : path.TrimEnd('/');
}
#endregion
}
}

View File

@@ -94,7 +94,8 @@ namespace Umbraco.Web.Routing
docreq.PublishedContent = node;
LogHelper.Debug<ContentFinderByNiceUrl>("Query matches, id={0}", () => docreq.PublishedContent.Id);
var iscanon = _doDomainLookup && !DomainHelper.ExistsDomainInPath(docreq.Domain, node.Path);
var rootNodeId = docreq.Domain == null ? (int?) null : docreq.Domain.RootNodeId;
var iscanon = _doDomainLookup && !DomainHelper.ExistsDomainInPath(DomainHelper.GetAllDomains(), node.Path, rootNodeId);
if (!iscanon)
LogHelper.Debug<ContentFinderByNiceUrl>("Non canonical url");

View File

@@ -32,7 +32,7 @@ namespace Umbraco.Web.Routing
/// </remarks>
public virtual string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, bool absolute)
{
Uri domainUri;
DomainAndUri domainUri;
string path;
if (!current.IsAbsoluteUri)
@@ -51,7 +51,7 @@ namespace Umbraco.Web.Routing
// route is /<path> or <domainRootId>/<path>
int pos = route.IndexOf('/');
path = pos == 0 ? route : route.Substring(pos);
domainUri = pos == 0 ? null : DomainUriAtNode(int.Parse(route.Substring(0, pos)), current);
domainUri = pos == 0 ? null : DomainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current);
}
else
{
@@ -70,7 +70,7 @@ namespace Umbraco.Web.Routing
// or we reach the content root, collecting urls in the way
var pathParts = new List<string>();
var n = node;
domainUri = DomainUriAtNode(n.Id, current);
domainUri = DomainHelper.DomainForNode(n.Id, current);
while (domainUri == null && n != null) // n is null at root
{
// get the url
@@ -79,7 +79,7 @@ namespace Umbraco.Web.Routing
// move to parent node
n = n.Parent;
domainUri = n == null ? null : DomainUriAtNode(n.Id, current);
domainUri = n == null ? null : DomainHelper.DomainForNode(n.Id, current);
}
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
@@ -119,7 +119,7 @@ namespace Umbraco.Web.Routing
public virtual IEnumerable<string> GetOtherUrls(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current)
{
string path;
IEnumerable<Uri> domainUris;
IEnumerable<DomainAndUri> domainUris;
// will not read cache if previewing!
var route = umbracoContext.InPreviewMode
@@ -132,7 +132,7 @@ namespace Umbraco.Web.Routing
// route is /<path> or <domainRootId>/<path>
int pos = route.IndexOf('/');
path = pos == 0 ? route : route.Substring(pos);
domainUris = pos == 0 ? null : DomainUrisAtNode(int.Parse(route.Substring(0, pos)), current);
domainUris = pos == 0 ? null : DomainHelper.DomainsForNode(int.Parse(route.Substring(0, pos)), current);
}
else
{
@@ -151,7 +151,7 @@ namespace Umbraco.Web.Routing
// or we reach the content root, collecting urls in the way
var pathParts = new List<string>();
var n = node;
domainUris = DomainUrisAtNode(n.Id, current);
domainUris = DomainHelper.DomainsForNode(n.Id, current);
while (domainUris == null && n != null) // n is null at root
{
// get the url
@@ -160,7 +160,7 @@ namespace Umbraco.Web.Routing
// move to parent node
n = n.Parent;
domainUris = n == null ? null : DomainUrisAtNode(n.Id, current);
domainUris = n == null ? null : DomainHelper.DomainsForNode(n.Id, current);
}
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
@@ -185,7 +185,7 @@ namespace Umbraco.Web.Routing
#region Utilities
Uri AssembleUrl(Uri domainUri, string path, Uri current, bool absolute)
Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, bool absolute)
{
Uri uri;
@@ -202,10 +202,10 @@ namespace Umbraco.Web.Routing
{
// a domain was found : return an absolute or relative url
// ignore vdir at that point
if (!absolute && current != null && domainUri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority))
uri = new Uri(CombinePaths(domainUri.AbsolutePath, path), UriKind.Relative); // relative
if (!absolute && current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority))
uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative); // relative
else
uri = new Uri(CombinePaths(domainUri.GetLeftPart(UriPartial.Path), path)); // absolute
uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); // absolute
}
// UriFromUmbraco will handle vdir
@@ -220,7 +220,7 @@ namespace Umbraco.Web.Routing
}
// always build absolute urls unless we really cannot
IEnumerable<Uri> AssembleUrls(IEnumerable<Uri> domainUris, string path)
IEnumerable<Uri> AssembleUrls(IEnumerable<DomainAndUri> domainUris, string path)
{
// no domain == no "other" url
if (domainUris == null)
@@ -228,64 +228,13 @@ namespace Umbraco.Web.Routing
// if no domain was found and then we have no "other" url
// else return absolute urls, ignoring vdir at that point
var uris = domainUris.Select(domainUri => new Uri(CombinePaths(domainUri.GetLeftPart(UriPartial.Path), path)));
var uris = domainUris.Select(domainUri => new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)));
// UriFromUmbraco will handle vdir
// meaning it will add vdir into domain urls too!
return uris.Select(UriUtility.UriFromUmbraco);
}
Uri DomainUriAtNode(int nodeId, Uri current)
{
// be safe
if (nodeId <= 0)
return null;
// get the domains on that node
var domains = Domain.GetDomainsById(nodeId);
// filter those that match
var domainAndUri = DomainHelper.DomainMatch(domains, current, domainAndUris => MapDomain(current, domainAndUris));
return domainAndUri == null ? null : domainAndUri.Uri;
}
/// <summary>
/// Filters a list of <c>DomainAndUri</c> to pick one that best matches the current request.
/// </summary>
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
/// <param name="current">The Uri of the current request.</param>
/// <returns>The selected <c>DomainAndUri</c>.</returns>
/// <remarks>
/// <para>If the filter is invoked then <paramref name="domainAndUris"/> is _not_ empty and
/// <paramref name="current"/> is _not_ null, and <paramref name="current"/> could not be
/// matched with anything in <paramref name="domainAndUris"/>.</para>
/// <para>The filter _must_ return something else an exception will be thrown.</para>
/// </remarks>
protected virtual DomainAndUri MapDomain(Uri current, DomainAndUri[] domainAndUris)
{
// all we can do at the moment
return domainAndUris.First();
}
Uri[] DomainUrisAtNode(int nodeId, Uri current)
{
// be safe
if (nodeId <= 0)
return null;
var domainAndUris = DomainHelper.DomainMatches(Domain.GetDomainsById(nodeId), current).ToArray();
// if any, then filter them (and maybe return empty) else return null
return !domainAndUris.Any() ? null : MapDomains(current, domainAndUris).Select(d => d.Uri).ToArray();
}
protected virtual IEnumerable<DomainAndUri> MapDomains(Uri current, DomainAndUri[] domainAndUris)
{
// all we can do at the moment
return domainAndUris;
}
static void ApplyHideTopLevelNodeFromPath(UmbracoContext umbracoContext, IPublishedContentStore contentCache, Core.Models.IPublishedContent node, IList<string> pathParts)
{
// in theory if hideTopLevelNodeFromPath is true, then there should be only once

View File

@@ -1,47 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Umbraco.Core;
using umbraco.cms.businesslogic.web;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides utilities to handle domains.
/// </summary>
/// <summary>
/// Provides utilities to handle domains.
/// </summary>
internal class DomainHelper
{
private static Domain SanitizeForBackwardCompatibility(Domain d)
{
// this is a _really_ nasty one that should be removed in 6.x
// some people were using hostnames such as "/en" which happened to work pre-4.10
// but make _no_ sense at all... and 4.10 throws on them, so here we just try
// to find a way so 4.11 does not throw.
// but, really.
// no.
var context = System.Web.HttpContext.Current;
if (context != null && d.Name.StartsWith("/"))
{
// turn /en into http://whatever.com/en so it becomes a parseable uri
var authority = context.Request.Url.GetLeftPart(UriPartial.Authority);
d.Name = authority + d.Name;
}
return d;
}
#region Temp. abstract Umbraco's API
/// <summary>
/// Finds the domain that best matches the current uri, into an enumeration of domains.
/// Gets all domains defined in the system.
/// </summary>
/// <param name="domains">The enumeration of Umbraco domains.</param>
/// <param name="current">The uri of the current request, or null.</param>
/// <returns>All domains defined in the system.</returns>
/// <remarks>This is to temporarily abstract Umbraco's API.</remarks>
internal static Domain[] GetAllDomains()
{
return Domain.GetDomains().ToArray();
}
/// <summary>
/// Gets all domains defined in the system at a specified node.
/// </summary>
/// <param name="nodeId">The node identifier.</param>
/// <returns>All domains defined in the system at the specified node.</returns>
/// <remarks>This is to temporarily abstract Umbraco's API.</remarks>
internal static Domain[] GetNodeDomains(int nodeId)
{
return Domain.GetDomainsById(nodeId);
}
#endregion
#region Domain for Node
/// <summary>
/// Finds the domain for the specified node, if any, that best matches a specified uri.
/// </summary>
/// <param name="nodeId">The node identifier.</param>
/// <param name="current">The uri, or null.</param>
/// <returns>The domain and its uri, if any, that best matches the specified uri.</returns>
internal static DomainAndUri DomainForNode(int nodeId, Uri current)
{
// be safe
if (nodeId <= 0)
return null;
// get the domains on that node
var domains = GetNodeDomains(nodeId);
// filter those that match
var helper = SiteDomainHelperResolver.Current.Helper;
var domainAndUri = DomainForUri(domains, current, domainAndUris => helper.MapDomain(current, domainAndUris));
// return null or the uri
return domainAndUri;
}
/// <summary>
/// Find the domains for the specified node, if any, that match a specified uri.
/// </summary>
/// <param name="nodeId">The node identifier.</param>
/// <param name="current">The uri, or null.</param>
/// <returns>The domains and their uris, that match the specified uri.</returns>
internal static IEnumerable<DomainAndUri> DomainsForNode(int nodeId, Uri current)
{
// be safe
if (nodeId <= 0)
return null;
// get the domains on that node
var domains = GetNodeDomains(nodeId);
// filter those that match
var domainAndUris = DomainsForUri(domains, current).ToArray();
// return null or a (maybe empty) enumerable of uris
var helper = SiteDomainHelperResolver.Current.Helper;
var domainAndUris2 = helper.MapDomains(current, domainAndUris).ToArray();
return domainAndUris2.Any() ? domainAndUris2 : null;
}
#endregion
#region Domain for Uri
/// <summary>
/// Finds the domain that best matches a specified uri, into a group of domains.
/// </summary>
/// <param name="domains">The group of domains.</param>
/// <param name="current">The uri, or null.</param>
/// <param name="filter">A function to filter the list of domains, if more than one applies, or <c>null</c>.</param>
/// <returns>The domain and its normalized uri, that best matches the current uri.</returns>
/// <returns>The domain and its normalized uri, that best matches the specified uri.</returns>
/// <remarks>
/// <para>If more than one domain matches, then the <paramref name="filter"/> function is used to pick
/// the right one, unless it is <c>null</c>, in which case the method returns <c>null</c>.</para>
/// <para>The filter, if any, will be called only with a non-empty argument, and _must_ return something.</para>
/// </remarks>
public static DomainAndUri DomainMatch(Domain[] domains, Uri current, Func<DomainAndUri[], DomainAndUri> filter = null)
internal static DomainAndUri DomainForUri(Domain[] domains, Uri current, Func<DomainAndUri[], DomainAndUri> filter = null)
{
// sanitize the list to have proper uris for comparison (scheme, path end with /)
// we need to end with / because example.com/foo cannot match example.com/foobar
@@ -84,73 +145,113 @@ namespace Umbraco.Web.Routing
return domainAndUri;
}
/// <summary>
/// Gets an enumeration of <see cref="DomainAndUri"/> matching an enumeration of Umbraco domains.
/// </summary>
/// <param name="domains">The enumeration of Umbraco domains.</param>
/// <param name="current">The uri of the current request, or null.</param>
/// <returns>The enumeration of <see cref="DomainAndUri"/> matching the enumeration of Umbraco domains.</returns>
public static IEnumerable<DomainAndUri> DomainMatches(Domain[] domains, Uri current)
{
/// <summary>
/// Gets the domains that match a specified uri, into a group of domains.
/// </summary>
/// <param name="domains">The group of domains.</param>
/// <param name="current">The uri, or null.</param>
/// <returns>The domains and their normalized uris, that match the specified uri.</returns>
internal static IEnumerable<DomainAndUri> DomainsForUri(Domain[] domains, Uri current)
{
var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme;
var domainsAndUris = domains
.Where(d => !d.IsWildcard)
.Select(SanitizeForBackwardCompatibility)
return domains
.Where(d => !d.IsWildcard)
.Select(SanitizeForBackwardCompatibility)
.Select(d => new DomainAndUri(d, scheme))
.OrderByDescending(d => d.Uri.ToString());
return domainsAndUris;
}
}
/// <summary>
/// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node.
/// </summary>
/// <param name="current">The current domain.</param>
/// <param name="path">The path to a node under the current domain's root node.</param>
/// <returns>A value indicating if there is another domain defined down in the path.</returns>
public static bool ExistsDomainInPath(Domain current, string path)
{
var domains = Domain.GetDomains();
var stopNodeId = current == null ? -1 : current.RootNodeId;
#endregion
return path.Split(',')
.Reverse()
.Select(int.Parse)
.TakeWhile(id => id != stopNodeId)
.Any(id => domains.Any(d => d.RootNodeId == id && !d.IsWildcard));
}
#region Utilities
/// <summary>
/// Gets the deepest wildcard <see cref="Domain"/> in a node path.
/// </summary>
/// <param name="domains">The Umbraco domains.</param>
/// <param name="path">The node path.</param>
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
/// <returns>The deepest wildcard <see cref="Domain"/> in the path, or null.</returns>
public static Domain LookForWildcardDomain(Domain[] domains, string path, int? rootNodeId)
{
// "When you perform comparisons with nullable types, if the value of one of the nullable
// types is null and the other is not, all comparisons evaluate to false."
/// <summary>
/// Sanitize a Domain.
/// </summary>
/// <param name="domain">The Domain to sanitize.</param>
/// <returns>The sanitized domain.</returns>
/// <remarks>This is a _really_ nasty one that should be removed at some point. Some people were
/// using hostnames such as "/en" which happened to work pre-4.10 but really make no sense at
/// all... and 4.10 throws on them, so here we just try to find a way so 4.11 does not throw.
/// But really... no.</remarks>
private static Domain SanitizeForBackwardCompatibility(Domain domain)
{
var context = System.Web.HttpContext.Current;
if (context != null && domain.Name.StartsWith("/"))
{
// turn "/en" into "http://whatever.com/en" so it becomes a parseable uri
var authority = context.Request.Url.GetLeftPart(UriPartial.Authority);
domain.Name = authority + domain.Name;
}
return domain;
}
/// <summary>
/// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node.
/// </summary>
/// <param name="domains">The domains.</param>
/// <param name="path">The path to a node under the current domain's root node eg '-1,1234,5678'.</param>
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
/// <returns>A value indicating if there is another domain defined down in the path.</returns>
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
internal static bool ExistsDomainInPath(Domain[] domains, string path, int? rootNodeId)
{
return FindDomainInPath(domains, path, rootNodeId) != null;
}
return path
.Split(',')
.Select(int.Parse)
.Skip(1)
.Reverse()
.TakeWhile(id => !rootNodeId.HasValue || id != rootNodeId)
.Select(nodeId => domains.FirstOrDefault(d => d.RootNodeId == nodeId && d.IsWildcard))
.FirstOrDefault(domain => domain != null);
}
/// <summary>
/// Gets the deepest non-wildcard Domain, if any, from a group of Domains, in a node path.
/// </summary>
/// <param name="domains">The domains.</param>
/// <param name="path">The node path eg '-1,1234,5678'.</param>
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
/// <returns>The deepest non-wildcard Domain in the path, or null.</returns>
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
internal static Domain FindDomainInPath(Domain[] domains, string path, int? rootNodeId)
{
var stopNodeId = rootNodeId ?? -1;
/// <summary>
/// Returns the part of a path relative to the uri of a domain.
/// </summary>
/// <param name="domainUri">The normalized uri of the domain.</param>
/// <param name="path">The full path of the uri.</param>
/// <returns>The path part relative to the uri of the domain.</returns>
/// <remarks>Eg the relative part of <c>/foo/bar/nil</c> to domain <c>example.com/foo</c> is <c>/bar/nil</c>.</remarks>
public static string PathRelativeToDomain(Uri domainUri, string path)
{
return path.Substring(domainUri.AbsolutePath.Length).EnsureStartsWith('/');
}
}
return path.Split(',')
.Reverse()
.Select(int.Parse)
.TakeWhile(id => id != stopNodeId)
.Select(id => domains.FirstOrDefault(d => d.RootNodeId == id && !d.IsWildcard))
.SkipWhile(domain => domain == null)
.FirstOrDefault();
}
/// <summary>
/// Gets the deepest wildcard Domain, if any, from a group of Domains, in a node path.
/// </summary>
/// <param name="domains">The domains.</param>
/// <param name="path">The node path eg '-1,1234,5678'.</param>
/// <param name="rootNodeId">The current domain root node identifier, or null.</param>
/// <returns>The deepest wildcard Domain in the path, or null.</returns>
/// <remarks>Looks _under_ rootNodeId but not _at_ rootNodeId.</remarks>
internal static Domain FindWildcardDomainInPath(Domain[] domains, string path, int? rootNodeId)
{
var stopNodeId = rootNodeId ?? -1;
return path.Split(',')
.Reverse()
.Select(int.Parse)
.TakeWhile(id => id != stopNodeId)
.Select(id => domains.FirstOrDefault(d => d.RootNodeId == id && d.IsWildcard))
.FirstOrDefault(domain => domain != null);
}
/// <summary>
/// Returns the part of a path relative to the uri of a domain.
/// </summary>
/// <param name="domainUri">The normalized uri of the domain.</param>
/// <param name="path">The full path of the uri.</param>
/// <returns>The path part relative to the uri of the domain.</returns>
/// <remarks>Eg the relative part of <c>/foo/bar/nil</c> to domain <c>example.com/foo</c> is <c>/bar/nil</c>.</remarks>
internal static string PathRelativeToDomain(Uri domainUri, string path)
{
return path.Substring(domainUri.AbsolutePath.Length).EnsureStartsWith('/');
}
#endregion
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides utilities to handle site domains.
/// </summary>
internal interface ISiteDomainHelper
{
/// <summary>
/// Filters a list of <c>DomainAndUri</c> to pick one that best matches the current request.
/// </summary>
/// <param name="current">The Uri of the current request.</param>
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
/// <returns>The selected <c>DomainAndUri</c>.</returns>
/// <remarks>
/// <para>If the filter is invoked then <paramref name="domainAndUris"/> is _not_ empty and
/// <paramref name="current"/> is _not_ null, and <paramref name="current"/> could not be
/// matched with anything in <paramref name="domainAndUris"/>.</para>
/// <para>The filter _must_ return something else an exception will be thrown.</para>
/// </remarks>
DomainAndUri MapDomain(Uri current, DomainAndUri[] domainAndUris);
/// <summary>
/// Filters a list of <c>DomainAndUri</c> to pick those that best matches the current request.
/// </summary>
/// <param name="current">The Uri of the current request.</param>
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
/// <returns>The selected <c>DomainAndUri</c> items.</returns>
/// <remarks>The filter must return something, even empty, else an exception will be thrown.</remarks>
IEnumerable<DomainAndUri> MapDomains(Uri current, DomainAndUri[] domainAndUris);
}
}

View File

@@ -166,7 +166,7 @@ namespace Umbraco.Web.Routing
LogHelper.Debug<PublishedContentRequestEngine>("{0}Uri=\"{1}\"", () => tracePrefix, () => _pcr.Uri);
// try to find a domain matching the current request
var domainAndUri = DomainHelper.DomainMatch(Domain.GetDomains().ToArray(), _pcr.Uri);
var domainAndUri = DomainHelper.DomainForUri(Domain.GetDomains().ToArray(), _pcr.Uri);
// handle domain
if (domainAndUri != null)
@@ -216,7 +216,7 @@ namespace Umbraco.Web.Routing
var nodePath = _pcr.PublishedContent.Path;
LogHelper.Debug<PublishedContentRequestEngine>("{0}Path=\"{1}\"", () => tracePrefix, () => nodePath);
var rootNodeId = _pcr.HasDomain ? _pcr.Domain.RootNodeId : (int?)null;
var domain = DomainHelper.LookForWildcardDomain(Domain.GetDomains().ToArray(), nodePath, rootNodeId);
var domain = DomainHelper.FindWildcardDomainInPath(Domain.GetDomains().ToArray(), nodePath, rootNodeId);
if (domain != null)
{

View File

@@ -0,0 +1,314 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Text.RegularExpressions;
using Umbraco.Core;
using umbraco.cms.businesslogic.web;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Provides utilities to handle site domains.
/// </summary>
internal class SiteDomainHelper : ISiteDomainHelper
{
#region Configure
private static readonly ReaderWriterLockSlim ConfigLock = new ReaderWriterLockSlim();
private static Dictionary<string, string[]> _sites;
private static Dictionary<string, List<string>> _bindings;
private static Dictionary<string, Dictionary<string, string[]>> _qualifiedSites;
// these are for unit tests *only*
internal static Dictionary<string, string[]> Sites { get { return _sites; } }
internal static Dictionary<string, List<string>> Bindings { get { return _bindings; } }
// these are for validation
//private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$";
private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$";
private static readonly Regex DomainValidation = new Regex(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary>
/// Returns a disposable object that represents safe write access to config.
/// </summary>
/// <remarks>Should be used in a <c>using(SiteDomainHelper.ConfigWriteLock) { ... }</c> mode.</remarks>
protected static IDisposable ConfigWriteLock
{
get { return new WriteLock(ConfigLock); }
}
/// <summary>
/// Returns a disposable object that represents safe read access to config.
/// </summary>
/// <remarks>Should be used in a <c>using(SiteDomainHelper.ConfigWriteLock) { ... }</c> mode.</remarks>
protected static IDisposable ConfigReadLock
{
get { return new ReadLock(ConfigLock); }
}
/// <summary>
/// Clears the entire configuration.
/// </summary>
public static void Clear()
{
using (ConfigWriteLock)
{
_sites = null;
_bindings = null;
_qualifiedSites = null;
}
}
private static IEnumerable<string> ValidateDomains(IEnumerable<string> domains)
{
// must use authority format w/optional scheme and port, but no path
// any domain should appear only once
return domains.Select(domain =>
{
if (!DomainValidation.IsMatch(domain))
throw new ArgumentOutOfRangeException("domains", string.Format("Invalid domain: \"{0}\"", domain));
return domain;
});
}
/// <summary>
/// Adds a site.
/// </summary>
/// <param name="key">A key uniquely identifying the site.</param>
/// <param name="domains">The site domains.</param>
/// <remarks>At the moment there is no public way to remove a site. Clear and reconfigure.</remarks>
public static void AddSite(string key, IEnumerable<string> domains)
{
using (ConfigWriteLock)
{
_sites = _sites ?? new Dictionary<string, string[]>();
_sites[key] = ValidateDomains(domains).ToArray();
_qualifiedSites = null;
}
}
/// <summary>
/// Adds a site.
/// </summary>
/// <param name="key">A key uniquely identifying the site.</param>
/// <param name="domains">The site domains.</param>
/// <remarks>At the moment there is no public way to remove a site. Clear and reconfigure.</remarks>
public static void AddSite(string key, params string[] domains)
{
using (ConfigWriteLock)
{
_sites = _sites ?? new Dictionary<string, string[]>();
_sites[key] = ValidateDomains(domains).ToArray();
_qualifiedSites = null;
}
}
/// <summary>
/// Removes a site.
/// </summary>
/// <param name="key">A key uniquely identifying the site.</param>
internal static void RemoveSite(string key)
{
using (ConfigWriteLock)
{
if (_sites != null && _sites.ContainsKey(key))
{
_sites.Remove(key);
if (_sites.Count == 0)
_sites = null;
if (_bindings != null && _bindings.ContainsKey(key))
{
foreach (var b in _bindings[key])
{
_bindings[b].Remove(key);
if (_bindings[b].Count == 0)
_bindings.Remove(b);
}
_bindings.Remove(key);
if (_bindings.Count > 0)
_bindings = null;
}
_qualifiedSites = null;
}
}
}
/// <summary>
/// Binds some sites.
/// </summary>
/// <param name="keys">The keys uniquely identifying the sites to bind.</param>
/// <remarks>
/// <para>At the moment there is no public way to unbind sites. Clear and reconfigure.</para>
/// <para>If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3.</para>
/// </remarks>
public static void BindSites(params string[] keys)
{
using (ConfigWriteLock)
{
foreach (var key in keys.Where(key => !_sites.ContainsKey(key)))
throw new ArgumentException(string.Format("Not an existing site key: {0}", key), "keys");
_bindings = _bindings ?? new Dictionary<string, List<string>>();
var allkeys = _bindings
.Where(kvp => keys.Contains(kvp.Key))
.SelectMany(kvp => kvp.Value)
.Union(keys)
.ToArray();
foreach (var key in allkeys)
{
if (!_bindings.ContainsKey(key))
_bindings[key] = new List<string>();
var xkey = key;
var addKeys = allkeys.Where(k => k != xkey).Except(_bindings[key]);
_bindings[key].AddRange(addKeys);
}
}
}
#endregion
#region Map domains
/// <summary>
/// Filters a list of <c>DomainAndUri</c> to pick one that best matches the current request.
/// </summary>
/// <param name="current">The Uri of the current request.</param>
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
/// <returns>The selected <c>DomainAndUri</c>.</returns>
/// <remarks>
/// <para>If the filter is invoked then <paramref name="domainAndUris"/> is _not_ empty and
/// <paramref name="current"/> is _not_ null, and <paramref name="current"/> could not be
/// matched with anything in <paramref name="domainAndUris"/>.</para>
/// <para>The filter _must_ return something else an exception will be thrown.</para>
/// </remarks>
public virtual DomainAndUri MapDomain(Uri current, DomainAndUri[] domainAndUris)
{
var currentAuthority = current.GetLeftPart(UriPartial.Authority);
var qualifiedSites = GetQualifiedSites(current);
return MapDomain(domainAndUris, qualifiedSites, currentAuthority);
}
/// <summary>
/// Filters a list of <c>DomainAndUri</c> to pick those that best matches the current request.
/// </summary>
/// <param name="current">The Uri of the current request.</param>
/// <param name="domainAndUris">The list of <c>DomainAndUri</c> to filter.</param>
/// <returns>The selected <c>DomainAndUri</c> items.</returns>
/// <remarks>The filter must return something, even empty, else an exception will be thrown.</remarks>
public virtual IEnumerable<DomainAndUri> MapDomains(Uri current, DomainAndUri[] domainAndUris)
{
var currentAuthority = current.GetLeftPart(UriPartial.Authority);
KeyValuePair<string, string[]>[] candidateSites;
IEnumerable<DomainAndUri> ret;
using (ConfigReadLock) // so nothing changes between GetQualifiedSites and access to bindings
{
var qualifiedSites = GetQualifiedSitesInsideLock(current);
// exclude the current one (avoid producing the absolute equivalent of what GetUrl returns)
ret = domainAndUris.Where(d => d.Uri.GetLeftPart(UriPartial.Authority) != currentAuthority);
// exclude the default one (avoid producing a possible duplicate of what GetUrl returns)
var mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority); // what GetUrl would get
ret = ret.Where(d => d != mainDomain);
// we do our best, but can't do the impossible
if (qualifiedSites == null)
return ret;
// find a site that contains the current authority
var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
// if current belongs to a site, pick every element from domainAndUris that also belong
// to that site -- or to any site bound to that site
candidateSites = new[] { currentSite };
if (_bindings != null && _bindings.ContainsKey(currentSite.Key))
{
var boundSites = qualifiedSites.Where(site => _bindings[currentSite.Key].Contains(site.Key));
candidateSites = candidateSites.Union(boundSites).ToArray();
// .ToArray ensures it is evaluated before the configuration lock is exited
}
}
return ret.Where(d =>
{
var authority = d.Uri.GetLeftPart(UriPartial.Authority);
return candidateSites.Any(site => site.Value.Contains(authority));
});
}
private static Dictionary<string, string[]> GetQualifiedSites(Uri current)
{
using (ConfigReadLock)
{
return GetQualifiedSitesInsideLock(current);
}
}
private static Dictionary<string, string[]> GetQualifiedSitesInsideLock(Uri current)
{
// we do our best, but can't do the impossible
if (_sites == null)
return null;
// cached?
if (_qualifiedSites != null && _qualifiedSites.ContainsKey(current.Scheme))
return _qualifiedSites[current.Scheme];
_qualifiedSites = _qualifiedSites ?? new Dictionary<string, Dictionary<string, string[]>>();
// convert sites into authority sites based upon current scheme
// because some domains in the sites might not have a scheme -- and cache
return _qualifiedSites[current.Scheme] = _sites
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.Select(d => new Uri(UriUtility.StartWithScheme(d, current.Scheme)).GetLeftPart(UriPartial.Authority)).ToArray()
);
// .ToDictionary will evaluate and create the dictionary immediately
// the new value is .ToArray so it will also be evaluated immediately
// therefore it is safe to return and exit the configuration lock
}
private static DomainAndUri MapDomain(DomainAndUri[] domainAndUris, Dictionary<string, string[]> qualifiedSites, string currentAuthority)
{
// we do our best, but can't do the impossible
if (qualifiedSites == null)
return domainAndUris.First();
// find a site that contains the current authority
var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
// if current belongs to a site - try to pick the first element
// from domainAndUris that also belongs to that site
var ret = currentSite.Equals(default(KeyValuePair<string, string[]>))
? null
: domainAndUris.FirstOrDefault(d => currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority)));
// no match means that either current does not belong to a site, or the site it belongs to
// does not contain any of domainAndUris. Yet we have to return something. here, it becomes
// a bit arbitrary.
// look through sites in order and pick the first domainAndUri that belongs to a site
ret = ret ?? qualifiedSites
.Where(site => site.Key != currentSite.Key)
.Select(site => domainAndUris.FirstOrDefault(domainAndUri => site.Value.Contains(domainAndUri.Uri.GetLeftPart(UriPartial.Authority))))
.FirstOrDefault(domainAndUri => domainAndUri != null);
// random, really
ret = ret ?? domainAndUris.First();
return ret;
}
#endregion
}
}

View File

@@ -0,0 +1,38 @@
using System;
using Umbraco.Core.ObjectResolution;
namespace Umbraco.Web.Routing
{
/// <summary>
/// Resolves the <see cref="ISiteDomainHelper"/> implementation.
/// </summary>
internal sealed class SiteDomainHelperResolver : SingleObjectResolverBase<SiteDomainHelperResolver, ISiteDomainHelper>
{
/// <summary>
/// Initializes a new instance of the <see cref="SiteDomainHelperResolver"/> class with an <see cref="ISiteDomainHelper"/> implementation.
/// </summary>
/// <param name="helper">The <see cref="ISiteDomainHelper"/> implementation.</param>
internal SiteDomainHelperResolver(ISiteDomainHelper helper)
: base(helper)
{ }
/// <summary>
/// Can be used by developers at runtime to set their IDomainHelper at app startup
/// </summary>
/// <param name="helper"></param>
public void SetHelper(ISiteDomainHelper helper)
{
Value = helper;
}
/// <summary>
/// Gets or sets the <see cref="ISiteDomainHelper"/> implementation.
/// </summary>
public ISiteDomainHelper Helper
{
get { return Value; }
}
}
}

View File

@@ -10,6 +10,8 @@ namespace Umbraco.Web.Routing
/// </summary>
internal class UrlProvider
{
#region Ctor and configuration
/// <summary>
/// Initializes a new instance of the <see cref="UrlProvider"/> class with an Umbraco context, a content cache, and a list of url providers.
/// </summary>
@@ -34,6 +36,10 @@ namespace Umbraco.Web.Routing
/// </summary>
public bool EnforceAbsoluteUrls { get; set; }
#endregion
#region GetUrl
/// <summary>
/// Gets the url of a published content.
/// </summary>
@@ -85,6 +91,10 @@ namespace Umbraco.Web.Routing
return url ?? "#"; // legacy wants this
}
#endregion
#region GetOtherUrls
/// <summary>
/// Gets the other urls of a published content.
/// </summary>
@@ -117,5 +127,7 @@ namespace Umbraco.Web.Routing
return urls;
}
#endregion
}
}

View File

@@ -385,10 +385,13 @@
<Compile Include="Routing\ContentFinderByPageIdQuery.cs" />
<Compile Include="Mvc\SurfaceControllerResolver.cs" />
<Compile Include="Routing\ContentFinderByNotFoundHandlers.cs" />
<Compile Include="Routing\ISiteDomainHelper.cs" />
<Compile Include="Routing\RoutableAttemptEventArgs.cs" />
<Compile Include="Routing\DefaultUrlProvider.cs" />
<Compile Include="Routing\DomainAndUri.cs" />
<Compile Include="Routing\IUrlProvider.cs" />
<Compile Include="Routing\SiteDomainHelper.cs" />
<Compile Include="Routing\SiteDomainHelperResolver.cs" />
<Compile Include="Routing\UrlProviderResolver.cs" />
<Compile Include="Routing\NotFoundHandlerHelper.cs" />
<Compile Include="Routing\EnsureRoutableOutcome.cs" />

View File

@@ -283,7 +283,7 @@ namespace Umbraco.Web
});
UrlProviderResolver.Current = new UrlProviderResolver(
typeof(AliasUrlProvider),
//typeof(AliasUrlProvider), // not enabled by default
typeof(DefaultUrlProvider)
);
@@ -305,6 +305,8 @@ namespace Umbraco.Web
typeof (ContentFinderByNotFoundHandlers)
);
SiteDomainHelperResolver.Current = new SiteDomainHelperResolver(new SiteDomainHelper());
RoutesCacheResolver.Current = new RoutesCacheResolver(new DefaultRoutesCache(_isForTesting == false));
ThumbnailProvidersResolver.Current = new ThumbnailProvidersResolver(