7

In my ASP.NET MVC app I'm using a small helper to iterate through all the controllers. This helper is located at a different assembly than my MVC app and I'm referencing to it.

The problem is, that when calling the Assembly.GetCallingAssembly() method in the helper, it doesn't returns the MVC app assembly, but it returns the helper assembly instead. This is not what I'm expecting to get, because all my controllers are living in the MVC app assembly and I need to reflect it.

The view code(MVC app assembly):

<nav>
   <ul id="menu">
      @foreach(var item in new MvcHelper().GetControllerNames())
      {
         @Html.ActionMenuItem(
              (string)HttpContext.GetGlobalResourceObject("StringsResourse", item), "Index",
              item)
      }
   </ul>
</nav>

The Helper code(independent assembly):

public class MvcHelper
{
    public  List<string> GetControllerNames()
    {
        var controllerNames = new List<string>();
        GetSubClasses<Controller>().ForEach(
            type => controllerNames.Add(type.Name));
        return controllerNames;
    }

    private static List<Type> GetSubClasses<T>()
    {
        return Assembly.GetCallingAssembly().GetTypes().Where(
            type => type.IsSubclassOf(typeof(T))).ToList();
    }
}

What am I doing wrong here?

hellow
  • 12,430
  • 7
  • 56
  • 79
Yair Nevet
  • 12,725
  • 14
  • 66
  • 108

3 Answers3

17

What am I doing wrong here?

Nothing. You are probably missing the fact that Razor views are compiled as separate assemblies by the ASP.NET runtime. Those assemblies are dynamic. They have nothing to do with your ASP.NET MVC application assembly. And since you are calling the helper in your view the Assembly.GetCallingAssembly() method will return something like this:

App_Web_fqxdopd5, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

If you want to get all controllers why not just loop through all referenced assemblies and look for types deriving from Controller? You could use the AppDomain.CurrentDomain.GetAssemblies() method for this. Then for each assembly just GetTypes() and filter upon:

public class MvcHelper
{
    private static List<Type> GetSubClasses<T>()
    {
        return AppDomain
            .CurrentDomain
            .GetAssemblies()
            .SelectMany(
                a => a.GetTypes().Where(type => type.IsSubclassOf(typeof(T)))
            ).ToList();
    }

    public List<string> GetControllerNames()
    {
        var controllerNames = new List<string>();
        GetSubClasses<Controller>().ForEach(
            type => controllerNames.Add(type.Name));
        return controllerNames;
    }
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
1

From the GetCallingAssembly MSDN docs:

Returns the Assembly of the method that invoked the currently executing method.

In your case, GetSubClasses is called by GetControllerNames in the same object so it should be returning the helper assembly.

Edit:

From the Remarks on the MSDN docs:

If the method that calls the GetCallingAssembly method is expanded inline by the just-in-time (JIT) compiler, or if its caller is expanded inline, the assembly that is returned by GetCallingAssembly may differ unexpectedly. For example, consider the following methods and assemblies:

Method M1 in assembly A1 calls GetCallingAssembly.

Method M2 in assembly A2 calls M1.

Method M3 in assembly A3 calls M2.

When M1 is not inlined, GetCallingAssembly returns A2. When M1 is inlined, GetCallingAssembly returns A3. Similarly, when M2 is not inlined, GetCallingAssembly returns A2. When M2 is inlined, GetCallingAssembly returns A3.

So assuming the GetSubClasses isn't inlined, it should be returning the Assembly which GetControllerNames belongs to.

M.Babcock
  • 18,753
  • 6
  • 54
  • 84
  • No, it won't be the helper assembly. The helper assembly is the current executing assembly. The calling assembly will be the one that that invoked this helper method. Which happens to be the assembly executing the Razor view. – Darin Dimitrov Feb 11 '12 at 17:13
  • Dear Darin, when debugging, the resultant assembly was the helper assembly and not the dynamic view assembly as you think... – Yair Nevet Feb 11 '12 at 17:16
  • Darin, take my words back, I did the GetCallingAssembly from the GetControllerNames and the assembly is: App_Web_5u2v2fi4, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null as you said. I will follow your instructions. I have to say also that M.Babcock help me to figure it out thanks to the fact I did call the method from another method in the helper. so thank you both! – Yair Nevet Feb 11 '12 at 17:25
  • The problem with this is that inlining is not predictable, so one can't use this method with certainty :S, surely? – nicodemus13 May 22 '14 at 11:38
  • Just came here an age late to say that GetCallingAssembly most likely will return garbage in async code for somewhat obvious reasons I had to spent a few minutes to realize. – Svend Sep 18 '19 at 08:43
0

I believe GetCallingAssembly is working - the method that calls GetSubClasses is within your MvcHelper module (and assembly) rather than the MVC app itself. If you invoke Assembly.GetCallingAssembly directly within GetControllerNames you may find you get a different result.

Also note that the behaviour of GetCallingAssembly can vary depending on whether methods are inlined or not - see http://msdn.microsoft.com/en-us/library/system.reflection.assembly.getcallingassembly.aspx

Roman Marusyk
  • 23,328
  • 24
  • 73
  • 116
kaj
  • 5,133
  • 2
  • 21
  • 18