3

Visual Studio recommended I install the new Microsoft.CodeAnalysis.FxCopAnalyzers in my project. It nicely identifies a number of improvements in the source, but one of its recommendations is:

CA1822: Member Application_Start does not access instance data and can be marked as static (Shared in `VisualBasic`).

I examined the routine and sure enough, FxCopAnalyzer was right. So I changed:

protected void Application_Start()

to

protected static void Application_Start()

However, when I make that change, I now get this:

HTTP Error 403.14 - Forbidden

The Web server is configured to not list the contents of this directory.

If I take out "static", it works again. But I'm a little confused why the framework cares if this method is static.

Community
  • 1
  • 1
Glenn
  • 175
  • 1
  • 7
  • Why? because there are a lot of times this is acceptable. Why does your program break, because obviously `Application_Start` is not one of the cases, and cannot be static. You will need to "*Suppress code analysis warnings*". – TheGeneral Oct 28 '19 at 02:42
  • I've already figured out it doesn't work with static in there, so when you answer "because it doesn't work", that doesn't really address why it doesn't work. – Glenn Oct 28 '19 at 02:55

2 Answers2

4

This was such an interesting question. It took me a while to understand the Asp.Net bootstrapping pipeline. I will not go into detail so much because it would take a lot so I will leave the details to OP.

Basically, Asp.Net framework dynamically creates an assembly and creates a dynamically created type which inherits your MvcApplication.

So here is the default MvcApplication

public class MvcApplication : HttpApplication {

