Friday, September 14, 2007

Web Deployment Projects with Visual Studio 2005

Using Web Deployment Projects with Visual Studio 2005



Abstract


Visual Studio 2005 provides deployment support through its Copy Web Site and Publish Web Site features. While these are ideal for many scenarios, there are other, more advanced scenarios where developers need the following capabilities:



  • More control over assembly naming and output.

  • Custom pre-processing and post-processing for the build.

  • The ability to exclude, add, and transform files and directories during builds.

  • The ability to modify the Web.config file to change database connection strings, application settings, or the URLs for Web references, depending on the build configuration. (For example, it might be necessary to use different values for development, test, staging, and release settings).


This white paper describes a solution to these advanced scenarios and introduces a new feature called Web Deployment Projects for Visual Studio 2005.


Download the code sample that accompanies this article, WebDeploymentSetup.msi.



Web Deployment Projects


Web Deployment Projects is an add-in package to Visual Studio 2005. It does not change the behavior of any feature in Visual Studio 2005; it simply adds additional functionality specific to Web site deployment.


A Web Deployment project is an MSBuild project file for a Web site. It fully integrates into the Visual Studio 2005 build configuration manager and supports configuration options for compilation and deployment, such as Debug and Release. A Web Deployment project is extensible, enabling you to create pre-build and post-build actions.


Web Deployment projects do not change the way Visual Studio 2005 Web site projects build. Instead, they take an existing Web site project as input and generate a precompiled Web site as output. A Web Deployment project does not change the files in the source Web site project in any way.


Feature Summary


A Web Deployment project provides the following features for building and deploying ASP.NET 2.0 Web sites:



  • ASP.NET 2.0 precompilation as part of the build process.

  • More flexible options for generating compiled assemblies from a Web project, including these alternatives:

    • A single assembly for the entire Web site.

    • One assembly per content folder.

    • A single assembly for all UI components.

    • An assembly for each compiled file in the Web site.



  • Assembly signing options.

  • The ability to define custom pre-build and post-build actions.

  • The ability to exclude folders from the build.

  • The ability to modify settings in the Web.config file, such as the <connectionString> element, based on the Visual Studio build configuration.

  • Support for creating .msi files with setup projects.


The extensibility of Web Deployment projects enables you to tailor the build and deploy process to suit your needs. This is done without sacrificing the optimized workflow improvements achieved with Visual Studio 2005 Web site projects.


Installing Web Deployment Projects


Installing Web Deployment projects installs both a Visual Studio 2005 add-in package and the new ASP.NET merge utility (aspnet_merge.exe).


By default, the installation process installs files into the locations listed in the following table.






















Location Files
C:\Program Files\MSBuild\Microsoft\WebDeployment\v8.0 aspnet_merge.exe

Microsoft.WebDeployment.targets


Microsoft.WebDeployment.Tasks.dll


C:\Program Files\Microsoft Visual Studio 8\Common7\Packages MsWebDeployProj.dll

WebDeploy.wdproj


C:\Program Files\Microsoft Visual Studio 8\Common7\Packages\1033 MsWebDeployProjUi.dll


You can install Web Deployment projects on a computer that does not have Visual Studio 2005 installed. This is useful if you want to use MSBuild features on a dedicated build computer. MSBuild is part of the Microsoft .NET Framework version 2.0 and does not require installation of Visual Studio 2005.


New Menu Commands


Installing Web Deployment Projects adds the Add Web Deployment Project command to the Build menu and to the shortcut menu for the selected Web project in Solution Explorer. The new command is supported only for local IIS Web sites and for file system Web sites. Web Deployment Projects do not support remote FrontPage or FTP Web sites.


Note   The Add Web Deployment Project command is the only way to add a Web Deployment project to the solution. This new project type is not available in the New Project dialog box.

When you create a Web Deployment project, the new Web Deployment project is associated with the Web site you created it for. Generally there is a one-to-one relationship between the Web site and the Web Deployment project. Each Web site in a solution can have its own Web Deployment project. You can also optionally create multiple Web Deployment projects for the same site.


Advanced scenarios merging multiple Web sites into a single Web site can be achieved through custom build actions as described in the section Customizing Web Deployment Projects later in this paper.


Adding a Web Deployment Project


To add a Web Deployment project to a Web site, follow these steps.



  1. In Solution Explorer, select the name of the Web site to work with.

  2. In the Build menu, choose Add Web Deployment Project.

    The Add Web Deployment Project dialog box is displayed:




  3. Specify a name and location for the project.

    By default, the project name is the same as the Web site with _deploy appended to the name. The default location is in the same folder as the solution.


    In the example shown here, the Web site path is C:\MyProjects\MyWeb. The project file will be named MyWeb_deploy.webdeployproj, and the project folder path will be C:\MyProjects\MyWeb_deploy\.



  4. Click OK.

    A new project is added to the solution and automatically associated with the Web site. The new Web Deployment project is now part of the build process.


    Tip   When creating a new Web site project that you will add other projects to (such as a class library project or a Web Deployment project), start by creating an empty solution in the location of your choice. Then add the new Web site to that solution. This ensures that Visual Studio puts all of the project files in the same set of directories and subdirectories.


    The new Web Deployment project does not contain any files or child nodes in the solution hierarchy. Instead, it is an MSBuild project file that enables you to customize the build rules for the associated Web site.


    You can view properties for the current Web Deployment project in the Properties window. (These properties are distinct from the settings for build configurations, which you edit using the Property Pages dialog box, as discussed in the next section.)





Web Deployment Project Property Pages


You can create and edit individual configurations for the current Web Deployment project using the Property Pages dialog box. (This dialog box is available by right-clicking the project in Solution Explorer and then selecting Property Pages). The Property Pages dialog box for a Web Deployment project displays four property pages:



  • Compilation

  • Output Assemblies

  • Signing

  • Deployment


The properties on each of these property pages can have unique values for each build configuration. Visual Studio 2005 provides two default configurations: Debug and Release. You can use these predefined configurations or define new configurations (such as Staging or Production), depending on your needs. All the settings you make in the Property Pages dialog box apply to the selected configuration.


Compilation Page


The Compilation page of the Web Deployment project's property pages dialog box looks like the following:


 


Click here for larger image


The following table describes the properties you can set in the Compilation page.
























Setting Description
Output folder Specifies the folder to contain the precompiled image of the Web site. The default is to create a subfolder with the same name as the configuration in the folder where the Web Deployment project file is stored.
Generate debug information Generates .pdb files when compiling. This setting will also update the value of the <compilation debug="true"/> element in the configuration file of the precompiled Web site.
Use IIS Metabase path for source input Specifies the full IIS metabase path of the source Web site application. This setting corresponds to the –m option of the aspnet_compiler.exe command.

For example, a metabase path might be /LM/W3SVC/1/ROOT/MyWeb/, where MyWeb is the virtual directory.


This setting is useful if your Web site contains one or more sub-Web sites. During compilation, the compiler processes all the files in a directory tree. If there are sub-Web sites, this will result in build errors. To avoid these errors, you can specify the IIS metabase path of the Web site you are compiling, which causes the ASP.NET compiler to skip any sub-Web sites defined in the IIS metabase.


To compile the sub-Web sites, you must add a separate Web Deployment project for each of the sub-Web sites.


Allow this precompiled site to be updatable Enables ASP.NET Web pages and user controls (.aspx and .ascx files) to be updated after compilation; only the code-behind files are compiled. If this option is not checked, the HTML markup for pages and controls is removed and compiled into the assembly output.


Output Assemblies Page


The Output Assemblies page of the Web Deployment project's property pages dialog box looks like the following:


 


Click here for larger image


The following table describes the properties you can set in the Output Assemblies page.
































Setting Description
Merge all outputs to a single assembly Merges all the output assemblies from the compiler into a single assembly. This setting is equivalent to the -o assemblyname option of the aspnet_merge.exe command. This results in the same behavior that Visual Studio .NET 2003 supports, where one assembly is generated for each Web project.
Treat as library component Removes the App_code.compiled file. This enables the project's App_Code.dll assembly to be added to the Bin folder of another Web site without conflicting with the App_Code.dll assembly in the other Web site. This setting is equivalent to the -r option of the aspnet_merge.exe command.

