|
|||||||||||||||||||||||||
|
|||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionHave you ever wanted to break up that monolithic web project into multiple projects, or reuse those user controls and web forms in multiple web projects? Currently, reusing web forms and user controls in multiple ASP.NET projects requires copying the associated aspx and ascx pages. You can put web controls in separate assemblies, but you lose the design-time drag and drop that makes user controls and web forms so easy to create in the first place. If you've ever tried to put a user control or web form in a separate assembly, you probably ended up with a blank page or runtime errors. The reason is because What I wanted was the ability to dynamically call Would it be possible to embed those ascx and aspx files as assembly resources and then load them? The SolutionThe Virtual Path Provider in ASP.NET 2.0 can be used to load ascx files from a location of your choosing. For my purposes I've decided to store the ascx files in the assembly itself. No more outdated ascx pages that don't work with the updated assembly. Only one file to deploy, the assembly itself, and if you add the assembly as a reference, VS will copy it automatically! To do embed the ascx/aspx file into the assembly, you must change the Build Action of the file on the property page to Embedded Resource, the virtual path provider we create will do the rest. When a Virtual Path needs to be resolved, ASP.NET will ask the most recently registered Virtual Path Provider if the file exists, and if it does, it will call Before we can load a resource, we need to know what assembly the resource is located in and the name of the resource to load. I've chosen to encode this information into the virtual path. My final URL looks like this: ~/App_Resources/WebApplicationControls.dll/WebApplicationControls.WebUserControl1.ascx It's a bit lengthy, but it includes all the information I need. I don't want to intercept all URLs, so we need to be able to identify which URLs to process and which ones to let the default virtual path provider handle. To do this, I've chosen to process only URLs located in App_Resources. This folder doesn't exist, and that's the point as all paths at this location will be intercepted. The second part contains the assembly name, and the final part is the resource name, which includes the namespace. I've implemented the Virtual Provider as follows: public class AssemblyResourceProvider : System.Web.Hosting.VirtualPathProvider
{
public AssemblyResourceProvider() {}
private bool IsAppResourcePath(string virtualPath)
{
String checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.StartsWith("~/App_Resource/",
StringComparison.InvariantCultureIgnoreCase);
}
public override bool FileExists(string virtualPath)
{
return (IsAppResourcePath(virtualPath) ||
base.FileExists(virtualPath));
}
public override VirtualFile GetFile(string virtualPath)
{
if (IsAppResourcePath(virtualPath))
return new AssemblyResourceVirtualFile(virtualPath);
else
return base.GetFile(virtualPath);
}
public override System.Web.Caching.CacheDependency
GetCacheDependency( string virtualPath,
System.Collections.IEnumerable virtualPathDependencies,
DateTime utcStart)
{
if (IsAppResourcePath(virtualPath))
return null;
else
return base.GetCacheDependency(virtualPath,
virtualPathDependencies, utcStart);
}
}
class AssemblyResourceVirtualFile : VirtualFile
{
string path;
public AssemblyResourceVirtualFile(string virtualPath) : base(virtualPath)
{
path = VirtualPathUtility.ToAppRelative(virtualPath);
}
public override System.IO.Stream Open()
{
string[] parts = path.Split('/');
string assemblyName = parts[2];
string resourceName = parts[3];
assemblyName = Path.Combine(HttpRuntime.BinDirectory,
assemblyName);
System.Reflection.Assembly assembly =
System.Reflection.Assembly.LoadFile(assemblyName);
if (assembly != null)
{
return assembly.GetManifestResourceStream(resourceName);
}
return null;
}
}
All we need to do here is parse All we need to do now is tell ASP.NET to use our virtual path provider. This can be done in one of two places: Global.asax public static void AppInitialize() { ... }
The important thing is that it must be registered before any virtual paths trapped by our provider can be resolved. I chose to put this in the Application_Start, which looks like the following. protected void Application_Start(object sender, EventArgs e)
{
System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(
new AssemblyResourceProvider());
}
Now, we can load our custom controls into a protected void Page_Load(object sender, EventArgs e)
{
Control ctrl = LoadControl("/App_Resource/WebApplicationControls.dll/"+
"WebApplicationControls.WebUserControl1.ascx");
PlaceHolder1.Controls.Add(ctrl);
}
The URLs are a bit lengthy because they include information about the assembly and class name, but you can intercept any URL you want. I chose to prefix it with App_Resources to more easily identify what URLs to intercept. An alternate approach might be to define a Virtual Path Provider that uses reflection to search all assemblies in the bin directory for resources ending in ascx/aspx and intercept only those URLs with or without a namespace. Using the CodeThe demo project uses the Web application project update for VS 2005 which can be found here: MSDN.
|
||||||||||||||||||||||||