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;