turquoiseowl / i18n

Smart internationalization for ASP.NET
Other
556 stars 156 forks source link

GET Api calls are re-directed #215

Closed mmunchandersen closed 8 years ago

mmunchandersen commented 9 years ago

I'm adding web api 2 methods to a asp.net mvc 5 project, however, when i18n is enabled, my GET method calls are being redirected to selected country. For example: http://xxx/api/v1/system/getappversion2 is redirected to http://xxx/en/v1/system/getappversion2

My POST methods are called correctly with or without i18n.

Must I somewhere exclude api from the i18n logic?

Two examples: [Route("api/v1/system/getappversion"), HttpPost] public HttpResponseMessage GetAppVersion() { const int appVersion = 1; return Request.CreateResponse(HttpStatusCode.OK, new { AppVersion = appVersion }); }

[Route("api/v1/system/getappversion2"), HttpGet] public HttpResponseMessage GetAppVersion2() { const int appVersion = 1; return Request.CreateResponse(HttpStatusCode.OK, new { AppVersion = appVersion }); }

GetAppVersion (POST) work with or without i18n. GetAppVersion2 (GET) only works without i18n. When i18n api/v1/system/getappversion2 is re-directed to en/v1/system/getappversion2

In Fiddler I get:

Terse summary GET http://localhost:50708/api/v1/system/getappversion2 302 Redirect to /en/v1/system/getappversion2

GET http://localhost:50708/en/v1/system/getappversion2 404 Not Found (text/html)

Full summary Result Protocol Host URL Body Caching Content-Type Process Comments Custom
1 302 HTTP localhost:50708 /api/v1/system/getappversion2 0 fiddler:9088
2 404 HTTP localhost:50708 /en/v1/system/getappversion2 3.204 private text/html; charset=utf-8 fiddler:9088

Headers only GET http://localhost:50708/api/v1/system/getappversion2 HTTP/1.1 User-Agent: Fiddler Host: localhost:50708

HTTP/1.1 302 Moved Temporarily Location: /en/v1/system/getappversion2 X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcTW9ydGVuXFByb2plY3RzXE5TQVxJblN0b3JlRXhjZWxsZW5jZVxzcmNcSW5TdG9yZUV4Y2VsbGVuY2UuV2ViXGFwaVx2MVxzeXN0ZW1cZ2V0YXBwdmVyc2lvbjI=?= X-Powered-By: ASP.NET X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' https://_.googleapis.com https://_.gstatic.com https://www.google-analytics.com https://*.google.com; frame-ancestors 'self' https://www.google.com Date: Wed, 08 Jul 2015 19:40:56 GMT Content-Length: 0


GET http://localhost:50708/en/v1/system/getappversion2 HTTP/1.1 User-Agent: Fiddler Host: localhost:50708

HTTP/1.1 404 Not Found Cache-Control: private Content-Type: text/html; charset=utf-8 X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcTW9ydGVuXFByb2plY3RzXE5TQVxJblN0b3JlRXhjZWxsZW5jZVxzcmNcSW5TdG9yZUV4Y2VsbGVuY2UuV2ViXHYxXHN5c3RlbVxnZXRhcHB2ZXJzaW9uMg==?= X-Powered-By: ASP.NET X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' https://_.googleapis.com https://_.gstatic.com https://www.google-analytics.com https://*.google.com; frame-ancestors 'self' https://www.google.com Date: Wed, 08 Jul 2015 19:40:56 GMT Content-Length: 3204

turquoiseowl commented 9 years ago

Please see https://github.com/turquoiseowl/i18n#exclude-urls-from-being-localized. Thank you.

turquoiseowl commented 9 years ago

Here is how you can exclude APIs from localization:

            i18n.UrlLocalizer.OutgoingUrlFilters += delegate(string url, Uri currentRequestUrl) {
                Uri uri;
                if (Uri.TryCreate(url, UriKind.Absolute, out uri)
                    || Uri.TryCreate(currentRequestUrl, url, out uri)) {
                    if (uri.LocalPath.IndexOf("/Api", StringComparison.OrdinalIgnoreCase) != -1) {
                        return false; }
                    if (uri.LocalPath.IndexOf("xdomain", StringComparison.OrdinalIgnoreCase) != -1) {
                        return false; }
                }
                return true;
            };