This option is useful for building a library of .ascx controls.


Merge each folder output to its own assembly Creates a separate output assembly for each folder. This enables you to update your Web site at the folder level rather than updating the entire application. This setting is equivalent to the -prefix prefixname option of the aspnet_merge.exe command.

You can optionally specify a prefix that will be pre-pended to all generated assembly names. For example, if you specify the prefix MyCompany, the name becomes MyCompany.SubfolderName.


Merge all pages and controls to a single assembly Merges the output assemblies for all pages and user controls in the Web site into a single assembly. This enables you to update UI elements separately from updating other code. Special folders such as App_Code, App_WebReferences, and so on are each compiled into a separate assembly.

This setting is equivalent to the –w assemblyname option of the aspnet_merge.exe command.


Create a separate assembly for each page and control Compiles each page and user control into a separate assembly. This setting does not run aspnet_merge.exe. Instead, it uses the -fixednames option of the aspnet_compiler.exe command.
Note   Compiling with the -fixednames option disables the compiler's batch optimizations and can result in longer compile times for large Web sites.

This option is useful for granular updates of your deployed Web site.


Version Output Assemblies

Assembly Version


File Version


Sets the assembly version and/or the file version attribute of merged assemblies. The format is 0.0.0.0.

This setting overrides the assembly attributes defined in the AssemblyInfo.vb or AssemblyInfo.cs file in the App_Code directory.


This setting is equivalent to the -copyattrs assemblyfile option of the aspnet_merge.exe command.




Signing Page


The Signing page of the Web Deployment project's property pages dialog box looks like the following:


 


Click here for larger image


The following table describes the properties you can set in the Signing page.




















Setting Description
Key file location Specifies the path to the keyfile used to sign the assemblies. Keyfiles are produced with the Sn.exe utility included with the .NET Framework SDK.
Delay signing Compiles the assemblies with delay signing. This enables the assemblies to be signed as part of a post-build process.
Mark assemblies with AllowPartiallyTrustedCallers attribute (APTCA). Specifies that types in strongly named assemblies can be called by partially trusted code only when the assemblies have been marked with the AllowPartiallyTrustedCallers attribute (APTCA). This attribute removes the implicit link demand for the Full trust permission set that is otherwise automatically enforced on each publicly accessible method in each type.


Deployment Page


The Deployment page of the Web Deployment project's property pages dialog box looks like the following:


 


Click here for larger image


The following table describes the properties you can set in the Deployment page.
































Setting Description
Enable Web.config file section replacement Enables the replacement of any section in the root Web.config file with the content of a matching section in a separate (external) .config file. The external file can contain only the section being replaced.

This option enables you to define elements that are written to the root Web.config file at deployment time, which in turn provides a way to create deployment-specific configuration sections.


Enforce matching section replacements Requires that the specified section in the Web.config file have the same number of elements as the replacement section in the external .config file. A build error occurs if there is a mismatch in the number of elements. This option helps you detect when changes have been made to the Web.config file but are not reflected in external .config files.
Use external configuration source file Updates the Web.config file by replacing existing sections with elements that reference a .config file, using the configSource attribute:


<appSettings 
configSource="appSettings.config" />


If this option is not selected, the section is replaced entirely with the elements in the .config file.


This option is useful for changing the database connection strings or Web reference URLs for each build configuration.


Create an IIS virtual directory for the output folder Creates an IIS virtual directory pointing to the output folder for this build configuration, using the name you specify. This is useful for immediate testing of the precompiled Web application. To avoid conflict with other configurations, include the configuration name as part of the virtual directory name. For example, use virtual directory names such as MyWeb_Staging and MyWeb_Release.
Replace the existing virtual directory If the virtual directory already exists and is pointing to a different folder, updates the metabase to point to the output folder for this configuration . If this option is not selected, an error occurs if the virtual directory path does not match the output folder.
Remove the App_Data folder Removes the App_Data folder from the precompiled image of the Web site. This is useful if you are changing connection strings to access a SQL Server database and no longer require a SQL Server Express or Access database in the App_Data folder.


Managing Custom Build Configurations


Using Visual Studio build configurations, you can define the specific build processes that are necessary for producing a staging or release build of your Web application. There are three build configurations commonly used for building Web site applications:



  • Debug build configuration   This is the default build configuration used at design time. In team development scenarios, it is often desirable to isolate developers so that they do not overwrite one another's changes while editing and debugging. In those cases, developers typically use either a local IIS Web site (and a locally installed instance of IIS) or a file system Web site and the built-in ASP.NET development server. Similarly, each developer will typically use a local SQL Server, SQL Server Express, or Access database. Developers can even implement Web services locally and consume them using a Web reference that points to http://localhost/.

  • Staging build configuration   Once developers have a version of the Web site ready for testing, they can produce a build configured for a testing or staging environment. The staging environment is often a replica of the production or release environment. For example, a staging environment will likely use a shared SQL Server database containing test data. Web services may also be distributed across other computers in the staging environment.

  • Release build configuration   When the Web site is ready for a production release, database connections and Web references must be changed to reflect the production environment. For optimal performance, it is also recommend that the Debug compilation mode be set to False in the application's Web.config file.


Web Deployment projects enable you to customize the build process and tailor the Web site application to the environment in which it will be deployed.


All property values for a Web deployment project apply to a specific build configuration. By changing the selected configuration, you can change the settings for a specific configuration. Alternatively, by selecting All Configurations, you can apply a change globally across all configurations.


To create a new configuration, in the project Property Pages dialog box, click the Configuration Manager button:


 


Click here for larger image


This displays the Configuration Manager dialog box. The configuration manager enables you to create new configurations and to specify which projects to build as part of that configuration.

Visual Studio exposes two levels of build configurations: solution configurations and project configurations. For more information about build configurations, see Build Configurations on the MSDN Web site.


The configuration manager shows the active solution configuration and the build configuration setting for each project for that solution configuration. After you add a Web Deployment project to the solution, the configuration manager lists both the Web site project and the Web Deployment project. By default, both projects are configured to build whenever the solution is built:



You should disable building the Web Deployment project in the Debug configuration. Building a Web Deployment project unnecessarily increases the time it takes to build a solution or start a debug session when you press F5. You should enable building the Web Deployment project only in solution configurations intended for deployment builds.


Using the Configuration Manager dialog box, you can create a new Staging configuration. You can also specify which projects should build when a specific solution configuration is built. For more information about the configuration manager, see Configuration Manager Dialog Box on the MSDN Web site.


The builds for Web site projects are optimized for developer productivity and do not run the ASP.NET 2.0 compiler to precompile the site. The build for the Web Deployment projects invoke the ASP.NET 2.0 compiler to precompile the site and take advantage of the new assembly merge utility (aspnet_merge.exe). When you create your solution configurations, you should build either the Web site project or the Web Deployment project, but not both. The following table presents a matrix suggesting the project build settings for different solution configurations.




























  Solution configuration
Build strategy Debug Staging Release
Build Web site project True False False
Build Web Deployment project False True True


To specify the solution configuration to use when you build, select the configuration name from the Solution Configurations list on the toolbar:



Note   Some Visual Studio Development Settings, such as the Web Development Settings and Visual Basic Development Settings, do not include the Solution Configurations command on the toolbar. To add the command, from the Tools menu, choose Customize. Click the Commands tab, select the Build category, and then from the commands list drag the Solution Configurations command to the toolbar.


Building a Web Deployment Project


When you have your build configurations established, you can initiate a build for any configuration. There are two ways to build a Web Deployment project—from within Visual Studio 2005 or from the command line using MSBuild.


Building Within Visual Studio 2005


To build within Visual Studio 2005, in the toolbar, select Release from the Solution Configurations list in the toolbar. This makes Release the current solution configuration. Then from the Build menu, choose Build Solution.


The output for the build is displayed in the Output window. An example of the output from a build is shown in the following listing. Notice that in this example, the Web site project was skipped, and instead the Web Deployment project was built.




