4

I've a following setup in my Manage Websites panel

General Url is set to alloy.com

alloy.no is set for no culture

alloy.se is set for sv culture

alloy.com is set for en culture

In my code, i want to get the friendly external url for given language for given page. So for Search page I want to get absolute friendly url in all languages.

I use following code to get friendly url for page (found on Anders G. Nordby blog):

var urlResolver = ServiceLocator.Current.GetInstance<UrlResolver>();
var pageAddress = urlResolver.GetUrl(reference, language);

var builder = new UrlBuilder(pageAddress);

Global.UrlRewriteProvider.ConvertToExternal(builder, null, Encoding.UTF8);

var friendlyUrl = builder.Uri.IsAbsoluteUri
    ? builder.ToString()
    : UriSupport.AbsoluteUrlBySettings(builder.ToString());

return friendlyUrl;

It is simple if I will use the alloy.com webpage and in my custom code generate friendly url.

  • no - alloy.no/søk
  • se - alloy.se/sök
  • en - alloy.com/search

But when I use alloy.no to enter edit mode and I will try to generate address for no i get alloy.com/søk when it should be alloy.no/søk.

I found that if I use alloy.no to go to Edit Mode, code :

urlResolver.GetUrl(reference, language)

returns only /søk and code

UriSupport.AbsoluteUrlBySettings(builder.ToString())

add the General URL (alloy.com) instead of alloy.no.

How can I improve this code to take correct host name for page in different culture?

Community
  • 1
  • 1
gringo_dave
  • 1,372
  • 17
  • 24

4 Answers4

4

The GetUrl method of the UrlResolver will return a URL to a page that is relative or absolute depending on the current request context. A URL will be relative if the page is located in the current site and absolute if in another site or if the call is made outside a request.

If you are using EPiServer.CMS.Core version 8.0 or later there is also support for identifying one site as the primary site. This update also made it possible to explicitly request that the URL should be to the primary site by setting the ForceCanonical flag on the VirtualPathArguments parameter. If not the flag is not set, it will prefer the current site URL over the primary (given the requested content is located on the current site).

So, with this in mind, you can assume that the returned URL, if not absolute, will be relative to the currently requested site and safely combine it with the currently requested URL such as:

private static string ExternalUrl(this ContentReference contentLink, CultureInfo language)
{
    var urlString = UrlResolver.Current.GetUrl(contentLink, language.Name, new VirtualPathArguments { ForceCanonical = true });
    if (string.IsNullOrEmpty(urlString) || HttpContext.Current == null) return urlString;

    var uri = new Uri(urlString, UriKind.RelativeOrAbsolute);
    if (uri.IsAbsoluteUri) return urlString;

    return new Uri(HttpContext.Current.Request.Url, uri).ToString();
}

In most cases I would probably prefer not to use HttpContext.Current directly and instead pass in the current request URL. But in this case I have opted to use it directly to keep the example more contained.

Henrik N
  • 529
  • 4
  • 12
  • I'm using version 7.19 of Episerver and I dont know when we will migrate to version 8. – gringo_dave Apr 29 '15 at 09:16
  • Why don't just always return the absolute url? What's the point of returning the relative url? It feels inconsistent, a better approach would be to always returning the absolute url OR let us decide if we want the absolute or relative URL... – JOSEFtw Apr 29 '15 at 18:59
  • 1
    The method is used by a lot of components, internally as well as externally, so the behavior couldn't just be changed without consequences. In regards to letting you decide, that is just a matter of prioritizing features. And for product feature requests I would refer to EPiServer World rather than Stack Overflow. :) – Henrik N Apr 29 '15 at 21:37
3

We have a support case regarding this. We want the url resolver to always return an absolute url when requesting the canonical url. This is our current solution:

