Understanding Paths in ASP.NET
Date: Sep 12, 2003
A quick scan of the classes in the System.Web.* hierarchy reveals more than 30 methods that deal with paths and/or URLs. In this article, I will cover the more commonly used path and URL functions in ASP.NETpresenting what they do, how they work, and when to use them.
The best way to understand what each of these functions and properties do is to try them out and review the results. I have prepared a page that makes calls to the most commonly used path functions and properties in ASP.NET, which you can download from http://staff.develop.com/onion/pathsample.zip. I recommend that you download this file, unzip it, and place the contents in a virtual directory on a machine with ASP.NET installed. Try to access the pathsample.aspx page from a browser, inspect the results, and refer to your running page as you read the rest of this article.
In case you're not near a suitably equipped computer to run the page, Table 1 shows the results of running the test page. This page was run and expressions were all evaluated in the context of a page called client.aspx, responding to a request made to the following URL http://localhost/informit/subdir/pathsample.aspx/extra. The physical directory on the Web server corresponding to this virtual directory mapping was c:\mywebdirs\informit\subdir, and the top-level virtual directory for this application was called informit and was located at c:\mywebdirs\informit.
Table 1 Path Functions in ASP.NET and Their Results
Expression |
Evaluation |
this.TemplateSourceDirectory |
/informit/subdir |
Request.MapPath("log.txt") |
c:\mywebdirs\informit\subdir\log.txt |
this.MapPathSecure("log.txt") |
c:\mywebdirs\informit\subdir\log.txt |
Request.Path |
/informit/subdir/pathsample.aspx/extra |
Request.FilePath |
/informit/subdir/pathsample.aspx |
Request.CurrentExecutionFilePath |
/informit/subdir/pathsample.aspx |
Request.PathInfo |
/extra |
Request.PhysicalPath |
c:\mywebdirs\informit\subdir\pathsample.aspx |
Request.PhysicalApplicationPath |
c:\mywebdirs\informit\ |
Request.ApplicationPath |
/informit |
Request.Url |
http://localhost/informit/subdir/client.aspx/extra |
Request.RawUrl |
/informit/subdir/pathsample.aspx/extra |
Response.ApplyAppPathModifier("foo.aspx") |
/informit/subdir/foo.aspx |
this.ResolveUrl("~/client.aspx") |
/informit/pathsample.aspx |
Path Mapping
We start by looking at one of the most commonly used path methods in ASP.NET: MapPath. This useful function converts a virtual path into its corresponding physical path and is typically used to read or modify physical files on the server in the context of a Web request. It is available through the HttpRequest object, and for backward compatibility with classic ASP, it is also available through the HttpServerUtility object (accessed via the HttpContext or Page Server property). As an example of its use, suppose you wanted to write an entry into a log file when a page was accessed. Assuming that the log file is named log.txt and is located in the same directory as the .aspx page that is accessing it, you could write to the file with the following code:
// within a method of the Page class string logfile = Request.MapPath("log.txt"); using (StreamWriter sw = File.AppendText(logfile)) { sw.WriteLine("I was here at {0}", DateTime.Now); }
Notice in Table 1 that the call to MapPath with a string of log.txt resulted in the complete path c:\mywebdirs\informit\subdir\log.txt corresponding to the physical location of the virtual directory accessed in the request. In general, any time you call MapPath, it prefixes the string you pass in with the complete physical directory path to the current directory to which the request was dispatched. This is useful any time you need to access a physical file on disk that is placed in the same directory as your page, particularly for things such as loading XML files, writing to log files, and performing file manipulation.
Be aware that any time you access the file system from within an ASP.NET application, you are operating under the restrictive rights associated with the ASPNET account (or, in Windows Server 2003, NETWORK SERVICE). This account is specially created for ASP.NET on your machine, and by default is granted only User privileges. In a typical installation, this means that you cannot modify existing or create new files from within an ASP.NET page. If you find yourself wanting to use MapPath to modify or create files, the best solution is to grant the ASP.NET account write and modify privileges to that one subdirectory.
There is an additional function for performing virtual to physical path mapping in ASP.NET called MapPathSecure. This method, also of the HttpRequest class, differs only in the fact that it makes a File I/O security demand as you request the translation, but it is used internally in many of the ASP.NET methods. This demand is not the same as requesting whether the account has privileges to write to a file, but rather whether the code that is currently being executed has sufficient trust to perform file I/O (done through a code access security or CAS check). Most of the time, your ASP.NET code will be running with full privileges, so this function will always succeed. It is possible, however, in ASP.NET 1.1 to specify a lower trust level at which to run your application using the trust element in your configuration file (or more likely, for some third-party host environment to run your application at a lower level of trust). If this is a possibility for you, then it may make sense to call MapPathSecure instead of MapPath, as it will trigger a code access security exception sooner rather than later, giving your more flexibility in dealing with the failure.
There are two other potentially useful properties available in the Request object: PhysicalPath and PhysicalApplicationPath. The former is the complete physical path to the endpoint that was requested, and the latter is the complete physical path to the root of the application.
Root Path Reference Syntax
One of the most useful (and perhaps least-well-advertised) path-related features of ASP.NET is root path reference syntax (~). This syntax, supported in all path references in server-side control declarations, lets you add references to pages, controls, images, or other resources without hard-coding relative paths in your referring URLs. For example, suppose you wanted to reference an image file called happy.gif, located in an images subdirectory at the top of the virtual root of your application. If the page to which you wanted to add this reference was in another subdirectory of its own, you might end up with an image reference tag that looked like the following:
<img src="../images/happy.gif" />
Although using relative paths such as this will work, and it is in fact a common technique in traditional ASP pages, it is laborious and error-prone to update if the referring page ever changes location. Instead, ASP.NET supports root-relative path references using the tilde (~) character. So we could rewrite the image reference as follows:
<img runat="server" src="~/images/happy.gif" />
This new path reference to the happy.gif file would work from any file in the application, regardless of its relative location to the root directory. Note that this is possible only with server-side controls because ASP.NET must evaluate the path on the server and translate it to a relative path reference.
The following .aspx page shows several common uses for this syntax, including a user control source file reference, an anchor link, and an image file reference.
<%@ Page Language="C#" %> <%@ Register TagPrefix="uc" TagName="menu" Src="~/ctrls/menu.ascx" %> <html> <body> <form runat="server"> <uc:menu runat="server" /> <asp:HyperLink NavigateUrl="~/otherpages/hi.aspx" runat="server"> <asp:Image runat="server" ImageUrl="~/images/hi.gif"/> </asp:HyperLink> </form> </body> </html>
There may be occasions when you want to resolve a relative reference programmatically instead of declaratively. Unfortunately, there is no publicly accessible way to perform exactly the same resolution that the ~ character does for declarative path references. The closest you can come is to use the ResolveUrl method of the Control class, which will translate a ~ character in the string you pass it to a top-level reference to the name of the application. Although this should work correctly too in most cases, it is just curious that ASP.NET chose two different methods for resolving root-relative path references. The differences in the resolution between the two techniques are shown in Table 2.
Table 2 Differences Between Declarative versus Programmatic Path Resolution
|
Evaluation |
<asp:Image runat="server" ImageUrl="~/images/hi.gif" /> |
../images/hi.gif |
ResolveUrl("~/images/hi.gif") |
informit/images/hi.gif |
The following .aspx page shows a practical example of using the ResolveUrl method:
<%@ Page Language="c#" %> <script runat="server"> protected override OnLoad(EventArgs e) { _homeLink.NavigateUrl = ResolveUrl("~/default.aspx"); } </script> <html> <form runat="server"> <asp:HyperLink Runat="server" ID="_homeLink">home</asp:HyperLink> </form> </html>
The root path reference syntax can also be used in conjunction with the MapPath function described earlier. For example, suppose you had an XML file called foo.xml located in a subdirectory called data at the top of the application directory for your ASP.NET application. You could load that file into an XmlDocument from anywhere in the application using the following:
string foo = Request.MapPath("~/data/foo.xml"); XmlDocument dom = new XmlDocument(); dom.Load(foo); // use dom
URLs
Finally, there are a number of properties available to inspect (and potentially alter) the URL of the request that was made. The first place you should typically look for URL information is the Url property of the HttpRequest class. This property returns a reference to a class of type Uri. For each request, this property will be populated with all of the pieces of the URL of the request. In fact, although there are several other URL-related properties in the HttpRequest class, many of them are redundant with the information in the Url property. Here are most of the relevant properties of the Uri class with accompanying samples of what each property might look like:
public class Uri : MarshalByRefObject, ISerializable { // sample public string AbsolutePath { get; } // /foo/tst.aspx public string AbsoluteUri { get; } // http://www.foo.com/foo/tst.aspx?x=10 public string Authority { get; } // http://www.foo.com public string Fragment { get; } // public string Host { get; } // http://www.foo.com public UriHostNameType HostNameType { get; } // Dns public string LocalPath { get; } // /foo/tst.aspx public string PathAndQuery { get; } // /foo/tst.aspx?x=10 public int Port { get; } // 80 public string Query { get; } // ?x=10 public string Scheme { get; } // http //... }
Several other URL-related properties are available in the HttpRequest class, as shown in the first sampling of properties and methods in Table 1. The distinctions between most of them should be obvious from the results shown in the table, with the exception of CurrentExecutionFilePath. This property returns the current path even if you have performed a server-side transfer or execute. That is, if you are processing a request made to foo.aspx and within that request perform Server.Transfer("bar.aspx") then CurrentExecutionFilePath will correctly point to bar.aspx while FilePath will still point to foo.aspx.
Summary
In addition to all the path-related functions available in classic ASP, ASP.NET provides several new convenient path query and manipulation features. As you build your Web applications with ASP.NET, make sure that you look for an existing path function or property before you begin to write your own because you'll very often find that it has already been written for you.