为什么将 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.Instance
和 Binding.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);
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.Instance
和 Binding.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);