public static string ExternalUrl(this PageData p, bool absoluteUrl, string languageBranch)
{
    var result = ServiceLocator.Current.GetInstance<UrlResolver>().GetUrl(
        p.ContentLink,
        languageBranch,
        new VirtualPathArguments
        {
            ContextMode = ContextMode.Default,
            ForceCanonical = absoluteUrl
        });

    // HACK: Temprorary fix until GetUrl and ForceCanonical works as expected,
    // i.e returning an absolute URL even if there is a HTTP context that matches the page's 
    // site definition and host.
    if (absoluteUrl)
    {
        Uri relativeUri;

        if (Uri.TryCreate(result, UriKind.RelativeOrAbsolute, out relativeUri))
        {
            if (!relativeUri.IsAbsoluteUri)
            {
                var siteDefinitionResolver = ServiceLocator.Current.GetInstance<SiteDefinitionResolver>();
                var siteDefinition = siteDefinitionResolver.GetDefinitionForContent(p.ContentLink, true, true);
                var hosts = siteDefinition.GetHosts(p.Language, true);
                var host = hosts.FirstOrDefault(h => h.Type == HostDefinitionType.Primary) ?? hosts.FirstOrDefault(h => h.Type == HostDefinitionType.Undefined);

                var basetUri = siteDefinition.SiteUrl;

                if (host != null)
                {
                    // Try to create a new base URI from the host with the site's URI scheme. Name should be a valid
                    // authority, i.e. have a port number if it differs from the URI scheme's default port number.
                    Uri.TryCreate(siteDefinition.SiteUrl.Scheme + "://" + host.Name, UriKind.Absolute, out basetUri);
                }

                var absoluteUri = new Uri(basetUri, relativeUri);

                return absoluteUri.AbsoluteUri;
            }
        }
    }

    return result;
}
Johan Petersson
  • 972
  • 4
  • 13
  • 1
    While your solution is fine, the ForceCanonical does not suggest that the returned URL is absolute, only that it should always point to the primary site regardless of context. As this means that the URL only is relative when the current site is the primary, it's possible to simplify the solution by using the current host rather than having to go through the SiteDefinitionResolver. – Henrik N Apr 28 '15 at 01:46
  • Hi Henrik, thanks for comment. Could you please post an answer below with code? – gringo_dave Apr 28 '15 at 04:33
  • That is true Henrik, but current host is not always the page's host though. Regarding the absolute url, why not Always return an absolute url and then let us decide whether we want to render the absolute or the relative url? – Johan Petersson Apr 29 '15 at 07:01
  • Short version: Previous implementation details complicated things which made us prioritize getting the feature ready for version 8.0 rather making the change and stash until release v 9.0. Happy to discuss further in more appropriate forum. – Henrik N Apr 29 '15 at 21:33
1

I also ran into this issue when writing a scheduled job running on a multilanguage, multihostname site. In order to get an absolute url with the right hostname in a HttpContext-less situation I had to combine Henrik's code with Dejan's code from here: https://www.dcaric.com/blog/episerver-how-to-get-external-page-url.

This is what I came up with:

public static string ExternalUrl(this ContentReference contentLink, CultureInfo language)
{
    var urlString = UrlResolver.Current.GetUrl(contentLink, language.Name, new VirtualPathArguments { ForceCanonical = true });

    var uri = new Uri(urlString, UriKind.RelativeOrAbsolute);
    if (uri.IsAbsoluteUri) return urlString;

    string externalUrl = HttpContext.Current == null
        ? UriSupport.AbsoluteUrlBySettings(urlString)
        : HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority) + urlString;

    return externalUrl;
}
Per
  • 33
  • 7
0

I've manage to write something base on the Johan & Henry answers:

public static string GetExternalAbsoluteFriendlyUrl(
    this ContentReference reference,
    string language)
{
    var urlResolver = ServiceLocator.Current.GetInstance<UrlResolver>();
    var friendlyUrl = urlResolver.GetUrl(reference, language);

    if (string.IsNullOrEmpty(friendlyUrl))
        return friendlyUrl;

    var uri = new Uri(friendlyUrl, UriKind.RelativeOrAbsolute);
    if (uri.IsAbsoluteUri)
        return friendlyUrl;

    if (HttpContext.Current != null)
        return new Uri(HttpContext.Current.Request.Url, uri).ToString();

    var siteDefinitionResolver =
        ServiceLocator.Current.GetInstance<SiteDefinitionResolver>();
    var siteDefinition =
        siteDefinitionResolver.GetDefinitionForContent(reference, true, true);

    return new Uri(siteDefinition.SiteUrl, friendlyUrl).ToString();
}
Community
  • 1
  • 1
gringo_dave
  • 1,372
  • 17
  • 24
  • I've selfishly selected my reply as an answer, because it worked for me. It is a merge of both answers provided by Henrik and Johan. Thank you guys. – gringo_dave Apr 30 '15 at 09:43