Merge branch 'release/2.1' into dev

This commit is contained in:
Pranav K 2018-03-22 14:22:46 -07:00
commit 12f8e10e1a
No known key found for this signature in database
GPG Key ID: 1963DA6D96C3057A
23 changed files with 789 additions and 457 deletions

View File

@ -205,7 +205,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <summary> /// <summary>
/// Gets or sets an value indicating whether the model binding system will bind undefined values to /// Gets or sets an value indicating whether the model binding system will bind undefined values to
/// enum types. The default value of the property is <c>false</c>. /// enum types. The default value of the property is <c>false</c>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para> /// <para>

View File

@ -1,48 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal namespace Microsoft.AspNetCore.Mvc.RazorPages
{ {
public class PageSaveTempDataPropertyFilter : SaveTempDataPropertyFilterBase, IPageFilter internal class PageSaveTempDataPropertyFilter : SaveTempDataPropertyFilterBase, IPageFilter
{ {
public PageSaveTempDataPropertyFilter(ITempDataDictionaryFactory factory) public PageSaveTempDataPropertyFilter(ITempDataDictionaryFactory factory)
: base(factory) : base(factory)
{ {
} }
public PageSaveTempDataPropertyFilterFactory FilterFactory { get; set; }
public void OnPageHandlerSelected(PageHandlerSelectedContext context) public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{ {
} }
public void OnPageHandlerExecuting(PageHandlerExecutingContext context) public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{ {
if (context.HandlerInstance == null)
{
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
nameof(PageHandlerExecutingContext.HandlerInstance),
typeof(PageHandlerExecutingContext).Name));
}
if (FilterFactory == null)
{
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
nameof(FilterFactory),
typeof(PageSaveTempDataPropertyFilter).Name));
}
var tempData = _factory.GetTempData(context.HttpContext);
Subject = context.HandlerInstance; Subject = context.HandlerInstance;
Properties = FilterFactory.GetTempDataProperties(Subject.GetType()); var tempData = _tempDataFactory.GetTempData(context.HttpContext);
SetPropertyVaules(tempData, Subject); SetPropertyVaules(tempData);
} }
public void OnPageHandlerExecuted(PageHandlerExecutedContext context) public void OnPageHandlerExecuted(PageHandlerExecutedContext context)

View File

@ -4,14 +4,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal namespace Microsoft.AspNetCore.Mvc.RazorPages
{ {
public class PageSaveTempDataPropertyFilterFactory : IFilterFactory internal class PageSaveTempDataPropertyFilterFactory : IFilterFactory
{ {
public IList<TempDataProperty> Properties { get; set; }
public PageSaveTempDataPropertyFilterFactory(IReadOnlyList<LifecycleProperty> properties)
{
Properties = properties;
}
public IReadOnlyList<LifecycleProperty> Properties { get; }
public bool IsReusable => false; public bool IsReusable => false;
@ -23,22 +30,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
} }
var service = serviceProvider.GetRequiredService<PageSaveTempDataPropertyFilter>(); var service = serviceProvider.GetRequiredService<PageSaveTempDataPropertyFilter>();
service.FilterFactory = this; service.Properties = Properties;
return service; return service;
} }
public IList<TempDataProperty> GetTempDataProperties(Type modelType)
{
// TempDataProperties are stored here as a cache for the filter. But in pages by the time we know the type
// of our model we no longer have access to the factory, so we store the factory on the filter so it can
// call this method to populate its TempDataProperties.
if (Properties == null)
{
Properties = SaveTempDataPropertyFilterBase.GetTempDataProperties(modelType);
}
return Properties;
}
} }
} }

View File

@ -3,11 +3,20 @@
using System; using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal namespace Microsoft.AspNetCore.Mvc.RazorPages
{ {
public class TempDataFilterPageApplicationModelProvider : IPageApplicationModelProvider internal class TempDataFilterPageApplicationModelProvider : IPageApplicationModelProvider
{ {
private readonly MvcViewOptions _options;
public TempDataFilterPageApplicationModelProvider(IOptions<MvcViewOptions> options)
{
_options = options.Value;
}
// The order is set to execute after the DefaultPageApplicationModelProvider. // The order is set to execute after the DefaultPageApplicationModelProvider.
public int Order => -1000 + 10; public int Order => -1000 + 10;
@ -23,9 +32,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
} }
var pageApplicationModel = context.PageApplicationModel; var pageApplicationModel = context.PageApplicationModel;
var handlerType = pageApplicationModel.HandlerType.AsType();
// Support for [TempData] on properties var tempDataProperties = SaveTempDataPropertyFilterBase.GetTempDataProperties(handlerType, _options);
pageApplicationModel.Filters.Add(new PageSaveTempDataPropertyFilterFactory()); if (tempDataProperties == null)
{
return;
}
var filter = new PageSaveTempDataPropertyFilterFactory(tempDataProperties);
pageApplicationModel.Filters.Add(filter);
} }
} }
} }

View File

@ -3,4 +3,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -142,6 +142,8 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddEnumerable( services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcViewOptions>, MvcViewOptionsSetup>()); ServiceDescriptor.Transient<IConfigureOptions<MvcViewOptions>, MvcViewOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcViewOptions>, MvcViewOptionsConfigureCompatibilityOptions>());
services.TryAddEnumerable( services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, TempDataMvcOptionsSetup>()); ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, TempDataMvcOptionsSetup>());

View File

@ -2,10 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{ {
public class ControllerSaveTempDataPropertyFilter : SaveTempDataPropertyFilterBase, IActionFilter internal class ControllerSaveTempDataPropertyFilter : SaveTempDataPropertyFilterBase, IActionFilter
{ {
public ControllerSaveTempDataPropertyFilter(ITempDataDictionaryFactory factory) public ControllerSaveTempDataPropertyFilter(ITempDataDictionaryFactory factory)
: base(factory) : base(factory)
@ -20,10 +21,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
public void OnActionExecuting(ActionExecutingContext context) public void OnActionExecuting(ActionExecutingContext context)
{ {
Subject = context.Controller; Subject = context.Controller;
var tempData = _factory.GetTempData(context.HttpContext); var tempData = _tempDataFactory.GetTempData(context.HttpContext);
SetPropertyVaules(tempData, Subject); SetPropertyVaules(tempData);
} }
} }
} }

View File

