Using Custom Resource Managers in ASP.NET 2.0


ASP.NET 1.1 applications have no further requirements for using custom resource managers beyond what has been covered so far. So if you are not using ASP.NET 2.0 and do not intend to upgrade to ASP.NET 2.0, you can skip this section.


As we saw in Chapter 5, "ASP.NET Specifics," Microsoft has made significant advances in ASP.NET 2.0, particularly in the area of internationalization. The issue of interest to us in this chapter is the introduction of a resource provider model. This section discusses how the existing model works and how we can write resource providers that plug into this model to use the resource managers that we have created in this chapter. We start with a description of ASP.NET's Resource Provider Model; then we implement a new resource provider that mimics the behavior of the existing provider. Finally, we implement a provider for the DbResourceManager from this chapter. From these examples, you should understand the mechanism sufficiently to write a resource provider for any of the custom resource managers in this chapter.

The Resource Provider Model

ASP.NET 2.0 uses the ResourceManager class, by default, for retrieving all resources from resource assemblies, both fallback and satellite. The model described in Chapter 3 is still true for ASP.NET 2.0. However, ASP.NET 2.0 allows developers to specify a resource provider that is responsible for providing localized resources. By default, the existing provider returns ResourceManager objects, but the model allows us to override this behavior. The essential processes that the Resource Manager class executes are still true for ASP.NET:

  • Resources are created from resx/resources/restext/txt files.

  • Resources are embedded in an assembly.

  • Resources are accessed using the ResourceManager class.

  • The ResourceManager uses the fallback process we are familiar with.

There are, however, two differences that ASP.NET 2.0 must cope with:

  • ASP.NET applications are compiled to temporary directories with generated names, so code that loads resources from these assemblies needs to use these generated names.

  • ASP.NET applications have both global and local resources, and these resources are placed in separate assemblies.

Before we get into the resource provider mechanism, let's put some flesh on what this means to an ASP.NET application. In Visual Studio 2005, create a new WebSite; add a button and some controls to the Default page; and select Tools, Generate Local Resource to generate local resources. Create a French version of the page by adding a Default.aspx.fr.resx file to the App_LocalResources folder and include a new entry called Button1Resources.Text for the French version of the button. Now add a new global resource file called ProductInfo.resx to the App_Global Resources folder. Add a key called Name and give it a value. Add a second resource file called ProductInfo.fr.resx to the App_GlobalResources folder, with a French value for the Name key. Set your browser's Language Preference to French. When the Web site runs, the resources will be compiled into resource assemblies. In our example, there will be four separate resource assemblies:

  • The Global fallback resource assembly

  • The Global French resource assembly

  • The Local fallback resource assembly

  • The Local French resource assembly

The Global resource assembly path is determined by the following formula:

 <Temporary ASP.NET Folder>\<WebSiteName>\<GeneratedName1>\ <GeneratedName2>\App_GlobalResources.<GeneratedName3>.dll 


So if <Temporary ASP.NET Folder> is "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files" and the <WebSiteName> is "WebSite1", the Global fallback assembly could be something like this:

 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\website1\62a3b0ac\1072591c\App_GlobalResources.w1qus9s2.dll 


The Global French resource assembly is placed relative to the fallback assemblies' folder in the fr folder and given the ".resources.dll" extension; in this example, it would be:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\website1\62a3b0ac\1072591c\fr\App_GlobalResources.w1qus9s2.resources.dll 


The Local fallback resource assembly path is determined by a similar formula:

<Temporary ASP.NET Folder>\<WebSiteName>\<GeneratedName1>\ <GeneratedName2>\App_LocalResources.<FolderName>.<GeneratedName4>.dll 


The <FolderName> is the name of folder where the .aspx files reside, where "root" is used for the Web site's root. So the Local fallback assembly could be something like this:

 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\website1\62a3b0ac\1072591c\App_LocalResources.root.ogd3clye.dll 


