【问题标题】:GetEntryAssembly for web applications用于 Web 应用程序的 GetEntryAssembly
【发布时间】:2011-05-15 17:30:37
【问题描述】:

Assembly.GetEntryAssembly() 不适用于 Web 应用程序。

但是……我真的需要这样的东西。 我使用一些在 Web 和非 Web 应用程序中使用的深度嵌套代码。

我目前的解决方案是浏览 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 临时文件夹中的给定代码生成程序集获取有关包含该页面/视图的项目的信息? (我认为不是,但谁知道...)

【问题讨论】:

  • “入口程序集”在 ASP.NET 应用程序中没有任何意义,因为许多 ASP.NET 应用程序是在适当的时间串联执行代码的大量程序集。你到底想做什么?
  • 我完全同意你的观点,一个没有“入口程序集”的网络应用程序。事实上,我们可以认为它们有多个入口点。我最终需要的是在入口程序集中获取 AssemblyInfo.cs 文件中的内容。我为什么要这样做不是重点。
  • 我需要做一个类似的任务,通常找不到比这更好的方法。 ExcludeFromStackTraceAttribute 是你的班级吗?我似乎无法在 BCL 中找到它。与GetAttribute&lt;&gt; 相同的问题,这是您为方便起见而制作的方法吗?
  • 另外,IoC 在这个过程中投入了一把大扳手......
  • 似乎有一个更简单的解决方案(使用 HttpContext.ApplicationInstance,根据 cmets):stackoverflow.com/questions/756031/…

标签: c# reflection assemblies code-generation stack-frame


【解决方案1】:

这似乎是获取 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 是什么?
  • 它是您从 HttpApplication 继承的任何类,并使用 global.asax 中的 Inherits 属性指向。只是在我使用单独的控制器程序集完成的项目中,HttpApplication 派生类已进入控制器程序集而不是视图程序集。
  • 只为那些想知道的人:在 App_Start System.Web.HttpContext.Current.ApplicationInstance 为 NULL。
  • 对我来说,这只是返回 System.Web,而不是我想要的。
  • 我还遇到了在 System.Web.HttpContext.Current.ApplicationInstance 初始化之前需要“入口程序集”的情况。我下面的解决方案有效,并且不依赖于任何 System.Web 代码。
【解决方案2】:

在我的例子中,我需要在 System.Web.HttpContext.Current.ApplicationInstance 初始化之前获取 Web 应用程序的“入口程序集”。此外,我的代码需要适用于各种应用程序类型(窗口服务、桌面应用程序等),而且我不喜欢用 Web 问题污染我的通用代码。

我创建了一个自定义程序集级属性,可以在您要指定为入口点程序集的程序集的 AssembyInfo.cs 文件中声明该属性。然后,您只需调用属性的静态 GetEntryAssembly 方法即可获取条目程序集。如果 Assembly.GetEntryAssembly 返回非空值,则使用该值,否则它会在加载的程序集中搜索具有自定义属性的程序集。结果缓存在 Lazy 中。

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

【讨论】:

  • 实际上,这是唯一对我的 MVC 应用程序可靠工作的东西。我需要从依赖链深处的某个地方访问“主程序集”(用于日志记录,从主 dll 获取版本)并且没有 HttpContext 在应用程序中的任何地方都不可用,这是唯一的方法。感谢分享!
  • 这真的很棒。 Assembly.GetEntryAssembly() 和 HttpContext.Current 都可以为空,我需要一致性。我在做同样的事情,一个库在许多不同的应用程序类型之间共享,其中一些不是基于网络的,不应该有网络问题。此属性也使您可以非常清楚地了解意图。太棒了。
【解决方案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 表单应用程序。对于这种应用程序类型,HttpContext.Current.Handler 是包含当前请求处理程序入口点的代码模块。 Handler.GetType().Assembly 是一个临时的 ASP.Net 程序集,但 Handler.GetType().BaseType.Assembly 是我的应用程序真正的“入口程序集”。我很好奇这是否适用于其他各种 ASP.Net 应用程序类型。

【讨论】:

  • 在 MVC.NET 中,System.Web.HttpContext.Current.Handler 是由框架或 nuget 包提供的System.Web.Mvc.MvcHandler。所以,这似乎不适用于那种情况。
【解决方案4】:

我能够让 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;

【讨论】:

  • 这没有回答问题。
  • 当然可以 - 我是说您不能在 Web 应用程序中使用 GetEntryAssembly,您必须使用 GetExecutingAssembly。为了使静态方法起作用,您向 GetEntyAssembly() 添加一个参数 - 使其成为 GetEntyAssembly(Assembly assy),然后在方法中引用 assy。然后一切都会正常工作。
猜你喜欢
  • 2016-02-17
  • 2013-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-25
相关资源
最近更新 更多