GetEntryAssembly para aplicaciones web

Assembly.GetEntryAssembly () no funciona para aplicaciones web.

Pero … realmente necesito algo así. Trabajo con un código profundamente nested que se usa en aplicaciones web y no web.

Mi solución actual es buscar StackTrace para encontrar el primer conjunto llamado.

///  /// Version of 'GetEntryAssembly' that works with web applications ///  /// The entry assembly, or the first called assembly in a web application 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(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() == 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; } 

Para garantizar que el assembly es lo que queremos, tenemos 3 condiciones:

  • el assembly no está en el GAC
  • el assembly no es dynamic
  • el ensamblado no se genera (para evitar archivos asp.net temporales)

El último problema que encuentro es cuando la página base se define en un ensamblaje por separado. (Uso ASP.Net MVC, pero será lo mismo con ASP.Net). En ese caso particular, es ese ensamble separado el que se devuelve, no el que contiene la página.

Lo que estoy buscando ahora es:

1) ¿Son suficientes las condiciones de validación de mi ensamblaje? (Puede que haya olvidado casos)

2) ¿Hay alguna manera, desde un ensamblado generado por código dado en la carpeta temporal ASP.Net, de obtener información sobre el proyecto que contiene esa Página / Vista? (Creo que no, pero quién sabe …)

Esta parece ser una manera confiable y simple de obtener la “entrada” o el ensamblaje principal para una aplicación web.

Si coloca los controladores en un proyecto separado, puede encontrar que la clase base de ApplicationInstance no está en el mismo ensamblaje que su proyecto MVC que contiene las Vistas, pero esta configuración parece bastante rara (lo menciono porque lo he intentado configuración en un punto, y un tiempo atrás algunos blogs apoyaron la idea).

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

En mi caso, necesitaba obtener el “ensamblaje de entrada” para una aplicación web antes de que se inicialice System.Web.HttpContext.Current.ApplicationInstance. Además, mi código necesitaba funcionar para una variedad de tipos de aplicaciones (servicios de ventana, aplicaciones de escritorio, etc.), y no me gusta contaminar mi código común con problemas de la Web.

Creé un atributo de nivel de ensamblado personalizado, que se puede declarar en el archivo AssembyInfo.cs de un ensamblaje que desea designar como ensamblado de punto de entrada. Luego, simplemente llame al método GetEntryAssembly estático del atributo para obtener el ensamblado de entrada. Si Assembly.GetEntryAssembly devuelve un valor no nulo, se utiliza, de lo contrario, busca en ensamblajes cargados el que tiene el atributo personalizado. El resultado se guarda en caché en un Lazy .

 using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace EntryAssemblyAttributeDemo { ///  /// For certain types of apps, such as web apps,  /// returns null. With the , we can designate /// an assembly as the entry assembly by creating an instance of this attribute, /// typically in the AssemblyInfo.cs file. ///  /// [assembly: EntryAssembly] ///  ///  [AttributeUsage(AttributeTargets.Assembly)] public sealed class EntryAssemblyAttribute : Attribute { ///  /// Lazily find the entry assembly. ///  private static readonly Lazy EntryAssemblyLazy = new Lazy(GetEntryAssemblyLazily); ///  /// Gets the entry assembly. ///  /// The entry assembly. public static Assembly GetEntryAssembly() { return EntryAssemblyLazy.Value; } ///  /// Invoked lazily to find the entry assembly. We want to cache this value as it may /// be expensive to find. ///  /// The entry assembly. private static Assembly GetEntryAssemblyLazily() { return Assembly.GetEntryAssembly() ?? FindEntryAssemblyInCurrentAppDomain(); } ///  /// Finds the entry assembly in the current app domain. ///  /// The entry assembly. private static Assembly FindEntryAssemblyInCurrentAppDomain() { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); var entryAssemblies = new List(); 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().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(); } } } 

Como respuesta a mi propia pregunta (algunas personas aquí son realmente sensibles a la velocidad aceptada) => No encontré una manera mejor que el código proporcionado en la pregunta.

Eso significa que la solución no es perfecta, pero funciona siempre que su página base esté definida en el ensamblaje frontal.

El algoritmo propuesto en la pregunta realmente funcionó para mí, mientras que el método que usa System.Web.HttpContext.Current.ApplicationInstance no lo hizo. Creo que mi problema es que la antigua aplicación ASP.Net para la que necesito una solución carece de un controlador global.asax.

Esta solución más corta también funcionó para mí y, en general, creo que funcionará con la condición de que el controlador de página esté definido en el ensamblado frontal:

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

Mi aplicación es una aplicación de formularios web ASP.Net 4.x. Para este tipo de aplicación, HttpContext.Current.Handler es el módulo de código que contiene el punto de entrada del controlador de solicitud actual. Handler.GetType (). Assembly es un ensamblado temporal de ASP.Net, pero Handler.GetType (). BaseType.Assembly es el verdadero “ensamblado de entrada” de mi aplicación. Tengo curiosidad si lo mismo funciona para otros tipos de aplicaciones ASP.Net.

La única forma en que pude hacer que esto funcionara de forma consistente para las aplicaciones web (al menos en .NET 4.5.1) fue hacer Assembly.GetExecutingAssembly () en el proyecto de la aplicación web.

Si intenta crear un proyecto de utilidades con métodos estáticos y realiza la llamada allí, en su lugar obtendrá la información del ensamblado de ese ensamblado, tanto para GetExecutingAssembly () como para GetCallingAssembly ().

GetExecutingAssembly () es un método estático que devuelve una instancia del tipo de ensamblaje. El método no existe en una instancia de la clase de assembly en sí.

Entonces, lo que hice fue crear una clase que acepta el tipo de ensamblaje en el constructor, y creé una instancia de esta clase que pasa los resultados de Assembly.GetExecutingAssembly ().

  public class WebAssemblyInfo { Assembly assy; public WebAssemblyInfo(Assembly assy) { this.assy = assy; } public string Description { get { return GetWebAssemblyAttribute(a => a.Description); } } // I'm using someone else's idea below, but I can't remember who it was private string GetWebAssemblyAttribute(Func 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; } } } 

Y para usarlo

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