Path Management
December 8, 2010 Leave a comment
In this first post I am going to talk about managing paths in our applications. Being you coding in .Net or any other platform, managing paths is something you will do sometimes across layers for locating files, transform relative to absolute (or the other way around), and so on. Of course, I am talking about path managing at file system level when your application allows users to store (or upload) files to the host.
I used to leave this things to each business object, as every business object must know what and how it needs to perform file system operations. But after six projects where building paths are a requirement, I am facing the seventh one creating a *PathManager class for each business object, where * is the name of the business domain that this class generates paths for (User, Project, Finance…).
In fact, the architecture is more complex. I have a base FilesystemPathManager that implements IPathManager interface so I can instantiate it using a Factory Pattern (Unity, most times). The rest of the path management classes inherits from this one where they can find protected properties for building or splitting file system paths.
So, how does IPathManager looks like?
public interface IPathManager
{
string GetPhysicalPath(string resourceName);
string GetVirtualPath(string resourceName);
string GetNameFromVirtualPath(string virtualPath);
string GetNameFromPhysicalPath(string physicalPath);
}
GetPhysicalPath returns the physical location of the file for the application to load its contents, while the GetVirtualPath method returns a ready-to-be-consumed-by-client path. In desktop applications, both of them could be implemented seamlessly, but they really make sense in web environments.
The GetNameFromVirtualPath and GetNameFromPhysicalPath helps in splitting the path and extract the name of a resource from a path generated by the other methods respectively.
The FilesystemPathManager implements the IPathManager interface in this way (for web environments):
public class FilesystemPathManager : IPathManager
{
private string ContextId
{
get { return HttpContext.Current.Request.QueryString["id"]; }
}
private const string PhysicalPathFormat = "{0}..\Data\{1}\{2}";
public string GetPhysicalPath(string resourceName)
{
string physicalPath = String.Format(PhysicalPathFormat,
AppDomain.CurrentDomain.BaseDirectory,
this.ContextId,
resourceName);
return Path.GetFullPath(physicalPath);
}
private const string VirtualPathFormat = "~/Resource.aspx?id={0}&res={1}";
public string GetVirtualPath(string resourceName)
{
return String.Format(VirtualPathFormat,
this.ContextId,
resourceName);
}
public string GetNameFromVirtualPath(string virtualPath)
{
Uri virtualUri = new Uri(virtualPath);
return HttpUtility.ParseQueryString(builder.Query).Get("res");
}
public string GetNameFromPhysicalPath(string physicalPath)
{
return Path.GetFilename(physicalPath);
}
}
Lets start with GetPhysicalPath. It makes use of a constant that helps it standardize the file locations, but the path is outside the web environment. We know this because the second parameter of the String.Format is the current BaseDirectory of the AppDomain, which points to the root of the website (not the /bin folder). So, what’s the problem with dealing with outside paths? Referring to them may be not very reliable. We cannot use an absolute path in the constant, as we really do not know where the application is going to be deployed. That is why PhysicalPathFormat contains ..\, so we can go to the parent directory.
After building the physical path, we have something like:
C:\inetpub\wwwroot\MyWebApplication\Web\..\1245\avatar.jpg
Being C:\inetpub\…\Web\ the BaseDirectory, 1245 the ID passed as a QueryString parameter and avatar.jpg the resourceName. Did you see that ..\? Duh, we have to clean this out. Entering Path.GetFullPath. Normally it gets a full path for a given resource, I will not discuss here how this is done. Here we are using it for resolving the path and get rid of those annoying points.
Now, lets head to GetVirtualPath: simple, easy to understand. Nothing else to say. Maybe some doubt about why returning a virtual path beginning with ~? Only user interface should be able to translate a virtual path to its full URL. It has something to do with where the context of the request is made ![]()
The other two methods, GetNameFrom*, splits an existing path, probably generated with the Get*Path methods, so they can be easily isolated.
Everything about paths is under control in this class. And it even forces a good practice: standardize paths all over the application. The logic may change to more complex situations where different locations for different resource types are needed, but the architecture forces to think twice before implementing the building and splitting functionality outside these classes.