Virtualized File Systems and Xaml Resource Resolution
File Access Considerations for a Smart Client
When developing our Smart Client, one of our requirements was that we support multiple deployment scenarios, including deployment to a terminal server. This requirement had serious implications for file and resource management. A typical per-user install is straightforward enough, but things get more complicated when you have a single install on a terminal server shared by several users. Applications running within the Smart Client may need to manage resources that are shared between users, as well as those that are specific to the individual user. We also needed to support automatic updates to files that may have been originally deployed to the application directory. This becomes very tricky when you consider that:
- The user may not have write access to the application’s install location. This is often the case on terminal server installs.
- Several users may be running the application concurrently on a terminal server, effectively preventing the original application files from being overwritten.
- Some resources should roam with the user’s profile, while others should remain on the local profile.
To ensure that we could support these more complicated scenarios, I designed a virtualized file system or “VFS”. The VFS provides a layer of indirection for file management. The VFS is composed of multiple virtual file stores, each corresponding do a different physical directory. By default, there are three virtual file stores: one in the application directory, one in the user’s local profile, and one in the user’s roaming profile. When an application requests a file, it provides a virtual path to the VFS service (the virtual path being a relative path). The VFS service then returns a virtual file handle, which provides various methods for file manipulation (Create, OpenRead, OpenWrite, etc.). When one of these methods is called, the VFS determines which virtual file store to use for the operation. Depending on whether the file is being read or written, different file resolution algorithms are applied. It is also possible to request a file from a specific virtual store.
A virtual path may take one of two forms: the standard relative path syntax or a vfs URI. A standard vfs URI uses the “vfs” scheme and an empty hostname. A specific virtual file store may be specified by using the name of the virtual store as the hostname. For example:
- Resources/Images/SplashScreen.png
- vfs:///Resources/Images/SplashScreen.png
- vfs://Application/Resources/Images/SplashScreen.png
My VFS implementation ended up working out extremely well. I was particularly fond of my vfs URI syntax. There was but one caveat in my mind: the added layer of indirection made it much more tedious to load resources in WPF applications. Instead of using relative file paths or pack URIs directly in Xaml, files requests had to to through the VFS in C# code. Wouldn’t it be great, I thought, if vfs URIs could be used directly in Xaml? I was determined to make that happen.
Custom URI Schemes in Xaml
Several of the standard WPF type converters accept file paths and pack URIs as input. Knowing that, it took only a few minutes in Reflector to determine that these URI values result in a WebRequest being created, allowing the resources to be read via a Stream. Thus, all I had to do to enable vfs URI support was implement a custom web request factory (by implementing IWebRequestCreate) and write the corresponding WebRequest and WebResponse implementations:
public class VfsWebRequestFactory : IWebRequestCreate
{ public WebRequest Create(Uri uri) { return new VfsWebRequest(uri);
}
}
internal class VfsWebRequest : WebRequest
{ private readonly Uri _requestUri;
public override Uri RequestUri
{ get { return _requestUri; } }
public VfsWebRequest([NotNull] Uri requestUri) { if (requestUri == null)
throw new ArgumentNullException("requestUri");
_requestUri = requestUri;
}
public override WebResponse GetResponse()
{ return new VfsWebResponse(RequestUri);
}
}
internal class VfsWebResponse : WebResponse
{ private static IVirtualFileManager s_virtualFileManager;
private readonly Uri _responseUri;
protected IVirtualFileManager VirtualFileManager { get
{ if (s_virtualFileManager == null)
s_virtualFileManager = ServiceLocator.Current.GetInstance<IVirtualFileManager>();
return s_virtualFileManager; }
}
public sealed override Uri ResponseUri
{ get { return _responseUri; } }
public VfsWebResponse([NotNull] Uri responseUri) { if (responseUri == null)
throw new ArgumentNullException("responseUri");
_responseUri = responseUri;
}
public override Stream GetResponseStream()
{ var fileInfo = this.VirtualFileManager.GetFile(_responseUri.ToString()); if (fileInfo.Exists) return fileInfo.OpenRead(); return null;
}
}
Once the web request factory was written, I had only to register it by adding an entry into the Smart Client’s App.config file:
<configuration>
<system.net>
<webRequestModules>
<add prefix="vfs" type="assembly-qualified type name goes here"/>
</webRequestModules>
</system.net>
</configuration>
Once that was done, vfs URIs could be used almost anywhere a relative path or pack URI was accepted in Xaml:
<BitmapImage x:Key="IdentityCredentialImage"
UriSource="vfs://Application/Scc/Images/NT.png" />
The one exception is that font resources cannot be loaded with a vfs URI. Font resources may only be specified using a file path or pack URI. Despite this limitation, I have been extremely pleased with how well my custom URIs have worked. While the same thing could be accomplished using custom markup extensions, you can’t deny the added geek factor of having your own custom URI format. Just remember that there are a finite number of URI schemes available, so if you’re a component vendor considering using a custom URI format for resource management, be sure to pick a proprietary URI scheme that isn’t likely to clash with someone else’s (i.e. mycompany://). Happy coding!


0 Comments:
Post a Comment
Subscribe to Post Comments [Atom]
<< Home