------ Skipped Build: Project: c:\...\MyWeb\, Configuration: Debug .NET ------
Project not selected to build for this solution configuration
------ Build started: Project: MyWeb_deploy, Configuration: Release Any CPU ------
Build started 11/7/2005 9:40:09 AM.
Target AspNetCompiler:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe –
v /MyWeb -p c:\MyProjects\MyWeb\MyWeb -u -f
c:\MyProjects\MyWeb\MyWeb_deploy\Release\
Updateing web.config compilation debug = 'False' ...
Successfully updated web.config compilation debug = 'False' ...
Removing directory "c:\MyProjects\MyWeb\MyWeb_deploy\Release\\App_Data".
Target GenerateAssemblyInfo:
Generating AssemblyInfo ...
Setting [assembly: AssemblyFileVersion("2.0.0.0")]
Setting [assembly: AssemblyVersion("2.0.0.0")]
Successfully Generated AssebmlyInfo ...
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Csc.exe
/out:c:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Release\AssemblyInfo
.dll /target:library
c:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Release\AssemblyInfo.cs
Target AspNetMerge:
Running aspnet_merge.exe ...
C:\Program Files\MSBuild\Microsoft\WebDeployment\v8.0\aspnet_merge.exe
c:\MyProjects\MyWeb\MyWeb_deploy\Release -o MyCompany.MyWeb -copyattrs
c:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Release\AssemblyInfo.dll
Successfully merged 'c:\MyProjects\MyWeb\MyWeb_deploy\Release'.
Target ReplaceWebConfigSections:
Updating web.config: RootPath =
c:\MyProjects\MyWeb\MyWeb_deploy\Release\, ValidateSections = False,
UseExternalConfigSource = True
Replacing section connectionStrings with file release.config
Update of web.config Succeeded.
Target CreateVirtualDirectory:
Initializing IIS Web Server...
Successfully created virtual directory 'MyWeb_Release'.
Granting IIS read access to the folder 'c:\MyProjects\MyWeb\MyWeb_deploy\Release'...
Grant folder access successful.
Not setting IIS access to folder
'c:\MyProjects\MyWeb\MyWeb_deploy\Release\App_Data'. Folder does not exist.

Build succeeded.

Time Elapsed 00:00:11.52
========== Build: 1 succeeded or up-to-date, 0 failed, 1 skipped
==========


To control the amount of information displayed by MSBuild, from the Tools menu, choose Options. Choose Projects and Solutions, and under Build and Run, select the level of detail you want from the MSBuild project build output verbosity list. The settings are Quiet, Minimal, Normal, Detailed, and Diagnostic.


Building from the Command Line Using MSBuild


You can also build the solution or .wdproj file from the command line using the msbuild.exe command. To specify which configuration to build, set the configuration to use by passing it on the command line with the /p switch, as shown in the following example:




C:\MyProjects\MyWeb>msbuild MyWeb.sln /p:Configuration=Release


The output from the build process will look like the following listing:




Microsoft (R) Build Engine Version 2.0.50727.42
[Microsoft .NET Framework, Version 2.0.50727.42]
Copyright (C) Microsoft Corporation 2005. All rights reserved.

Build started 11/7/2005 10:01:05 AM.
__________________________________________________
Project "C:\MyProjects\MyWeb\MyWeb.sln" (default targets):

Target ValidateSolutionConfiguration:
Building solution configuration "Release|Mixed Platforms".
Target Build:
Target MyWeb_deploy:
__________________________________________________
Project "C:\MyProjects\MyWeb\MyWeb.sln" is building
"C:\MyProjects\MyWeb\MyWeb_deploy\MyWeb_deploy.wdproj" (default
targets):

Target AspNetCompiler:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe -v
/MyWeb -p C:\MyProjects\MyWeb\MyWeb -u -f
C:\MyProjects\MyWeb\MyWeb_deploy\Release\
Updateing web.config compilation debug = 'False' ...
Successfully updated web.config compilation debug = 'False' ...
Removing directory
"C:\MyProjects\MyWeb\MyWeb_deploy\Release\\App_Data".
Target GenerateAssemblyInfo:
Generating AssemblyInfo ...
Setting [assembly: AssemblyFileVersion("2.0.0.0")]
Setting [assembly: AssemblyVersion("2.0.0.0")]
Successfully Generated AssebmlyInfo ...
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Csc.exe
/out:C:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Release\AssemblyInfo.dll /target:library
C:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Release\AssemblyInfo.cs
Target AspNetMerge:
Running aspnet_merge.exe ...
C:\Program
Files\MSBuild\Microsoft\WebDeployment\v8.0\aspnet_merge.exe
C:\MyProjects\MyWeb\MyWeb_deploy\Release -o MyCompany.MyWeb -copyattrs
C:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Release\AssemblyInfo.dll
Successfully merged
'C:\MyProjects\MyWeb\MyWeb_deploy\Release'.
Target ReplaceWebConfigSections:
Updating web.config: RootPath =
C:\MyProjects\MyWeb\MyWeb_deploy\Release\, ValidateSections = False,
UseExternalConfigSource = True
Replacing section connectionStrings with file
release.config
Update of web.config Succeeded.
Target CreateVirtualDirectory:
Initializing IIS Web Server...
Successfully created virtual directory 'MyWeb_Release'.
Granting IIS read access to the folder
'C:\MyProjects\MyWeb\MyWeb_deploy\Release'...
Grant folder access successful.
Not setting IIS access to folder
'C:\MyProjects\MyWeb\MyWeb_deploy\Release\App_Data'. Folder does not
exist.

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:14.13


The files generated for this build are in the MyWeb_deploy\Release folder. Note the single output assembly MyCompany.MyWeb.dll.


















Location Files
C:\MyProjects\MyWeb\MyWeb_deploy\Release Default.aspx

PrecompiledApp.config


release.config


web.config


C:\MyProjects\MyWeb\MyWeb_deploy\Release\bin App_Code.compiled

MyCompany.MyWeb.dll




Customizing Web Deployment Projects


Web Deployment projects are MSBuild project files. The Web Deployment project file has the file name extension .wdproj. All the properties you set using the Web Deployment projects property pages are persisted in the .wdproj file as MSBuild properties or items. For more information about MSBuild and its properties and settings, see MSBuild Overview on the MSDN Web site.


You can customize the build process for Web Deployment projects by editing the Web Deployment project file. To do this, right-click the Web deployment project in Solution Explorer, and then select Open Project File.


In the file you will find one property group for each configuration. This is where the Web Deployment Property Pages dialog box persists the settings for each configuration you create.


Near the bottom of the file you see a comment block containing four MSBuild targets:




  <!-- To modify your build process, add your task inside one of the 
targets below and uncomment it.
Other similar extension points exist, see
Microsoft.WebDeployment.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="BeforeMerge">
</Target>
<Target Name="AfterMerge">
</Target>
<Target Name="AfterBuild">
</Target>
-->


You can override these targets to add custom build tasks. For example, the Personal Web Starter Kit included with Visual Studio 2005 uses a special folder named Upload for uploading images from users. This folder must exist for upload functionality to work properly. To ensure that this folder always exists in the precompiled Web site, you can add the following target and task:




  <Target Name="AfterBuild">
<MakeDir Directories="$(TargetDir)\Upload" />
</Target>


MSBuild includes many predefined tasks, such as MakeDir. MSBuild also provides a way for you to create your own tasks. For more information on custom MSBuild tasks, see How To: Write a Task on the MSDN Web site.


In addition to overriding targets and creating custom tasks, you can add properties and items to the MSBuild tasks included with Web Deployment projects. For example, by adding the following <ItemGroup> section to a Web Deployment project, you can exclude the Test and Images folder from the build process:




  <ItemGroup>
<ExcludeFromBuild Include="$(SourceWebPhysicalPath)\Test\**\*.*"/>
<ExcludeFromBuild
Include="$(SourceWebPhysicalPath)\Images\**\*.*"/>
</ItemGroup>


This is useful if you have test code in the Web site project that should not be included in the staging or release builds.