@ -4,13 +4,19 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{ {
public class ControllerSaveTempDataPropertyFilterFactory : IFilterFactory internal class ControllerSaveTempDataPropertyFilterFactory : IFilterFactory
{ {
public IList<TempDataProperty> TempDataProperties { get; set; } public ControllerSaveTempDataPropertyFilterFactory(IReadOnlyList<LifecycleProperty> properties)
{
TempDataProperties = properties;
}
public IReadOnlyList<LifecycleProperty> TempDataProperties { get; }
public bool IsReusable => false; public bool IsReusable => false;

View File

@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. 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.Diagnostics;
using System.Reflection;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{
[DebuggerDisplay("{PropertyInfo, nq}")]
public readonly struct LifecycleProperty
{
private readonly PropertyHelper _propertyHelper;
private readonly bool _isReferenceTypeOrNullable;
public LifecycleProperty(PropertyInfo propertyInfo, string key)
{
Key = key;
_propertyHelper = new PropertyHelper(propertyInfo);
var propertyType = propertyInfo.PropertyType;
_isReferenceTypeOrNullable = !propertyType.IsValueType || Nullable.GetUnderlyingType(propertyType) != null;
}
public string Key { get; }
public PropertyInfo PropertyInfo => _propertyHelper.Property;
public object GetValue(object instance) => _propertyHelper.GetValue(instance);
public void SetValue(object instance, object value)
{
if (value != null || _isReferenceTypeOrNullable)
{
_propertyHelper.SetValue(instance, value);
}
}
}
}

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection; using System.Reflection;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
@ -10,14 +11,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{ {
public abstract class SaveTempDataPropertyFilterBase : ISaveTempDataCallback public abstract class SaveTempDataPropertyFilterBase : ISaveTempDataCallback
{ {
protected const string Prefix = "TempDataProperty-"; protected readonly ITempDataDictionaryFactory _tempDataFactory;
protected readonly ITempDataDictionaryFactory _factory; public SaveTempDataPropertyFilterBase(ITempDataDictionaryFactory tempDataFactory)
{
_tempDataFactory = tempDataFactory;
}
/// <summary> /// <summary>
/// Describes the temp data properties which exist on <see cref="Subject"/> /// Describes the temp data properties which exist on <see cref="Subject"/>
/// </summary> /// </summary>
public IList<TempDataProperty> Properties { get; set; } public IReadOnlyList<LifecycleProperty> Properties { get; set; }
/// <summary> /// <summary>
/// The <see cref="object"/> which has the temp data properties. /// The <see cref="object"/> which has the temp data properties.
@ -29,64 +33,90 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
/// </summary> /// </summary>
public IDictionary<PropertyInfo, object> OriginalValues { get; } = new Dictionary<PropertyInfo, object>(); public IDictionary<PropertyInfo, object> OriginalValues { get; } = new Dictionary<PropertyInfo, object>();
public SaveTempDataPropertyFilterBase(ITempDataDictionaryFactory factory)
{
_factory = factory;
}
/// <summary> /// <summary>
/// Puts the modified values of <see cref="Subject"/> into <paramref name="tempData"/>. /// Puts the modified values of <see cref="Subject"/> into <paramref name="tempData"/>.
/// </summary> /// </summary>
/// <param name="tempData">The <see cref="ITempDataDictionary"/> to be updated.</param> /// <param name="tempData">The <see cref="ITempDataDictionary"/> to be updated.</param>
public void OnTempDataSaving(ITempDataDictionary tempData) public void OnTempDataSaving(ITempDataDictionary tempData)
{ {
if (Subject != null && Properties != null) if (Subject == null)
{ {
for (var i = 0; i < Properties.Count; i++) return;
{ }
var property = Properties[i];
OriginalValues.TryGetValue(property.PropertyInfo, out var originalValue);
var newValue = property.GetValue(Subject); for (var i = 0; i < Properties.Count; i++)
if (newValue != null && !newValue.Equals(originalValue)) {
{ var property = Properties[i];
tempData[property.TempDataKey] = newValue; OriginalValues.TryGetValue(property.PropertyInfo, out var originalValue);
}
var newValue = property.GetValue(Subject);
if (newValue != null && !newValue.Equals(originalValue))
{
tempData[property.Key] = newValue;
} }
} }
} }
public static IList<TempDataProperty> GetTempDataProperties(Type type) /// <summary>
/// Sets the values of the properties of <see cref="Subject"/> from <paramref name="tempData"/>.
/// </summary>
/// <param name="tempData">The <see cref="ITempDataDictionary"/>.</param>
protected void SetPropertyVaules(ITempDataDictionary tempData)
{ {
List<TempDataProperty> results = null; if (Properties == null)
{
return;
}
Debug.Assert(Subject != null, "Subject must be set before this method is invoked.");
for (var i = 0; i < Properties.Count; i++)
{
var property = Properties[i];
var value = tempData[property.Key];
OriginalValues[property.PropertyInfo] = value;
property.SetValue(Subject, value);
}
}
public static IReadOnlyList<LifecycleProperty> GetTempDataProperties(Type type, MvcViewOptions viewOptions)
{
List<LifecycleProperty> results = null;
var propertyHelpers = PropertyHelper.GetVisibleProperties(type: type); var propertyHelpers = PropertyHelper.GetVisibleProperties(type: type);
var prefix = viewOptions.SuppressTempDataAttributePrefix ?
string.Empty :
"TempDataProperty-";
for (var i = 0; i < propertyHelpers.Length; i++) for (var i = 0; i < propertyHelpers.Length; i++)
{ {
var propertyHelper = propertyHelpers[i]; var propertyHelper = propertyHelpers[i];
if (propertyHelper.Property.IsDefined(typeof(TempDataAttribute))) var property = propertyHelper.Property;
var tempDataAttribute = property.GetCustomAttribute<TempDataAttribute>();
if (tempDataAttribute != null)
{ {
ValidateProperty(propertyHelper); ValidateProperty(propertyHelper.Property);
if (results == null) if (results == null)
{ {
results = new List<TempDataProperty>(); results = new List<LifecycleProperty>();
} }
results.Add(new TempDataProperty( var key = tempDataAttribute.Key;
Prefix + propertyHelper.Name, if (string.IsNullOrEmpty(key))
propertyHelper.Property, {
propertyHelper.GetValue, key = prefix + property.Name;
propertyHelper.SetValue)); }
results.Add(new LifecycleProperty(property, key));
} }
} }
return results; return results;
} }
private static void ValidateProperty(PropertyHelper propertyHelper) private static void ValidateProperty(PropertyInfo property)
{ {
var property = propertyHelper.Property;
if (!(property.SetMethod != null && if (!(property.SetMethod != null &&
property.SetMethod.IsPublic && property.SetMethod.IsPublic &&
property.GetMethod != null && property.GetMethod != null &&
@ -108,34 +138,5 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
throw new InvalidOperationException($"{messageWithPropertyInfo} {errorMessage}"); throw new InvalidOperationException($"{messageWithPropertyInfo} {errorMessage}");
} }
} }
/// <summary>
/// Sets the values of the properties of <paramref name="subject"/> from <paramref name="tempData"/>.
/// </summary>
/// <param name="tempData">The <see cref="ITempDataDictionary"/> with the data to set on <paramref name="subject"/>.</param>
/// <param name="subject">The <see cref="object"/> which will have it's properties set.</param>
protected void SetPropertyVaules(ITempDataDictionary tempData, object subject)
{
if (Properties == null)
{
return;
}
for (var i = 0; i < Properties.Count; i++)
{
var property = Properties[i];
var value = tempData[Prefix + property.PropertyInfo.Name];
OriginalValues[property.PropertyInfo] = value;
var propertyTypeInfo = property.PropertyInfo.PropertyType.GetTypeInfo();
var isReferenceTypeOrNullable = !propertyTypeInfo.IsValueType || Nullable.GetUnderlyingType(property.GetType()) != null;
if (value != null || isReferenceTypeOrNullable)
{
property.SetValue(subject, value);
}
}
}
} }
} }

View File

@ -4,11 +4,20 @@
using System; using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{ {
public class TempDataApplicationModelProvider : IApplicationModelProvider internal class TempDataApplicationModelProvider : IApplicationModelProvider
{ {
private readonly MvcViewOptions _options;
public TempDataApplicationModelProvider(IOptions<MvcViewOptions> options)
{
_options = options.Value;
}
/// <inheritdoc /> /// <inheritdoc />
/// <remarks>This order ensures that <see cref="TempDataApplicationModelProvider"/> runs after the <see cref="DefaultApplicationModelProvider"/>.</remarks> /// <remarks>This order ensures that <see cref="TempDataApplicationModelProvider"/> runs after the <see cref="DefaultApplicationModelProvider"/>.</remarks>
public int Order => -1000 + 10; public int Order => -1000 + 10;
@ -28,19 +37,16 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
foreach (var controllerModel in context.Result.Controllers) foreach (var controllerModel in context.Result.Controllers)
{ {
var modelType = controllerModel.ControllerType.AsType(); var modelType = controllerModel.ControllerType.AsType();
var tempDataProperties = SaveTempDataPropertyFilterBase.GetTempDataProperties(modelType);
if (tempDataProperties != null) var tempDataProperties = SaveTempDataPropertyFilterBase.GetTempDataProperties(modelType, _options);
if (tempDataProperties == null)
{ {
var factory = new ControllerSaveTempDataPropertyFilterFactory() continue;
{
TempDataProperties = tempDataProperties
};
controllerModel.Filters.Add(factory);
} }
var filter = new ControllerSaveTempDataPropertyFilterFactory(tempDataProperties);
controllerModel.Filters.Add(filter);
} }
} }
} }

View File

@ -1,37 +0,0 @@
// Copyright (c) .NET Foundation. 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.Reflection;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{
public struct TempDataProperty
{
private readonly Func<object, object> _getter;
private readonly Action<object, object> _setter;
public TempDataProperty(string tempDataKey, PropertyInfo propertyInfo, Func<object, object> getter, Action<object, object> setter)
{
TempDataKey = tempDataKey;
PropertyInfo = propertyInfo;
_getter = getter;
_setter = setter;
}
public string TempDataKey { get; }
public PropertyInfo PropertyInfo { get; }
public object GetValue(object obj)
{
return _getter(obj);
}
public void SetValue(object obj, object value)
{
_setter(obj, value);
}
}
}

View File

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
@ -12,10 +14,21 @@ namespace Microsoft.AspNetCore.Mvc
/// <summary> /// <summary>
/// Provides programmatic configuration for views in the MVC framework. /// Provides programmatic configuration for views in the MVC framework.
/// </summary> /// </summary>
public class MvcViewOptions public class MvcViewOptions : IEnumerable<ICompatibilitySwitch>
{ {
private readonly CompatibilitySwitch<bool> _suppressTempDataAttributePrefix;
private readonly ICompatibilitySwitch[] _switches;
private HtmlHelperOptions _htmlHelperOptions = new HtmlHelperOptions(); private HtmlHelperOptions _htmlHelperOptions = new HtmlHelperOptions();
public MvcViewOptions()
{
_suppressTempDataAttributePrefix = new CompatibilitySwitch<bool>(nameof(SuppressTempDataAttributePrefix));
_switches = new[]
{
_suppressTempDataAttributePrefix,
};
}
/// <summary> /// <summary>
/// Gets or sets programmatic configuration for the HTML helpers and <see cref="Rendering.ViewContext"/>. /// Gets or sets programmatic configuration for the HTML helpers and <see cref="Rendering.ViewContext"/>.
/// </summary> /// </summary>
@ -33,6 +46,47 @@ namespace Microsoft.AspNetCore.Mvc
} }
} }
/// <summary>
/// <para>
/// Gets or sets a value that determines if the <see cref="ITempDataDictionary"/> keys for
/// properties annotated with <see cref="TempDataAttribute"/> include the prefix <c>TempDataProperty-</c>.
/// </para>
/// <para>
/// When <see cref="TempDataAttribute.Key"/> is not specified, the lookup key for properties annotated
/// with <see cref="TempDataAttribute"/> is derived from the property name. In releases prior to ASP.NET Core 2.1,
/// the calculated key was the property name prefixed by the value <c>TempDataProperty-</c>.
/// e.g. <c>TempDataProperty-SuccessMessage</c>. When this option is <c>true</c>, the calculated key for the property is
/// the property name e.g. <c>SuccessMessage</c>.
/// </para>
/// <para>
/// Defaults to <c>false</c>.
/// </para>
/// </summary>
/// <remarks>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
/// Configuring the desired value of the compatibility switch by calling this property's setter will take precedence
/// over the value implied by the application's <see cref="CompatibilityVersion"/>.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_0"/> then
/// this setting will have the value <c>false</c> unless explicitly configured.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// higher then this setting will have the value <c>true</c> unless explicitly configured.
/// </para>
/// </remarks>
public bool SuppressTempDataAttributePrefix
{
get => _suppressTempDataAttributePrefix.Value;
set => _suppressTempDataAttributePrefix.Value = value;
}
/// <summary> /// <summary>
/// Gets a list <see cref="IViewEngine"/>s used by this application. /// Gets a list <see cref="IViewEngine"/>s used by this application.
/// </summary> /// </summary>
@ -43,5 +97,12 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary> /// </summary>
public IList<IClientModelValidatorProvider> ClientModelValidatorProviders { get; } = public IList<IClientModelValidatorProvider> ClientModelValidatorProviders { get; } =
new List<IClientModelValidatorProvider>(); new List<IClientModelValidatorProvider>();
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
{
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator();
} }
} }

