Загрузка сборки при наличии определенного атрибута - PullRequest
6 голосов
/ 27 октября 2011

Хорошо, вот сделка:

Я хочу загрузить пользовательский Assembly в мои AppDomain, , но Я хочу сделать это, только если указано Assembly соответствует некоторым требованиям.В моем случае, он должен иметь, среди прочих требований, Assembly уровень Attribute, который мы могли бы назвать MandatoryAssemblyAttribute.

Есть два пути, которые я могу пройти, насколько я вижу:

  1. Загрузите Assembly в мой текущий домен приложений и проверьте, присутствует ли Attribute.Легко, но неудобно, так как я застрял с загруженной сборкой, даже если у нее нет MandatoryAssemblyAttribute.Не хорошо.

  2. Я могу создать новый AppDomain и загрузить оттуда Assembly и проверить, присутствует ли мой старый добрый MandatoryAddemblyAttribute.Если это так, сбросьте созданный AppDomain и продолжайте загружать Assembly в мой CurrentAppDomain, если нет, просто сбросьте новый AppDomain, сообщите пользователю и попросите его повторить попытку.

Легче сказать, чем сделать.Выполняя поиск в Интернете, я нашел несколько примеров того, как это сделать, включая предыдущий вопрос, опубликованный в SO: Загрузка DLL в отдельный домен приложений

Проблема, с которой я сталкиваюсьэто решение заключается в том, что вы на самом деле должны знать тип (полное имя) в 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.Почему?

Другой путь, по которому я мог бы пойти: Как загрузить DLL в отдельный домен приложений Но мне тоже не повезло.Я получаю FileNotFoundException всякий раз, когда выбираю какой-то случайный .NET Assembly, и кроме того, он отрицает цель, так как мне нужно знать имя сборки (не имя файла), чтобы загрузить его, что не соответствует моим требованиям.

Есть ли другой способ сделать этот запрет MEF (я не ориентируюсь на .NET 3.5)?Или я застрял с созданием какого-то фиктивного объекта для загрузки с Assembly по CreateInstanceFromAndUnwrap?И если да, то почему я не могу перебрать загруженные сборки, не получив FileNotFoundException?Что я делаю не так?

Большое спасибо за любые советы.

Ответы [ 2 ]

5 голосов
/ 27 октября 2011

Проблема, которую я вижу с этим решением, состоит в том, что вы действительно должны знать тип (полное имя) в сборке

Это не совсем точно. Вам нужно знать, что имя типа - это некая сборка, а не обязательно сборка, которую вы пытаетесь исследовать. Вы должны создать класс MarshalByRef в вашей сборке и затем использовать CreateInstanceAndUnwrap , чтобы создать его экземпляр из вашей собственной сборки (а не той, которую вы пытаетесь исследовать). Затем этот класс будет выполнять загрузку (поскольку он находится в новом домене приложения) и проверять и возвращать логический результат исходному домену приложения.

Вот код, который поможет вам. Эти классы идут в вашей собственной сборке (не той, которую вы пытаетесь исследовать):

Этот первый класс используется для создания экзаменационного 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 , который фактически будет жить в новом домене приложений и будет фактически проверять сборку:

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;
   }
}
3 голосов
/ 27 октября 2011

Это можно легко сделать с помощью управляемого считывателя сборок, например Mono.Cecil . Вы бы проверили, украшена ли сборка атрибутом, не загружая сборку в AppDomain и вообще не связываясь с AppDomains. Например, с Сесилом:

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

Это в основном то, что делают большинство подключаемых систем, так как создание домена AppDomain и его демонтаж довольно дороги для времени выполнения.

...