By default, the ASP.NET merge tool applies the assembly attributes from the compiler-generated App_Code assembly to the merged assemblies. The attributes are defined in the AssemblyInfo.cs or AssemblyInfo.vb file included in the App_Code folder. Alternatively, you can define assembly attributes in the Web Deployment project file. This is useful if the attributes will vary between configurations. For example, when you define Assembly Version and File Version in the Web Deployment project property pages, the following <ItemGroup> section is added to the Web Deployment project file:




  <ItemGroup>
<AssemblyAttributes Include="AssemblyVersion">
<value>3.0.0.0</value>
</AssebmlyAttributes>
<AssebmlyAttributes Include="AssemblyFileVersion">
<value>3.0.0.0</value>
</ItemGroup>


This <ItemGroup> section could also contain any other assembly attribute you want to be included in the merged assemblies. The following listing shows examples of additional assembly attributes defined in an <ItemGroup> section:




  <ItemGroup>
</AssebmlyAttributes>
<AssemblyAttributes Include="AssemblyTitle">
<value>MyCompany MyWeb</value>
</AssemblyAttributes>
<AssemblyAttributes Include="AssemblyDescription">
<value>Corporate Site</value>
</AssemblyAttributes>
<AssemblyAttributes Include="AssemblyCompany">
<value>MyCompany</value>
</AssemblyAttributes>
<AssemblyAttributes Include="AssemblyCopyright">
<value>Copyright © MyCompany 2005</value>
</AssemblyAttributes>
</ItemGroup>


Web Deployment Projects Reference


The following table lists the MSBuild tasks included in Web Deployment projects.






































Tasks Description
ReplaceConfigSections Replaces a section in the Web.config file with the matching section from an external (.config) file.
CreateVirtualDirectory Creates an IIS virtual directory.
AspNetCompiler Calls aspnet_compiler.exe.
AspNetMerge Calls aspnet_merge.exe.
GrantServerAccess Grants ASP.NET read or write access to specific folders.
ToggleDebugCompilation Changes the value of compilation debug setting in the Web.config file.
GenerateAssemblyInfo Generates an assembly with the assembly attributes defined in the AssemblyAttributes MSBuild item.


The following table lists the MSBuild properties included in Web Deployment projects.


































































































Properties Description
EnableCopyBeforeBuild Makes a copy of the Web site before running aspnet_compiler.exe. This is useful for modifying files before they are compiled.
CopyBeforeBuildTargetPath Specifies the folder where the copied Web site should be placed.
SourceWebPhysicalPath Specifies the path to the Web site being compiled.
EnableUpdateable Enables the Web site to be updatable.
AllowPartiallyTrustedCallers Allows partially trusted callers.
DeleteAppDataFolder Deletes the App_Data folder in the precompiled Web site.
UseMerge When true, causes the AspNetMerge target to execute.
AssemblyPrefixName Specifies the prefix for merged assemblies.
SingleAssemblyName Specifies the assembly name used when merging into a single assembly.
ContentAssemblyName Specifies the assembly name used when merging into a separate content assembly.
MergeErrorStack Adds error stack trace information to the build output.
DeleteAppCodeCompiledFiles Deletes the App_Code.compiled file.
CopyAssemblyAttributes Specifies that aspnet_merge.exe should copy the assembly attributes from the App_Code assembly into the merged assemblies.
AssemblyInfoDll Specifies an alternate DLL containing the assembly attributes that aspnet_merge.exe should use.
MergeXmlDocs Merge the xml documentation files associated with the assemblies compiled by the aspnet_compiler.exe. This is equivalent to the –xmldocs argument of aspnet_merge.exe.
MergeErrorLogFile Specifies an alternate log file for the merge process.
UseWebConfigReplacement When true, causes the ReplaceWebConfigSections target to execute.
UseExernalWebConfigReplacementFile Specifies whether replaced sections will use the configSource file reference. If false, the section is replaced in place.
ValidateWebConfigReplacement Validates that the Web.config file section being replaced has the same number of elements as the replacement section.
VirtualDirectoryAlias Specifies the alias for the virtual directory that is created for the precompiled build output.
VirtualDirectoryServer Specifies the server on which to create the virtual directory. The default is 1.
ReplaceExistingVirtualDirectory Replaces the existing virtual directory. If false, the build will fail if the path of the existing virtual directory in the IIS metabase does not match the path of the precompiled Web site.


The following table lists the MSBuild items included in Web Deployment projects.


























Items Description
ExcludeFromBuild Files to exclude from a build.
WebConfigReplacementFiles Sections to replace in the Web.config file.
AssemblyAttributes Assembly attributes to apply to merged assemblies.
PrecompiledOutput All output files from the AspNetCompiler or AspNetMerge targets.


The following table lists the MSBuild targets included in Web Deployment projects.


























































Targets Description
Clean Executes when performing a complete build.
Rebuild Executes when rebuilding.
AspNetCompiler Invokes the AspNetCompiler task.
AspNetMerge Invokes the AspNetMerge task.
ReplaceWebConfigSections Invokes the ReplaceConfigSections task.
CreateVirtualDirectory Invokes the CreateVirtualDirectory task.
BuiltProjectOutputGroup Populates the project output group.
GenerateAssemblyInfo Invokes the GenerateAssemblyInfo task.
BeforeBuild Specifies an override for the BeforeBuild target.
BeforeMerge Specifies an override for the BeforeMerge target.
AfterMerge Specifies an override for the AfterMerge target.
AfterBuild Specifies an override for the AfterBuild target.



 




This article is taken from: http://msdn2.microsoft.com/en-us/library/aa479568.aspx

Globalization and localization demystified in ASP.NET 2.0

Introduction


Globalization and localization are two important porcesses which every developer should be aware of while creating global products or applications. Though there are many articles which explain the subject well, I did not find a single resource which explains all important concepts reagrding globalization/localization practically and comprehensively. This article aims to provide practical step-by-step approach to globalizing a web application in ASP.NET 2.0.


Background Theory


Globalization is defined as the process of developing a program or an application so that it is usable across multiple cultures and regions, irrespective of the language and regional differences. For e.g. you have made a small inventory management program and you live in a region where English is the main language, assume England. Now if you want to sell your program in a different country, let’s say Germany, and then you need to make sure that your program displays and takes input in German language.


Localization is the process to create content,input and output data, in a region specific culture and language. Culture will decide date display settings (like mm/dd/yyyy or dd/mm/yyyy), currency display formats etc. Now, the process by which we can make sure that our program will be localized is known as Internationalization or Globalization. In simpler terms, Globalization can be defined as the set of activities which will ensure that our program will run in regions with different languages and cultures.


So globalization is related to intrinsic code changes to support such changes, like using Resource files etc. Whereas localization is the process of using a particular culture and regional info so that the program uses that local languages and culture. This means translating strings into a particular local language. This covers putting language specific strings in the resource files. Globalization starts in the main construction phase along with the code development. Localization generally comes later.


Globalizing an ASP.NET 2.0 website


Let’s start with a simple example. For the purposes of explaining localization and keeping things simple, I have created a new website in ASP.NET and C# called TestSite (source code of my example is included in this article). I have added a MasterPage and a default page. This default page has a TextBox and a Calendar control. The TextBox control has a double number which will represent currency and we will see how the currency format varies as user selects different languages. The default page looks like this when I run the application:


Sample screenshot


I have published the test web application and you can see the functional version here:


http://63.134.215.124/testsite/default.aspx


Cultures and Locale


Now, before we move ahead, let me throw some light on cultures and locale.


Languages also depend upon the geographical location. For e.g.: French is spoken in France as well as Canada (besides many other countries). But linguistically speaking, Canadian French is quite different from French spoken in France. Similarly, there are linguistic differences between US English and British English. Therefore the language needs to be associated with the particualr region where it is spoken, and this is done by using locale (language + location).


For e.g.: fr is the code for French language. fr-FR means French language in France. So fr specifies only the language whereas fr-FR is the locale. Similarly fr-CA defines another locale implying French language and culture in Canada. If we use only fr, it implies a neutral culture (i.e. location neutral).


How do we define or change the current culture?


There are two properties of CultureInfo class in .NET FCL (Framework class Library) which we can set using the overloaded constructor of the class and then use it to change the culture of the currently executing thread:


1. UICulture: gets/sets the user interface culture for the currently executing thread. This property helps the runtime to load the resource strings from a specific resource file(which we will see later). This property can take neutral cultures as well as locales. For e.g.:


