GetEntryAssembly для веб-приложений

Assembly.GetEntryAssembly() не работает для веб-приложений.

Но... Мне действительно нужно что-то подобное. Я работаю с некоторым глубоко вложенным кодом, который используется как в веб-приложениях, так и в не-веб-приложениях.

Мое текущее решение - просмотреть StackTrace, чтобы найти первую вызванную сборку.

/// <summary>
/// Version of 'GetEntryAssembly' that works with web applications
/// </summary>
/// <returns>The entry assembly, or the first called assembly in a web application</returns>
public static Assembly GetEntyAssembly()
{
    // get the entry assembly
    var result = Assembly.GetEntryAssembly();

    // if none (ex: web application)
    if (result == null)
    {
        // current method
        MethodBase methodCurrent = null;
        // number of frames to skip
        int framestoSkip = 1;


        // loop until we cannot got further in the stacktrace
        do
        {
            // get the stack frame, skipping the given number of frames
            StackFrame stackFrame = new StackFrame(framestoSkip);
            // get the method
            methodCurrent = stackFrame.GetMethod();
            // if found
            if ((methodCurrent != null)
                // and if that method is not excluded from the stack trace
                && (methodCurrent.GetAttribute<ExcludeFromStackTraceAttribute>(false) == null))
            {
                // get its type
                var typeCurrent = methodCurrent.DeclaringType;
                // if valid
                if (typeCurrent != typeof (RuntimeMethodHandle))
                {
                    // get its assembly
                    var assembly = typeCurrent.Assembly;

                    // if valid
                    if (!assembly.GlobalAssemblyCache
                        && !assembly.IsDynamic
                        && (assembly.GetAttribute<System.CodeDom.Compiler.GeneratedCodeAttribute>() == null))
                    {
                        // then we found a valid assembly, get it as a candidate
                        result = assembly;
                    }
                }
            }

            // increase number of frames to skip
            framestoSkip++;
        } // while we have a working method
        while (methodCurrent != null);
    }
    return result;
}

Чтобы обеспечить сборку, мы хотим, чтобы у нас было 3 условия:

  • сборка не находится в GAC
  • сборка не является динамической.
  • сборка не сгенерирована (чтобы избежать временных файлов asp.net

Последняя проблема, с которой я встречаюсь, - это когда базовая страница определена в отдельной сборке. (Я использую ASP.Net MVC, но это будет то же самое с ASP.Net). В этом конкретном случае это означает, что возвращается отдельная сборка, а не та, которая содержит страницу.

Теперь я ищу:

1) Достаточно ли условий проверки моей сборки? (Возможно, я забыл о случаях)

2) Есть ли способ из созданной сгенерированной кодом сборки в временной папке ASP.Net получить информацию о проекте, который содержит эту страницу/представление? (Думаю, нет, но кто знает...)

Ответ 1

Кажется, это надежный, простой способ получить "запись" или основную сборку для веб-приложения.

Если вы поместите контроллеры в отдельный проект, вы можете обнаружить, что базовый класс ApplicationInstance не находится в той же сборке, что и ваш проект MVC, который содержит Views, но эта настройка кажется довольно редкой (я упоминаю это, вы пробовали эту настройку в какой-то момент, а некоторое время назад несколько блогов поддержали идею).

    static private Assembly GetWebEntryAssembly()
    {
        if (System.Web.HttpContext.Current == null ||
            System.Web.HttpContext.Current.ApplicationInstance == null) 
        {
            return null;
        }

        var type = System.Web.HttpContext.Current.ApplicationInstance.GetType();
        while (type != null && type.Namespace == "ASP") {
            type = type.BaseType;
        }

        return type == null ? null : type.Assembly;
    }

Ответ 2

Как ответ на мой собственный вопрос (некоторые люди здесь очень обидны по принятому тарифу) = > Я не нашел лучшего способа, чем код, заданный в вопросе.

Это означает, что решение te не идеально, но оно работает так долго, как ваша базовая страница определена в интерфейсной сборке.

Ответ 3

В моем случае мне нужно было получить "сборку входа" для веб-приложения до того, как System.Web.HttpContext.Current.ApplicationInstance будет инициализирован. Кроме того, мой код должен был работать для различных типов приложений (оконные службы, настольные приложения и т.д.), И я не люблю загрязнять свой общий код веб-проблемами.

Я создал настраиваемый атрибут уровня сборки, который может быть объявлен в файле AssembyInfo.cs сборки, которую вы хотите назначить как сборку точки входа. Затем вы просто вызываете атрибут static GetEntryAssembly для получения сборки записи. Если Assembly.GetEntryAssembly возвращает ненулевое значение, которое используется, в противном случае он ищет через загруженные сборки для той, у которой есть пользовательский атрибут. Результат кэшируется в Lazy <T> .

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