Following the same practice as the Global satellite resource assembly, the Local French resource assembly is placed in the fr folder and given the ".resources.dll" extension; in this example, it would be:

 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\website1\62a3b0ac\1072591c\fr\App_LocalResources.root.ogd3clye.resources.dll 


With these challenges in mind, we can look at how the ASP.NET Resource Provider model works.

The Resource Provider story starts with the ResourceProviderFactory abstract class. This class has a single implementation in the .NET Framework 2.0namely, ResXResourceProviderFactory (see Figure 12.7).

Figure 12.7. ResourceProviderFactory Class Hierarchy


ResXResourceProviderFactory is the default factory and is the factory that has been in use in all of the ASP.NET 2.0 Web sites in this book up to this point. The ResourceProviderClass has two methods that must be overridden by the subclass:

 public abstract IResourceProvider CreateGlobalResourceProvider(     string classKey); public abstract IResourceProvider CreateLocalResourceProvider(     string virtualPath); 


These methods return an IResourceProvider interface. IResourceProvider is a simple interface:

 public interface IResourceProvider {       object GetObject(string resourceKey, CultureInfo culture);       IResourceReader ResourceReader { get; } } 


So the ResourceProviderFactory must return objects that support a GetObject method and a ResourceReader property. The ResXResourceProviderFactory creates a new GlobalResXResourceProvider object when its CreateGlobalResource Provider method is called and a LocalResXResourceProvider object when its CreateLocalResourceProvider method is called.

Figure 12.8 shows the class hierarchy for the classes that support IResource Provider in the .NET Framework 2.0.

Figure 12.8. Implementations of IResourceProvider


The BaseResXResourceProvider implements the GetObject method and ResourceReader property required by the IResourceReader. The GetObject method calls an abstract method called CreateResourceManager to create a ResourceManager object and store it in a private field, and then calls the Resource Manager's GetObject method. The GlobalResXResourceProvider and Local ResXResourceProvider classes both override the CreateResourceManager method to create a ResourceManager, using the correct resource name and the correct assembly. The GlobalResXResourceProvider overrides the ResourceReader property to throw a NotSupportedException. This doesn't affect the normal execution of a Web site because the IResourceProvider.ResourceReader property is not called by the .NET Framework 2.0 for global resources. The LocalResX ResourceProvider overrides the ResourceReader property to return a Resource Reader to read the relevant resource from the assembly.

Setting the ResourceProviderFactory

The ResourceProviderFactory class can be set in the web.config's globalization section using the resourceProviderFactoryType attribute. The syntax is:

 <globalization resourceProviderFactoryType=     [FullClassName[, Assembly]]/> 


So in the next example, our ResourceProviderFactory class is Internationalization.Resources.Web.ResourceManagerResourceProviderFactory, and it is in an assembly called ResourceProviderFactories; the complete web.config is:

 <configuration     xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">     <appSettings/>     <connectionStrings/>     <system.web>         <globalization resourceProviderFactoryType=             "Internationalization.Resources.Web.             ResourceManagerResourceProviderFactory,             ResourceProviderFactories"/>     </system.web> </configuration> 


Note that the assembly name must not include the .dll extension, and the Resource ProviderFactories assembly must be available to the Web site, so it should be either installed in the GAC or added to the Web site's bin folder (you can do this by adding the ResourceProviderFactories project to the Web site's references).

Alternatively, if you include the factory class in the Web site's App_Code folder, you do not need to specify the assembly in the resourceProviderFactoryType setting.

ResourceManagerResourceProviderFactory

To see how this works in practice, we'll create a ResourceManagerResource ProviderFactory. This class mimics the behavior of the ResXResourceProvider Factory and gives us an insight into how the default provider solves its problems. In the subsequent section, we create a provider factory for the DbResourceManager class that we wrote in this chapter. The ResourceManagerResourceProviderFactory class is as follows:

 public class ResourceManagerResourceProviderFactory:     ResourceProviderFactory {     public ResourceManagerResourceProviderFactory()     {     }     public override IResourceProvider         CreateGlobalResourceProvider(string classKey)     {         return new GlobalResourceManagerResourceProvider(classKey);     }     public override IResourceProvider         CreateLocalResourceProvider(string virtualPath)     {         return new LocalResourceManagerResourceProvider(virtualPath);     } } 