Thread.CurrentThread.CurrentUICulture = new CultureInfo(“fr”);

Or,


Thread.CurrentThread.CurrentUICulture = new CultureInfo(“fr-CA”); 

2. Culture: gets/sets the region specific culture and formats of currency, dates etc. This needs language as well as location (locale).


Thread.CurrentThread.CurrentCulture = new CultureInfo(“fr-A”); //correct as 
///we have given locale
Thread.CurrentThread.CurrentCulture = new CultureInfo(“fr”); //wrong, will
// not work

Sometimes we need a culture which does not belong to any language or locale, which is invariant of any region/language. For this we have CultureInfo.InvariantCulture property. It is used during internal system processes which need to be culture independent or to store data which does not need to be displayed directly to the end user.


Both UICulture and Culture properties can be defined in the Web.Config file under <GLOBALIZATION>property. They can also be specified at page level too. But we don’t want to hard code these values and would like to set them dynamically instead. As seen above, we could also get/set these values from the code using Thread.CurrentThread.CurrentCulture and Thread.CurrentThread.CurrentUICulture properties. So we will use these properties in our application.


Switching locale


Coming back to our application, we need a way to switch the locale. There are two approaches regarding this:


1. Use the Browsers settings: in IE the user can change the culture by going to Internet Options->General->Languages. For this to work, we need to set both the Culture and the UICulture to auto and enableClientBasedCulture = true as:


<GLOBALIZATION culture="auto" uiculture="auto" enableClientBasedCulture="”true”" />

2. User specified settings: We can give an option for user to specify and change the

culture and the language at runtime. This is the recommended approach as sometimes the browser itself may not have the user specific culture set (for example a French tourist might be surfing net in India). Also, sometimes changing Language settings via the browser is blocked.


Going by the second recommended approach, I have created a section on the top (inside a Panel control) in the MasterPage where I have a DropDown with these language options which let the users choose a particular locale.


For illustration purposes I have the option of only 4 languages to choose from:


Hindi, US English, British English, and French.


For my application to be globalized, I want that whenever the user selects a locale from the language, the following should happen:


1. All content should be localized: This means that all strings and text should be displayed in the chosen language and locale.


2. Each control’s caption/content should also show text in local language.


3. Date and currency formatting should occur according to the chosen locale.


4. All messages displayed to the user should be in the local language.


To achieve the above goals, first thing you need to make sure is to take out content from the code and put it in separate resource files, which are simple XML file in .NET with .resx extension.


Since this content will vary from language to language, we will have resource files for every culture. Each such file has Name & Value fields (like a Dictionary). Below are the sample entries in two resources assuming we have to enter a string “Welcome”:


1. Add a new resource file and name it as TestSiteResource.resx (you can use any name) and open it in VS editor. Enter “Banner” in the Name field and “Test Website for Localization” in the value field. This resource file is default for American English.


2. Add another resource file and name it as TestSiteResources.fr-FR.resx. This file is for French language strings. Add “Banner” in the Name field and “Examinez le site Web pour le comportement de localisation” in the Value field. 


If you want to add Canadian French resources, then you need to create another resource file by the name of TestSiteResources.fr-CA.resx. the middle part of this name defines the locale and this should be the same as specified by the UICulture property.


3. These files would be saved in App_GlobalResources folder in ASP.NET 2.0.


Tip/Trick: If you want that only certain pages show localized strings, you can restrict the localization behavior throughout the application by putting resource files in the App_LocalDirectory folder. This will make localization page specific and not application wide. The naming should be like (assuming you want to localize only a page named MyPage.aspx):



MyPage.aspx.resx
: this is the default resource file for MyPage.aspx.

MyPage.aspx.fr-FR.resx : this will be used when the culture changes to French, but only MyPage.aspx in the application would be localized.  


All the above .resx files would be compiled into assemblies at runtime. These assemblies are known by the name of “satellite assemblies” and have strongly typed wrappers for the .resx files. So we don’t need to worry about creating resource assemblies ourselves in ASP.NET 2.0. These assemlies are placed in separate folders (by the name of locale) under /bin folder after you have published your website:


Sample screenshot


For non ASP.NET applications, we need to use two tools:


1. Resource file generator: resgen.exe.


2. Assembly linker (al.exe)


There is lot of detailed information on how to use these tools on MSDN and the user can refer to these links:


http://msdn2.microsoft.com/en-us/library/ccec7sz1.aspx


http://msdn2.microsoft.com/en-us/library/c405shex.aspx


Now that we have created the resource files for different cultures and languages, we need a way to load them at runtime when the user changes culture dynamically. Fortunately implementing this in ASP.NET 2.0 is quite easy. See the code below:


String welcome = Resources.TestSiteResources.Welcome;

In this line of code, we are using the Resources namespace which was created automatically by ASP.NET when it compiled the resource files into satellite assemblies and we used TestSiteResources class, same name as the resource file we created. We then accessed the Welcome property which will give actual text from the resource file based on the current culture. If we want the text of the label control lblWelcome, we can set the same using two methods in ASP.NET 2.0:


1. Implicit localization: Here we specify the new meta tags in the control definition and let ASP.NET get the value from the resource files based on the resourcekey attribute:




<asp:Label id=lblWelcome meta:resourcekey="lblWelcome" Text="Welcome" 
runat="server"></asp:Label>

For this to work, we need to have page specific resource files in the

/App_LocalDirectory folder. Implicit localization helps trimming down the size of the global resource files and helps in better overall resource management. Use it when you have largely page specific content.


You do not need to do anything manually to set these implicit localization properties. Just open your web page in the Design mode, go to Tools->Generate Local Resources. This will automatically create a resource file for your Page. You only need to set the values (Control.Property) of different fields for each control in the resource file editor in Visual Studio 2005


2. Explicit localization: This works when we have Global resource files. Here we use Expressions to set the values from the resource files as:




<asp:Label id=lblWelcome Text="<%$Resources:TestSiteResources, Welcome %>" 
runat="server"></asp:Label>

 


We can set this using the VS IDE. Select the label control, go to Properties window, select Expressions->Text. Then choose Resources from the drop down and enter the class name (TestSiteResources for this example) and the Resource key (Banner). This is the recommended way to localize the UI controls on a page.


Sample screenshot


3. Programmatically accessing strongly typed resource classes as:


lblWelcome.Text = Resources.TestSiteResources.Welcome;

This will work but then it needs to be coded for every control in the page. So use #2 for all the controls and use this method to access resource strings for other content, if needed. Also note that controls like Calendar control have localization in built. As soon as the UICulture and Culture of the current thread changes, it shows localized content by itself, thanks to ASP.NET!


Incorporating globalization


In my website, after creating resource files and putting some localized data, I first start using the explicit localization to set the text of the controls such as labels in my website so that they get their values from the resource files. Since there are four languages, I have created 4 resources files besides a fifth fallback resource file (with no locale name).


Sample screenshot


Notice that the resource files have the locale as their middle names, so I need to set the UICulture to the same named locale in order for ASP.NET to access these resource files.


But the problem is: how should I change the culture dynamically on the PostBack event? Fortunately ASP.NET provides a method in the Page class to override: InitializeCulture(). This method executes very early in the page lifecycle (much before any control is generated) and here we can set the UICulture and Culture of the current thread.


Since this method is in the Page class and I do not want to repeat the same code for each web page, I created a BasePage class and all the aspx pages in my application derive from this BasePage class. But I faced another problem now. Let me explain:


Going back to the UI design: I had a MasterPage and a Header user control in it (inside a ContentPlaceHolder). I had a default page associated with that MasterPage. The entire site had to be localized dynamically. So in the header there was a dropdown from where the user could select a language/culture. In the BasePage’s InitilializeCulture method, I had to get the value of the item the user selected from the drop down, but since it was not initialized as yet (since InitializeCulture() is called much earlier in the page life cycle) I cannot access any control's value. The answer: Use the Form collection (from the Response object). Here is the code:


