System.Web.Routing.RouteCollection.GetRouteData中的exception

我在iis7上运行的asp.net mvc代码中随机获得了两个exception:

Exception type: InvalidOperationException Exception message: Collection was modified; enumeration operation may not execute. at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource) at System.Collections.Generic.List'1.Enumerator.MoveNextRare() at System.Collections.Generic.List'1.Enumerator.MoveNext() at System.Web.Routing.RouteCollection.GetRouteData(HttpContextBase httpContext) at System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context) at System.Web.Routing.UrlRoutingModule.OnApplicationPostResolveRequestCache(Object sender, EventArgs e) at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) 

 Exception type: NullReferenceException Exception message: Object reference not set to an instance of an object. at System.Web.Routing.RouteCollection.GetRouteData(HttpContextBase httpContext) at System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context) at System.Web.Routing.UrlRoutingModule.OnApplicationPostResolveRequestCache(Object sender, EventArgs e) at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) 

它不是一贯可重复的,但我认为这是改变(或腐败) RouteTable.Routes的东西。 我在项目中访问RouteTable.Routes的唯一地方是Global.asax.cs ,我知道那里的代码只被调用一次,所以这不是问题所在。 有关如何追踪它的任何想法?

在我的例子中,它最终成为HttpModule: Ext.Direct.Mvc (ASP.NET MVC的Ext.Direct)。 此模块有一个错误(在0.8.0版本中已修复),每次为IHttpModule调用Init()时,它都会再次注册路由。 ( 可能多次调用 )。 如果时机正确,它将破坏RouteTable.Routes集合,并导致上述两个例外之一。

该错误与.Net中不具有线程安全性的集合一致。

根据关于RouteCollection.GetWriteLock()的MSDN文章, RouteTable.Routes不是线程安全的。 这个问题是因为某些代码试图以一种非线程安全的方式修改RouteTable.Routes

最佳实践是修改Application_StartRouteTable.Routes 。 如果必须以可以由多个线程同时访问的方式对其进行修改,请确保以线程安全的方式进行修改。 检查以下复制的MSDN示例,以便于参考:

 using (RouteTable.Routes.GetWriteLock()) { Route newRoute = new Route("{action}/{id}", new ReportRouteHandler()); RouteTable.Routes.Add(newRoute); } 

同样适用于从RouteTable读取 – RouteCollection.GetReadLock() 。

可能,如果通过删除HttpModule来解决这个问题,那是因为这个HttpModule没有实现这样的锁定机制。

其他答案解释了发生了什么,但提供了如何实际跟踪它的细节。 它可能不是你自己的代码导致所有这些麻烦,所以Ctrl-F是不够的..

这个想法

解决问题的原因是,您通常会在实际修改RouteCollection的请求上获得错误。 通常安全地调用UrlHelper.GenerateUrl()等会在看似随机的时间失败。 为了追踪它,我选择在初始设置后禁止改变路线,将受害者的例外情况转移到罪魁祸首。

第1步:实现自定义RouteCollection

如果设置了标志,那么当有人试图改变路线时会大声喊叫。 这是一个粗略的实现,但你明白了。

 public class RestrictedRouteCollection : RouteCollection { public Boolean EnableRestrictions { get; set; } protected override void InsertItem(Int32 index, RouteBase item) { if (EnableRestrictions) { throw new Exception("Unexpected route added".); } base.InsertItem(index, item); } protected override void SetItem(Int32 index, RouteBase item) { if (EnableRestrictions) { throw new Exception("Unexpected change of RouteCollection item."); } base.SetItem(index, item); } protected override void RemoveItem(Int32 index) { if (EnableRestrictions) { throw new Exception("Unexpected removal from RouteCollection, index: " + index); } base.RemoveItem(index); } protected override void ClearItems() { if (EnableRestrictions) { throw new Exception("Unexpected clearing of routecollection."); } base.ClearItems(); } } 

第2步:替换默认集合

在Global.asax的Application_start中,在设置路由之前用您的实例替换默认的RouteCollection。 因为似乎没有替换它的API,所以我们强制它在私有字段上reflection:

 var routeTable = new LockableRouteCollection(); var field = typeof(RouteTable) .GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic); if (field == null) { throw new Exception("Expected field _instance was not found."); } field.SetValue(null, routeTable); Debug.Assert(RouteTable.Routes == routeTable); 

步骤3:在设置预期路线后限制更改

 RouteConfig.RegisterRoutes(routeTable); routeTable.EnableRestrictions = true; 

全部完成! 现在,在exception日志中查看错误请求。

可能的罪魁祸首:ImageResizer

在我的例子中,它是一个名为ImageResizer (v4.0.4)的第三方组件,其内部MvcRoutingShimPlugin自由添加/删除一个没有锁定的路由。 此特定错误已经报告并修复 (虽然此时尚未正式发布)。