Web Controls and External Resources
This has come up on the DOTNET-WEB mailing list a couple times lately and I figured I should make an entry about it to help people out in the future. I was actually surprised that this wasn't covered in Fritz's book.
Unlike Windows control resources where it usually makes the most sense to package your icons, format strings, images, etc. into an assembly, Web controls have to deal with emitting URLs to their resources so that the browser can go fetch them. The ASP.NET people at Microsoft realized this and provided support for it within the System.Web.UI.Control class.
The problem:
You're about to write a control that needs to emit HTML that has an img element in it. How do you generate the value for the src attribute such that if the control is hosted on a web page located at the URL /foo.aspx or the URL /someApplication/bar.aspx the control still emits the proper URL?
The solution:
Luckily the ASP.NET developers at Microsoft were smart enough to catch this potential problem and they supplied the Control class with the TemplateSourceDirectory property and the ResolveUrl method.
Using these properties it is possible to solve the external resource problem in an elegant fashion that requires the same xcopy deployment as your control might. First, you can override the TemplateSourceDirectory property to return the following type of URL for your control: ~/Company/Controls/SomeControl. The ~ is a special indicator that tells ResolveUrl to prefix the rest of the supplied URL with the current HttpContext's HttpRequest::ApplicationPath value.
Example 1
If the control is hosted on a page /foo.aspx, then / is your ApplicationPath and a call to ResolveUrl for ~/Company/Controls/SomeControl would simply resolve to /Company/Controls/SomeControl.
Example 2
If the control is hosted on a page /someApplication/bar.aspx, where /someApplication is explicitly configured under IIS as a standalone appication, your ApplicationPath is now /someApplication and a call to ResolveUrl for ~/Company/Controls/SomeControl would resolve to /someApplication/Company/Controls/SomeControl.
Now, you might be tempted to forgo using the ~ in your TemplateSourceDirectory URL and instead make the control's resources global for the entire server on which it's installed. By all means this can be done by forcing the control's resources to be installed at the root site and returning a URL like /Company/Controls/SomeControl (note there's no ~ prefix). The major problem with this is versioning as sub applications might need to use different versions of the control and then you would be forced to come up with a versioning scheme for your control's URLs (i.e. /Company/Controls/SomeControl/v[Major].[Minor].[Build].[Revision]). This would also defeat the whole xcopy deployment scenario because now you can't just pick up a whole directory and move it somewhere to deploy the application. Instead you have to worry about moving the control's resources into an entirely different directory. As a final deterrent to using this method, consider the web farm scenario where the person that might be using your control doesn't have any control over the machine and can't get resources into the root application.
On a final note, in the above example scenario I used an img element's src attribute as a example where a URL might need to be resolved, but obviously this goes for any external resource (i.e. stylesheets, scripts, etc.). Also, since the System.Web.UI.Page class ultimately derives from Control, it also supports these features. When might this be useful? Well, consider the scenario where all pages in an application may derive from a custom base Page subclass which might render some portion of the HTML itself, this can be just as useful since the base Page subclass has no idea what the URL of it's subclasses may end up being.