///<SUMMARY>
/// The name of the culture selection dropdown list in the common header.
/// We need to use this name as we don't have any other
/// control property as the control (dropdown) itself is not initialized yet.
/// So we use the "nested" dropdown name through which we will get the
/// dropdown's value from the Request.Form[] collection.
/// </SUMMARY>
public const string LanguageDropDownID = "ctl00$cphHeader$Header1$ddlLanguage";
/// <SUMMARY>
/// The name of the PostBack event target field in a posted form. You can use
/// this to see which control triggered a PostBack:
/// Request.Form[PostBackEventTarget] .
/// </SUMMARY>
public const string PostBackEventTarget = "__EVENTTARGET";

See how I am using "parentControl:ChildControl" method to access the control from the Form collection. You can access any nested control generated by ASP.NET by adopting this convention. Using this value of the selected item from the Form collection, I set the culture in a switch case statement as:


 


Collapse

 


    /// <SUMMARY>
/// Overriding the InitializeCulture method to set the user selected
/// option in the current thread. Note that this method is called much
/// earlier in the Page lifecycle and we don't have access to any controls
/// in this stage, so have to use Form collection.
/// </SUMMARY>
protected override void InitializeCulture()
{
///<remarks><REMARKS>
///Check if PostBack occured. Cannot use IsPostBack in this method
///as this property is not set yet.
///</remarks>
if (Request[PostBackEventTarget] != null)
{
string controlID = Request[PostBackEventTarget];

if (controlID.Equals(LanguageDropDownID))
{
string selectedValue =
Request.Form[Request[PostBackEventTarget]].ToString();

switch (selectedValue)
{
case "0": SetCulture("hi-IN", "hi-IN");
break;
case "1": SetCulture("en-US", "en-US");
break;
case "2": SetCulture("en-GB", "en-GB");
break;
case "3": SetCulture("fr-FR", "fr-FR");
break;
default: break;
}
}
}
///<remarks>
///Get the culture from the session if the control is tranferred to a
///new page in the same application.
///</remarks>
if (Session["MyUICulture"] != null && Session["MyCulture"] != null)
{
Thread.CurrentThread.CurrentUICulture = (CultureInfo)Session["MyUICulture"];
Thread.CurrentThread.CurrentCulture = (CultureInfo)Session["MyCulture"];
}
base.InitializeCulture();
}
/// <Summary>
/// Sets the current UICulture and CurrentCulture based on
/// the arguments
/// </Summary>
/// <PARAM name="name"></PARAM>
/// <PARAM name="locale"></PARAM>
protected void SetCulture(string name, string locale)
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo(name);
Thread.CurrentThread.CurrentCulture = new CultureInfo(locale);
///<remarks>
///Saving the current thread's culture set by the User in the Session
///so that it can be used across the pages in the current application.
///</remarks>
Session["MyUICulture"] = Thread.CurrentThread.CurrentUICulture;
Session["MyCulture"] = Thread.CurrentThread.CurrentCulture;
}

So the user will see the content in his/her selected language. We need to save the culture selected in a Session or a Cookie variable because if the user moves to some other page in the same application, the thread's culture information would be lost as the new Page class will instantiate from the beginning (HTTP is stateless!). Cookies can be used if you do not want to lose the current thread's Culture on user's Session expiry.

Once we have pulled out all content from the web application, set the Culture and UICulture based in the user choice and used Resources.TestWebSite.XXXPropertyName, we are ready with our globalization framework. Now the only thing left is the adding of resource specific data in the resource files. For each culture we need to have a separate (and appropriately named) resource file. This process is localization. In my web.config file I have used the following properties:


<globalization responseEncoding"=utf-8” requestEncoding="utf-8” 
fileEncoding="utf-8" />

Note the encoding attributes: utf-8 (8 bit Unicode Transformation Format) is used since

it is variable length character encoding and can represent languages such as Greek, Arabic etc., besides it is ASCII compatible too. For more info on UTF-8 encoding, see this link:




 http://en.wikipedia.org/wiki/UTF-8



Also, an important point to note is that though we can have the resource files in raw XML form on the deployement server (so that the user can edit them without re-compling the entire site), the application will re-start if we make any modification in the resource files. This can hamper performance of the deployed application.


dir Attribute for language direction


Many times we also need to set the direction of the localized text (which is set using the dir attribute of the <html> or the <body> tag). This is neccessary because some languages are read from right-to-left (RTL), for e.g. Arabic, instead of the standard left-to-right (LTR) like Hindi and English. This can be achieved quite easily by setting the dir attribute to appropriate value from the .resx file. 


First create a Direction (you use any name) field in all your resource files, setting its property to RTL or LTR based on individual resource files. For Arabic, the value of this field would be RTL and for Hindi it would be LTR. Then set the same in the dir attribute of the <body> tag as:


 

 

<body runat="server" dir="><%$ Resources: TestSiteResources, Direction %>"


This will set the right direction as the value will come from the resouce file based on the current thread's culture.


 


Using a Database for Localization


We have seen how to localize the text of the controls and presentation in the UI. But what about the content stored in a database? This content also needs to be localized but since it is stored in a DB, we cannot use resource files for the same. We need to create new tables for the same.


Suppose I have a table which stores the user comments. The table structure is:


Sample screenshot


Now, we want the Comments and the Name fields to be displayed in localized text. But we can’t store all the different language versions of these fields in this same table as it will not be normalized (since there are other fields which don’t need to be localized but will be repeated). Hence we need to re-organize the table structure and create another table which will hold the localized version of these two fields. First we need to remove these two fields from this table and create a new table as:


Sample screenshot


Here we have added a new field as CultureID, which is equivalent to LCID, or the Locale Identifier, an integer which a particular culture. We can add culture specific localized data as:


Sample screenshot


Now we can use SQL queries with CultureID (LCID) as one of the parameters to get the localized content. We can also provide a user interface to enter localized data into such tables so that the content can be created in an interactive way.


Summary


I have tried to cover some important aspects of implementing Globalization in ASP.NET 2.0, and we saw that it is easy and simple but there are a few important points to note:


1. Do not rely on the web browser’s settings. Give a link on the application (may be in the header) so that the users can select their choice of language by clicking it.


2. Use Resources files to separate Presentation related data in the GUI. Resource fallback is the approach used by ASP.NET when it is unable to find the resource file for a particular culture. It will first go to the neutral resource file and then the default or fallback resource file (TestSiteResource.resx).


3. Use database tables for data or content stored in a DB. You need to create separate tables to store localized content.


4. If you used sn.exe to create a strong name of your main application assembly, then you need to use the private key from the same set of key pair (generated by sn.exe) to sign your satellite assemblies as well. Strong named assemblies require that satellite assemblies should also be strongly named.


Though I tried my best to cover important topics, but in case I missed something I would appreciate if readers can send in their suggestions on the same.


Happy globalizing!


This article is taken from: http://www.codeproject.com/useritems/localization.asp

Globalization and localization demystified in ASP.NET 2.0

Introduction


Globalization and localization are two important porcesses which every developer should be aware of while creating global products or applications. Though there are many articles which explain the subject well, I did not find a single resource which explains all important concepts reagrding globalization/localization practically and comprehensively. This article aims to provide practical step-by-step approach to globalizing a web application in ASP.NET 2.0.


Background Theory


Globalization is defined as the process of developing a program or an application so that it is usable across multiple cultures and regions, irrespective of the language and regional differences. For e.g. you have made a small inventory management program and you live in a region where English is the main language, assume England. Now if you want to sell your program in a different country, let’s say Germany, and then you need to make sure that your program displays and takes input in German language.


Localization is the process to create content,input and output data, in a region specific culture and language. Culture will decide date display settings (like mm/dd/yyyy or dd/mm/yyyy), currency display formats etc. Now, the process by which we can make sure that our program will be localized is known as Internationalization or Globalization. In simpler terms, Globalization can be defined as the set of activities which will ensure that our program will run in regions with different languages and cultures.


So globalization is related to intrinsic code changes to support such changes, like using Resource files etc. Whereas localization is the process of using a particular culture and regional info so that the program uses that local languages and culture. This means translating strings into a particular local language. This covers putting language specific strings in the resource files. Globalization starts in the main construction phase along with the code development. Localization generally comes later.