namespace EntryAssemblyAttributeDemo
{
    /// <summary>
    /// For certain types of apps, such as web apps, <see cref="Assembly.GetEntryAssembly"/> 
    /// returns null.  With the <see cref="EntryAssemblyAttribute"/>, we can designate 
    /// an assembly as the entry assembly by creating an instance of this attribute, 
    /// typically in the AssemblyInfo.cs file.
    /// <example>
    /// [assembly: EntryAssembly]
    /// </example>
    /// </summary>
    [AttributeUsage(AttributeTargets.Assembly)]
    public sealed class EntryAssemblyAttribute : Attribute
    {
        /// <summary>
        /// Lazily find the entry assembly.
        /// </summary>
        private static readonly Lazy<Assembly> EntryAssemblyLazy = new Lazy<Assembly>(GetEntryAssemblyLazily);

        /// <summary>
        /// Gets the entry assembly.
        /// </summary>
        /// <returns>The entry assembly.</returns>
        public static Assembly GetEntryAssembly()
        {
            return EntryAssemblyLazy.Value;
        }

        /// <summary>
        /// Invoked lazily to find the entry assembly.  We want to cache this value as it may 
        /// be expensive to find.
        /// </summary>
        /// <returns>The entry assembly.</returns>
        private static Assembly GetEntryAssemblyLazily()
        {
            return Assembly.GetEntryAssembly() ?? FindEntryAssemblyInCurrentAppDomain();
        }

        /// <summary>
        /// Finds the entry assembly in the current app domain.
        /// </summary>
        /// <returns>The entry assembly.</returns>
        private static Assembly FindEntryAssemblyInCurrentAppDomain()
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            var entryAssemblies = new List<Assembly>();
            foreach (var assembly in assemblies)
            {
                // Note the usage of LINQ SingleOrDefault.  The EntryAssemblyAttribute AttrinuteUsage 
                // only allows it to occur once per assembly; declaring it more than once results in 
                // a compiler error.
                var attribute =
                    assembly.GetCustomAttributes().OfType<EntryAssemblyAttribute>().SingleOrDefault();
                if (attribute != null)
                {
                    entryAssemblies.Add(assembly);
                }
            }

            // Note that we use LINQ Single to ensure we found one and only one assembly with the 
            // EntryAssemblyAttribute.  The EntryAssemblyAttribute should only be put on one assembly 
            // per application.
            return entryAssemblies.Single();
        }
    }
}

Ответ 4

Алгоритм, предложенный в вопросе, действительно работал у меня, тогда как метод с использованием System.Web.HttpContext.Current.ApplicationInstance этого не сделал. Я думаю, что моя проблема в том, что приложение ASP.Net в старом стиле, для которого мне нужно решение, не имеет обработчика global.asax.

Это более короткое решение также сработало для меня, и я думаю, что обычно будет работать при условии, что обработчик страницы определен в интерфейсной сборке:

    private static Assembly GetMyEntryAssembly()
    {
      if ((System.Web.HttpContext.Current == null) || (System.Web.HttpContext.Current.Handler == null))
        return Assembly.GetEntryAssembly(); // Not a web application
      return System.Web.HttpContext.Current.Handler.GetType().BaseType.Assembly;
    }

Мое приложение представляет собой приложение ASP.NET 4.x для веб-форм. Для этого типа приложения HttpContext.Current.Handler - это модуль кода, содержащий точку входа текущего обработчика запросов. Handler.GetType(). Assembly - это временная сборка ASP.Net, но Handler.GetType(). BaseType.Assembly - это истинная "сборка" моего приложения. Мне любопытно, если то же самое работает для разных других типов приложений ASP.Net.

Ответ 5

Единственный способ, которым я смог заставить это работать последовательно для веб-приложений (по крайней мере, в .NET 4.5.1), должен был выполнить Assembly.GetExecutingAssembly() в самом проекте веб-приложения.

Если вы попытаетесь создать проект утилит со статическими методами и выполните там вызов, вы получите информацию о сборке из этой сборки - для GetExecutingAssembly() и GetCallingAssembly().

GetExecutingAssembly() - это статический метод, возвращающий экземпляр типа Assembly. Этот метод не существует в экземпляре самого класса Assembly.

Итак, что я сделал, был создан класс, который принимает тип Assembly в конструкторе и создал экземпляр этого класса, передающий результаты Assembly.GetExecutingAssembly().

    public class WebAssemblyInfo
    {
        Assembly assy;

        public WebAssemblyInfo(Assembly assy)
        {
            this.assy = assy;
        }

        public string Description { get { return GetWebAssemblyAttribute<AssemblyDescriptionAttribute>(a => a.Description); } }


         // I'm using someone else idea below, but I can't remember who it was
        private string GetWebAssemblyAttribute<T>(Func<T, string> value) where T : Attribute
        {
            T attribute = null;

            attribute = (T)Attribute.GetCustomAttribute(this.assy, typeof(T));

            if (attribute != null)
                return value.Invoke(attribute);
            else
                return string.Empty;
        }
    }
}

И использовать его

string Description = new WebAssemblyInfo(Assembly.GetExecutingAssembly()).Description;