View File

@ -3,5 +3,6 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ViewFeatures.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ViewFeatures.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

View File

@ -2,16 +2,22 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Microsoft.AspNetCore.Mvc namespace Microsoft.AspNetCore.Mvc
{ {
/// <summary> /// <summary>
/// Properties decorated with <see cref="TempDataAttribute"/> will have their values stored in /// Properties decorated with <see cref="TempDataAttribute"/> will have their values stored in
/// and loaded from the <see cref="ViewFeatures.TempDataDictionary"/>. <see cref="TempDataAttribute"/> /// and loaded from the <see cref="ITempDataDictionary"/>. <see cref="TempDataAttribute"/>
/// is supported on properties of Controllers, Razor Pages, and Razor Page Models. /// is supported on properties of Controllers, Razor Pages, and Razor Page Models.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class TempDataAttribute : Attribute public sealed class TempDataAttribute : Attribute
{ {
/// <summary>
/// Gets or sets the key used to get or add the property from value from <see cref="ITempDataDictionary"/>.
/// When unspecified, the key is derived from the property name.
/// </summary>
public string Key { get; set; }
} }
} }

View File

@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
internal class MvcViewOptionsConfigureCompatibilityOptions : ConfigureCompatibilityOptions<MvcViewOptions>
{
public MvcViewOptionsConfigureCompatibilityOptions(
ILoggerFactory loggerFactory,
IOptions<MvcCompatibilityOptions> compatibilityOptions)
: base(loggerFactory, compatibilityOptions)
{
}
protected override IReadOnlyDictionary<string, object> DefaultValues
{
get
{
var values = new Dictionary<string, object>();
if (Version >= CompatibilityVersion.Version_2_1)
{
values[nameof(MvcViewOptions.SuppressTempDataAttributePrefix)] = true;
}
return values;
}
}
}
}

