Monday, August 29, 2011

How to localize a website - ASP.NET

There are many different ways of doing localization for the website. This post explains one simple way of localization for the ASP.NET website. By and large, there are 3 steps of doing it...

Step 1. Create ASP.NET resource file

The first step is to create ASP.NET resource file (.resx). There are two types of ASP.NET resource file - Global Resource and Local Resource. Global resource file can be shared by all ASP.NET pages and local resource file only applies to one ASP.NET page. In this post, let's use Global resource file for simplicity. To add resource file, create a special ASP.NET folder called App_GlobalResources and add resource files under the folder.
  1.  (VS 2010) In ASP.NET web project, rightclick and select Add -> Add ASP.NET Folder -> App_GlobalResources. 
  2.  Under App_GlobalResources, add New Item (ex: WebResource1.resx file). This is English (or neutral) resource file.
  3. Under App_GlobalResources, add another New Item whose filename has locale characters such as ko,jp,es, etc. (ex: WebResource1.ko.resx file).
  4. Add the same resource [Name]s to the resource files and just localize resource [Value].

Step 2. Use Resource String in ASPX
Once resource strings are stored in .resx file, simply build the project and then VS will automatically generate strongly type resource class, just like WinForm .resx file. Now, you can use resource string in your .aspx file or .aspx.cs code behnid file.

<div>
<asp:label runat="server" text="<%$ Resources:WebResource1,Title %>">
</asp:label>
</div>

The example above shows that resource class is WebResource1 and resource name is Title. One thing to note is that you have to put the resource expression in ASP.NET server control such as asp:Label or asp:Literal, not in HTML tag. For example, you will get an error if you put resource expression in SPAN html tag.

In code behind, you can set the same way of .NET resource.

   using Resources;
   .....
   labelTitle.Text = WebResource1.Title;

Step 3. Set Page to use resource string based on Culture

Now, the last step is to set locale to the web page, so that the page reads the resource string from the specific language. You can set the entire web site to use specific culture by using web.config or set each page by using @page directive or programmatically. Here is an example of doing it in the code - overrides InitializeCulture() method and set 4 Culture properties as follows.

protected override void InitializeCulture()
{
  if (Session["lang"] != null)
  {
    lang = Session["lang"].ToString();
    Page.Culture = lang;
    Page.UICulture = lang;
    Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(lang);
    Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(lang);
  }
  base.InitializeCulture();
}

If you use Session to store language information, each user can use different language for the same web page. Even if this is not required to implment localization, I found it useful sometimes.

Sunday, August 28, 2011

WCF - how to fix maxStringContentLength 8192 limit

If you use WCF service from Web application or desktop application, you might have the following error if you send/receive big data.

The maximum string content length quota (8192) has been exceeded while reading XML data.

This error occurs since WCF by default sets 8K limit to prevent DoS attack. However, there are many business needs to increase this limit. And it can be adjusted by change some values in .config file (web.config for ASP.NET/WCF or app.config for Windows application).  There are many sites that talked about this topic. For example, you can refer to this discussion (The maximum string content length quota (8192) has been exceeded). I just decided to summarize this topic (well, primarily for myself) since those sites didn't work for me somehow, at least for my case.

Here is a simple example of increasing the 8K limit. Basically both WCF side web.config and client side web.config (let's assume ASP.NET, but the same applies to app.config) should be changed accordingly.

(1) Server side (WCF web.config)

In web.config for WCF, you need to increase maxBufferSize, maxReceivedMessageSize, maxStringContentLength. Below example increased the value to 2GB, but it purely depends on business need.

<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
 
  <system.serviceModel>

    <services>
      <service name="MyWcfService.MyService">
        <endpoint address="" binding="basicHttpBinding" bindingConfiguration="basicHttpConfiguration" contract="MyWcfService.IMyService">
        </endpoint>       
      </service> 
    </services>
   
    <bindings>     
      <basicHttpBinding>
        <binding name="basicHttpConfiguration" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">
          <readerQuotas maxStringContentLength="2147483647" />
        </binding>
      </basicHttpBinding>
    </bindings>   
   
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="false"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>   
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel> 
 
 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer> 
</configuration>



2) Client side (ASP.NET web.config)

In client side web.config, you need to increase maxBufferSize, maxBufferPoolSize, maxReceivedMessageSize, maxStringContentLength.


<system.serviceModel>
   <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_ICipherService" closeTimeout="00:01:00"
           openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
           allowCookies="false" bypassProxyOnLocal="false"
           hostNameComparisonMode="StrongWildcard"
           maxBufferSize="2147483647" maxBufferPoolSize="2147483647"
           maxReceivedMessageSize="2147483647"           messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
           useDefaultWebProxy="true">
           <readerQuotas maxDepth="32" maxStringContentLength="2147483647"
                  maxArrayLength="16384" maxBytesPerRead="4096"
                  maxNameTableCharCount="16384" />
           <security mode="None">
              <transport clientCredentialType="None" proxyCredentialType="None"
                           realm="" />
               <message clientCredentialType="UserName" algorithmSuite="Default" />
           </security>
         </binding>
     </basicHttpBinding>
  </bindings>
    
  <client>
     <endpoint address="http://www.mydomain.com/wcfservice/MyService.svc"
       binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IMyService"
       contract="MyWcfServiceReference.IMyService" name="BasicHttpBinding_IMyService" />
  </client>    
</system.serviceModel>