Writeable Resource Managers


The custom resource managers that we have looked at so far read resources but have no capability for writing to resources. This solution is adequate if you intend to maintain the resources yourself. However, the next custom resource manager is the TRanslationResourceManager. This resource manager performs translations onthe-fly for missing resources and needs to write back these translations to the original source. To do this our resource managers must have a write capability, and that's what this section is about.

Our first attempts at the DbResourceSet and ResourcesResourceSet classes were minimalist, to say the least. The solution to the problem of writing to resources lies in modifying these classes. In the previous discussion, I briefly mentioned that ResourceSet has two methods, GetdefaultReader and GeTDefaultWriter, which allow us to specify which Types are used to create reader and writer objects for the resource. These are implemented as follows:

 public override Type GetDefaultReader() {     return typeof(DbResourceReader); } public override Type GetDefaultWriter() {     return typeof(DbResourceWriter); } 


Unfortunately, these methods do not help us solve our problem. Nothing in the .NET Framework ever calls these methods. They exist solely to provide a means to allow generic code to create readers and writers as necessary. Certainly, this is what we want to do, but this approach isn't sufficient for our purposes. The problem with simply specifying a Type to create a new object from is that you are at the mercy of the caller to call the constructor signature that you want called. The following generic code illustrates the problem:

 ResourceSet resourceSet = new ResourceSet(fileName); Type resourceReaderType = resourceSet.GetDefaultReader(); IResourceReader resourceReader = (IResourceReader)     Activator.CreateInstance(resourceReaderType,     new object[] {fileName}); 


In this example, a new IResourceReader is being created from the resource reader type using Activator.CreateInstance. The generic code has determined that it will use the resource reader constructor, which accepts a single string parameter. Both of the IResourceReader classes that we have implemented so far (DbResourceReader and ResourcesResourceReader) do not support this constructor. Furthermore, they do not share a common constructor signature at all:

 public DbResourceReader(     string baseNameField, CultureInfo cultureInfo) public ResourcesResourceReader(string baseNameField,     CultureInfo cultureInfo, string extension) 


It is for this reason that I have implemented a slightly more versatile solution. Both ResourceSet classes now inherit from a new class, CustomResourceSet:

 public class CustomResourceSet: ResourceSet {     public CustomResourceSet(IResourceReader resourceReader):         base(resourceReader)     {     }     public virtual IResourceReader CreateDefaultReader()     {         Type resourceReaderType = GetDefaultReader();         return (IResourceReader)             Activator.CreateInstance(resourceReaderType);     }     public virtual IResourceWriter CreateDefaultWriter()     {         Type resourceWriterType = GetDefaultWriter();         return (IResourceWriter)             Activator.CreateInstance(resourceWriterType);     }     public virtual void Add(string key, object value)     {         Table.Add(key, value);     }     public new Hashtable Table     {         get {return base.Table;}     } } 


CustomResourceSet implements two new methods: CreateDefaultReader and CreateDefaultWriter. These methods can be overridden by subclasses to create IResourceReader and IResourceWriter objects using whatever constructor the developer sees fit. You can also see a new method, Add, and a public property, Table, which we return to later. The DbResourceSet now becomes:

 public class DbResourceSet: CustomResourceSet {     private string baseNameField;     private CultureInfo cultureInfo;     public DbResourceSet(         string baseNameField, CultureInfo cultureInfo):         base(new DbResourceReader(baseNameField, cultureInfo))     {         this.baseNameField = baseNameField;         this.cultureInfo = cultureInfo;     }     public override Type GetDefaultReader()     {         return typeof(DbResourceReader);     }     public override Type GetDefaultWriter()     {         return typeof(DbResourceWriter);     }     public override IResourceReader CreateDefaultReader()     {         return new DbResourceReader(baseNameField, cultureInfo);     }     public override IResourceWriter CreateDefaultWriter()     {         return new DbResourceWriter(baseNameField, cultureInfo);     } } 


The DbResourceSet constructor works as it did before, creating a new DbResourceReader object. In addition, it saves the parameters passed to two private fields. The CreateDefaultReader and CreateDefaultWriter methods are overridden, and the base name field and culture are passed to the DbResourceReader and DbResourceWriter constructors. The GeTDefaultReader and GeTDefaultWriter methods are redundant, but I have implemented them anyway for completeness. All that remains for the DbResourceSet class now is for us to implement the new DbResourceWriter class.

