diff --git a/src/Microsoft.AspNet.Mvc.Common/CopyOnWriteDictionary.cs b/src/Microsoft.AspNet.Mvc.Common/CopyOnWriteDictionary.cs
new file mode 100644
index 0000000000..716e9245ea
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Common/CopyOnWriteDictionary.cs
@@ -0,0 +1,148 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// A that defers creating a shallow copy of the source dictionary until
+ /// a mutative operation has been performed on it.
+ ///
+ internal class CopyOnWriteDictionary : IDictionary
+ {
+ private readonly IDictionary _sourceDictionary;
+ private readonly IEqualityComparer _comparer;
+ private IDictionary _innerDictionary;
+
+ public CopyOnWriteDictionary([NotNull] IDictionary sourceDictionary,
+ [NotNull] IEqualityComparer comparer)
+ {
+ _sourceDictionary = sourceDictionary;
+ _comparer = comparer;
+ }
+
+ private IDictionary ReadDictionary
+ {
+ get
+ {
+ return _innerDictionary ?? _sourceDictionary;
+ }
+ }
+
+ private IDictionary WriteDictionary
+ {
+ get
+ {
+ if (_innerDictionary == null)
+ {
+ _innerDictionary = new Dictionary(_sourceDictionary,
+ _comparer);
+ }
+
+ return _innerDictionary;
+ }
+ }
+
+ public virtual ICollection Keys
+ {
+ get
+ {
+ return ReadDictionary.Keys;
+ }
+ }
+
+ public virtual ICollection Values
+ {
+ get
+ {
+ return ReadDictionary.Values;
+ }
+ }
+
+ public virtual int Count
+ {
+ get
+ {
+ return ReadDictionary.Count;
+ }
+ }
+
+ public virtual bool IsReadOnly
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public virtual TValue this[[NotNull] TKey key]
+ {
+ get
+ {
+ return ReadDictionary[key];
+ }
+ set
+ {
+ WriteDictionary[key] = value;
+ }
+ }
+
+ public virtual bool ContainsKey([NotNull] TKey key)
+ {
+ return ReadDictionary.ContainsKey(key);
+ }
+
+ public virtual void Add([NotNull] TKey key, TValue value)
+ {
+ WriteDictionary.Add(key, value);
+ }
+
+ public virtual bool Remove([NotNull] TKey key)
+ {
+ return WriteDictionary.Remove(key);
+ }
+
+ public virtual bool TryGetValue([NotNull] TKey key, out TValue value)
+ {
+ return ReadDictionary.TryGetValue(key, out value);
+ }
+
+ public virtual void Add(KeyValuePair item)
+ {
+ WriteDictionary.Add(item);
+ }
+
+ public virtual void Clear()
+ {
+ WriteDictionary.Clear();
+ }
+
+ public virtual bool Contains(KeyValuePair item)
+ {
+ return ReadDictionary.Contains(item);
+ }
+
+ public virtual void CopyTo([NotNull] KeyValuePair[] array, int arrayIndex)
+ {
+ ReadDictionary.CopyTo(array, arrayIndex);
+ }
+
+ public bool Remove(KeyValuePair item)
+ {
+ return WriteDictionary.Remove(item);
+ }
+
+ public virtual IEnumerator> GetEnumerator()
+ {
+ return ReadDictionary.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/ViewDataDictionary.cs b/src/Microsoft.AspNet.Mvc.Core/ViewDataDictionary.cs
index 60cd085413..061952922d 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ViewDataDictionary.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ViewDataDictionary.cs
@@ -24,12 +24,12 @@ namespace Microsoft.AspNet.Mvc
}
public ViewDataDictionary([NotNull] IModelMetadataProvider metadataProvider,
- [NotNull] ModelStateDictionary modelState)
+ [NotNull] ModelStateDictionary modelState)
+ : this(metadataProvider,
+ modelState: modelState,
+ data: new Dictionary(StringComparer.OrdinalIgnoreCase),
+ templateInfo: new TemplateInfo())
{
- ModelState = modelState;
- TemplateInfo = new TemplateInfo();
- _data = new Dictionary(StringComparer.OrdinalIgnoreCase);
- _metadataProvider = metadataProvider;
}
///
@@ -45,24 +45,26 @@ namespace Microsoft.AspNet.Mvc
/// exceptions a derived class may throw when is called.
///
public ViewDataDictionary([NotNull] ViewDataDictionary source, object model)
- : this(source.MetadataProvider)
+ : this(source.MetadataProvider,
+ new ModelStateDictionary(source.ModelState),
+ new CopyOnWriteDictionary(source, StringComparer.OrdinalIgnoreCase),
+ new TemplateInfo(source.TemplateInfo))
{
_modelMetadata = source.ModelMetadata;
- TemplateInfo = new TemplateInfo(source.TemplateInfo);
-
- foreach (var entry in source.ModelState)
- {
- ModelState.Add(entry.Key, entry.Value);
- }
-
- foreach (var entry in source)
- {
- _data.Add(entry.Key, entry.Value);
- }
-
SetModel(model);
}
+ private ViewDataDictionary(IModelMetadataProvider metadataProvider,
+ ModelStateDictionary modelState,
+ IDictionary data,
+ TemplateInfo templateInfo)
+ {
+ _metadataProvider = metadataProvider;
+ ModelState = modelState;
+ _data = data;
+ TemplateInfo = templateInfo;
+ }
+
public object Model
{
get { return _model; }
@@ -130,6 +132,12 @@ namespace Microsoft.AspNet.Mvc
}
#endregion
+ // for unit testing
+ internal IDictionary Data
+ {
+ get { return _data; }
+ }
+
public object Eval(string expression)
{
var info = GetViewDataInfo(expression);
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelStateDictionary.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelStateDictionary.cs
index 42ec4596c5..57d248bfe4 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelStateDictionary.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelStateDictionary.cs
@@ -11,19 +11,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ModelStateDictionary : IDictionary
{
- private readonly IDictionary _innerDictionary =
- new Dictionary(StringComparer.OrdinalIgnoreCase);
+ private readonly IDictionary _innerDictionary;
public ModelStateDictionary()
{
+ _innerDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase);
}
public ModelStateDictionary([NotNull] ModelStateDictionary dictionary)
{
- foreach (var entry in dictionary)
- {
- _innerDictionary.Add(entry.Key, entry.Value);
- }
+ _innerDictionary = new CopyOnWriteDictionary(dictionary,
+ StringComparer.OrdinalIgnoreCase);
}
#region IDictionary properties
@@ -76,6 +74,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
+ // For unit testing
+ internal IDictionary InnerDictionary
+ {
+ get { return _innerDictionary; }
+ }
+
public void AddModelError([NotNull] string key, [NotNull] Exception exception)
{
var modelState = GetModelStateForKey(key);
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/CopyOnWriteDictionaryTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/CopyOnWriteDictionaryTest.cs
new file mode 100644
index 0000000000..f1d7263235
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/CopyOnWriteDictionaryTest.cs
@@ -0,0 +1,102 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.Core
+{
+ public class CopyOnWriteDictionaryTest
+ {
+ [Fact]
+ public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed()
+ {
+ // Arrange
+ var values = new List