    public static void Application_Start() {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

So what do I mean by Asp.Net framework dynamically creating another assembly and a type? You will notice that by checking the following code→ Let us modify Application_Start method:

public static void Application_Start() {
    var whatIsMyType =  GetType();
    //You will see that our actual type is of ASP.global_asax,
    //which inherits  MvcApplication, which inherits  HttpApplication      
    //Other Stuff...
}

Where does ASP.global_asax type is created? You will need to dig deep into source code but let me give you a hint

So how does the actual ASP.global_asax looks like?

[System.Runtime.CompilerServices.CompilerGlobalScopeAttribute()]
public class global_asax : global::<YourNameSpace>.MvcApplication {

    private static bool @__initialized;

    [System.Diagnostics.DebuggerNonUserCodeAttribute()]
    public global_asax() {
        if ((global::ASP.global_asax.@__initialized == false)) {
            global::ASP.global_asax.@__initialized = true;
        }
    }

    protected System.Web.Profile.DefaultProfile Profile {
        get {
            return ((System.Web.Profile.DefaultProfile)(this.Context.Profile));
        }
    }
}

Finally, we can move onto the actual answer: Why does making Application_Start static make the application behave in an unexpected way?

In HttpApplicationFactory class, which is used in the bootstrapping your application, there is the following line of code

methods = _theApplicationType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
foreach (MethodInfo m in methods) {
    if (ReflectOnMethodInfoIfItLooksLikeEventHandler(m))
        handlers.Add(m);
}

The puzzle is solved at _theApplicationType. Please remember that this is of ASP.global_asax type. Your static method Application_Start is defined in MvcApplication type so reflection will not find it through _theApplicationType and as a result it will not be assigned to be called when application starts.

Here is a simple code to check this.

public class BaseClass {
    public static void StaticMethodInBaseClass() {
    }
}

public class DerivedClass {
    public void DerivedClassMethod() {
    }
}

//You will not get `StaticMethodInBaseClass` here
var methods = typeof(DerivedClass).GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
Hasan Emrah Süngü
  • 3,488
  • 1
  • 15
  • 33
  • What an utterly stellar answer, Hasan, to a very difficult question. I'm amazed you were able to figure this out so well. Follow-up question: the CA1822 warning and recommended fix seems to break all .Net Framework applications. Sure… I could suppress that warning, but I try to address warnings rather than suppress them. I'm wondering... even if Microsoft had to resort to brute hard-coding, should they suppress this warning for all .Net Framework web projects? Also... here's a related issue: https://stackoverflow.com/questions/12360985/c-sharp-getmethod-doesnt-return-a-parent-method – Glenn Oct 29 '19 at 12:57
  • 1
    @Glenn, Thanks a lot, I learned a great deal as well! As for your `CA1822`, you should keep in mind that those are just suggestions and care should be taken when applying the suggestions. You should keep in mind that ASP.Net framework has assumptions, like these, about your code and FxCop would not know any better. I would suggest suppressing this warning as the correct way of dealing. – Hasan Emrah Süngü Oct 30 '19 at 01:04
1

Making Application_Start() static makes it not being part of the ASP.NET pipeline. Making the method static changes the method's signature and the framework is no longer able to find the method its looking for.

However...

I don't understand why it's not called, it should.

Here's are parts of HttpApplicationFactory that show that that it looks for BindingFlags.Instance and Binding.Static methods.

methods = _theApplicationType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
foreach (MethodInfo m in methods) {
    if (ReflectOnMethodInfoIfItLooksLikeEventHandler(m))
        handlers.Add(m);
}

Then the method is invoked on HttpApplication object, since the method is now static the first param should be ignored and the static method should be called.

if (paramCount == 0) {
   method.Invoke(this, new Object[0]);
}

Why 404 / 403?

Application_Start is, by convention the place where routes are configured.

My toy app

I put together a toy up to eliminate anything obvious. The method is called.

using System;
using System.Reflection;
using System.Web;

namespace NETFrameworkConsoleApp2
{
    public class MyHttpApp : HttpApplication
    {
        protected static void Application_Start()
        {
            Console.WriteLine("Very important work");
        }
    }

    class Program
    {
        private MethodInfo _onStartMethod;        // Application_OnStart

        public static void Main()
        {
            //Flags from https://referencesource.microsoft.com/#System.Web/HttpApplicationFactory.cs,74e5273062f54e5f,references
            var methods = typeof(MyHttpApp).GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

            var a = new MyHttpApp();

            var p = new Program();

            foreach (MethodInfo m in methods)
            {
                p.ReflectOnMethodInfoIfItLooksLikeEventHandler(m);
            }

            p._onStartMethod.Invoke(a, new Object[0]);
            Console.ReadLine();
        }


        private bool ReflectOnMethodInfoIfItLooksLikeEventHandler(MethodInfo m)
        {
            // From https://referencesource.microsoft.com/#System.Web/HttpApplicationFactory.cs,b0a90d9df37ace19,references
            if (m.ReturnType != typeof(void))
                return false;

            // has to have either no args or two args (object, eventargs)
            ParameterInfo[] parameters = m.GetParameters();

            switch (parameters.Length)
            {
                case 0:
                    // ok
                    break;
                case 2:
                    // param 0 must be object
                    if (parameters[0].ParameterType != typeof(System.Object))
                        return false;
                    // param 1 must be eventargs
                    if (parameters[1].ParameterType != typeof(System.EventArgs) &&
                        !parameters[1].ParameterType.IsSubclassOf(typeof(System.EventArgs)))
                        return false;
                    // ok
                    break;

                default:
                    return false;
            }

            // check the name (has to have _ not as first or last char)
            String name = m.Name;
            int j = name.IndexOf('_');
            if (j <= 0 || j > name.Length - 1)
                return false;

            // special pseudo-events
            if (StringUtil.EqualsIgnoreCase(name, "Application_OnStart") ||
                StringUtil.EqualsIgnoreCase(name, "Application_Start"))
            {
                _onStartMethod = m;
                //_onStartParamCount = parameters.Length;
            }
            else if (StringUtil.EqualsIgnoreCase(name, "Application_OnEnd") ||
                     StringUtil.EqualsIgnoreCase(name, "Application_End"))
            {
                //_onEndMethod = m;
                //_onEndParamCount = parameters.Length;
            }
            else if (StringUtil.EqualsIgnoreCase(name, "Session_OnEnd") ||
                     StringUtil.EqualsIgnoreCase(name, "Session_End"))
            {
                //_sessionOnEndMethod = m;
                //_sessionOnEndParamCount = parameters.Length;
            }

            return true;
        }

        internal static class StringUtil
        {
            //From https://referencesource.microsoft.com/#System.Web/Util/StringUtil.cs,d3a0b2a26cb3f1e1
            internal static bool EqualsIgnoreCase(string s1, string s2)
            {
                if (String.IsNullOrEmpty(s1) && String.IsNullOrEmpty(s2))
                {
                    return true;
                }
                if (String.IsNullOrEmpty(s1) || String.IsNullOrEmpty(s2))
                {
                    return false;
                }
                if (s2.Length != s1.Length)
                {
                    return false;
                }
                return 0 == string.Compare(s1, 0, s2, 0, s2.Length, StringComparison.OrdinalIgnoreCase);
            }
        }

        static Program() => Console.WriteLine(GetFrameworkName());

        static string GetFrameworkName()
            => ((System.Runtime.Versioning.TargetFrameworkAttribute)
                    (System.Reflection.Assembly.GetEntryAssembly()
                    .GetCustomAttributes(typeof(System.Runtime.Versioning.TargetFrameworkAttribute), true)[0]))
                    .FrameworkName; // Example: .NETCoreApp,Version=v3.0
    }
}
Community
  • 1
  • 1
tymtam
  • 31,798
  • 8
  • 86
  • 126
  • Now this answer seems right to me. By making the method static, the routine would have to be called like this: ClassName.Application_Start() rather than like this InstanceVariable.Application_Start(), and the Framework doesn't do it that way. And yes... because routes are defined there, that would explain the 404 error. Thanks much, Tymtam. – Glenn Oct 28 '19 at 03:31