Globalizing an ASP.NET 2.0 website


Let’s start with a simple example. For the purposes of explaining localization and keeping things simple, I have created a new website in ASP.NET and C# called TestSite (source code of my example is included in this article). I have added a MasterPage and a default page. This default page has a TextBox and a Calendar control. The TextBox control has a double number which will represent currency and we will see how the currency format varies as user selects different languages. The default page looks like this when I run the application:


Sample screenshot


I have published the test web application and you can see the functional version here:


http://63.134.215.124/testsite/default.aspx


Cultures and Locale


Now, before we move ahead, let me throw some light on cultures and locale.


Languages also depend upon the geographical location. For e.g.: French is spoken in France as well as Canada (besides many other countries). But linguistically speaking, Canadian French is quite different from French spoken in France. Similarly, there are linguistic differences between US English and British English. Therefore the language needs to be associated with the particualr region where it is spoken, and this is done by using locale (language + location).


For e.g.: fr is the code for French language. fr-FR means French language in France. So fr specifies only the language whereas fr-FR is the locale. Similarly fr-CA defines another locale implying French language and culture in Canada. If we use only fr, it implies a neutral culture (i.e. location neutral).


How do we define or change the current culture?


There are two properties of CultureInfo class in .NET FCL (Framework class Library) which we can set using the overloaded constructor of the class and then use it to change the culture of the currently executing thread:


1. UICulture: gets/sets the user interface culture for the currently executing thread. This property helps the runtime to load the resource strings from a specific resource file(which we will see later). This property can take neutral cultures as well as locales. For e.g.:


Thread.CurrentThread.CurrentUICulture = new CultureInfo(“fr”);

Or,


Thread.CurrentThread.CurrentUICulture = new CultureInfo(“fr-CA”); 

2. Culture: gets/sets the region specific culture and formats of currency, dates etc. This needs language as well as location (locale).


Thread.CurrentThread.CurrentCulture = new CultureInfo(“fr-A”); //correct as 
///we have given locale
Thread.CurrentThread.CurrentCulture = new CultureInfo(“fr”); //wrong, will
// not work

Sometimes we need a culture which does not belong to any language or locale, which is invariant of any region/language. For this we have CultureInfo.InvariantCulture property. It is used during internal system processes which need to be culture independent or to store data which does not need to be displayed directly to the end user.


Both UICulture and Culture properties can be defined in the Web.Config file under <GLOBALIZATION>property. They can also be specified at page level too. But we don’t want to hard code these values and would like to set them dynamically instead. As seen above, we could also get/set these values from the code using Thread.CurrentThread.CurrentCulture and Thread.CurrentThread.CurrentUICulture properties. So we will use these properties in our application.


Switching locale


Coming back to our application, we need a way to switch the locale. There are two approaches regarding this:


1. Use the Browsers settings: in IE the user can change the culture by going to Internet Options->General->Languages. For this to work, we need to set both the Culture and the UICulture to auto and enableClientBasedCulture = true as:


<GLOBALIZATION culture="auto" uiculture="auto" enableClientBasedCulture="”true”" />

2. User specified settings: We can give an option for user to specify and change the

culture and the language at runtime. This is the recommended approach as sometimes the browser itself may not have the user specific culture set (for example a French tourist might be surfing net in India). Also, sometimes changing Language settings via the browser is blocked.


Going by the second recommended approach, I have created a section on the top (inside a Panel control) in the MasterPage where I have a DropDown with these language options which let the users choose a particular locale.


For illustration purposes I have the option of only 4 languages to choose from:


Hindi, US English, British English, and French.


For my application to be globalized, I want that whenever the user selects a locale from the language, the following should happen:


1. All content should be localized: This means that all strings and text should be displayed in the chosen language and locale.


2. Each control’s caption/content should also show text in local language.


3. Date and currency formatting should occur according to the chosen locale.


4. All messages displayed to the user should be in the local language.


To achieve the above goals, first thing you need to make sure is to take out content from the code and put it in separate resource files, which are simple XML file in .NET with .resx extension.


Since this content will vary from language to language, we will have resource files for every culture. Each such file has Name & Value fields (like a Dictionary). Below are the sample entries in two resources assuming we have to enter a string “Welcome”:


1. Add a new resource file and name it as TestSiteResource.resx (you can use any name) and open it in VS editor. Enter “Banner” in the Name field and “Test Website for Localization” in the value field. This resource file is default for American English.


2. Add another resource file and name it as TestSiteResources.fr-FR.resx. This file is for French language strings. Add “Banner” in the Name field and “Examinez le site Web pour le comportement de localisation” in the Value field. 


If you want to add Canadian French resources, then you need to create another resource file by the name of TestSiteResources.fr-CA.resx. the middle part of this name defines the locale and this should be the same as specified by the UICulture property.


3. These files would be saved in App_GlobalResources folder in ASP.NET 2.0.


Tip/Trick: If you want that only certain pages show localized strings, you can restrict the localization behavior throughout the application by putting resource files in the App_LocalDirectory folder. This will make localization page specific and not application wide. The naming should be like (assuming you want to localize only a page named MyPage.aspx):



MyPage.aspx.resx
: this is the default resource file for MyPage.aspx.

MyPage.aspx.fr-FR.resx : this will be used when the culture changes to French, but only MyPage.aspx in the application would be localized.  


All the above .resx files would be compiled into assemblies at runtime. These assemblies are known by the name of “satellite assemblies” and have strongly typed wrappers for the .resx files. So we don’t need to worry about creating resource assemblies ourselves in ASP.NET 2.0. These assemlies are placed in separate folders (by the name of locale) under /bin folder after you have published your website:


Sample screenshot


For non ASP.NET applications, we need to use two tools:


1. Resource file generator: resgen.exe.


2. Assembly linker (al.exe)


There is lot of detailed information on how to use these tools on MSDN and the user can refer to these links:


http://msdn2.microsoft.com/en-us/library/ccec7sz1.aspx


http://msdn2.microsoft.com/en-us/library/c405shex.aspx


Now that we have created the resource files for different cultures and languages, we need a way to load them at runtime when the user changes culture dynamically. Fortunately implementing this in ASP.NET 2.0 is quite easy. See the code below:


String welcome = Resources.TestSiteResources.Welcome;

In this line of code, we are using the Resources namespace which was created automatically by ASP.NET when it compiled the resource files into satellite assemblies and we used TestSiteResources class, same name as the resource file we created. We then accessed the Welcome property which will give actual text from the resource file based on the current culture. If we want the text of the label control lblWelcome, we can set the same using two methods in ASP.NET 2.0:


1. Implicit localization: Here we specify the new meta tags in the control definition and let ASP.NET get the value from the resource files based on the resourcekey attribute:




<asp:Label id=lblWelcome meta:resourcekey="lblWelcome" Text="Welcome" 
runat="server"></asp:Label>

For this to work, we need to have page specific resource files in the

/App_LocalDirectory folder. Implicit localization helps trimming down the size of the global resource files and helps in better overall resource management. Use it when you have largely page specific content.


You do not need to do anything manually to set these implicit localization properties. Just open your web page in the Design mode, go to Tools->Generate Local Resources. This will automatically create a resource file for your Page. You only need to set the values (Control.Property) of different fields for each control in the resource file editor in Visual Studio 2005


2. Explicit localization: This works when we have Global resource files. Here we use Expressions to set the values from the resource files as:




<asp:Label id=lblWelcome Text="<%$Resources:TestSiteResources, Welcome %>" 
runat="server"></asp:Label>

 


We can set this using the VS IDE. Select the label control, go to Properties window, select Expressions->Text. Then choose Resources from the drop down and enter the class name (TestSiteResources for this example) and the Resource key (Banner). This is the recommended way to localize the UI controls on a page.


Sample screenshot


3. Programmatically accessing strongly typed resource classes as:


lblWelcome.Text = Resources.TestSiteResources.Welcome;

This will work but then it needs to be coded for every control in the page. So use #2 for all the controls and use this method to access resource strings for other content, if needed. Also note that controls like Calendar control have localization in built. As soon as the UICulture and Culture of the current thread changes, it shows localized content by itself, thanks to ASP.NET!


