获取Web应用程序的GetEntryAssembly

58

Assembly.GetEntryAssembly()无法在Web应用程序中使用。

但是... 我确实需要类似的功能。 我正在使用一些嵌套很深的代码,这些代码既可以在Web应用程序中使用,也可以在非Web应用程序中使用。

我目前的解决方案是浏览堆栈跟踪以查找第一个调用的程序集。

/// <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;
}
为确保程序集符合我们的要求,我们有以下三个条件:
  • 程序集不在GAC中
  • 程序集不是动态的
  • 程序集没有被生成(以避免临时的ASP.NET文件)
当基本页面定义在单独的程序集中时(我使用的是ASP.Net MVC,但在ASP.Net中情况相同),我遇到的最后一个问题是返回的是单独的程序集,而不是包含页面的那个程序集。
现在我正在寻找以下信息:
1)我的程序集验证条件是否足够?(我可能忘记了一些情况)
2)是否有一种方法可以从ASP.Net临时文件夹中的某个代码生成的程序集中获取有关包含该页面/视图的项目的信息?(我认为不行,但谁知道呢……)

1
“Entry assembly”在ASP.NET应用程序中并没有实际意义,因为许多ASP.NET应用程序是由大量协同工作的程序集,在适当的时间执行代码。您实际上想要做什么? - Levi
我完全同意你的观点,一个Web应用程序没有“入口程序集”。实际上,我们可以认为它们有多个入口点。我最终需要的是获取AssemblyInfo.cs文件中入口程序集中的内容。我为什么要这样做并不是重点。 - Mose
1
我需要完成一个类似的任务,通常情况下找不到比这更好的方法。ExcludeFromStackTraceAttribute 是你的一个类吗?我在 BCL 中找不到它。同样的问题也出现在 GetAttribute<> 上,这是你为了方便而创建的方法吗? - quentin-starin
此外,IoC 对这个过程造成了很大的干扰... - quentin-starin
似乎有一个更简单的解决方案(根据评论使用HttpContext.ApplicationInstance):https://dev59.com/THRA5IYBdhLWcg3w_DHF#4803419 - quentin-starin
假设您已经在AssemblyInfo.cs中查找信息,可能是属性,我认为我的解决方案更加优雅且保证可行,它涉及自定义的程序集级别属性。 - robertburke
4个回答

60

这似乎是一种可靠且简单的方式,用于获取Web应用程序的“入口”或主要程序集。

如果你将控制器放在一个单独的项目中,你可能会发现ApplicationInstance的基类不在包含视图的MVC项目相同的程序集中 - 但是,这种设置似乎相当罕见(我提及它是因为我曾经尝试过这种设置,在一段时间以前也有一些博客支持这种想法)。

    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;
    }

好主意!如果您将控制器放在单独的程序集中,我想知道ApplicationInstance是什么? - Mose
1
它是从HttpApplication继承的任何类,并在global.asax中使用Inherits属性指向的类。只是在我用单独的控制器程序集完成的项目中,HttpApplication派生类放在控制器程序集而不是视图程序集中。 - quentin-starin
4
只是为了那些想知道的人:在 App_Start 时,System.Web.HttpContext.Current.ApplicationInstance 为 NULL。 - Stefan Steiger
2
对我来说,这只返回了System.Web,而不是我要找的内容。 - Berend Engelbrecht
我也遇到了需要在System.Web.HttpContext.Current.ApplicationInstance初始化之前获取“entry assembly”的情况。我的解决方案如下,不依赖于任何System.Web代码。 - robertburke

18

在我的情况下,我需要在System.Web.HttpContext.Current.ApplicationInstance初始化之前获取Web应用程序的“入口程序集”。此外,我的代码需要适用于各种应用程序类型(如Windows服务、桌面应用程序等),而且我不喜欢用Web相关内容污染我的通用代码。

我创建了一个自定义的程序集级别属性,可以在想要指定为入口点程序集的程序集的AssembyInfo.cs文件中声明。然后,只需调用该属性的静态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's 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();
        }
    }
}

2
这实际上是我 MVC 应用程序中唯一可靠的工作方式。我需要从依赖链的深处某个地方访问“主程序集”(用于记录日志,获取主 DLL 的版本),而且由于 HttpContext 并不在应用程序的所有位置都可用,因此这是唯一的方法。感谢分享! - Yuri Makassiouk
这真的很棒。Assembly.GetEntryAssembly()和HttpContext.Current都可能为空,而我需要一致性。我正在做同样的事情,一个库在许多不同的应用程序类型之间共享,其中一些不是基于Web的,也不应该有Web方面的问题。这个属性允许你非常清楚地表达意图。太棒了。 - Casey Plummer

3

这个问题中提出的算法对我确实有效,而使用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 Web Forms 应用程序。对于这种应用程序类型,HttpContext.Current.Handler 是包含当前请求处理程序入口点的代码模块。Handler.GetType().Assembly 是一个临时 ASP.Net 程序集,但是 Handler.GetType().BaseType.Assembly 才是我的应用程序的真正“入口程序集”。我想知道在其他各种 ASP.Net 应用程序类型中是否也适用这个规则。


1
еңЁMVC.NETдёӯпјҢSystem.Web.HttpContext.Current.HandlerжҳҜз”ұжЎҶжһ¶жҲ–NuGetеҢ…жҸҗдҫӣзҡ„System.Web.Mvc.MvcHandlerгҖӮеӣ жӯӨпјҢзңӢиө·жқҘе®ғеҜ№дәҺйӮЈз§Қжғ…еҶөдёҚиө·дҪңз”ЁгҖӮ - binki

0

我能保证这种方法在Web应用程序中(至少在.NET 4.5.1中)工作一致的唯一途径是在Web应用程序项目本身中执行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's 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;

1
那并没有回答问题。 - Mose
当然可以 - 我的意思是在 Web 应用程序中不能使用 GetEntryAssembly,必须使用 GetExecutingAssembly。为了使静态方法正常工作,您需要向 GetEntyAssembly() 添加一个参数 - 将其变为 GetEntyAssembly(Assembly assy),然后在方法中引用 assy。这样一切都会正常工作。 - Paul Berglund

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接