1
votes

So my scenarios a little funny but theres a reason for it.

I have a parent web application, called Parent, and a second web application called Child. Child is a virtual directory in IIS7 under Parent which is an application in IIS. Child is not a child directory of parent in the file system, only in IIS as a virtual directory. On application loading (Application_Start in global.asax) in the parent web application i tell it to load the child web dlls from childs bin folder using Assembly.LoadFrom() loading it into the app domain of Parent. Then when i try to visit /Child/Default.aspx I get an error saying:

Parser Error

Parser Error Message: Could not load type 'Child._Default'.

Now the Child.dll (the web dll containing childs code behind etc) is in the app domain of the parent application and i can successfully reflect it and its members from code behind in the Parent page Default.aspx.

Furthermore on the Child/Default.aspx if i change the Inherits="Child._Default" to Inherits="System.Web.UI.Page" and then in <% %> tags on the page enumerate the dlls in the app domain i can see Child.dll and reflect its members and invoke functions.

One thing that works is changing CodeBehind to CodeFile in the page directive. Then the page parses correctly. However this only works when the websites are in uncompiled, non published form.

1

1 Answers

2
votes

What's happening is the appdomain isn't looking within it's assembly list when it's trying to resolve the "Child" assembly for the page in the Child project.

What you need to do is use the AssemblyResolve event handler in the AppDomain. You can do so like this:

First we create and AssemblyLoader class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.IO;

namespace Parent
{
    internal class AssemblyLoader
    {
        private static List<AssemblyInformation> virtualDirectoryAssemblies = new List<AssemblyInformation>();
        private static readonly string virtualDirectoryBinFolderFormatString = "~/{0}/bin/";
        private static readonly string[] pathSplitParams = new string[1] { "\\" };
        private static readonly string[] assemblyNameSplitParams = new string[1] { "," };

        internal static Assembly AssemblyResolve(object sender, ResolveEventArgs e)
        {
            var name = e.Name.Split(assemblyNameSplitParams, StringSplitOptions.RemoveEmptyEntries).First();
            if (!virtualDirectoryAssemblies.Exists(a => a.Name.Equals(name)))
                return null;

            return Assembly.LoadFrom(virtualDirectoryAssemblies.Single(a => a.Name.Equals(name)).Path);
        }

        internal static void LoadVirtualDirectories(List<string> virtualDirectories)
        {
            foreach (var v in virtualDirectories)
            {
                var path = HttpContext.Current.Server.MapPath(string.Format(virtualDirectoryBinFolderFormatString, v));
                AppDomain.CurrentDomain.AppendPrivatePath(path);
                AppDomain.CurrentDomain.SetShadowCopyPath(path);

                var assemblies = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).ToList();
                foreach (var a in assemblies)
                {
                    var name = a.Split(pathSplitParams, StringSplitOptions.RemoveEmptyEntries).Last().Replace(".dll", string.Empty);
                    if(!virtualDirectoryAssemblies.Exists(i => i.Name.Equals(name)))
                    {
                        virtualDirectoryAssemblies.Add(new AssemblyInformation
                        {
                            Name = name,
                            Path = a
                        });
                    }
                }
            }
        }

        class AssemblyInformation
        {
            public string Name { get;set; }
            public string Path { get; set; }
        }
    }
}

In the web.config file for the Parent project I added this (if you have more virtual directories, the idea is to have a comma deliminated list):

<appSettings>
    <add key="VirtualDirectories" value="Child"/>
</appSettings>

In the web.config of the child project, you add this reference to the assembly Child assembly:

<system.web>
    <compilation>
        <assemblies>
            <add assembly="Child, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
        </assemblies>
    </compilation>
</system.web>

It can also be like this:

<system.web>
    <compilation>
        <assemblies>
            <add assembly="Child"/>
        </assemblies>
    </compilation>
</system.web>

Now, last but not least, we put this into the Global.asax:

    protected void Application_Start(object sender, EventArgs e)
    {
        AppDomain.CurrentDomain.AssemblyResolve += AssemblyLoader.AssemblyResolve;

        var virtualDirectories = 
            ConfigurationManager.AppSettings.Get("VirtualDirectories").Split(new string[1] { "," }, StringSplitOptions.RemoveEmptyEntries).ToList();

        AssemblyLoader.LoadVirtualDirectories(virtualDirectories);
    }

And we're done... :P