View File

@ -1,12 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal namespace Microsoft.AspNetCore.Mvc.RazorPages
{ {
public class PageSaveTempDataPropertyFilterFactoryTest public class PageSaveTempDataPropertyFilterFactoryTest
{ {
@ -14,8 +15,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public void CreatesInstanceWithProperties() public void CreatesInstanceWithProperties()
{ {
// Arrange // Arrange
var factory = new PageSaveTempDataPropertyFilterFactory(); var property = typeof(TestPageModel).GetProperty(nameof(TestPageModel.Property1));
var lifecycleProperties = new[] { new LifecycleProperty(property, "key") };
var factory = new PageSaveTempDataPropertyFilterFactory(lifecycleProperties);
var serviceProvider = CreateServiceProvider(); var serviceProvider = CreateServiceProvider();
// Act // Act
@ -23,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Assert // Assert
var pageFilter = Assert.IsType<PageSaveTempDataPropertyFilter>(filter); var pageFilter = Assert.IsType<PageSaveTempDataPropertyFilter>(filter);
Assert.Same(factory, pageFilter.FilterFactory); Assert.Same(lifecycleProperties, pageFilter.Properties);
} }
private ServiceProvider CreateServiceProvider() private ServiceProvider CreateServiceProvider()
@ -36,5 +38,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
return serviceCollection.BuildServiceProvider(); return serviceCollection.BuildServiceProvider();
} }
private class TestPageModel : PageModel
{
[TempData]
public string Property1 { get; set; }
}
} }
} }

View File

