最终,我试图解决加载任何 MVC 页面失败中引用的相同问题,并出现错误“已添加具有相同密钥的项目。”</a> 和已添加具有相同密钥的项目。第一个链接的副本是All MVC pages failed with the message an item with the same key has been added但它有一些额外的相关信息,确认它只影响 MVC 页面,而 webforms 和应用程序的其他方面处理使用 appSettings 继续正常工作。
我现在已经在生产环境中看到了四次,并且在任何其他环境(开发、测试、UAT)中都没有看到过。我仔细检查和调试了 System.Web.WebPages 的源代码和 MVC 堆栈的相关部分,但没有遇到任何突出的问题。这个问题在从 MVC3 迁移到 MVC4 时仍然存在,并且来自 aspnetwebstack.codeplex.com 的最新变更集似乎没有解决这个问题。
问题的快速总结:
- 每个 MVC 页面都受到影响并且完全无法使用
- WebForms 和使用 appSettings 的应用程序的其他方面继续正常工作
- 只有重新启动 appPool 才能解决此问题
- 至少一次,正如上面一篇文章中所引用的,这发生在 IIS 定期回收 appPool 之后
- 这在低流量和高流量期间都发生过
- 这发生在多台生产服务器上,但该问题在任何给定时间仅影响单个服务器,因为其他服务器继续提供 MVC 页面
有问题的代码行是var items = new Lazy<Dictionary<object, object>>(() => appSettings.AllKeys.ToDictionary(key => key, key => (object)appSettings[key], comparer));
,但是当通过从items
AppSettings 变量中请求一个值来强制延迟初始化时会发生这种情况,该变量来自 System.Web.WebConfigurationManager.AppSettings,它是对 System.Configuration.ConfigurationManager.AppSettings 的直接静态引用。所以我相信这条线相当于:var items = new Lazy<Dictionary<object, object>>(() => System.Configuration.ConfigurationManager.AppSettings.AllKeys.ToDictionary(key => key, key => (object)appSettings[key], comparer));
我很少怀疑框架问题,但似乎 appSettings 有两个相同的不同键(与支持同一键的多个值的 NameValueCollection 不同)。MVC 堆栈中使用的comparer
是 StringComparer.OrdinalIgnoreCase,它似乎与配置系统使用的匹配。ToDictionary()
如果这是一个框架问题,当使用扩展方法将 NameValueColleciton 强制放入字典时,MVC 堆栈似乎非常无情。我相信使用appSettings.AllKeys.Distinct().ToDictionary(...)
可能会允许 MVC 堆栈像应用程序的其余部分一样正常运行,并且忽略重复键的可能性。这种无情的性质似乎也促成了WebConfigScopeDictionary 中 NullReferenceException 中描述的问题
Server stack trace:
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
at System.Web.WebPages.Scope.WebConfigScopeDictionary.<>c__DisplayClass4.<.ctor>b__0()
at System.Lazy`1.CreateValue()
Exception rethrown at [0]:
at System.Lazy`1.get_Value()
at System.Web.WebPages.Scope.WebConfigScopeDictionary.TryGetValue(Object key, Object& value)
at System.Web.Mvc.ViewContext.ScopeGet[TValue](IDictionary`2 scope, String name, TValue defaultValue)
at System.Web.Mvc.ViewContext.ScopeCache..ctor(IDictionary`2 scope)
at System.Web.Mvc.ViewContext.ScopeCache.Get(IDictionary`2 scope, HttpContextBase httpContext)
at System.Web.Mvc.ViewContext.GetClientValidationEnabled(IDictionary`2 scope, HttpContextBase httpContext)
at System.Web.Mvc.Html.FormExtensions.FormHelper(HtmlHelper htmlHelper, String formAction, FormMethod method, IDictionary`2 htmlAttributes)
at ASP._Page_Areas_Client_Views_Equipment_Index_cshtml.Execute()
at System.Web.WebPages.WebPageBase.ExecutePageHierarchy()
at System.Web.Mvc.WebViewPage.ExecutePageHierarchy()
at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage)
at System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context)
at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass1a.<InvokeActionResultWithFilters>b__17()
at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass25.<BeginInvokeAction>b__22(IAsyncResult asyncResult)
at System.Web.Mvc.Controller.<>c__DisplayClass1d.<BeginExecuteCore>b__18(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar)
at System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar)
at System.Web.Mvc.MvcHandler.<>c__DisplayClass8.<BeginProcessRequest>b__3(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
为了将我的问题与已经提出的问题区分开来,我会问是否有人看到配置系统因多个重复键而损坏,或者 NameValueCollection.AllKeys 返回两个相同的键?我知道您可以在配置文件中定义多个键,但最后一个键获胜,并且该场景不会重现此问题。
虽然我不是一个人多次看到这种行为,但描述这个问题的帖子相对较少,所以我也怀疑这可能是配置/环境问题,但同样,服务器将运行几个月而没有遇到此问题,并且appPool 重新启动会立即纠正问题。
如果服务器开始看到此错误,我已通过强制重新启动 appPool 来缓解此问题,但管理层对这种“hacky”解决方案不满意,因为某些用户仍会遇到错误。
帮助?!?!?
编辑:
这是一个人为的、俗气的测试,可以重现该场景,但无助于解决问题。它发生在大约。20% 的测试运行。该代码将因其他线程原因而崩溃,但令人感兴趣的是“已添加相同的键”错误。
[TestClass]
public class UnitTest1
{
readonly NameValueCollection _nameValueCollection = new NameValueCollection();
private Lazy<Dictionary<object, object>> _items;
[TestMethod]
public void ReproduceSameKeyHasAlreadyBeenAdded()
{
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++)
{
ThreadStart threadStart = AddEntry;
Thread thread = new Thread(threadStart);
threads[i] = thread;
}
foreach (var thread in threads)
thread.Start();
Thread.Sleep(100);
_items = new Lazy<Dictionary<object, object>>(() => _nameValueCollection.AllKeys.ToDictionary(key => key, key => (object)_nameValueCollection[key], ScopeStorageComparer.Instance));
object value;
_items.Value.TryGetValue("4", out value); // approx. 20% of time, blows up here with mentioned error
Assert.IsTrue(value != null);
}
private int _counter;
private void AddEntry()
{
_counter++;
try
{ // add a bunch of even keys, every other one a duplicate
_nameValueCollection.Add((_counter%2) == 0 ? _counter.ToString() : (_counter + 1).ToString(), "some value");
}
catch {}
}
}
StackTrace:
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
at UnitTestProject1.ReproduceSameKeyHasAlreadyBeenAdded.<TestMethod1>b__0() in c:\Git\AspNetWebStack\aspnetwebstack\UnitTestProject1\UnitTest1.cs:line 37
at System.Lazy`1.CreateValue()