turquoiseowl / i18n

Smart internationalization for ASP.NET

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Relative URLs for images, css, JavaScript

amainiii opened this issue · comments

When resources (image, css, javascript) are embedded using relative paths, the
browser will request them with the language tag. Because these resources would typically be excluded due to filtering, the language tag would not be removed from the URL. The only solution we can find to handle relative paths to resources is to set the QuickUrlExclusionFilter to null.

Because the application might not be installed directly underneath a host name, recommended best practice for ASP.NET is to use "~/images/resource.png" or "images/resource.png" rather than "/images/resource.png".

If I'm understanding this correctly, the QuickUrlExclusionFilter would not be useful in these cases and perhaps a caveat should be added to the manual/wiki to help future adopters.

Hi, I'm not totally with you. Would you try explaining again.

On the point about ~/images/resource.png: doesn't that get resolved to an absolute URL or can it still be resolved to a relative one? But anyhow i18n should work with relative paths okay.

BTW, it really helps me to talk in terms of HTTP and requests/responses. Thanks.

On the point about ~/images/resource.png: doesn't that get resolved to an absolute URL or can it still be resolved to a relative one? But anyhow i18n should work with relative paths okay.

ASP.NET converts ~/images/resource.png to a relative path: "images/resource.png". The browser thinks that the web page that contains the reference to the image is (for example) "http://host.domain.tld/en/Default.aspx". So when the browser does a GET on the image, it requests "http://host.domain.tld/en/images/resource.png". Because the QuickUrlExclusionFilter bypasses these image files, the language tag (en) is not removed from the path, and the result is 404, not found.

I think ASP.NET used to convert tilde paths to absolute URLs prior to .NET 4.0, but I can't swear to it. I'll provide a sample ASP.NET solution for Visual Studio 2015, if that helps.

Checking a site here (ASP.NET v4.0), for this markup (in Razor):

<img src="~/Content/img/dslogo-full-inverse-32.png" />

I get in the response:

<img src="/Content/img/dslogo-full-inverse-32.png"  />

(Note the leading slash.)

So your leading slash is being dropped? I.e. "~/" goes to "" and not "/"?

It's hard to find any hard and fast documentation on what the tilde means, but the best I can find is that it represents the application root, in which case I'm struggling to see how/why the original slash is being dropped from "images". What is your version of ASP.NET and IIS? This must be something new in those versions.

Our current theory is that MVC is converting "~" paths to "rooted" paths, but Web Forms is converting everything to relative paths. The corresponding .NET method calls are ResolveUrl (which "roots" everything) and ResolveClientUrl which generates relative paths. We've been looking at this all morning, and would like to debug the i18n in visual studio, but have never implemented/tested a System.Web.IHttpModule, so are not sure exactly how to proceed to debug.

Attached is a Visual Studio 2015 ASP.NET 4.5 Web Forms solution that illustrates the problems we are having with relative URLs.
Samplei18n.zip

If it helps, some notes here on how to debug i18n: #109

Here's a hack on the web:

<a href="<%= Page.ResolveUrl("~/contactus.aspx")%>" >Contact us</a>

We have added/modified the following code near the end of LocalizeUrl in i18n/Concrete/EarlyUrlLocalizer.cs and it seems to resolve our issues with ASP.NET web forms (at least so far). Not sure if this has an adverse impact on MVC so we don't feel comfortable forking/pushing/pulling yet.

// root the url relative to the application root, eliminating "../..", "./", etc.
Uri newUri = new Uri(requestUrl, url);
String newUrl = newUri.PathAndQuery;

// Localize the URL.
return m_urlLocalizer.SetLangTagInUrlPath(context, newUrl, 
     UriKind.RelativeOrAbsolute, langtag);

Yeah, that looks good. I've tweaked slightly in the hope of making it optimally efficient. Let me know if it still works or not. If it does I can patch it in:

            // If url is un-rooted...make it absolute based on the current request URL.
            // By un-rooted we mean it is not absolute (i.e. it starts from the path component),
            // and that that path component itself is a relative path (i.e. it doesn't start with a /).
            // In doing so we also eliminate any "../..", "./", etc.
            // Examples:
            //      url (before)                            requestUrl                  url (after)
            //      -------------------------------------------------------------------------------------------------------------------
            //      content/fred.jpg                        http://example.com/blog     /blog/content/fred.jpg              (changed)
            //      /content/fred.jpg                       http://example.com/blog     /content/fred.jpg                   (unchanged)
            //      http://example.com/content/fred.jpg     http://example.com/blog     http://example.com/content/fred.jpg (unchanged)
            //
            if (!url.StartsWith("/")
                && !Uri.IsWellFormedUriString(url, UriKind.Absolute)) {
                Uri newUri = new Uri(requestUrl, url);
                url = newUri.PathAndQuery;
            }

            // Localize the URL.
            return m_urlLocalizer.SetLangTagInUrlPath(context, url, UriKind.RelativeOrAbsolute, langtag);

We've tested your changes and they work fine. We had some concerns about the performance of the Uri.IsWellFormedUriString method http://referencesource.microsoft.com/#System/net/System/UriExt.cs so we tried performance testing with several other alternatives, but none were clearly superior. So, if you wouldn't mind making the change next time its convenient, we'd appreciate it. - Thanks!

Good discovery about Uri.IsWellFormedUriString! I should have suspected. Anyhow it occurred to me we only need call it if the url doesn't contain a colon (in which case maybe we don't need to call it at all -- bit late for me to work that through).

Anyhow, you may want to check the commit. I've added some test cases as well so hopefully it's cracked.