mmunchandersen commented 8 years ago

update:

when I use:

i18n.UrlLocalizer.IncomingUrlFilters += delegate(Uri url)
{
    if (url.LocalPath.Contains("/api"))
        return false;
    return true;
};

instead of:

i18n.UrlLocalizer.OutgoingUrlFilters += delegate(string url, Uri currentRequestUrl)
{
    Uri uri;
    if (Uri.TryCreate(url, UriKind.Absolute, out uri)
        || Uri.TryCreate(currentRequestUrl, url, out uri))
    {
        if (uri.LocalPath.IndexOf("/api", StringComparison.OrdinalIgnoreCase) != -1)
            return false;
    }
    return true;
};

things to works as expected.

Hi Martin

thanks for your response. Still have the trouble, and hope you could give me a few pointers.

I just forked v2.1.4.0, build it, updated dll's in asp.net mvc project (tested it works), read the documentation and added your code suggestion (i18n.UrlLocalizer.OutgoingUrlFilters). However, I'm still getting the re-direct behaviour.

If I comment out the following in web.config the GET api method runs correctly:

<system.web><httpModules>
<add name="i18n.LocalizingModule" type="i18n.LocalizingModule, i18n" />

<system.webServer><modules>
<add name="i18n.LocalizingModule" type="i18n.LocalizingModule, i18n" />

In Global.asax.cs in protected void Application_Start() I have adding the following code:

i18n.UrlLocalizer.OutgoingUrlFilters += delegate(string url, Uri currentRequestUrl)
{
    Uri uri;
    if (Uri.TryCreate(url, UriKind.Absolute, out uri)
        || Uri.TryCreate(currentRequestUrl, url, out uri))
    {
        if (uri.LocalPath.IndexOf("/api", StringComparison.OrdinalIgnoreCase) != -1)
            return false;
    }
    return true;
};

The code (i18n.UrlLocalizer.OutgoingUrlFilters in Application_Start()) is never touched when calling the GET api method, and I'm still getting the re-direct behaviour:

Api methods:

public class SystemController : BaseApiController
{
    [Route("api/v1/system/GetApiVersion"), HttpPost]
    public HttpResponseMessage GetApiVersion()
    {
        return Request.CreateResponse(HttpStatusCode.OK, new
        {
            Version = Constants.ApiVersion
        });
    }

    [Route("api/v1/system/GetApiVersion2"), HttpGet]
    public HttpResponseMessage GetApiVersion2()
    {
        return Request.CreateResponse(HttpStatusCode.OK, new
        {
            Version = Constants.ApiVersion
        });
    }
}

Fiddler GET call (the one failing):

GET http://localhost:50708/api/v1/system/GetApiVersion2 HTTP/1.1
Host: localhost:50708

Fiddler response to GET call:

#   Result  Protocol    Host        URL             Body    Caching Content-Type    Process Comments    Custom  
8   302 HTTP        localhost:50708 /api/v1/system/GetApiVersion2   0                           fiddler:9088            
9   404 HTTP        localhost:50708 /en/v1/system/GetApiVersion2    3.204   private text/html; charset=utf-8        fiddler:9088            

Fiddler POST call (works):

POST http://localhost:50708/api/v1/system/GetApiVersion HTTP/1.1
Host: localhost:50708

Fiddler response to POST call:

#   Result  Protocol    Host    URL Body    Caching Content-Type    Process Comments    Custom  
42  200 HTTP    localhost:50708 /api/v1/system/GetApiVersion    13  no-cache; Expires: -1   application/json; charset=utf-8             

screenshot01592

the i18n web.config -> appSettings keys:

<add key="i18n.DirectoriesToScan" value="" />
<!--Rel to web.config file-->
<add key="i18n.WhiteList" value="*.cs;*.cshtml;*.sitemap;*.js" />
<add key="i18n.NuggetBeginToken" value="[#[" />
<add key="i18n.NuggetEndToken" value="]#]" />
turquoiseowl commented 8 years ago

Hmm, I can't see anything that you're doing wrong.

