为什么将 Application_Start 设为静态方法(FxCopAnalyzer 的推荐)会导致 404 错误?

Why does making Application_Start a static method (a recommendation of FxCopAnalyzer) cause 404 errors?

Visual Studio 建议我在我的项目中安装新的 Microsoft.CodeAnalysis.FxCopAnalyzers。它很好地识别了源代码中的许多改进,但它的建议之一是:

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

我检查了套路,果然,FxCopAnalyzer是对的。所以我改变了:

protected void Application_Start()

protected static void Application_Start()

但是,当我进行更改时,我现在得到了这个:

HTTP Error 403.14 - Forbidden

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

如果我去掉“静态”,它又会起作用。但是我有点困惑为什么框架会关心这个方法是否是静态的。

使 Application_Start() 静态化使其 而不是 成为 ASP.NET 管道的一部分。使方法 static 更改了方法的签名,框架不再能够找到它正在寻找的方法。

然而...

我不明白为什么不叫它,应该叫它。

这是 HttpApplicationFactory 的部分内容,显示它寻找 BindingFlags.InstanceBinding.Static 方法。

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

然后在 HttpApplication 对象上调用该方法,因为该方法现在是静态的,应该忽略第一个参数并调用静态方法。

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

为什么是 404 / 403?

Application_Start 按照惯例是 routes are configured.

的位置

我的玩具应用程序

我组装了一个玩具来消除任何明显的东西。该方法被调用。

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

这是一个非常有趣的问题。我花了一段时间才理解 Asp.Net 引导管道。我不会详细介绍太多,因为这会花费很多,所以我会把细节留给 OP。

基本上,Asp.Net 框架动态创建一个程序集并创建一个动态创建的类型,它继承了您的 MvcApplication

所以这里是默认的MvcApplication

public class MvcApplication : HttpApplication {

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

那么 Asp.Net 框架动态创建另一个程序集和类型是什么意思?您会注意到,通过检查以下代码→ 让我们修改Application_Start方法:

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

ASP.global_asax类型是在哪里创建的?您将需要深入研究源代码,但让我给您一个 hint

那么实际的 ASP.global_asax 是什么样子的?

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

最后,我们可以进入实际答案:为什么使 Application_Start 静态会使应用程序以意外方式运行?

HttpApplicationFactory class 中,用于引导您的应用程序,有以下代码行

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

谜题已在 _theApplicationType 解开。请记住这是 ASP.global_asax type 的。您的静态方法 Application_Start 是在 MvcApplication 类型中定义的,因此反射不会通过 _theApplicationType 找到它,因此它不会被分配在应用程序启动时调用。

这是一个简单的代码来检查这个。

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