如果存在某个属性,则加载程序集

本文关键字:加载 程序集 属性 存在 如果 | 更新日期: 2023-09-27 18:12:30

好吧,事情是这样的:

我想加载一个用户定义的Assembly到我的AppDomain但是我只想这样做,如果指定的Assembly匹配一些要求。在我的情况下,它必须具有Assembly级别的Attribute,我们可以称之为MandatoryAssemblyAttribute

在我看来有两条路可以走:

  1. 加载Assembly到我当前的AppDomain并检查Attribute是否存在。容易但不方便,因为我被困与加载的大会,即使它没有MandatoryAssemblyAttribute。不好。

  2. 我可以创建一个新的AppDomain并从那里加载Assembly,并检查我的旧MandatoryAddemblyAttribute是否存在。如果是,则转储已创建的AppDomain,并继续将Assembly加载到我的CurrentAppDomain中,如果不是,则转储新的AppDomain,告诉用户,并让他再试一次。

说起来容易做起来难。在网上搜索,我发现了一些关于如何做到这一点的例子,包括之前在SO中发布的问题:将dll加载到单独的AppDomain

我看到这个解决方案的问题是,你实际上必须知道类型(全名)在Assembly你想要加载开始。这不是我喜欢的解决方案。重点是尝试插入一个任意的Assembly,它匹配一些需求,并通过属性发现要使用的类型。事先不知道Assembly将有什么类型。当然,我可以要求任何以这种方式使用的Assembly都应该实现一些虚拟类,以便为CreateInstanceFromAndUnwrap提供一个"入口点"。我宁愿不。

同样,如果我继续在这一行做一些事情:

using (var frm = new OpenFileDialog())
{
    frm.DefaultExt = "dll";
    frm.Title = "Select dll...";
    frm.Filter = "Model files (*.dll)|*.dll";
    answer = frm.ShowDialog(this);
     if (answer == DialogResult.OK)
     {
          domain = AppDomain.CreateDomain("Model", new Evidence(AppDomain.CurrentDomain.Evidence));
          try
          {
              domain.CreateInstanceFrom(frm.FileName, "DummyNamespace.DummyObject");
                    modelIsValid = true;
          }
          catch (TypeLoadException)
          {
              ...
          }
          finally
          {
              if (domain != null)
                   AppDomain.Unload(domain);
          }
     }
}

这将正常工作,但如果然后我继续做以下操作:

foreach (var ass in domain.GetAssemblies()) //Do not fret, I would run this before unloading the AppDomain
    Console.WriteLine(ass.FullName); 

我得到一个FileNotFoundException。为什么?

我可以采取的另一个路径是这个:如何在单独的AppDomain加载DLL,但我也没有得到任何运气。每当我选择一些随机的。net Assembly时,我都会得到一个FileNotFoundException,除此之外,它违背了目的,因为我需要知道程序集的名称(不是文件名),以便加载它,这与我的要求不匹配。

是否有另一种方法来阻止MEF(我不是针对。net 3.5)?或者我是否坚持创建一些虚拟对象,以便通过CreateInstanceFromAndUnwrap加载Assembly ?如果是这样,为什么我不能迭代通过加载的程序集没有得到一个FileNotFoundException ?我做错了什么?

非常感谢您的建议。

如果存在某个属性,则加载程序集

我认为这个解决方案的问题是,你实际上必须知道程序集中的类型(全名)

那不太准确。您需要知道的是类型名是某个程序集,而不一定是您要检查的程序集。您应该在程序集中创建MarshalByRef类,然后使用CreateInstanceAndUnwrap从您自己的程序集中创建它的实例(而不是您试图检查的那个)。然后,该类将执行加载(因为它位于新appdomain中),并检查并向原始appdomain返回一个布尔值结果。

这里有一些代码可以让你开始。这些类放在您自己的程序集中(而不是您试图检查的程序集中):

第一个类用于创建考试AppDomain和创建MarshalByRefObject类的实例(见底部):

using System;
using System.Security.Policy;
internal static class AttributeValidationUtility
{
   internal static bool ValidateAssembly(string pathToAssembly)
   {
      AppDomain appDomain = null;
      try
      {
         appDomain = AppDomain.CreateDomain("ExaminationAppDomain", new Evidence(AppDomain.CurrentDomain.Evidence));
         AttributeValidationMbro attributeValidationMbro = appDomain.CreateInstanceAndUnwrap(
                              typeof(AttributeValidationMbro).Assembly.FullName,
                              typeof(AttributeValidationMbro).FullName) as AttributeValidationMbro;
         return attributeValidationMbro.ValidateAssembly(pathToAssembly);
      }
      finally
      {
         if (appDomain != null)
         {
            AppDomain.Unload(appDomain);
         }
      }
   }
}

这是MarshalByRefObject,它实际上将驻留在新的AppDomain中,并将对程序集进行实际检查:

using System;
using System.Reflection;
public class AttributeValidationMbro : MarshalByRefObject
{
   public override object InitializeLifetimeService()
   {
      // infinite lifetime
      return null;
   }
   public bool ValidateAssembly(string pathToAssembly)
   {
      Assembly assemblyToExamine = Assembly.LoadFrom(pathToAssembly);
      bool hasAttribute = false;
      // TODO: examine the assemblyToExamine to see if it has the attribute
      return hasAttribute;
   }
}

这可以很容易地通过使用托管程序集读取器来完成,例如monol . cecil。您可以检查程序集是否使用属性进行了装饰,而不需要在AppDomain中加载程序集,实际上根本不需要干扰AppDomain。例如,与Cecil:

bool HasMandatoryAttribute (string fileName)
{
    return AssemblyDefinition.ReadAssembly (fileName)
        .CustomAttributes
        .Any (attribute => attribute.AttributType.Name == "MandatoryAttribute");
}

这基本上是大多数插件系统所做的,因为创建一个AppDomain并将其拆除对于运行时来说是相当昂贵的。