3 votes

Les types ne correspondent pas lors de l'utilisation de Activator.CreateInstanceFrom pendant le débogage ASP.NET

Dans le cadre d'un projet sur lequel je travaille, je crée un fichier AppDomain et lui demande de charger l'assemblage en cours d'exécution. J'invoque ensuite Activator.CreateInstanceFrom pour créer une instance marshallisée d'une classe interne (de type MyType ) en tant que tel (et vous pouvez voir qu'il utilise également le même emplacement d'assemblage) :

        ObjectHandle handle = Activator.CreateInstanceFrom(
            Factory.operatingDomain,
            Assembly.GetExecutingAssembly().Location,
            typeof(MyType).FullName,
            false,
            BindingFlags.NonPublic | BindingFlags.Instance,
            null,
            new object[] { config },
            null,
            null);

Tout fonctionne correctement lorsque je teste cet assemblage à l'unité, mais lorsque je l'utilise dans un environnement de débogage ASP.NET, il génère une erreur de type MissingMethodException . J'ai pris le temps de créer ma propre classe qui étend le concept de Binder pour voir ce qui se passe. La seule différence semble être que dans l'environnement de débogage, le paramètre passé ( config ) tire son type de l'assemblage situé dans le répertoire habituel, tandis que l'élément ParameterInfo obtient son type de l'assembly situé dans le dossier temporaire ASP.NET.

Même si j'ai réussi typeof(config).Assembly.Location J'obtiendrais le même résultat. Il s'agit donc de quelque chose d'interne au cadre.

Y a-t-il quelque chose que je puisse faire pour corriger cela, à part conserver un classeur personnalisé accessible en interne et destiné à une utilisation de niche, qui renvoie la valeur "true" lorsque les types de paramètres correspondent conformément à la norme FullName ?

MISE À JOUR

J'ai essayé de passer l'emplacement de l'assemblage tel qu'il est pris dans les assemblages chargés du domaine de l'application et toujours pas de dés. Mon binder personnalisé n'a pas fonctionné non plus - il m'a fallu implémenter Binder.ChangeType où j'ai découvert que je ne pouvais même pas passer d'un type à l'autre (même si, comme indiqué, la seule différence entre eux est l'emplacement de l'assemblage).

MISE À JOUR 2

J'ai essayé le code suivant pour m'assurer que le domaine d'application chargeait l'assemblage approprié à partir de l'emplacement adéquat :

        if (binPath != String.Empty)
        {
            Factory.operatingDomain.SetData("assemblyLocation", Assembly.GetExecutingAssembly().Location);

            Factory.operatingDomain.DoCallBack(() =>
            {
                String location = AppDomain.CurrentDomain.GetData("assemblyLocation").ToString();
                String filename = System.IO.Path.GetFileName(location);
                List<String> paths = AppDomain.CurrentDomain.RelativeSearchPath.Split(';').ToList();

                foreach (String path in paths.ToArray())
                {
                    paths.Remove(path);
                    paths.AddRange(System.IO.Directory.GetFiles(path, filename));
                }

                Assembly.LoadFrom(paths[0]);
            });
        }

Et c'est vrai ! Il a chargé le bon assemblage ! Mais ici, juste un peu plus tard :

String location = Factory.operatingDomain
                         .GetAssemblies()
                         .Single(a => a.FullName == Assembly.GetExecutingAssembly().FullName)
                         .Location;

Ceci renvoie le chemin du dossier temporaire .NET. Ce n'est pas correct. Je considère qu'il s'agit d'un bogue dans le cadre de travail !

1voto

tuespetre Points 909

Le cadre .NET ne semble pas résoudre correctement les emplacements d'assemblages lorsque vous demandez à un domaine d'application l'un de ses emplacements d'assemblages. Pour contourner ce problème, j'ai dû rechercher l'assemblage moi-même :

        // This code I was already using:
        String binPath = String.Empty;

        if (!String.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath))
        {
            String[] paths = AppDomain.CurrentDomain.RelativeSearchPath.Split(';');

            for (var i = 0; i < paths.Length; i++)
            {
                paths[i].Remove(0, AppDomain.CurrentDomain.BaseDirectory.Length);
            }

            binPath = String.Join(";", paths);
        }

        Factory.operatingDomain = AppDomain.CreateDomain("my_remote_domain", null,
            new AppDomainSetup
            {
                ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
                // Sometimes, like in a web app, your bin folder is not the same
                // as the base dir.
                PrivateBinPath = binPath
            });

        // The new code that solved my problem begins here:
        if (binPath != String.Empty)
        {
            Factory.operatingDomain.SetData("assemblyLocation", Assembly.GetExecutingAssembly().Location);

            Factory.operatingDomain.DoCallBack(() =>
            {
                String location = AppDomain.CurrentDomain.GetData("assemblyLocation").ToString();
                String filename = System.IO.Path.GetFileName(location);
                List<String> paths = AppDomain.CurrentDomain.RelativeSearchPath.Split(';').ToList();

                foreach (String path in paths.ToArray())
                {
                    paths.Remove(path);
                    paths.AddRange(System.IO.Directory.GetFiles(path, filename));
                }

                Assembly.LoadFrom(paths[0]);

                AppDomain.CurrentDomain.SetData("assemblyLocation", paths[0]);
            });

            Factory.realAssemblyLocation = Factory.operatingDomain.GetData("assemblyLocation").ToString();
        }
        else
        {
            Factory.operatingDomain.Load(Assembly.GetExecutingAssembly().FullName);
        }

Ensuite, lorsque j'essaie de créer des instances d'un objet distant, je fais ceci :

        String location = Factory.realAssemblyLocation ?? Assembly.GetExecutingAssembly().Location;

        ObjectHandle handle = Activator.CreateInstanceFrom(
            Factory.operatingDomain,
            location,
            typeof(MyType).FullName,
            false,
            BindingFlags.NonPublic | BindingFlags.Instance,
            null,
            new object[] { config },
            null,
            null);

MISE À JOUR 4/16/2014

Il s'avère que tout ce que j'ai vraiment a dû faire était de définir l'ApplicationBase du nouvel AppDomain au répertoire qui contenait l'assembly en cours d'exécution. Beaucoup plus simple :)

1voto

Zverev Evgeniy Points 2705

Quelle est votre demande ? Une application ASP.NET ? Si c'est le cas, vous devez être au courant de l'existence d'une bête nommée assembly shadow copy (voir http://msdn.microsoft.com/en-us/library/ms404279.aspx ). Cette chose est utilisée par défaut en ASP.NET. En bref, lorsque vous utilisez un assemblage situé dans le dossier bin avec l'assemblage de l'application ASP.NET, il est copié dans le dossier temporaire d'ASP.NET et chargé à partir de là.

Pour vous assurer que c'est ce qui provoque le comportement que vous expérimentez, essayez de placer votre assemblage qui définit le type de paramètre "config" dans un dossier séparé et chargez l'assemblage en attrapant l'événement AppDomain_AssemblyResolve ou chargez le manuellement avant de l'utiliser. De cette façon, vous négligerez le comportement de la copie fantôme de l'assemblage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X