views:

311

answers:

3

I'm trying to write a plug-in system where assemblies can be dropped in a folder that ASP.NET has no knowledge about. This plug-in system works fine for ASP.NET MVC based assemblies, but for old-school WebForm assemblies (where the .aspx files Inherits the System.Web.UI.Page derived classes) System.Web.Compilation.BuildManager is responsible for compiling the .aspx file into a dynamic assembly.

My problem is that the BuildManager knows nothing about the assemblies within my plug-in folder and it seems to be absolutely nothing I can do to help it. If I do:

BuildManager.GetType("PluginAssembly.DefinedType", true, true)

it throws. If I first get a reference to the Type and then try:

var instance = BuildManager.CreateInstanceFromVirtualPath(path, type);

it still throws, even though I've now passed in the specific type it needs to compile the .aspx file. Is there anything I can do to help BuildManager find the types it needs to compile the .aspx file?

Update: I've come one step further, by looking into what BuildManager.GetType() actually does. By specifying the assembly the type is defined in (such as "PluginAssembly.DefinedType, PluginAssembly") and then hooking myself onto the System.AppDomain.CurrentDomain.AssemblyResolve event, I can now find the plug-in assembly and return it so BuildManager can successfully construct the type. This makes the following work with flying colors:

BuildManager.GetType("PluginAssembly.DefinedType, PluginAssembly", true, true)

However, this still fails:

var instance = BuildManager.CreateInstanceFromVirtualPath(path, type);

Even though the .aspx file now has the same assembly reference in its Inherits directive:

<%@ Page Language="C#"              
         CodeBehind="Index.aspx.cs"
         Inherits="PluginAssembly.DefinedType, PluginAssembly" %>

The error I receive is:

"Compiler Error Message: CS0234: The type or namespace name 'DefinedType' does not exist in the namespace 'PluginAssembly' (are you missing an assembly reference?)" with the following source output:

Line 205:
Line 206:    [System.Runtime.CompilerServices.CompilerGlobalScopeAttribute()]
Line 207:    public class plugins_pluginassembly_dll_index_aspx
                 : global::PluginAssembly.DefinedType,
                   System.Web.SessionState.IRequiresSessionState, 
                   System.Web.IHttpHandler {
Line 208:        
Line 209:        private static bool @__initialized;

It seems like what happens inside BuildManager.CreateInstanceFromVirtualPath() involves a certain System.Web.Util.IWebObjectFactory that it might be responsible for throwing this exception by not finding my assembly. I can implement this interface without any problems, but what does that help if I can't tell the BuildManager about it?

A: 

I see two ways you can specify the assemblies used to compile a page :

  • Calling BuildManager.AddReferencedAssembly (but I assume you already tried that one ?)
  • Putting in the compiling page's virtual directory configuration a list of the required assemblies (in the system.web/compilation/assemblies section) and having these assemblies accessible in the appdomain (the framework seems to use Assembly.Load to find assemblies found in the config files).
Shtong
`BuildManager.AddReferencedAssembly` throws with "This method can only be called during the application's pre-start initialization stage" and modifying web.config isn't a viable option as that requires the application to restart and the modules to be pre-known which isn't always the case.
asbjornu
A: 

I don't know how BuildManager loads the types, but you could try using AssemblyResolve - subscribe to the AppDomain.CurrentDomain.AssemblyResolve event, and load the assembly yourself and return (yes, return) the Assembly instance (or null if you don't recognise it).

Not all such code uses approaches compatible with this, but it is worth a try.

Marc Gravell
I've already done that and it works for resolving the assembly when compiling the .aspx.cs file, but when the .aspx file is compiled it references `global::PluginAssembly.DefinedType` which doesn't provide any means to resolve any assembly as the BuildManager assumes the assembly already exists in the app domain.
asbjornu
A: 

I ended up solving this with Web Deployment Projects [1] by pre-compiling the whole web application into two separate assemblies and then digging into the right assembly with Assembly.GetTypes() to instantiate the correct Page for the given HTTP request.

It puts more on the shoulders of the plug-in developers, but yields better performance with the added benefit of having all plug-ins completely verified by the ASP.NET compiler before they're executed in a (security sensitive and fragile) web context.

asbjornu