If I set a breakpoint in my OutgoingUrlFilter delegate then debug a request, here's my call stack when the BP get's hit:

    Lynx.dll!Lynx.MvcApplication.Application_Start.AnonymousMethod__2(string url = "/Scripts/modernizr-2.6.2.js", System.Uri currentRequestUrl = {System.Uri})  C#
>   i18n.dll!i18n.UrlLocalizer.FilterOutgoing(string url = "/Scripts/modernizr-2.6.2.js", System.Uri currentRequestUrl = {System.Uri})  C#
    i18n.dll!i18n.EarlyUrlLocalizer.LocalizeUrl(System.Web.HttpContextBase context = {System.Web.HttpContextWrapper}, string url = "/Scripts/modernizr-2.6.2.js", string langtag = "en-GB", System.Uri requestUrl = {System.Uri}, bool incomingUrl = false) C#
    i18n.dll!i18n.EarlyUrlLocalizer.ProcessOutgoing.AnonymousMethod__0(System.Text.RegularExpressions.Match match = {System.Text.RegularExpressions.Match}) C#
    System.dll!System.Text.RegularExpressions.RegexReplacement.Replace(System.Text.RegularExpressions.MatchEvaluator evaluator = {Method = {System.Reflection.RuntimeMethodInfo}}, System.Text.RegularExpressions.Regex regex, string input, int count = -1, int startat)   
    System.dll!System.Text.RegularExpressions.Regex.Replace(string input, System.Text.RegularExpressions.MatchEvaluator evaluator, int count, int startat)  
    System.dll!System.Text.RegularExpressions.Regex.Replace(string input, System.Text.RegularExpressions.MatchEvaluator evaluator)  
    i18n.dll!i18n.EarlyUrlLocalizer.ProcessOutgoing(string entity, string langtag = "en-GB", System.Web.HttpContextBase context = {System.Web.HttpContextWrapper})  C#
    i18n.dll!i18n.ResponseFilter.Flush()    C#
    System.Web.dll!System.Web.HttpWriter.FilterIntegrated(bool finalFiltering = true, System.Web.Hosting.IIS7WorkerRequest wr)  
    System.Web.dll!System.Web.HttpResponse.FilterOutput()   
    System.Web.dll!System.Web.HttpApplication.CallFilterExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()   
    System.Web.dll!System.Web.HttpApplication.ExecuteStep(System.Web.HttpApplication.IExecutionStep step = {System.Web.HttpApplication.CallFilterExecutionStep}, ref bool completedSynchronously = false)   
    System.Web.dll!System.Web.HttpApplication.PipelineStepManager.ResumeSteps(System.Exception error)   
    System.Web.dll!System.Web.HttpApplication.BeginProcessRequestNotification(System.Web.HttpContext context, System.AsyncCallback cb)  
    System.Web.dll!System.Web.HttpRuntime.ProcessRequestNotificationPrivate(System.Web.Hosting.IIS7WorkerRequest wr = {System.Web.Hosting.IIS7WorkerRequest}, System.Web.HttpContext context = {System.Web.HttpContext})    
    System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext = 194144004, System.IntPtr moduleData, int flags) 
    System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags)   
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags) 
    System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags)   
    [Appdomain Transition]  

If you can try setting breakpoints back along that chain you might be able to see where it goes wrong.

turquoiseowl commented 8 years ago

Ah yes, sorry, I also have a matching IncomingUrlFilters delegate:

            i18n.UrlLocalizer.IncomingUrlFilters += delegate(Uri url)
            {
                if (url.LocalPath.IndexOf("/Api", StringComparison.OrdinalIgnoreCase) != -1) {
                    return false; }
                if (url.LocalPath.IndexOf("xdomain", StringComparison.OrdinalIgnoreCase) != -1) {
                    return false; }
               //
                return true;
            };
mmunchandersen commented 8 years ago

Thanks. As mentioned, when I use:

i18n.UrlLocalizer.IncomingUrlFilters += delegate(Uri url)
{
    if (url.LocalPath.Contains("/api"))
        return false;
    return true;
};

my Api GET calls (http://domain/api/xxx) works just fine.