One of the problems with ASP.Net when trying to keep a degree of separation between project components is that you can’t easily implement a page or a user control inside a library project and simply reference it from the main ASP.Net project.
This, I think, its not entirely by lack of vision or interest from the ASP.Net development team but instead by the complexity of the some issues of the technique like the possibility of overlapping file names (just like trying to mount 2 filesystems under the same folder), to name one.
This said, just let me add that my approach doesn’t solve that problem, but like most uncommon problems we are usually faced with uncommon manual steps and “check lists” before deploying. To a relatively small project of one with a nice amount of control over the libraries this can open nice possibilities specially for addin based development on ASP.Net. So, the code:
class AssemblyResourceVirtualFile : VirtualFile { protected string path; public AssemblyResourceVirtualFile(string virtualPath) : base(virtualPath) { path = VirtualPathUtility.ToAppRelative(virtualPath); } public override Stream Open() { string[] parts = path.Split('/'); string assemblyName = parts[2] + ".dll"; string resourceName = parts[3]; assemblyName = Path.Combine(HttpRuntime.BinDirectory, assemblyName); Assembly assembly = Assembly.LoadFile(assemblyName); if (assembly != null) return assembly.GetManifestResourceStream(resourceName); return null; } } 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); } }
So, as you can see my technique is based on VirtualPathProvider and in this case I implement a Assembly Resource based implementation. To properly use this you need to use
HostingEnvironment.RegisterVirtualPathProvider(new Web.Net.AssemblyResourceProvider());
on Application_Start of global.asax or App_Code folder as stated in the MSDN Page. There are some limitations, like you can’t include a global.asax or web.config file using this technique but you can read all of those in the MSDN Page… Still, it can reduce the number of files by a great number.
To use this code on runtime you can use Page.Load for user controls (.ascx) or type the correct url on the browser like this “~/App_Resource/AssemblyName/Namespace.Tests.aspx”. In order to correctly work on the browser, the Assembly name is the filename without the “.dll” – else IIS would try to run the dll and pass the rest as args.
Tags: ajax, ASP.Net, Embed, User Control
Hi Alexandre,
Interesting technique!
One thing to be aware of though, is
that use of custom VirtualPathProvider doesn’t work in Medium Trust hosting environments which means this technique will not work in a lot of web hosting because more and more of them enforce Medium
Trust these days.
I’m not sure but loading assemblies might also not work in Medium Trust, as use of Reflection is very limited in Medium Trust.
Still, its a useful technique if you have full
control of the server.
Best Regards,
Joe