Incorporating globalization


In my website, after creating resource files and putting some localized data, I first start using the explicit localization to set the text of the controls such as labels in my website so that they get their values from the resource files. Since there are four languages, I have created 4 resources files besides a fifth fallback resource file (with no locale name).


Sample screenshot


Notice that the resource files have the locale as their middle names, so I need to set the UICulture to the same named locale in order for ASP.NET to access these resource files.


But the problem is: how should I change the culture dynamically on the PostBack event? Fortunately ASP.NET provides a method in the Page class to override: InitializeCulture(). This method executes very early in the page lifecycle (much before any control is generated) and here we can set the UICulture and Culture of the current thread.


Since this method is in the Page class and I do not want to repeat the same code for each web page, I created a BasePage class and all the aspx pages in my application derive from this BasePage class. But I faced another problem now. Let me explain:


Going back to the UI design: I had a MasterPage and a Header user control in it (inside a ContentPlaceHolder). I had a default page associated with that MasterPage. The entire site had to be localized dynamically. So in the header there was a dropdown from where the user could select a language/culture. In the BasePage’s InitilializeCulture method, I had to get the value of the item the user selected from the drop down, but since it was not initialized as yet (since InitializeCulture() is called much earlier in the page life cycle) I cannot access any control's value. The answer: Use the Form collection (from the Response object). Here is the code:


///<SUMMARY>
/// The name of the culture selection dropdown list in the common header.
/// We need to use this name as we don't have any other
/// control property as the control (dropdown) itself is not initialized yet.
/// So we use the "nested" dropdown name through which we will get the
/// dropdown's value from the Request.Form[] collection.
/// </SUMMARY>
public const string LanguageDropDownID = "ctl00$cphHeader$Header1$ddlLanguage";
/// <SUMMARY>
/// The name of the PostBack event target field in a posted form. You can use
/// this to see which control triggered a PostBack:
/// Request.Form[PostBackEventTarget] .
/// </SUMMARY>
public const string PostBackEventTarget = "__EVENTTARGET";

See how I am using "parentControl:ChildControl" method to access the control from the Form collection. You can access any nested control generated by ASP.NET by adopting this convention. Using this value of the selected item from the Form collection, I set the culture in a switch case statement as:


 


Collapse

 


    /// <SUMMARY>
/// Overriding the InitializeCulture method to set the user selected
/// option in the current thread. Note that this method is called much
/// earlier in the Page lifecycle and we don't have access to any controls
/// in this stage, so have to use Form collection.
/// </SUMMARY>
protected override void InitializeCulture()
{
///<remarks><REMARKS>
///Check if PostBack occured. Cannot use IsPostBack in this method
///as this property is not set yet.
///</remarks>
if (Request[PostBackEventTarget] != null)
{
string controlID = Request[PostBackEventTarget];

if (controlID.Equals(LanguageDropDownID))
{
string selectedValue =
Request.Form[Request[PostBackEventTarget]].ToString();

switch (selectedValue)
{
case "0": SetCulture("hi-IN", "hi-IN");
break;
case "1": SetCulture("en-US", "en-US");
break;
case "2": SetCulture("en-GB", "en-GB");
break;
case "3": SetCulture("fr-FR", "fr-FR");
break;
default: break;
}
}
}
///<remarks>
///Get the culture from the session if the control is tranferred to a
///new page in the same application.
///</remarks>
if (Session["MyUICulture"] != null && Session["MyCulture"] != null)
{
Thread.CurrentThread.CurrentUICulture = (CultureInfo)Session["MyUICulture"];
Thread.CurrentThread.CurrentCulture = (CultureInfo)Session["MyCulture"];
}
base.InitializeCulture();
}
/// <Summary>
/// Sets the current UICulture and CurrentCulture based on
/// the arguments
/// </Summary>
/// <PARAM name="name"></PARAM>
/// <PARAM name="locale"></PARAM>
protected void SetCulture(string name, string locale)
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo(name);
Thread.CurrentThread.CurrentCulture = new CultureInfo(locale);
///<remarks>
///Saving the current thread's culture set by the User in the Session
///so that it can be used across the pages in the current application.
///</remarks>
Session["MyUICulture"] = Thread.CurrentThread.CurrentUICulture;
Session["MyCulture"] = Thread.CurrentThread.CurrentCulture;
}

So the user will see the content in his/her selected language. We need to save the culture selected in a Session or a Cookie variable because if the user moves to some other page in the same application, the thread's culture information would be lost as the new Page class will instantiate from the beginning (HTTP is stateless!). Cookies can be used if you do not want to lose the current thread's Culture on user's Session expiry.

Once we have pulled out all content from the web application, set the Culture and UICulture based in the user choice and used Resources.TestWebSite.XXXPropertyName, we are ready with our globalization framework. Now the only thing left is the adding of resource specific data in the resource files. For each culture we need to have a separate (and appropriately named) resource file. This process is localization. In my web.config file I have used the following properties:


<globalization responseEncoding"=utf-8” requestEncoding="utf-8” 
fileEncoding="utf-8" />

Note the encoding attributes: utf-8 (8 bit Unicode Transformation Format) is used since

it is variable length character encoding and can represent languages such as Greek, Arabic etc., besides it is ASCII compatible too. For more info on UTF-8 encoding, see this link:




 http://en.wikipedia.org/wiki/UTF-8



Also, an important point to note is that though we can have the resource files in raw XML form on the deployement server (so that the user can edit them without re-compling the entire site), the application will re-start if we make any modification in the resource files. This can hamper performance of the deployed application.


dir Attribute for language direction


Many times we also need to set the direction of the localized text (which is set using the dir attribute of the <html> or the <body> tag). This is neccessary because some languages are read from right-to-left (RTL), for e.g. Arabic, instead of the standard left-to-right (LTR) like Hindi and English. This can be achieved quite easily by setting the dir attribute to appropriate value from the .resx file. 


First create a Direction (you use any name) field in all your resource files, setting its property to RTL or LTR based on individual resource files. For Arabic, the value of this field would be RTL and for Hindi it would be LTR. Then set the same in the dir attribute of the <body> tag as:


 

 

<body runat="server" dir="><%$ Resources: TestSiteResources, Direction %>"


This will set the right direction as the value will come from the resouce file based on the current thread's culture.


 


Using a Database for Localization


We have seen how to localize the text of the controls and presentation in the UI. But what about the content stored in a database? This content also needs to be localized but since it is stored in a DB, we cannot use resource files for the same. We need to create new tables for the same.


Suppose I have a table which stores the user comments. The table structure is:


Sample screenshot


Now, we want the Comments and the Name fields to be displayed in localized text. But we can’t store all the different language versions of these fields in this same table as it will not be normalized (since there are other fields which don’t need to be localized but will be repeated). Hence we need to re-organize the table structure and create another table which will hold the localized version of these two fields. First we need to remove these two fields from this table and create a new table as:


Sample screenshot


Here we have added a new field as CultureID, which is equivalent to LCID, or the Locale Identifier, an integer which a particular culture. We can add culture specific localized data as:


Sample screenshot


Now we can use SQL queries with CultureID (LCID) as one of the parameters to get the localized content. We can also provide a user interface to enter localized data into such tables so that the content can be created in an interactive way.


Summary


I have tried to cover some important aspects of implementing Globalization in ASP.NET 2.0, and we saw that it is easy and simple but there are a few important points to note:


1. Do not rely on the web browser’s settings. Give a link on the application (may be in the header) so that the users can select their choice of language by clicking it.


2. Use Resources files to separate Presentation related data in the GUI. Resource fallback is the approach used by ASP.NET when it is unable to find the resource file for a particular culture. It will first go to the neutral resource file and then the default or fallback resource file (TestSiteResource.resx).


3. Use database tables for data or content stored in a DB. You need to create separate tables to store localized content.


4. If you used sn.exe to create a strong name of your main application assembly, then you need to use the private key from the same set of key pair (generated by sn.exe) to sign your satellite assemblies as well. Strong named assemblies require that satellite assemblies should also be strongly named.


Though I tried my best to cover important topics, but in case I missed something I would appreciate if readers can send in their suggestions on the same.


Happy globalizing!