locked
Losing selected culture on page change question... RRS feed

  • Question

  • User758457977 posted
    Hello all.  I am in the process of localizing a website to en-US, fr-FR, de-DE, and es-ES.  I am using four .resx files in my App_GlobalResources folder.  My forms authentication page is Login.aspx, and on that page I have four flags (ImageButtons, one for each culture).  When a user clicks a flag, the following code is executed:

    //Note: there are four Event Handlers (one for each button)
    protected void ibGermany_Click(object sender, ImageClickEventArgs
    e)
    {
          CultureInfo c = new CultureInfo("de-DE"
    );
          System.Threading.Thread
    .CurrentThread.CurrentCulture = c;
          System.Threading.Thread
    .CurrentThread.CurrentUICulture = c;
          Login1.UserNameLabelText = Resources.AppResource.User_Name + ":"
    ;
          Login1.PasswordLabelText = Resources.AppResource.Password + ":"
    ;
    }

    This all works great.  However, my problem is when a user goes to a different page, the culture information is destroyed.  For example, I created a page called Test.aspx.  In the Page_Load method of Test.aspx I have the following:

    Label1.Text = System.Threading.Thread.CurrentThread.CurrentUICulture.ToString();

    This displays en-US everytime, regardless of what culture I chose at login.  Now, I was under the impression that each Session was in its own Thread, but this is obviously incorrect.  How can I retain the culture that is set at Login.aspx across every page while the user is logged in?  Thanks in advance.

    Thursday, October 27, 2005 2:46 PM

