Client / Server Localization – Transfer resources

In my last post I described the ResourceManager and how to create a filebased ResourceManager. This post covers  how we can get resources from the server to the client and how to handle multiple resource managers and basenames. As we can imagine a server could host multiple plugins and components which themselves would need to provide dynamic resources for the clients. In order to achieve a simple solution to compile also resources from plugins into binary resource files we need the following build task:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <AvailableItemName Include="DynamicClientResource" />
  </ItemGroup>

  <PropertyGroup>
    <ClientResourceDir>$(OutDir)ClientResources\</ClientResourceDir>
  </PropertyGroup>

  <PropertyGroup>
        <BuildDependsOn>BuildClientResources;$(BuildDependsOn)</BuildDependsOn>
  </PropertyGroup>

  <Target Name="BuildClientResources" Outputs="%(DynamicClientResource.Identity)">
    <PropertyGroup>
      <ActualRelativeDir>%(DynamicClientResource.RelativeDir)</ActualRelativeDir>
      <RelativeNamespace>$(ActualRelativeDir.Replace("\","."))</RelativeNamespace>
    </PropertyGroup>
    <MakeDir Directories="$(ClientResourceDir)" Condition="Exists('%(DynamicClientResource.identity)')"/>
    <GenerateResource
    UseSourcePath="true"
    Sources="@(DynamicClientResource)"
    OutputResources="@(DynamicClientResource->'$(ClientResourceDir)$(RootNamespace).$(RelativeNamespace)%(filename).resources')">
    <Output
      TaskParameter="OutputResources"
      ItemName="Resources"/>
    </GenerateResource>
  </Target>
</Project>

If we include this custom build target into our build process, plugins can simply choose the “DynamicClientResource” as custom build action for all our resource files.

The result of this custom target is that all plugins and components which define resources will build the binary resources into the same directory under “ClientResources\”. If we use WCF services we can now leverage WCF streaming services to transfer the binary resources to the client. A basic service contract could look like the following:

    [ServiceContract(Namespace = "https://www.planetgeek.ch/resources")]
    public interface IDynamicResourceService
    {
        [OperationContract]
        IEnumerable<ResourceCatalogEntry> ResourceCatalog();

        [OperationContract]
        Stream Resource(ResourceCatalogEntry resource);
    }

    [DataContract(Namespace = "https://www.planetgeek.ch/resources")]
    public class ResourceCatalogEntry
    {
        [DataMember]
        public string FileName { get; set; }

        [DataMember]
        public string BaseName { get; set; }
    }

The service contract is split into two operations. One operation is to retrieve the whole resource catalog which contains information about the original file name and the base name of the resource. The second operation allows the client to stream a binary resource for a certain catalog entry. I think it is unnecessary to show how to implement the WCF service which fulfills this service contract. It is dead simple: Scan the client resource directory and provide resource catalog entries from the directory. For each acquired resource from the client open a read stream of the acquired catalog entry and return it to the client.

If we have this infrastructure in place the client can use a proxy to access the dynamic resource service. Upon start of the client all resources can be downloaded from the server and saved on the local disk. As you can imagine from the previous post we now need multiple file based resource managers on the client which are created dynamically from the downloaded binary resources from the server. In order to provide a unified interface for the client integration we need a simple resource manager decorator which handles this task for us.

	public class ResourceManagerDecorator : IResourceManager
    {
        private readonly Dictionary<string, ResourceManager> resourceManagers;

        public ResourceManagerDecorator()
        {
            this.resourceManagers = new Dictionary<string, ResourceManager>();
        }

        public void AddResourceFile(string baseName, string resourceDirectory)
        {
            if (!this.resourceManagers.ContainsKey(baseName))
            {
                ResourceManager resourceManager = ResourceManager.CreateFileBasedResourceManager(baseName, resourceDirectory, null);
                this.resourceManagers.Add(baseName, resourceManager);
            }
        }

        public bool IsValid(string resourceKey)
        {
			// Will be covered later
        }

        public string GetString(string resourceKey)
        {
			// Will be covered later
        }
    }

The decorator above simply creates a file based resource manager for each base name and hosts the created resource manager for future use. The next posts will cover how to build up dynamic resource keys so that they won’t collide on the client and how to hook up some magic with auto mapper to dynamically map resources with the resource manager decorator.

About the author

Daniel Marbach

Add comment

Recent Posts