diff --git a/test/System.Web.Mvc.Test/Html/Test/MetadataOverrideScope.cs b/test/System.Web.Mvc.Test/Html/Test/MetadataOverrideScope.cs
new file mode 100644
index 00000000..3cbbb0fd
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/MetadataOverrideScope.cs
@@ -0,0 +1,168 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+using Moq;
+
+namespace System.Web.Mvc.Html.Test
+{
+ ///
+ ///
+ /// A scope within which the for a single is overridden. Could be
+ /// used for example to ensure the metadata for includes an additional property or
+ /// has a non-null .
+ ///
+ ///
+ /// Notes: Does _not_ override the metadata for subclasses of the given . And callers should
+ /// override (likely, mock) the metadata of the containing when changing the metadata of a
+ /// property e.g. modifying .
+ ///
+ ///
+ public class MetadataOverrideScope : IDisposable
+ {
+ private static readonly DataAnnotationsModelMetadataProvider AnnotationsProvider =
+ new DataAnnotationsModelMetadataProvider();
+
+ private readonly ModelMetadataProvider _oldMetadataProvider;
+ private readonly ModelMetadata _metadata;
+ private readonly Type _modelType;
+
+ public MetadataOverrideScope(ModelMetadata metadata)
+ {
+ if (metadata == null)
+ {
+ throw new ArgumentNullException("metadata");
+ }
+ if (metadata.ModelType == null)
+ {
+ throw new ArgumentException("Need ModelType", "metadata");
+ }
+
+ _oldMetadataProvider = ModelMetadataProviders.Current;
+ _metadata = metadata;
+ _modelType = metadata.ModelType;
+
+ // Mock a ModelMetadataProvider which delegates to the old one in most cases. No need to special-case
+ // GetMetadataForProperties() because product code uses it only within ModelMetadata.Properties and our
+ // metadata instance will call _oldMetadataProvider there.
+ var metadataProvider = new Mock();
+ metadataProvider
+ .Setup(p => p.GetMetadataForProperties(It.IsAny(), It.IsAny()))
+ .Returns((object container, Type containerType) =>
+ _oldMetadataProvider.GetMetadataForProperties(container, containerType));
+ metadataProvider
+ .Setup(p => p.GetMetadataForType(It.IsAny>(), It.IsAny()))
+ .Returns((Func modelAccessor, Type modelType) =>
+ _oldMetadataProvider.GetMetadataForType(modelAccessor, modelType));
+
+ // When metadata for _modelType is requested, then return a clone of the provided metadata instance.
+ // GetMetadataForProperty() is important because the static discovery methods (e.g.
+ // ModelMetadata.FromLambdaExpression) use it.
+ metadataProvider
+ .Setup(p => p.GetMetadataForType(It.IsAny>(), _modelType))
+ .Returns((Func modelAccessor, Type modelType) => GetMetadataForType(modelAccessor, modelType));
+ metadataProvider
+ .Setup(p =>
+ p.GetMetadataForProperty(It.IsAny>(), It.IsAny(), It.IsAny()))
+ .Returns((Func modelAccessor, Type containerType, string propertyName) =>
+ GetMetadataForProperty(modelAccessor, containerType, propertyName));
+
+ // Calls to GetMetadataForProperties for the modelType are incorrect because _metadata.Provider must
+ // reference _oldMetadataProvider and not this mock.
+ metadataProvider
+ .Setup(p => p.GetMetadataForProperty(It.IsAny>(), _modelType, It.IsAny()))
+ .Throws();
+
+ // Finally make our ModelMetadataProvider visible everywhere.
+ ModelMetadataProviders.Current = metadataProvider.Object;
+ }
+
+ public void Dispose()
+ {
+ ModelMetadataProviders.Current = _oldMetadataProvider;
+ }
+
+ private ModelMetadata GetMetadataForType(Func modelAccessor, Type modelType)
+ {
+ return CloneMetadata(modelAccessor);
+ }
+
+ private ModelMetadata GetMetadataForProperty(
+ Func modelAccessor,
+ Type containerType,
+ string propertyName)
+ {
+ var propertyMetadata =
+ _oldMetadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName);
+ if (propertyMetadata == null)
+ {
+ return null;
+ }
+
+ if (propertyMetadata.ModelType == _modelType)
+ {
+ return CloneMetadata(() => propertyMetadata.Model);
+ }
+
+ return propertyMetadata;
+ }
+
+ private ModelMetadata CloneMetadata(Func modelAccessor)
+ {
+ var cachedMetadata = _metadata as CachedDataAnnotationsModelMetadata;
+ var annotationsMetadata = _metadata as DataAnnotationsModelMetadata;
+ ModelMetadata clonedMetadata;
+ if (cachedMetadata != null)
+ {
+ clonedMetadata = new CachedDataAnnotationsModelMetadata(cachedMetadata, modelAccessor);
+ }
+ else if (annotationsMetadata != null)
+ {
+ var provider = (_oldMetadataProvider as DataAnnotationsModelMetadataProvider) ?? AnnotationsProvider;
+ clonedMetadata = new DataAnnotationsModelMetadata(
+ provider,
+ annotationsMetadata.ContainerType,
+ modelAccessor,
+ _modelType,
+ annotationsMetadata.PropertyName,
+ displayColumnAttribute: null); // Copying SimpleDisplayText below compensates for null here.
+ }
+ else
+ {
+ clonedMetadata = new ModelMetadata(
+ _oldMetadataProvider,
+ _metadata.ContainerType,
+ modelAccessor,
+ _modelType,
+ _metadata.PropertyName);
+ }
+
+ // Undo all the lazy-initialization of ModelMetadata and CachedDataAnnotationsModelMetadata...
+ clonedMetadata.Container = _metadata.Container; // May be incorrect.
+ clonedMetadata.ConvertEmptyStringToNull = _metadata.ConvertEmptyStringToNull;
+ clonedMetadata.DataTypeName = _metadata.DataTypeName;
+ clonedMetadata.Description = _metadata.Description;
+ clonedMetadata.DisplayFormatString = _metadata.DisplayFormatString;
+ clonedMetadata.DisplayName = _metadata.DisplayName;
+ clonedMetadata.EditFormatString = _metadata.EditFormatString;
+ clonedMetadata.HasNonDefaultEditFormat = _metadata.HasNonDefaultEditFormat;
+ clonedMetadata.HideSurroundingHtml = _metadata.HideSurroundingHtml;
+ clonedMetadata.HtmlEncode = _metadata.HtmlEncode;
+ clonedMetadata.IsReadOnly = _metadata.IsReadOnly;
+ clonedMetadata.IsRequired = _metadata.IsRequired;
+ clonedMetadata.NullDisplayText = _metadata.NullDisplayText;
+ clonedMetadata.Order = _metadata.Order;
+ clonedMetadata.RequestValidationEnabled = _metadata.RequestValidationEnabled;
+ clonedMetadata.ShortDisplayName = _metadata.ShortDisplayName;
+ clonedMetadata.ShowForDisplay = _metadata.ShowForDisplay;
+ clonedMetadata.ShowForEdit = _metadata.ShowForEdit;
+ clonedMetadata.SimpleDisplayText = _metadata.SimpleDisplayText;
+ clonedMetadata.TemplateHint = _metadata.TemplateHint;
+ clonedMetadata.Watermark = _metadata.Watermark;
+ foreach (var keyValuePair in _metadata.AdditionalValues)
+ {
+ clonedMetadata.AdditionalValues.Add(keyValuePair.Key, keyValuePair.Value);
+ }
+
+ return clonedMetadata;
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/Html/Test/TemplateHelpersSafeScope.cs b/test/System.Web.Mvc.Test/Html/Test/TemplateHelpersSafeScope.cs
new file mode 100644
index 00000000..0ea6b896
--- /dev/null
+++ b/test/System.Web.Mvc.Test/Html/Test/TemplateHelpersSafeScope.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.WebPages.Scope;
+using Moq;
+
+namespace System.Web.Mvc.Html.Test
+{
+ ///
+ /// A scope within which it is safe to invoke methods. For example
+ /// invokes ViewEngines.Engines.FindPartialView()
and
+ /// clones the current .
+ ///
+ /// Similar to TemplateHelpersTest.MockViewEngine but FindPartialView() succeed there and fail here. In
+ /// addition TemplateHelpersTest tests do not continue far enough to need the transient scope.
+ ///
+ public class TemplateHelpersSafeScope : IDisposable
+ {
+ private readonly List _oldEngines;
+ private IDisposable _transientScope;
+
+ public TemplateHelpersSafeScope()
+ {
+ // Copying an HtmlHelper instance reads and writes the current StorageScope.
+ // Ensure that's not the global scope.
+ _transientScope = ScopeStorage.CreateTransientScope();
+
+ // Do not want templates to check disk for anything.
+ var engine = new Mock(MockBehavior.Strict);
+ engine
+ .Setup(e => e.FindPartialView(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Returns(new ViewEngineResult(Enumerable.Empty()));
+
+ _oldEngines = ViewEngines.Engines.ToList();
+ ViewEngines.Engines.Clear();
+ ViewEngines.Engines.Add(engine.Object);
+ }
+
+ public void Dispose()
+ {
+ ViewEngines.Engines.Clear();
+ foreach (var oldEngine in _oldEngines)
+ {
+ ViewEngines.Engines.Add(oldEngine);
+ }
+
+ using (_transientScope)
+ {
+ _transientScope = null;
+ }
+ }
+ }
+}
diff --git a/test/System.Web.Mvc.Test/System.Web.Mvc.Test.csproj b/test/System.Web.Mvc.Test/System.Web.Mvc.Test.csproj
index 4f0067d3..bc772f78 100644
--- a/test/System.Web.Mvc.Test/System.Web.Mvc.Test.csproj
+++ b/test/System.Web.Mvc.Test/System.Web.Mvc.Test.csproj
@@ -56,7 +56,9 @@
+
+