57

Assembly.GetEntryAssembly() does not work for web applications.

But... I really need something like that. I work with some deeply-nested code that is used in both web and non-web applications.

My current solution is to browse the StackTrace to find the first called assembly.

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

To ensure the assembly is what we want, we have 3 conditions :

  • the assembly is not in the GAC
  • the assembly is no dynamic
  • the assembly is not generated (to avoid temporary asp.net files

The last problem I meet is when the base page is defined in a separate assembly. (I use ASP.Net MVC, but it'll be the same with ASP.Net). In that particular case, it's that separate assembly that is returned, not the one containing the page.

What I am looking for now is :

1) Are my assembly validation conditions enough ? (I may have forgotten cases)

2) Is there a way, from a given code-generated assembly in the ASP.Net temporary folder, to get information about the project that contains that Page / View ? (I think not, but who knows...)

Mose
  • 1,781
  • 3
  • 16
  • 35
  • 1
    "Entry assembly" doesn't really have any meaning in an ASP.NET application, as many ASP.NET applications are a ton of assemblies working in tandem executing code at the appropriate times. What are you actually trying to do? – Levi Nov 27 '10 at 10:42
  • I totally agree with you, a web application as no "entry assembly". In fact, we can consider they are have multiple entry points. What I ultimately need is to get what's in the AssemblyInfo.cs file, in the entry assembly. Why I want to do that is not the point here. – Mose Dec 01 '10 at 13:00
  • 1
    I need to do a similar task, and couldn't find a better way than this, generally. Is `ExcludeFromStackTraceAttribute` a class of yours? I can't seem to find it in the BCL. Same question with `GetAttribute<>`, is that a method you made for convenience? – quentin-starin Jul 19 '11 at 19:38
  • Also, IoC throws a big wrench into this process... – quentin-starin Jul 19 '11 at 20:41
  • It seems there is a simpler solution (using HttpContext.ApplicationInstance, per the comments): http://stackoverflow.com/questions/756031/using-the-web-application-version-number-from-an-assembly-asp-net-c/4803419#4803419 – quentin-starin Jul 19 '11 at 21:08
  • Given that you are already looking for info in AssemblyInfo.cs, presumably attributes, I think my solution below, which involves a custom assembly-level attribute, is more elegant and guaranteed to work. – robertburke Oct 26 '16 at 18:38

4 Answers4

60

This seems to be a reliable, simple way to get the "entry" or main assembly for a web app.

If you put controllers in a separate project, you may find that the base class of ApplicationInstance is not in the same assembly as your MVC project that contains the Views - but, this setup seems pretty rare (I mention it because I've tried this setup at one point, and a while back a few blogs supported the 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;
    }
quentin-starin
  • 26,121
  • 7
  • 68
  • 86
  • Nice idea ! I wonder what is the ApplicationInstance if you put controllers in a separate assembly ? – Mose Jul 20 '11 at 07:44
  • 1
    It is whatever class you have inherit from HttpApplication, and point to with the Inherits attribute in global.asax. It's just that in the projects I've done with a separate controllers assembly, the HttpApplication derived class has gone in the controllers assembly rather than the views assembly. – quentin-starin Jul 20 '11 at 14:57
  • 4
    Just for those who wonder: On App_Start System.Web.HttpContext.Current.ApplicationInstance is NULL. – Stefan Steiger Jul 03 '13 at 06:52
  • 2
    For me this just returns System.Web, not what I was looking for. – Berend Engelbrecht Nov 25 '15 at 12:27
  • I also ran into the situation where I needed the "entry assembly" before System.Web.HttpContext.Current.ApplicationInstance is initialized. My solution below works, and does not rely upon any System.Web code. – robertburke Oct 26 '16 at 18:41
18

In my case, I needed to get the "entry assembly" for a web app before System.Web.HttpContext.Current.ApplicationInstance is initialized. Also, my code needed to work for a variety of app types (window services, desktop apps, etc), and I don't like to pollute my common code with Web concerns.

I created a custom assembly-level attribute, which can be declared in the AssembyInfo.cs file of an assembly which you want to designate as the entry point assembly. Then, you just call the attribute's static GetEntryAssembly method to get the entry assembly. If Assembly.GetEntryAssembly returns non-null, that is used, otherwise it searches through loaded assemblies for the one with the custom attribute. The result is cached in a 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();
        }
    }
}
robertburke
  • 511
  • 1
  • 5
  • 11
  • 2
    This was the only thing that worked reliably for my MVC application, actually. I needed access to "main assembly" from somewhere deep down the chain of dependencies (for logging, to obtain the version from the main dll) and without HttpContext not being available everywhere in the application, this was the only way to go. Thanks for sharing! – Yuri Makassiouk Jun 02 '17 at 11:39
  • This is really great. Both Assembly.GetEntryAssembly() and HttpContext.Current can be null, and I need consistency. I'm doing the same thing, one library shared among many different app types, some of which are not web-based and shouldn't have an web concerns. This attribute allows you to be very clear about intent as well. Bravo. – Casey Plummer Feb 08 '19 at 18:53
3

The algorithm proposed in the question did indeed work for me, whereas the method using System.Web.HttpContext.Current.ApplicationInstance didn't. I think my problem is that the old-style ASP.Net application for which I need a solution lacks a global.asax handler.

This shorter solution also worked for me and I think will generally work on the condition that the page handler is defined in the front-end assembly:

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

My application is an ASP.Net 4.x web forms application. For this application type, HttpContext.Current.Handler is the code module containing the entry point of the current request handler. Handler.GetType().Assembly is a temporary ASP.Net assembly, but Handler.GetType().BaseType.Assembly is the true "entry assembly" of my application. I am curious if the same works for various other ASP.Net application types.

Berend Engelbrecht
  • 1,420
  • 15
  • 11
  • 1
    In MVC.NET, `System.Web.HttpContext.Current.Handler` is a `System.Web.Mvc.MvcHandler` which is provided either by the framework or a nuget package. So, it seems this doesn’t work for that situation. – binki Feb 01 '16 at 16:56
0

The only way I was able to get this to work consistently for Web Applications (at least in .NET 4.5.1) was to do the Assembly.GetExecutingAssembly() in the Web Application project itself.

If you try to create a utilities project with static methods and do the call there, you will get the assembly information from that assembly instead - for both GetExecutingAssembly() and GetCallingAssembly().

GetExecutingAssembly() is a static method that returns an instance of the Assembly type. The method does not exist on an instance of the Assembly class itself.

So, what I did was created a class that accepts Assembly type in the constructor, and created an instance of this class passing the results from 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;
        }
    }
}

And to use it

string Description = new WebAssemblyInfo(Assembly.GetExecutingAssembly()).Description;
  • 1
    That does not answer the question. – Mose Apr 18 '16 at 12:07
  • Sure it does - I'm saying you can't use GetEntryAssembly in a web application, you have to use GetExecutingAssembly. For the static method to work, you add a parameter to the GetEntyAssembly() - make it GetEntyAssembly(Assembly assy) and then reference assy in the method. Everything will work properly then. – Paul Berglund Jul 08 '16 at 15:20