DbResourceWriter

DbResourceWriter is an implementation of the IResourceWriter interface:

 public interface IResourceWriter : IDisposable {     void AddResource(string name, object value);     void AddResource(string name, string value);     void AddResource(string name, byte[] value);     void Close();     void Generate(); } 


A consumer of this interface simply calls the various AddResource methods adding string, object, or byte[] resources. It then calls Generate to create the resource and finally Close to close the resource. You can approach this interface in one of two ways:

  • You can write the resources to the target with every call to AddResource, and then either commit the changes when Generate is called or roll them back if Close is called without a Generate.

  • Alternatively, you can collect all of the resources added using AddResource in a cache and write the resource in its entirety in the Generate method.

I have chosen the latter approach because we will need to open a connection to the database, and I want the connection to be open for as short a duration as possible. The DbResourceWriter class (without the Generate method) is:

 public class DbResourceWriter: IResourceWriter {     private string baseNameField;     private CultureInfo cultureInfo;     private SortedList resourceList;     public DbResourceWriter(         string baseNameField, CultureInfo cultureInfo)     {         this.baseNameField = baseNameField;         this.cultureInfo = cultureInfo;         resourceList = new SortedList();     }     public void Close()     {         Dispose(true);     }     public void Dispose()     {         Dispose(true);     }     private void Dispose(bool disposing)     {         if (disposing && resourceList != null)             Generate();     }     public void AddResource(string name, object value)     {         if (name == null)             throw new ArgumentNullException("name");         if (resourceList == null)             throw new InvalidOperationException(                 "InvalidOperation_ResourceWriterSaved");         resourceList.Add(name, value);     }     public void AddResource(string name, string value)     {         AddResource(name, (Object) value);     }     public void AddResource(string name, byte[] value)     {         AddResource(name, (Object) value);     } } 


The DbResourceWriter simply assigns the incoming base name and culture parameters to private fields and initializes a resourceList private field to a SortedList. The resourceList is a temporary bucket into which all of the resources are added until the Generate method is called.

As the name implies, the Generate method is responsible for generating the resource. In the case of the .NET Framework ResourceWriter and ResX ResourceWriter classes, this means writing a new .resources or .resx file. The new file overwrites the old file. In the case of a database, this means deleting all of the existing resource entries for the given resource name and inserting a new row for each resource key.

 public void Generate() {     if (resourceList == null)         throw new InvalidOperationException(             "InvalidOperation_ResourceWriterSaved");     using (SqlConnection connection =         new SqlConnection(DbResourceManager.ConnectionString))     {         connection.Open();         SqlTransaction transaction = connection.BeginTransaction();         try         {             DeleteExistingResource(transaction);             foreach(DictionaryEntry dictionaryEntry in resourceList)             {                 if (dictionaryEntry.Value != null)                     InsertResource(transaction,                         dictionaryEntry.Key.ToString(),                         dictionaryEntry.Value);             }             transaction.Commit();         }         catch         {             transaction.Rollback();             throw;         }     }     resourceList = null; } 


The Generate method opens a connection, creates a transaction, deletes all of the existing resources, and, for each resource that has been added to resourceList, inserts a new row into the database. Finally, the transaction is either committed or rolled back, and the connection is closed.