This simple class returns a new GlobalResourceManagerResourceProvider object and a new LocalResourceManagerResourceProvider object for its two methods. The classKey parameter provided to the CreateGlobalResourceProvider method will be "ProductInfo" in our example. The virtualPath parameter provided to the CreateLocalResourceProvider method will be "/WebSite1/Default.aspx" in our example. Figure 12.9 shows the class hierarchy of the IResourceProvider implementations required for our ResourceManager and DbResourceManager implementations.

Figure 12.9. Class Hierarchy of Custom Implementations of IResourceProvider


Both of the IResourceProvider implementations inherit indirectly from the abstract BaseResourceProvider class:

 public abstract class BaseResourceProvider : IResourceProvider {     private ResourceManager resourceManager;     protected ResourceManager ResourceManager     {         get         {             if (resourceManager == null)                 resourceManager = CreateResourceManager();             return resourceManager;         }     }     protected abstract ResourceManager CreateResourceManager();     public object GetObject(string resourceKey,         System.Globalization.CultureInfo culture)     {         return ResourceManager.GetObject(resourceKey, culture);     }     public System.Resources.IResourceReader ResourceReader     {         get { throw new NotSupportedException(); }     } } 


BaseResourceProvider has a ResourceManager property that initializes a private resourceManager field by calling the abstract CreateResourceManager method. It implements the GetObject method to call the ResourceManager's GetObject method, and it implements the ResourceReader property to throw a NotSupported Exception. The BaseResourceProvider class is used in this example and also the next example to create a ResourceProviderFactory for the DbResourceManager class. The BaseResourceManagerResourceProvider class implements the Create ResourceManager method and provides a GetInternalStaticProperty method:

 public abstract class BaseResourceManagerResourceProvider :     BaseResourceProvider {     protected override ResourceManager CreateResourceManager()     {         Assembly resourceAssembly = GetResourceAssembly();         if (resourceAssembly == null)             return null;         ResourceManager resourceManager =             new ResourceManager(GetBaseName(), resourceAssembly);         resourceManager.IgnoreCase = true;         return resourceManager;     }     protected abstract string GetBaseName();     protected abstract Assembly GetResourceAssembly();     protected static object GetInternalStaticProperty(         Type type, string propertyName)     {         PropertyInfo propertyInfo =             type.GetProperty(propertyName,             System.Reflection.BindingFlags.Static |             System.Reflection.BindingFlags.NonPublic);         if (propertyInfo == null)             return null;         else             return propertyInfo.GetValue(null, null);     } } 


The CreateResourceManager method calls the abstract GetBaseName method to get the name of the resource, and the abstract GeTResourceAssembly to get the assembly that contains the resources. These two methods represent the only differences between the "global" resource manager and the "local" resource manager. The GetInternalStaticProperty method is a workaround for BuildManager and BuildResult classes, hiding information from us that we need to implement this solution. It uses reflection to obtain the value of internal static properties.

With this infrastructure in place, the GlobalResourceManagerResource Provider class is simple:

 public class GlobalResourceManagerResourceProvider :     BaseResourceManagerResourceProvider {     private string classKey;     public GlobalResourceManagerResourceProvider(string classKey)     {         this.classKey = classKey;     }     protected override string GetBaseName()     {         return "Resources." + classKey;     }     protected override Assembly GetResourceAssembly()     {         return (Assembly) GetInternalStaticProperty(             typeof(BuildManager), "AppResourcesAssembly");     } } 


The GetBaseName returns "Resources" plus the classKey, so if classKey is "ProductInfo", then the base name will be "Resources.ProductInfo". The GeTResourceAssembly method gets the resource assembly from the Build Manager's internal static AppResourcesAssembly property. The BuildManager is the class that is responsible for building the Web site when it is run.