All replies

  • User1183903743 posted
    check your web config for a global setting or the UICulture prop in the page directive, especially if it is set to auto and your test browser is set to en-us.

    HTH!
    Thursday, October 27, 2005 9:33 PM
  • User758457977 posted
    There is nothing in my global.asax or web.config file that mentions the UICulture property.  Also, my page directive for Test.aspx looks like:

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Test.aspx.cs" Inherits="Test" %>

    I'm wondering if my approach is even correct.  Is it true, that if I set the CurrentThread culture to whatever the user selects, that the user's Session should be in that culture throughout?  Or is it just for one page?

    Tuesday, November 1, 2005 9:13 AM
  • User1424575140 posted
    You have part of the right approach, but you will need to save the user's choice (cookie) and then set the thread culture and uiCulture on each request.
    Tuesday, November 1, 2005 4:27 PM
  • User1183903743 posted


    Tuesday, November 1, 2005 5:43 PM
  • User1183903743 posted

    Is it true, that if I set the CurrentThread culture to whatever the user selects, that the user's Session should be in that culture throughout?  Or is it just for one page?


    The executing thread needs to be set with every request. It can then be persisted across different requests in the Session collection and retrieved in the new request(not postbacks but different URLs.

    Your general question is right on point. This is really poorly explained and virtually no samples.

    You are, however, confusing System.Threading.Thread and System.Web.SessionState.HttpSessionState .
     Session is a collection that remains unique for the user, and the autogenerated ID is presented across requests via the client cookie. I think the ID algorithm was improved in v2.0 but essentially the same architecture.

    See a nice excerpt from an eBook about session in this post:
    http://forums.asp.net/1094746/ShowPost.aspx Ignore the static discussion and just read the Session stuff. The v1.x way of persisting this kind of data was the Session, and it was a so-so mechanism. That is why it was replaced with the personalization in v2.0. You should seriously consider that over session.

    The way we used to do it:


    protected void ibGermany_Click(object sender, ImageClickEventArgs e){
       CultureInfo ci = new CultureInfo("de-DE");
       Thread.CurrentThread.CurrentCulture = ci;
       Thread.CurrentThread.CurrentUICulture = ci;
       Session["culture"] = ci;

       Login1.UserNameLabelText = Resources.AppResource.User_Name + ":";
       Login1.PasswordLabelText = Resources.AppResource.Password + ":";
    }

     
    Then in your Test.aspx:

    protected void Page_Load(object sender, EventArgs e){
       CultureInfo ci = (CultureInfo)Session["culture"];
       Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci);
       Thread.CurrentThread.CurrentUICulture = ci;
       Label1.Text = "Current culture is: " + Thread
    .CurrentThread.CurrentUICulture.EnglishName;
    }



    //======================================

    Now you can simplify this by putting the code in the the Global.Asax Application_AcquireRequestState event handler:

    protected void Application_AcquireRequestState(object sender, EventArgs e) {
       //must incorporate error handling because this applies to a much wider range of pages 
       CultureInfo ci = Session["culture"] as CultureInfo;
       //let the system do the fallback to invariant 
       if (ci != null){
          System.Threading.Thread.CurrentThread.CurrentCulture = ci;
          //it's safer to make sure you are feeding it a specific culture and avoid exceptions 
          System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo
    .CreateSpecificCulture(ci.Name);
       }
    }

    Your login page only needs:

       //notice I am using the static method
       
    protected void ibGermany_Click(object sender, ImageClickEventArgs e){
       Session["culture"] = CultureInfo.GetCultureInfo("de-DE");
       //the rest of your code
    }

    Your Test.aspx:

    //"automated" session access,
    //again static prop, simpler no threading namespace directly, 'cause it really doesn't come into play 
    protected
    void Page_Load(object sender, EventArgs e){
         Label1.Text = "Current culture is: " + CultureInfo.CurrentUICulture.EnglishName;
    }

    HTH!

    Tuesday, November 1, 2005 5:52 PM
  • User758457977 posted
    Thank you for the insight rmprimo.  Your solution is much better better than what I was going to implement which was set the Culture in the Page Load of every page in my application.  I was hoping to avoid this and it looks like this will do the trick.  There is one issue with this solution however.  It appears that the CultureInfo object is always one step behind.  For example, when I first go to Login.aspx, the text is in English.  If I click the German flag, the page is reloaded with English again.  If I click the German flag again, the text becomes German.  Also, if I go to Login.aspx and click on Germany, the page is reloaded in English.  If I then click the Spain flag, the page is then showed in German.  If I then click the French flag, the page is loaded in Spanish. etc...  The Culture is always one step behind what the user clicked.  I have added:

    Login1.UserNameLabelText = Resources.AppResource.User_Name + ":";
    Login1.PasswordLabelText = Resources.AppResource.Password + ":";

    to the Page_Load method, but that doesn't seem to help.  My Event Handlers now look like:

    protected void ibGermany_Click(object sender, ImageClickEventArgs e)
    {
          Login1.UserNameLabelText = Resources.AppResource.User_Name + ":";
          Login1.PasswordLabelText = Resources.AppResource.Password + ":";
          Session["Culture"] = CultureInfo.GetCultureInfo("de-DE");
    }


    I even tried a Server.Transfer("Login.aspx") at the end of each Event Handler with no success.  Any ideas why this is happening?  Thanks again for your assistance.
    Wednesday, November 2, 2005 12:57 PM
  • User1183903743 posted

    protected void ibGermany_Click(object sender, ImageClickEventArgs e)
    {
          Login1.UserNameLabelText = Resources.AppResource.User_Name + ":";
          Login1.PasswordLabelText = Resources.AppResource.Password + ":";
          Session["Culture"] = CultureInfo.GetCultureInfo("de-DE");
    }



    Of course! You changed the order. It should be:

    protected void ibGermany_Click(object sender, ImageClickEventArgs e)
    {
          Session["Culture"] = CultureInfo.GetCultureInfo("de-DE");      
          Login1.UserNameLabelText = Resources.AppResource.User_Name + ":";
          Login1.PasswordLabelText = Resources.AppResource.Password + ":";      
    }


    Anyway, this is a hard-wired example which means that part or all the site is recompiled every time you change the string.

    You should plug in the selected value from a dropdown or querystring etc.


    protected void ibGermany_Click(object sender, ImageClickEventArgs e)
    {
          Session["Culture"] = CultureInfo.GetCultureInfo(ddlLangs.SelectedValue);      
          Login1.UserNameLabelText = Resources.AppResource.User_Name + ":";
          Login1.PasswordLabelText = Resources.AppResource.Password + ":";      
    }




    Wednesday, November 2, 2005 2:37 PM
  • User758457977 posted

    My bad.  I found something interesting after implementing this.  I have my Global.asax set to write to a log.txt file every time an error is thrown.  After a couple of hours of coding and testing the pages, an error was thrown so I went to check the log.  The log file was quite large.  I deleted the log file, went back to the Login.aspx and clicked a flag.  The following is written to the log every time I click on a flag.  What is interesting is that all three code blocks are written for each single click but I call Server.ClearError() at the end of the Application_Error method.  What is also interesting is that everything works as far as retaining the Culture (even though putting the Session["Culture"] = CultureInfo.GetCultureInfo("de-DE"); first did not change my previous problem). 

    Do you think I should be using the Application_PreRequestHandlerExecute method rather than Application_AcquireRequestState in Global.asax?

    11/2/2005 4:29:43 PM
    Offending Page:
    http://localhost/AirlineContracts/WebResource.axd?d=vKCCYdxn0HTrhX4lNr1Lew2&t=632623653726411296
    Exception: Session state is not available in this context.
    Stack Trace:    at System.Web.HttpApplication.get_Session()
       at ASP.Global_asax.Application_AcquireRequestState(Object sender, EventArgs e) in c:\Inetpub\wwwroot\AirlineContracts\Global.asax:line 8
       at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
       at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
     
    11/2/2005 4:29:43 PM
    Offending Page:
    http://localhost/AirlineContracts/WebResource.axd?d=uDHTGG5mv3tI41q8HuQb7P8kkxxmVf18_igBbBn4E4E1&t=632623653726411296
    Exception: Session state is not available in this context.
    Stack Trace:    at System.Web.HttpApplication.get_Session()
       at ASP.Global_asax.Application_AcquireRequestState(Object sender, EventArgs e) in c:\Inetpub\wwwroot\AirlineContracts\Global.asax:line 8
       at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
       at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
     
    11/2/2005 4:29:43 PM
    Offending Page:
    http://localhost/AirlineContracts/WebResource.axd?d=v0PbiOZvhdiZ6wbSB6Fr-g2&t=632623653726411296
    Exception: Session state is not available in this context.
    Stack Trace:    at System.Web.HttpApplication.get_Session()
       at ASP.Global_asax.Application_AcquireRequestState(Object sender, EventArgs e) in c:\Inetpub\wwwroot\AirlineContracts\Global.asax:line 8
       at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
       at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

     

     

    Wednesday, November 2, 2005 4:41 PM
  • User1183903743 posted
    (even though putting the Session["Culture"] = CultureInfo.GetCultureInfo("de-DE"); first did not change my previous problem).
    Works on my machine - v2.0 beta2

    Do you think I should be using the Application_PreRequestHandlerExecute method rather than Application_AcquireRequestState in Global.asax?
    NO!


    Wednesday, November 2, 2005 6:13 PM
  • User1424575140 posted
    Just to help with the concepts, you are using the session as a convinient place to remember the user's choice. Setting a session variable to a culture doesn't do anything by and of itself.
    Saving the users choice allows you to re-apply that same choice for each of the user's subsequent requests. (I have saved the choice with a cookie, not with sessions.)

    I have always used Application_BeginRequest to set the thread's Culture and UICulture from the saved value.

    string savedValue = Request.Cookies["CultureInfo"];
    //validation of value & other stuff

    System.Threading.Thread.CurrentThread.CurrentCulture CultureInfo.GetCultureInfo(savedValue);
    System.Threading.Thread.CurrentThread.CurrentUICulture CultureInfo.GetCultureInfo(savedValue);

    Wednesday, November 2, 2005 6:25 PM
  • User1183903743 posted
    I have always used Application_BeginRequest to set the thread's Culture and UICulture from the saved value.

    string savedValue = Request.Cookies["CultureInfo"];


    Absolutely. The culture is not sensitive data so it can be in the cookie. Only caveat if you already have too much other data in the cookie. There is a limit.

    Best practices for v2.0 suggest to have this value in the Profile or a similar data component.

    Since you are hitting the db for authentication and subsequently other tidbits of the user's profile will come into play why not the culture too and carry it in a profile object and bind it to controls etc. The beauty of this is all this mundane code will be taken care of.
    Wednesday, November 2, 2005 6:52 PM
  • User758457977 posted
    Thank you all for your help.  I will strongly consider allowing the user to save his/her culture preferences and storing the value in the database rather than a Session variable.  As far as the Application Error is concerned, I will keep working on that.  This is my first Localization project that I have worked on, and I have learned quite a bit over the past few days.  Thanks again.
    Thursday, November 3, 2005 7:39 AM
  • User1183903743 posted
    Having the culture in the db can later help you datamine the ethnic makeup of your users. You can have more than one language field in the profile: preferred, native, current.

    The one we have been talking about would be "current" it will be modified each time the user explicitly changes it and stay that way upon logout.

    It is up to you to decide which language to render the UI next time - the permanent ones(preferred, native) or the last saved(current). You could even let the user decide by filling a dropdown with his languages. You can let them browse with a current but knowing their permanent one is still the same. and you could make the choice itself a profile option.

    After a few months of these when you are bored you can play with the data. Combine this with some IP detection service etc. and you can have a world of info about your users.

    For example if your reports show that most Japanese users have English as secondary language and many even as preferred, then you might not want to waste the money for more translation.

    Or if you see that Spanish speaking users really stick to their Spanish, and they are a pretty nice chunk of your users, you might want to provide even more localization - say some content too and ads or whatever.


    Thursday, November 3, 2005 11:18 AM
  • User1424575140 posted
    I like having both the preferred and current.
    I keep a prefered language in the user's profile and the current language in a cookie.
    Friday, November 4, 2005 3:58 PM
  • User1309395204 posted

    Hallo.<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p></o:p>

    <o:p> </o:p>

    I read this thread and I implemented the internationalization feature with cookies because i found it a good idea.

    <o:p> </o:p>

    I have two comments:<o:p></o:p>

    • The Application_BeginRequest()in global.aspx is called each time a request is done. This means if my page has some images and other stuff a request is sended and all the instructions are applied again. This means for loading one page the content of BeginRequest is executed several times. Therefore I want to ask: “is there a better place to put the InitializeCulture() code?” <o:p></o:p>
    • I added a new page and I had the problem that when I localized my page Vs2005 tool: generate local resources added Culture="auto" and UICulture="auto" that means that browser choose the prefered language. Like I understood the Application_BeginRequest() in global.aspx occures early than the browser-language-selection therefore the System.Threading.Thread.CurrentThread.CurrentUICulture was set always wrong. <o:p></o:p>

    <o:p> </o:p>

    Kind regards <o:p></o:p>

    <o:p> </o:p>

    Daniel <o:p></o:p>

     

    Friday, March 3, 2006 5:52 AM
  • User1183903743 posted

    This means for loading one page the content of BeginRequest is executed several times. 


    No just once.

     

    You might want to go over what HttpRequest is. An img with a src="imgpath.gif" is not a request because it has a different extension from aspx. So in IIS it would not even go to the aspnet runtime. However, if there was an iframe, for example, with src="somepath.aspx" it would result in another request but that would be created after the first one.

    Sunday, March 5, 2006 2:09 PM
  • User1309395204 posted

    Thanks for your replay.<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p></o:p>

    <o:p> </o:p>

    But I am not sure that it works really like you are saying.<o:p></o:p>

    <o:p> </o:p>

    I tried to create a simple page with one pictures and I put a breakpoint on the method Sub Application_BeginRequest() in global.asax moreover I used an http-sniffer to take trace of the request. The result was following: the debugger stopped 2 times. <o:p></o:p>

    Than I added a page with 2 pictures and the debugger stopped 3 times.<o:p></o:p>

    <o:p> </o:p>

    So I suppose that it works like this: <o:p></o:p>

    The browser sends a get-request for a page. If there are some images and the images are not in the cache (temporary internet files of browser) the browser send the next request for getting the images. Therefore the Application_BeginRequest() is exectued more times for a single page.<o:p></o:p>

    If the browser already has the images it simple takes the cached images.<o:p></o:p>

    <o:p> </o:p>

    Kind regards <o:p></o:p>

    <o:p> </o:p>

    Daniel Carrarini<o:p></o:p>

    Monday, March 6, 2006 3:21 AM
  • User1183903743 posted

    have it your way!

    You should start another thread about this new question.

    Monday, March 6, 2006 7:57 AM
  • User-503940700 posted

    My two cents:

    Application_BeginRequest would be fired each time *any* page is requested. But Application_Start would only execute once for each request (and not for subsequent requests from the same user), unless the Application times out. There should be no separate requests for images and other stuff as rmprimo explains.

    ASP.NET worker process (aspnet_wp) has many instances of Application objects (it maintains a pool) and each request is treated on a new thread. I generally like to create a basepage class and in its InitializeCulture() method set the culture set by the user. Then I persist the same in the Session.

     -Vivek

    Thursday, September 7, 2006 2:04 PM
  • User-484987990 posted

    Hi folks,
       sorry to raise such an old thread, but i'm looking at the following code below and have a quick question. 

    protected void Application_AcquireRequestState(object sender, EventArgs e) {
       //must incorporate error handling because this applies to a much wider range of pages 
       CultureInfo ci = Session["culture"] as CultureInfo;
       //let the system do the fallback to invariant 
       if (ci != null){
          System.Threading.Thread.CurrentThread.CurrentCulture = ci;
          //it's safer to make sure you are feeding it a specific culture and avoid exceptions 
          System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo
    .CreateSpecificCulture(ci.Name);
       }
    }

     Why are you using the method CreateSpecificCulture in the second line for setting the culture info instead of the following:

                  System.Threading.Thread.CurrentThread.CurrentUICulture = ci;

    Thanks guys :)

    Saturday, October 27, 2007 8:12 AM