web-dev-qa-db-ja.com

Webアプリケーション用のGetEntryAssembly

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一時フォルダー内の特定のコード生成アセンブリから、そのページ/ビューを含むプロジェクトに関する情報を取得する方法はありますか? (私はそうは思わないが、誰が知っている...)

46
Mose

これは、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;
    }
50
quentin-starin

私の場合、System.Web.HttpContext.Current.ApplicationInstanceが初期化される前に、Webアプリの「エントリアセンブリ」を取得する必要がありました。また、私のコードはさまざまな種類のアプリ(ウィンドウサービス、デスクトップアプリなど)で機能する必要があり、Webの問題で一般的なコードを汚染したくありません。

カスタムのアセンブリレベル属性を作成しました。これは、エントリポイントアセンブリとして指定するアセンブリのAssembyInfo.csファイルで宣言できます。次に、属性の静的なGetEntryAssemblyメソッドを呼び出して、エントリAssemblyを取得します。 Assembly.GetEntryAssemblyがnull以外を返す場合はそれが使用されます。それ以外の場合は、読み込まれたアセンブリを検索して、カスタム属性を持つアセンブリを探します。結果は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();
        }
    }
}
10
robertburke

私自身の質問への回答として(ここにいる一部の人々は、受け入れられたレートに本当に敏感です)=>質問で与えられたコードよりも良い方法を見つけられませんでした。

つまり、ソリューションは完全ではありませんが、ベースページがフロントエンドアセンブリで定義されている限り機能します。

4
Mose

質問で提案されたアルゴリズムは実際に機能しましたが、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アプリケーションタイプでも機能するかどうか知りたいです。

3

これを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;
0
Paul Berglund