The LocalResourceManagerResourceProvider class isn't quite so simple. Here is an abbreviated version of it (see the source code for the book for the complete version):

 public class LocalResourceManagerResourceProvider :     BaseResourceManagerResourceProvider     {         private string virtualPath;         public LocalResourceManagerResourceProvider(            string virtualPath)         {             this.virtualPath = virtualPath;         }         protected override string GetBaseName()         {             return Path.GetFileName(virtualPath);         }         protected override Assembly GetResourceAssembly()         {             string virtualPathParent = GetVirtualPathParent();             string localAssemblyName =                 GetLocalResourceAssemblyName(virtualPathParent);             Object buildResult = GetBuildResultFromCache(cacheKey);             if (buildResult != null)                 return GetBuildResultResultAssembly(buildResult);             return null;         } } 


The GetBaseName method returns the base name from the virtual path. So if the virtual path is "/WebSite1/Default.aspx", the base name is "Default". The GetresourceAssembly method has the job of finding the local resource assembly, given that its path and part of its name has been generated on the fly. We'll take it line by line using our example. GetVirtualPathParent returns "/WebSite1". GetLocalResourceAssemblyName returns "App_LocalResources.root", assuming that the .aspx files are located in the root. GetBuildResultAssembly returns the Assembly object from the assembly name. Each of these methods is implemented in the LocalResourceManagerResourceProvider class. Our implementation of a ResourceProviderFactory and its associated classes is complete. Our class mimics the behavior of the .NET Framework 2.0's ResXResourceProviderFactory.

DbResourceManagerResourceProviderFactory

Our DbResourceManagerResourceProviderFactory solution isn't nearly as complex as the ResourceManagerResourceProviderFactory solution. The main difference between the two implementations lies in a decision that the ResourceSets table in our localization database will contain both global and local resources, so it is not necessary for us to make a distinction between the two. So in this example, we need to implement only one IResourceProvider class because the one class will suffice for both global and local resources. Here is the DbResourceManager ResourceProviderFactory:

 public class DbResourceManagerResourceProviderFactory :     ResourceProviderFactory {     public DbResourceManagerResourceProviderFactory()     {     }     public override IResourceProvider         CreateGlobalResourceProvider(string classKey)     {         return new DbResourceManagerResourceProvider(classKey);     }     public override IResourceProvider         CreateLocalResourceProvider(string virtualPath)     {         string classKey = Path.GetFileName(virtualPath);         if (classKey.ToUpper().EndsWith(".ASPX"))             // strip off the .aspx extension             classKey = classKey.Substring(0, classKey.Length - 5);         return new DbResourceManagerResourceProvider(classKey);     } } 


The CreateGlobalResourceProvider method simply returns a new DbResource ManagerResourceProvider object, passing in the class key (e.g., "ProductInfo"). The CreateLocalResourceProvider method needs to convert the virtualPath (e.g., "/WebSite1/Default.aspx") into a class key (e.g., "Default") by stripping off the path and the .aspx extension. The DbResourceManagerResourceProvider class inherits from the BaseResourceProvider class that we created in the previous section; therefore, it only needs to implement the CreateResourceManager method:

 public class DbResourceManagerResourceProvider :     BaseResourceProvider {     private string classKey;     public DbResourceManagerResourceProvider(string classKey)     {         this.classKey = classKey;     }     protected override ResourceManager CreateResourceManager()     {         DbResourceManager resourceManager =             new DbResourceManager(classKey);         resourceManager.IgnoreCase = true;         return resourceManager;     } } 


The CreateResourceManager method simply creates a new DbResource Manager and passes it the class key. Our implementation is complete. Armed with these examples, you should be able to create a ResourceProviderFactory and its associated classes for any custom resource manager.




.NET Internationalization(c) The Developer's Guide to Building Global Windows and Web Applications
.NET Internationalization: The Developers Guide to Building Global Windows and Web Applications
ISBN: 0321341384
EAN: 2147483647
Year: 2006
Pages: 213

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net