DbResourceManager.DeleteExistingResource and DbResourceManager. InsertResource are:

 protected virtual void DeleteExistingResource(     SqlTransaction transaction) {     // delete all of the existing resource values     if (cultureInfo.Equals(CultureInfo.InvariantCulture))     {         string deleteCommandText =             "DELETE FROM ResourceSets WHERE ResourceSetName="+             "@resourceSetName AND Culture IS NULL";         using (SqlCommand deleteCommand = new SqlCommand(             deleteCommandText, transaction.Connection, transaction))         {             deleteCommand.Parameters.Add("@resourceSetName",                 SqlDbType.VarChar, 100).Value = baseNameField;             deleteCommand.ExecuteNonQuery();         }     }     else     {         string deleteCommandText =             "DELETE FROM ResourceSets WHERE ResourceSetName="+             "@resourceSetName AND Culture=@culture";         using (SqlCommand deleteCommand = new SqlCommand(             deleteCommandText, transaction.Connection, transaction))         {             deleteCommand.Parameters.Add("@resourceSetName",                 SqlDbType.VarChar, 100).Value = baseNameField;             deleteCommand.Parameters.Add("@culture",                 SqlDbType.VarChar, 20).Value =                 cultureInfo.ToString();             deleteCommand.ExecuteNonQuery();         }     } } protected virtual void InsertResource(SqlTransaction transaction,     string resourceName, object resourceValue) (     string insertCommandText;     if (cultureInfo.Equals(CultureInfo.InvariantCulture))     {         if (resourceValue is System.String)             insertCommandText = "INSERT INTO ResourceSets "+                 "(ResourceSetName, ResourceName, "+                 "ResourceValue) VALUES (@resourceSetName, "+                 "@resourceName, @resourceValue)";         else             insertCommandText = "INSERT INTO ResourceSets "+                 "(ResourceSetName, ResourceName, "+                 "ResourceValue, ResourceType) VALUES "+                 "(@resourceSetName, @resourceName, "+                 "@resourceValue, @resourceType)";     }     else     {         if (resourceValue is System.String)             insertCommandText = "INSERT INTO ResourceSets "+                 "(ResourceSetName, Culture, ResourceName, "+                 "ResourceValue) VALUES (@resourceSetName, "+                 "@culture, @resourceName, @resourceValue)";         else             insertCommandText = "INSERT INTO ResourceSets "+                 "(ResourceSetName, Culture, ResourceName, "+                 "ResourceValue, ResourceType) VALUES "+                 "(@resourceSetName, @culture, @resourceName, "+                 "@resourceValue, @resourceType)";     }     using (SqlCommand insertCommand = new SqlCommand(         insertCommandText, transaction.Connection, transaction))     {         insertCommand.Parameters.Add(new SqlParameter(             "@resourceSetName", baseNameField));         if (! cultureInfo.Equals(CultureInfo.InvariantCulture))             insertCommand.Parameters.Add(new SqlParameter(                 "@culture", cultureInfo.ToString()));         insertCommand.Parameters.Add(new SqlParameter(             "@resourceName", resourceName));         insertCommand.Parameters.Add(new SqlParameter(             "@resourceValue", resourceValue.ToString()));         if (! (resourceValue is System.String))             insertCommand.Parameters.Add(new SqlParameter(                 "@resourceType",                 resourceValue.GetType().AssemblyQualifiedName));         insertCommand.ExecuteNonQuery();     } } 


Writeable ResourcesResourceManager

Writing to .resources and .resx files is a fair bit easier than writing to a database, as the necessary classes (ResourceWriter and ResXResourceWriter) are part of the .NET Framework. All that we have to do is use them. The revised Resources ResourceSet class follows the same pattern as the DbResourceSet class:

 public class ResourcesResourceSet: CustomResourceSet {     private string baseNameField;     private CultureInfo cultureInfo;     private string extension;     public ResourcesResourceSet(string baseNameField,         CultureInfo cultureInfo, string extension):         base(new ResourcesResourceReader(         baseNameField, cultureInfo, extension))     {         this.baseNameField = baseNameField;         this.cultureInfo = cultureInfo;         this.extension = extension;     }     public override Type GetDefaultReader()     {         return typeof(ResourcesResourceReader);     }     public override Type GetDefaultWriter()     {         if (extension == "resx")             return typeof(ResXResourceWriter);         else if (extension == "resources")             return typeof(ResourceWriter);         else             throw new ArgumentException(String.Format(                 "Unknown resource extension ({0})", extension));     }     public override IResourceReader CreateDefaultReader()     {         return new ResourcesResourceReader(             baseNameField, cultureInfo, extension);     }     protected virtual string GetResourceFileName()     {         if (cultureInfo.Equals(CultureInfo.InvariantCulture))             return baseNameField + "." + extension;         else             return baseNameField + "." +                 cultureInfo.Name + "." + extension;     }     public override IResourceWriter CreateDefaultWriter()     {         if (extension == "resx")             return new ResXResourceWriter(GetResourceFileName());         else if (extension == "resources")             return new ResourceWriter(GetResourceFileName());         else             throw new ArgumentException(String.Format(                 "Unknown resource extension ({0})", extension));     } } 


Once again, the parameters passed to the constructor are saved in private fields. The GetdefaultReader, GeTDefaultWriter, CreateDefaultReader, and Create DefaultWriter methods all check the extension private field and use ResourceReader/ResourceWriter or ResXResourceReader/ResXResource Writer classes, as necessary, or throw an exception for an unknown extension.




.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