@ -3,14 +3,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
@ -27,71 +22,29 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Arrange // Arrange
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>()); var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>())
{
var page = new TestPage() ["TempDataProperty-Test"] = "Old-Value",
};
var pageModel = new TestPageModel()
{ {
Test = "TestString", Test = "TestString",
Test2 = "Test2", Test2 = "Test2",
}; };
var filter = CreatePageSaveTempDataPropertyFilter(tempData); var filter = CreatePageSaveTempDataPropertyFilter(tempData, "TempDataProperty-");
filter.Subject = page; filter.Subject = pageModel;
var pageType = page.GetType();
var testProperty = pageType.GetProperty(nameof(TestPage.Test));
var test2Property = pageType.GetProperty(nameof(TestPage.Test2));
filter.OriginalValues[testProperty] = "SomeValue";
filter.OriginalValues[test2Property] = "Test2";
filter.Properties = new List<TempDataProperty>
{
new TempDataProperty("TempDataProperty-Test", testProperty, testProperty.GetValue, testProperty.SetValue),
new TempDataProperty("TempDataProperty-Test2", test2Property, test2Property.GetValue, test2Property.SetValue)
};
// Act // Act
filter.OnTempDataSaving(tempData); filter.OnTempDataSaving(tempData);
// Assert // Assert
Assert.Equal("TestString", page.Test);
Assert.Equal("TestString", tempData["TempDataProperty-Test"]); Assert.Equal("TestString", tempData["TempDataProperty-Test"]);
Assert.False(tempData.ContainsKey("TestDataProperty-Test2")); Assert.False(tempData.ContainsKey("TestDataProperty-Test2"));
} }
[Fact] [Fact]
public void OnPageExecuting_NullFilterFactory_Throws() public void OnPageExecuting_SetsPropertyValue()
{
// Arrange
var httpContext = new DefaultHttpContext();
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
tempData.Save();
var page = new TestPage();
var filter = CreatePageSaveTempDataPropertyFilter(tempData, filterFactory: false);
var context = new PageHandlerExecutingContext(
new PageContext()
{
ActionDescriptor = new CompiledPageActionDescriptor(),
HttpContext = httpContext,
RouteData = new RouteData(),
},
Array.Empty<IFilterMetadata>(),
null,
new Dictionary<string, object>(),
page);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => filter.OnPageHandlerExecuting(context));
Assert.Contains("FilterFactory", ex.Message);
}
[Fact]
public void OnPageExecuting_ToPageModel_SetsPropertyValue()
{ {
// Arrange // Arrange
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
@ -100,17 +53,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{ {
{ "TempDataProperty-Test", "Value" } { "TempDataProperty-Test", "Value" }
}; };
tempData.Save();
var pageModel = new TestPageModel(); var pageModel = new TestPageModel();
var filter = CreatePageSaveTempDataPropertyFilter(tempData); var filter = CreatePageSaveTempDataPropertyFilter(tempData, "TempDataProperty-");
filter.Subject = pageModel; filter.Subject = pageModel;
var pageType = typeof(TestPageModel);
var testProperty = pageType.GetProperty(nameof(TestPageModel.Test));
var test2Property = pageType.GetProperty(nameof(TestPageModel.Test2));
var context = new PageHandlerExecutingContext( var context = new PageHandlerExecutingContext(
new PageContext() new PageContext()
{ {
@ -131,50 +79,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Null(pageModel.Test2); Assert.Null(pageModel.Test2);
} }
[Fact]
public void OnPageExecuting_ToPage_SetsPropertyValue()
{
// Arrange
var httpContext = new DefaultHttpContext();
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>())
{
{ "TempDataProperty-Test", "Value" }
};
tempData.Save();
var page = new TestPage()
{
ViewContext = CreateViewContext(httpContext, tempData)
};
var filter = CreatePageSaveTempDataPropertyFilter(tempData);
filter.Subject = page;
var pageType = page.GetType();
var testProperty = pageType.GetProperty(nameof(TestPage.Test));
var test2Property = pageType.GetProperty(nameof(TestPage.Test2));
var context = new PageHandlerExecutingContext(
new PageContext()
{
ActionDescriptor = new CompiledPageActionDescriptor(),
HttpContext = httpContext,
RouteData = new RouteData(),
},
Array.Empty<IFilterMetadata>(),
null,
new Dictionary<string, object>(),
page);
// Act
filter.OnPageHandlerExecuting(context);
// Assert
Assert.Equal("Value", page.Test);
Assert.Null(page.Test2);
}
[Fact] [Fact]
public void OnPageExecuting_InitializesAndSavesProperties() public void OnPageExecuting_InitializesAndSavesProperties()
{ {
@ -189,21 +93,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var pageModel = new TestPageModel(); var pageModel = new TestPageModel();
var filter = CreatePageSaveTempDataPropertyFilter(tempData); var filter = CreatePageSaveTempDataPropertyFilter(tempData, "TempDataProperty-");
filter.Subject = pageModel; filter.Subject = pageModel;
var factory = filter.FilterFactory;
var pageType = typeof(TestPageModel); var pageType = typeof(TestPageModel);
var testProperty = pageType.GetProperty(nameof(TestPageModel.Test)); var testProperty = pageType.GetProperty(nameof(TestPageModel.Test));
var test2Property = pageType.GetProperty(nameof(TestPageModel.Test2)); var test2Property = pageType.GetProperty(nameof(TestPageModel.Test2));
filter.Properties = new List<TempDataProperty>
{
new TempDataProperty("TempDataProperty-Test", testProperty, testProperty.GetValue, testProperty.SetValue),
new TempDataProperty("TempDataProperty-Test2", test2Property, test2Property.GetValue, test2Property.SetValue)
};
var context = new PageHandlerExecutingContext( var context = new PageHandlerExecutingContext(
new PageContext() new PageContext()
{ {
@ -224,57 +120,98 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
filter.Properties.OrderBy(p => p.PropertyInfo.Name), filter.Properties.OrderBy(p => p.PropertyInfo.Name),
p => Assert.Equal(testProperty, p.PropertyInfo), p => Assert.Equal(testProperty, p.PropertyInfo),
p => Assert.Equal(test2Property, p.PropertyInfo)); p => Assert.Equal(test2Property, p.PropertyInfo));
Assert.Same(filter.Properties, factory.Properties);
} }
private static ViewContext CreateViewContext(HttpContext httpContext, ITempDataDictionary tempData) [Fact]
public void OnPageExecuting_ReadsTempDataPropertiesWithoutPrefix()
{ {
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); // Arrange
var metadataProvider = new EmptyModelMetadataProvider(); var httpContext = new DefaultHttpContext();
var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
var viewContext = new ViewContext(
actionContext,
NullView.Instance,
viewData,
tempData,
TextWriter.Null,
new HtmlHelperOptions());
return viewContext; var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>())
{
{ "TempDataProperty-Test", "Prefix-Value" },
{ "Test", "Value" }
};
tempData.Save();
var model = new TestPageModel();
var filter = CreatePageSaveTempDataPropertyFilter(tempData, string.Empty);
filter.Subject = model;
var context = new PageHandlerExecutingContext(
new PageContext()
{
ActionDescriptor = new CompiledPageActionDescriptor(),
HttpContext = httpContext,
RouteData = new RouteData(),
},
Array.Empty<IFilterMetadata>(),
null,
new Dictionary<string, object>(),
model);
// Act
filter.OnPageHandlerExecuting(context);
// Assert
Assert.Equal("Value", model.Test);
Assert.Null(model.Test2);
} }
private PageSaveTempDataPropertyFilter CreatePageSaveTempDataPropertyFilter( [Fact]
TempDataDictionary tempData, public void OnTempDataSaving_WritesToTempData_WithoutPrefix()
bool filterFactory = true) {
// Arrange
var httpContext = new DefaultHttpContext();
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>())
{
["Test"] = "Old-Value",
};
var pageModel = new TestPageModel
{
Test = "New-Value",
};
var filter = CreatePageSaveTempDataPropertyFilter(tempData, string.Empty);
filter.Subject = pageModel;
// Act
filter.OnTempDataSaving(tempData);
// Assert
Assert.Collection(
tempData,
item =>
{
Assert.Equal("Test", item.Key);
Assert.Equal("New-Value", item.Value);
});
}
private PageSaveTempDataPropertyFilter CreatePageSaveTempDataPropertyFilter(TempDataDictionary tempData, string prefix)
{ {
var factory = new Mock<ITempDataDictionaryFactory>(); var factory = new Mock<ITempDataDictionaryFactory>();
factory factory
.Setup(f => f.GetTempData(It.IsAny<HttpContext>())) .Setup(f => f.GetTempData(It.IsAny<HttpContext>()))
.Returns(tempData); .Returns(tempData);
var propertyFilter = new PageSaveTempDataPropertyFilter(factory.Object); var pageModelType = typeof(TestPageModel);
var property1 = pageModelType.GetProperty(nameof(TestPageModel.Test));
var property2 = pageModelType.GetProperty(nameof(TestPageModel.Test2));
if (filterFactory) var filter = new PageSaveTempDataPropertyFilter(factory.Object)
{ {
propertyFilter.FilterFactory = Mock.Of<PageSaveTempDataPropertyFilterFactory>(); Properties = new[]
} {
new LifecycleProperty(property1, prefix + property1.Name),
new LifecycleProperty(property2, prefix + property2.Name),
}
};
return propertyFilter; return filter;
}
public class TestPage : Page
{
[TempData]
public string Test { get; set; }
[TempData]
public string Test2 { get; set; }
public override Task ExecuteAsync()
{
throw new NotImplementedException();
}
} }
public class TestPageModel : PageModel public class TestPageModel : PageModel

View File

@ -1,8 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.Extensions.Options;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
@ -10,27 +13,149 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public class TempDataFilterPageApplicationModelProviderTest public class TempDataFilterPageApplicationModelProviderTest
{ {
[Fact] [Fact]
public void OnProvidersExecuting_AddsFiltersToModel() public void OnProvidersExecuting_DoesNotAddFilter_IfTypeHasNoTempDataProperties()
{ {
// Arrange // Arrange
var actionDescriptor = new PageActionDescriptor(); var type = typeof(TestPageModel_NoTempDataProperties);
var applicationModel = new PageApplicationModel( var options = Options.Create(new MvcViewOptions());
actionDescriptor, var provider = new TempDataFilterPageApplicationModelProvider(options);
typeof(object).GetTypeInfo(), var context = CreateProviderContext(type);
new object[0]);
var applicationModelProvider = new TempDataFilterPageApplicationModelProvider();
var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeof(object).GetTypeInfo())
{
PageApplicationModel = applicationModel,
};
// Act // Act
applicationModelProvider.OnProvidersExecuting(context); provider.OnProvidersExecuting(context);
// Assert // Assert
Assert.Empty(context.PageApplicationModel.Filters);
}
[Fact]
public void OnProvidersExecuting_ValidatesTempDataProperties()
{
// Arrange
var type = typeof(TestPageModel_PrivateSet);
var expected = $"The '{type.FullName}.Test' property with TempDataAttribute is invalid. A property using TempDataAttribute must have a public getter and setter.";
var options = Options.Create(new MvcViewOptions());
var provider = new TempDataFilterPageApplicationModelProvider(options);
var context = CreateProviderContext(type);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context));
Assert.Equal(expected, ex.Message);
}
[Fact]
public void AddsTempDataPropertyFilter_ForTempDataAttributeProperties()
{
// Arrange
var type = typeof(TestPageModel_OneTempDataProperty);
var options = Options.Create(new MvcViewOptions());
var provider = new TempDataFilterPageApplicationModelProvider(options);
var context = CreateProviderContext(type);
// Act
provider.OnProvidersExecuting(context);
// Assert
var filter = Assert.Single(context.PageApplicationModel.Filters);
Assert.IsType<PageSaveTempDataPropertyFilterFactory>(filter);
}
[Fact]
public void InitializeFilterFactory_WithExpectedPropertyHelpers_ForTempDataAttributeProperties()
{
// Arrange
var type = typeof(TestPageModel_OneTempDataProperty);
var options = Options.Create(new MvcViewOptions());
var provider = new TempDataFilterPageApplicationModelProvider(options);
var context = CreateProviderContext(type);
// Act
provider.OnProvidersExecuting(context);
// Assert
var filter = Assert.IsType<PageSaveTempDataPropertyFilterFactory>(Assert.Single(context.PageApplicationModel.Filters));
Assert.Collection( Assert.Collection(
applicationModel.Filters, filter.Properties,
filter => Assert.IsType<PageSaveTempDataPropertyFilterFactory>(filter)); property =>
{
Assert.Equal("TempDataProperty-Test2", property.Key);
Assert.Equal(type.GetProperty(nameof(TestPageModel_OneTempDataProperty.Test2)), property.PropertyInfo);
});
}
[Fact]
public void OnProvidersExecuting_SetsKeyPrefixToEmptyString_IfCompatSwitchIsSet()
{
// Arrange
var type = typeof(TestPageModel_OneTempDataProperty);
var options = Options.Create(new MvcViewOptions { SuppressTempDataAttributePrefix = true });
var provider = new TempDataFilterPageApplicationModelProvider(options);
var context = CreateProviderContext(type);
// Act
provider.OnProvidersExecuting(context);
// Assert
var filter = Assert.IsType<PageSaveTempDataPropertyFilterFactory>(Assert.Single(context.PageApplicationModel.Filters));
Assert.Collection(
filter.Properties,
property =>
{
Assert.Equal("Test2", property.Key);
});
}
private static PageApplicationModelProviderContext CreateProviderContext(Type handlerType)
{
var descriptor = new CompiledPageActionDescriptor();
var context = new PageApplicationModelProviderContext(descriptor, typeof(TestPage).GetTypeInfo())
{
PageApplicationModel = new PageApplicationModel(descriptor, handlerType.GetTypeInfo(), Array.Empty<object>()),
};
return context;
}
private static CompiledPageActionDescriptor CreateDescriptor(Type type)
{
return new CompiledPageActionDescriptor(new PageActionDescriptor())
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
HandlerTypeInfo = type.GetTypeInfo(),
};
}
private class TestPage : Page
{
public object Model => null;
public override Task ExecuteAsync() => null;
}
public class TestPageModel_NoTempDataProperties
{
public DateTime? DateTime { get; set; }
}
public class TestPageModel_NullableNonPrimitiveTempDataProperty
{
[TempData]
public DateTime? DateTime { get; set; }
}
public class TestPageModel_OneTempDataProperty
{
public string Test { get; set; }
[TempData]
public string Test2 { get; set; }
}
public class TestPageModel_PrivateSet
{
[TempData]
public string Test { get; private set; }
} }
} }
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
using Xunit; using Xunit;
@ -14,21 +13,16 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
public void CreateInstance_CreatesFilter() public void CreateInstance_CreatesFilter()
{ {
// Arrange // Arrange
var factory = new ControllerSaveTempDataPropertyFilterFactory(); var property = typeof(StringController).GetProperty(nameof(StringController.StringProp));
var propertyInfo = typeof(StringController).GetProperty("StringProp"); var lifecycleProperties = new[] { new LifecycleProperty(property, "key") };
var factory = new ControllerSaveTempDataPropertyFilterFactory(lifecycleProperties);
factory.TempDataProperties = new List<TempDataProperty>()
{
new TempDataProperty("TempDataProperty-StringProp", propertyInfo, null, null)
};
// Act // Act
var filter = factory.CreateInstance(CreateServiceProvider()); var filter = factory.CreateInstance(CreateServiceProvider());
// Assert // Assert
Assert.Collection( var tempDataFilter = Assert.IsType<ControllerSaveTempDataPropertyFilter>(filter);
Assert.IsType<ControllerSaveTempDataPropertyFilter>(filter).Properties, Assert.Same(lifecycleProperties, tempDataFilter.Properties);
property => Assert.Equal(propertyInfo, property.PropertyInfo));
} }
private ServiceProvider CreateServiceProvider() private ServiceProvider CreateServiceProvider()
@ -44,6 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
private class StringController private class StringController
{ {
[TempData]
public string StringProp { get; set; } public string StringProp { get; set; }
} }
} }

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
@ -28,13 +29,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
var controller = new TestController(); var controller = new TestController();
var controllerType = controller.GetType(); var controllerType = controller.GetType();
var testProperty = controllerType.GetProperty(nameof(TestController.Test)); var property1 = controllerType.GetProperty(nameof(TestController.Test));
var test2Property = controllerType.GetProperty(nameof(TestController.Test2)); var property2 = controllerType.GetProperty(nameof(TestController.Test2));
filter.Properties = new List<TempDataProperty> filter.Properties = new[]
{ {
new TempDataProperty("TempDataProperty-Test", testProperty, testProperty.GetValue, testProperty.SetValue), new LifecycleProperty(property1, "TempDataProperty-Test"),
new TempDataProperty("TempDataProperty-Test2", test2Property, test2Property.GetValue, test2Property.SetValue) new LifecycleProperty(property1, "TempDataProperty-Test2"),
}; };
var context = new ActionExecutingContext( var context = new ActionExecutingContext(
@ -73,13 +74,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
var controller = new TestController(); var controller = new TestController();
var controllerType = controller.GetType(); var controllerType = controller.GetType();
var testProperty = controllerType.GetProperty(nameof(TestController.Test));
var test2Property = controllerType.GetProperty(nameof(TestController.Test2));
filter.Properties = new List<TempDataProperty> var property1 = controllerType.GetProperty(nameof(TestController.Test));
var property2 = controllerType.GetProperty(nameof(TestController.Test2));
filter.Properties = new[]
{ {
new TempDataProperty("TempDataProperty-Test", testProperty, testProperty.GetValue, testProperty.SetValue), new LifecycleProperty(property1, "TempDataProperty-Test"),
new TempDataProperty("TempDataProperty-Test2", test2Property, test2Property.GetValue, test2Property.SetValue) new LifecycleProperty(property2, "TempDataProperty-Test2"),
}; };
var context = new ActionExecutingContext( var context = new ActionExecutingContext(
@ -95,13 +97,108 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
// Act // Act
filter.OnActionExecuting(context); filter.OnActionExecuting(context);
filter.OnTempDataSaving(tempData);
// Assert // Assert
Assert.Equal("FirstValue", controller.Test); Assert.Equal("FirstValue", controller.Test);
Assert.Equal(0, controller.Test2); Assert.Equal(0, controller.Test2);
} }
[Fact]
public void ReadsTempDataFromTempDataDictionary_WithoutKeyPrefix()
{
// Arrange
var httpContext = new DefaultHttpContext();
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>())
{
["TempDataProperty-Test"] = "ValueWithPrefix",
["Test"] = "Value"
};
var filter = CreateControllerSaveTempDataPropertyFilter(httpContext, tempData: tempData);
var controller = new TestController();
var controllerType = controller.GetType();
var property1 = controllerType.GetProperty(nameof(TestController.Test));
var property2 = controllerType.GetProperty(nameof(TestController.Test2));
filter.Properties = new[]
{
new LifecycleProperty(property1, "Test"),
new LifecycleProperty(property2, "Test2"),
};
var context = new ActionExecutingContext(
new ActionContext
{
HttpContext = httpContext,
RouteData = new RouteData(),
ActionDescriptor = new ActionDescriptor(),
},
new List<IFilterMetadata>(),
new Dictionary<string, object>(),
controller);
// Act
filter.OnActionExecuting(context);
// Assert
Assert.Equal("Value", controller.Test);
Assert.Equal(0, controller.Test2);
}
[Fact]
public void WritesTempDataFromTempDataDictionary_WithoutKeyPrefix()
{
// Arrange
var httpContext = new DefaultHttpContext();
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
var filter = CreateControllerSaveTempDataPropertyFilter(httpContext, tempData: tempData);
var controller = new TestController();
var controllerType = controller.GetType();
var property1 = controllerType.GetProperty(nameof(TestController.Test));
var property2 = controllerType.GetProperty(nameof(TestController.Test2));
filter.Properties = new[]
{
new LifecycleProperty(property1, "Test"),
new LifecycleProperty(property2, "Test2"),
};
var context = new ActionExecutingContext(
new ActionContext
{
HttpContext = httpContext,
RouteData = new RouteData(),
ActionDescriptor = new ActionDescriptor(),
},
new List<IFilterMetadata>(),
new Dictionary<string, object>(),
controller);
// Act
filter.OnActionExecuting(context);
controller.Test = "New-Value";
controller.Test2 = 42;
filter.OnTempDataSaving(tempData);
// Assert
Assert.Collection(
tempData.OrderBy(i => i.Key),
item =>
{
Assert.Equal(nameof(TestController.Test), item.Key);
Assert.Equal("New-Value", item.Value);
},
item =>
{
Assert.Equal(nameof(TestController.Test2), item.Key);
Assert.Equal(42, item.Value);
});
}
private ControllerSaveTempDataPropertyFilter CreateControllerSaveTempDataPropertyFilter( private ControllerSaveTempDataPropertyFilter CreateControllerSaveTempDataPropertyFilter(
HttpContext httpContext, HttpContext httpContext,
TempDataDictionary tempData) TempDataDictionary tempData)

View File

@ -0,0 +1,94 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{
public class LifecyclePropertyTest
{
[Fact]
public void GetValue_GetsPropertyValue()
{
// Arrange
var propertyInfo = typeof(TestSubject).GetProperty(nameof(TestSubject.TestProperty));
var lifecycleProperty = new LifecycleProperty(propertyInfo, "test-key");
var subject = new TestSubject { TestProperty = "test-value" };
// Act
var value = lifecycleProperty.GetValue(subject);
// Assert
Assert.Equal("test-value", value);
}
[Fact]
public void SetValue_SetsPropertyValue()
{
// Arrange
var propertyInfo = typeof(TestSubject).GetProperty(nameof(TestSubject.TestProperty));
var lifecycleProperty = new LifecycleProperty(propertyInfo, "test-key");
var subject = new TestSubject { TestProperty = "test-value" };
// Act
lifecycleProperty.SetValue(subject, "new-value");
// Assert
Assert.Equal("new-value", subject.TestProperty);
}
[Fact]
public void SetValue_SetsNullPropertyValue()
{
// Arrange
var propertyInfo = typeof(TestSubject).GetProperty(nameof(TestSubject.TestProperty));
var lifecycleProperty = new LifecycleProperty(propertyInfo, "test-key");
var subject = new TestSubject { TestProperty = "test-value" };
// Act
lifecycleProperty.SetValue(subject, null);
// Assert
Assert.Null(subject.TestProperty);
}
[Fact]
public void SetValue_NoopsIfNullIsBeingAssignedToValueType()
{
// Arrange
var propertyInfo = typeof(TestSubject).GetProperty(nameof(TestSubject.ValueTypeProperty));
var lifecycleProperty = new LifecycleProperty(propertyInfo, "test-key");
var subject = new TestSubject { ValueTypeProperty = 42 };
// Act
lifecycleProperty.SetValue(subject, null);
// Assert
Assert.Equal(42, subject.ValueTypeProperty);
}
[Fact]
public void SetValue_SetsNullValue_ForNullableProperties()
{
// Arrange
var propertyInfo = typeof(TestSubject).GetProperty(nameof(TestSubject.NullableProperty));
var lifecycleProperty = new LifecycleProperty(propertyInfo, "test-key");
var subject = new TestSubject { NullableProperty = 42 };
// Act
lifecycleProperty.SetValue(subject, null);
// Assert
Assert.Null(subject.NullableProperty);
}
public class TestSubject
{
public string TestProperty { get; set; }
public int ValueTypeProperty { get; set; }
public int? NullableProperty { get; set; }
}
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
@ -14,16 +13,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{ {
public class TempDataApplicationModelProviderTest public class TempDataApplicationModelProviderTest
{ {
[Theory] [Fact]
[InlineData(typeof(TestController_OneTempDataProperty))] public void OnProvidersExecuting_DoesNotAddFilter_IfTypeHasNoTempDataProperties()
[InlineData(typeof(TestController_ListOfString))]
[InlineData(typeof(TestController_OneNullableTempDataProperty))]
[InlineData(typeof(TestController_TwoTempDataProperties))]
[InlineData(typeof(TestController_NullableNonPrimitiveTempDataProperty))]
public void AddsTempDataPropertyFilter_ForTempDataAttributeProperties(Type type)
{ {
// Arrange // Arrange
var provider = new TempDataApplicationModelProvider(); var type = typeof(TestController_NoTempDataProperties);
var options = Options.Create(new MvcViewOptions());
var provider = new TempDataApplicationModelProvider(options);
var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions()));
var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() });
@ -34,14 +30,54 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
// Assert // Assert
var controller = Assert.Single(context.Result.Controllers); var controller = Assert.Single(context.Result.Controllers);
Assert.Single(controller.Filters, f => f is ControllerSaveTempDataPropertyFilterFactory); Assert.Empty(controller.Filters);
}
[Fact]
public void OnProvidersExecuting_ValidatesTempDataProperties()
{
// Arrange
var type = typeof(TestController_PrivateSet);
var options = Options.Create(new MvcViewOptions());
var provider = new TempDataApplicationModelProvider(options);
var expected = $"The '{type.FullName}.Test' property with TempDataAttribute is invalid. A property using TempDataAttribute must have a public getter and setter.";
var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions()));
var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() });
defaultProvider.OnProvidersExecuting(context);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context));
Assert.Equal(expected, ex.Message);
}
[Fact]
public void AddsTempDataPropertyFilter_ForTempDataAttributeProperties()
{
// Arrange
var type = typeof(TestController_NullableNonPrimitiveTempDataProperty);
var options = Options.Create(new MvcViewOptions());
var provider = new TempDataApplicationModelProvider(options);
var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions()));
var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() });
defaultProvider.OnProvidersExecuting(context);
// Act
provider.OnProvidersExecuting(context);
// Assert
var controller = Assert.Single(context.Result.Controllers);
Assert.IsType<ControllerSaveTempDataPropertyFilterFactory>(Assert.Single(controller.Filters));
} }
[Fact] [Fact]
public void InitializeFilterFactory_WithExpectedPropertyHelpers_ForTempDataAttributeProperties() public void InitializeFilterFactory_WithExpectedPropertyHelpers_ForTempDataAttributeProperties()
{ {
// Arrange // Arrange
var provider = new TempDataApplicationModelProvider(); var expected = typeof(TestController_OneTempDataProperty).GetProperty(nameof(TestController_OneTempDataProperty.Test2));
var options = Options.Create(new MvcViewOptions());
var provider = new TempDataApplicationModelProvider(options);
var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions()));
var context = new ApplicationModelProviderContext(new[] { typeof(TestController_OneTempDataProperty).GetTypeInfo() }); var context = new ApplicationModelProviderContext(new[] { typeof(TestController_OneTempDataProperty).GetTypeInfo() });
@ -50,79 +86,42 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
// Act // Act
provider.OnProvidersExecuting(context); provider.OnProvidersExecuting(context);
var controller = context.Result.Controllers.SingleOrDefault(); var controller = context.Result.Controllers.SingleOrDefault();
var filter = controller.Filters.OfType<ControllerSaveTempDataPropertyFilterFactory>(); var filter = Assert.IsType<ControllerSaveTempDataPropertyFilterFactory>(Assert.Single(controller.Filters));
var saveTempDataPropertyFilterFactory = filter.SingleOrDefault();
var expected = typeof(TestController_OneTempDataProperty).GetProperty(nameof(TestController_OneTempDataProperty.Test2));
// Assert // Assert
Assert.NotNull(saveTempDataPropertyFilterFactory); Assert.NotNull(filter);
var tempDataPropertyHelper = Assert.Single(saveTempDataPropertyFilterFactory.TempDataProperties); var property = Assert.Single(filter.TempDataProperties);
Assert.Same(expected, tempDataPropertyHelper.PropertyInfo); Assert.Same(expected, property.PropertyInfo);
Assert.Equal("TempDataProperty-Test2", property.Key);
} }
[Fact] [Fact]
public void ThrowsInvalidOperationException_PrivateSetter() public void OnProvidersExecuting_SetsKeyPrefixToEmptyString_IfCompatSwitchIsSet()
{ {
// Arrange // Arrange
var provider = new TempDataApplicationModelProvider(); var expected = typeof(TestController_OneTempDataProperty).GetProperty(nameof(TestController_OneTempDataProperty.Test2));
var options = Options.Create(new MvcViewOptions { SuppressTempDataAttributePrefix = true });
var provider = new TempDataApplicationModelProvider(options);
var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions())); var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions()));
var context = new ApplicationModelProviderContext(new[] { typeof(TestController_PrivateSet).GetTypeInfo() }); var context = new ApplicationModelProviderContext(new[] { typeof(TestController_OneTempDataProperty).GetTypeInfo() });
defaultProvider.OnProvidersExecuting(context); defaultProvider.OnProvidersExecuting(context);
// Act & Assert // Act
var exception = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context);
provider.OnProvidersExecuting(context)); var controller = context.Result.Controllers.SingleOrDefault();
var filter = Assert.IsType<ControllerSaveTempDataPropertyFilterFactory>(Assert.Single(controller.Filters));
Assert.Equal( // Assert
$"The '{typeof(TestController_PrivateSet).FullName}.{nameof(TestController_NonPrimitiveType.Test)}'" Assert.NotNull(filter);
+ $" property with {nameof(TempDataAttribute)} is invalid. A property using {nameof(TempDataAttribute)}" var property = Assert.Single(filter.TempDataProperties);
+ " must have a public getter and setter.", Assert.Same(expected, property.PropertyInfo);
exception.Message); Assert.Equal("Test2", property.Key);
} }
[Fact] public class TestController_NoTempDataProperties
public void ThrowsInvalidOperationException_NonPrimitiveType()
{ {
// Arrange public DateTime? DateTime { get; set; }
var provider = new TempDataApplicationModelProvider();
var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions()));
var context = new ApplicationModelProviderContext(new[] { typeof(TestController_NonPrimitiveType).GetTypeInfo() });
defaultProvider.OnProvidersExecuting(context);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
provider.OnProvidersExecuting(context));
Assert.Equal(
$"The '{typeof(TestController_NonPrimitiveType).FullName}.{nameof(TestController_NonPrimitiveType.Test)}'"
+ $" property with {nameof(TempDataAttribute)} is invalid. The '{typeof(TempDataSerializer).FullName}'"
+ $" cannot serialize an object of type '{typeof(Object).FullName}'.",
exception.Message);
}
[Fact]
public void ThrowsInvalidOperationException_NonStringDictionaryKey()
{
// Arrange
var provider = new TempDataApplicationModelProvider();
var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions()));
var context = new ApplicationModelProviderContext(
new[] { typeof(TestController_NonStringDictionaryKey).GetTypeInfo() });
defaultProvider.OnProvidersExecuting(context);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
provider.OnProvidersExecuting(context));
Assert.Equal(
$"The '{typeof(TestController_NonStringDictionaryKey).FullName}.{nameof(TestController_NonStringDictionaryKey.Test)}'"
+ $" property with {nameof(TempDataAttribute)} is invalid. The '{typeof(TempDataSerializer).FullName}'"
+ $" cannot serialize a dictionary with a key of type '{typeof(Object)}'. The key must be of type"
+ $" '{typeof(string).FullName}'.",
exception.Message);
} }
public class TestController_NullableNonPrimitiveTempDataProperty public class TestController_NullableNonPrimitiveTempDataProperty
@ -139,45 +138,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
public string Test2 { get; set; } public string Test2 { get; set; }
} }
public class TestController_TwoTempDataProperties
{
[TempData]
public string Test { get; set; }
[TempData]
public int Test2 { get; set; }
}
public class TestController_OneNullableTempDataProperty
{
public string Test { get; set; }
[TempData]
public int? Test2 { get; set; }
}
public class TestController_ListOfString
{
[TempData]
public IList<string> Test { get; set; }
}
public class TestController_PrivateSet public class TestController_PrivateSet
{ {
[TempData] [TempData]
public string Test { get; private set; } public string Test { get; private set; }
} }
public class TestController_NonPrimitiveType
{
[TempData]
public object Test { get; set; }
}
public class TestController_NonStringDictionaryKey
{
[TempData]
public IDictionary<object, object> Test { get; set; }
}
} }
} }