parent
ce28117b8b
commit
1197657e5b
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||||
using Microsoft.AspNetCore.Razor.Evolution;
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
|
@ -75,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
var index = parentDirectoryPath.LastIndexOf('/');
|
var index = parentDirectoryPath.LastIndexOf('/');
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
{
|
{
|
||||||
parentDirectoryPath = string.Empty;
|
parentDirectoryPath = string.Empty;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -357,7 +357,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
{
|
{
|
||||||
propertyFilter.OriginalValues = originalValues;
|
propertyFilter.OriginalValues = originalValues;
|
||||||
propertyFilter.Subject = _page;
|
propertyFilter.Subject = _page;
|
||||||
propertyFilter.Prefix = TempDataPropertyProvider.Prefix;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IActionResult result = null;
|
IActionResult result = null;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||||
using Microsoft.AspNetCore.Razor.Evolution;
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support for [TempData] on properties
|
// Support for [TempData] on properties
|
||||||
options.ConfigureFilter(new SaveTempDataPropertyFilter());
|
options.ConfigureFilter(new SaveTempDataPropertyFilterFactory());
|
||||||
// Always require an antiforgery token on post
|
// Always require an antiforgery token on post
|
||||||
options.ConfigureFilter(new AutoValidateAntiforgeryTokenAttribute());
|
options.ConfigureFilter(new AutoValidateAntiforgeryTokenAttribute());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
@ -197,6 +198,11 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
//
|
//
|
||||||
// Temp Data
|
// Temp Data
|
||||||
//
|
//
|
||||||
|
services.TryAddEnumerable(
|
||||||
|
ServiceDescriptor.Transient<IApplicationModelProvider, TempDataApplicationModelProvider>());
|
||||||
|
services.TryAddSingleton<SaveTempDataFilter>();
|
||||||
|
services.TryAddTransient<SaveTempDataPropertyFilter>();
|
||||||
|
|
||||||
// This does caching so it should stay singleton
|
// This does caching so it should stay singleton
|
||||||
services.TryAddSingleton<ITempDataProvider, CookieTempDataProvider>();
|
services.TryAddSingleton<ITempDataProvider, CookieTempDataProvider>();
|
||||||
|
|
||||||
|
|
@ -208,8 +214,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
|
||||||
// These are stateless so their lifetime isn't really important.
|
// These are stateless so their lifetime isn't really important.
|
||||||
services.TryAddSingleton<ITempDataDictionaryFactory, TempDataDictionaryFactory>();
|
services.TryAddSingleton<ITempDataDictionaryFactory, TempDataDictionaryFactory>();
|
||||||
services.TryAddSingleton<SaveTempDataFilter>();
|
|
||||||
|
|
||||||
services.TryAddSingleton(ArrayPool<ViewBufferValue>.Shared);
|
services.TryAddSingleton(ArrayPool<ViewBufferValue>.Shared);
|
||||||
services.TryAddScoped<IViewBufferScope, MemoryPoolViewBufferScope>();
|
services.TryAddScoped<IViewBufferScope, MemoryPoolViewBufferScope>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
// 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 Microsoft.AspNetCore.Mvc.Filters;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,26 @@
|
||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
{
|
{
|
||||||
public class SaveTempDataPropertyFilter : ISaveTempDataCallback
|
public class SaveTempDataPropertyFilter : ISaveTempDataCallback, IActionFilter
|
||||||
{
|
{
|
||||||
public string Prefix { get; set; }
|
private const string Prefix = "TempDataProperty-";
|
||||||
|
private readonly ITempDataDictionaryFactory _factory;
|
||||||
|
|
||||||
|
public SaveTempDataPropertyFilter(ITempDataDictionaryFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot be public as <c>PropertyHelper</c> is an internal shared source type
|
||||||
|
internal IList<PropertyHelper> PropertyHelpers { get; set; }
|
||||||
|
|
||||||
public object Subject { get; set; }
|
public object Subject { get; set; }
|
||||||
|
|
||||||
|
|
@ -24,12 +36,46 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
var originalValue = kvp.Value;
|
var originalValue = kvp.Value;
|
||||||
|
|
||||||
var newValue = property.GetValue(Subject);
|
var newValue = property.GetValue(Subject);
|
||||||
if (newValue != null && newValue != originalValue)
|
if (newValue != null && !newValue.Equals(originalValue))
|
||||||
{
|
{
|
||||||
tempData[Prefix + property.Name] = newValue;
|
tempData[Prefix + property.Name] = newValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnActionExecuting(ActionExecutingContext context)
|
||||||
|
{
|
||||||
|
if (PropertyHelpers == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(PropertyHelpers));
|
||||||
|
}
|
||||||
|
|
||||||
|
Subject = context.Controller;
|
||||||
|
var tempData = _factory.GetTempData(context.HttpContext);
|
||||||
|
|
||||||
|
OriginalValues = new Dictionary<PropertyInfo, object>();
|
||||||
|
|
||||||
|
for (var i = 0; i < PropertyHelpers.Count; i++)
|
||||||
|
{
|
||||||
|
var property = PropertyHelpers[i].Property;
|
||||||
|
var value = tempData[Prefix + property.Name];
|
||||||
|
|
||||||
|
OriginalValues[property] = value;
|
||||||
|
|
||||||
|
var propertyTypeInfo = property.PropertyType.GetTypeInfo();
|
||||||
|
|
||||||
|
var isReferenceTypeOrNullable = !propertyTypeInfo.IsValueType || Nullable.GetUnderlyingType(property.GetType()) != null;
|
||||||
|
if (value != null || isReferenceTypeOrNullable)
|
||||||
|
{
|
||||||
|
property.SetValue(Subject, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnActionExecuted(ActionExecutedContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
|
{
|
||||||
|
public class SaveTempDataPropertyFilterFactory : IFilterFactory
|
||||||
|
{
|
||||||
|
// Cannot be public as <c>PropertyHelper</c> is an internal shared source type
|
||||||
|
internal IList<PropertyHelper> TempDataProperties { get; set; }
|
||||||
|
|
||||||
|
public bool IsReusable => false;
|
||||||
|
|
||||||
|
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
if (serviceProvider == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(serviceProvider));
|
||||||
|
}
|
||||||
|
|
||||||
|
var service = serviceProvider.GetRequiredService<SaveTempDataPropertyFilter>();
|
||||||
|
service.PropertyHelpers = TempDataProperties;
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
|
{
|
||||||
|
public class TempDataApplicationModelProvider : IApplicationModelProvider
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks>This order ensures that <see cref="TempDataApplicationModelProvider"/> runs after the <see cref="DefaultApplicationModelProvider"/>.</remarks>
|
||||||
|
public int Order => -1000 + 10;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void OnProvidersExecuted(ApplicationModelProviderContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void OnProvidersExecuting(ApplicationModelProviderContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var controllerModel in context.Result.Controllers)
|
||||||
|
{
|
||||||
|
SaveTempDataPropertyFilterFactory factory = null;
|
||||||
|
var propertyHelpers = PropertyHelper.GetVisibleProperties(type: controllerModel.ControllerType.AsType());
|
||||||
|
for (var i = 0; i < propertyHelpers.Length; i++)
|
||||||
|
{
|
||||||
|
var propertyHelper = propertyHelpers[i];
|
||||||
|
if (propertyHelper.Property.IsDefined(typeof(TempDataAttribute)))
|
||||||
|
{
|
||||||
|
ValidateProperty(propertyHelper);
|
||||||
|
if (factory == null)
|
||||||
|
{
|
||||||
|
factory = new SaveTempDataPropertyFilterFactory()
|
||||||
|
{
|
||||||
|
TempDataProperties = new List<PropertyHelper>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory.TempDataProperties.Add(propertyHelper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (factory != null)
|
||||||
|
{
|
||||||
|
controllerModel.Filters.Add(factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateProperty(PropertyHelper propertyHelper)
|
||||||
|
{
|
||||||
|
var property = propertyHelper.Property;
|
||||||
|
if (!(property.SetMethod != null &&
|
||||||
|
property.SetMethod.IsPublic &&
|
||||||
|
property.GetMethod != null &&
|
||||||
|
property.GetMethod.IsPublic))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatTempDataProperties_PublicGetterSetter(property.DeclaringType.FullName, property.Name, nameof(TempDataAttribute)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(property.PropertyType.GetTypeInfo().IsPrimitive || property.PropertyType == typeof(string)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
Resources.FormatTempDataProperties_PrimitiveTypeOrString(property.DeclaringType.FullName, property.Name, nameof(TempDataAttribute)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,8 @@ using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
{
|
{
|
||||||
public class TempDataPropertyProvider
|
public class TempDataPropertyProvider
|
||||||
{
|
{
|
||||||
|
|
@ -18,6 +16,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
private ConcurrentDictionary<Type, IEnumerable<PropertyInfo>> _subjectProperties =
|
private ConcurrentDictionary<Type, IEnumerable<PropertyInfo>> _subjectProperties =
|
||||||
new ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>();
|
new ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads and tracks any changes to the properties of the <paramref name="subject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subject">The properties of the subject are loaded and tracked. May be a <see cref="Controller"/>.</param>
|
||||||
|
/// <param name="tempData">The <see cref="ITempDataDictionary"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
public IDictionary<PropertyInfo, object> LoadAndTrackChanges(object subject, ITempDataDictionary tempData)
|
public IDictionary<PropertyInfo, object> LoadAndTrackChanges(object subject, ITempDataDictionary tempData)
|
||||||
{
|
{
|
||||||
var properties = GetSubjectProperties(subject);
|
var properties = GetSubjectProperties(subject);
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -286,4 +286,10 @@
|
||||||
<data name="ViewEnginesAreRequired" xml:space="preserve">
|
<data name="ViewEnginesAreRequired" xml:space="preserve">
|
||||||
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.</value>
|
<value>'{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TempDataProperties_PrimitiveTypeOrString" xml:space="preserve">
|
||||||
|
<value>The '{0}.{1}' property with {2} is invalid. A property using {2} must be of primitive or string type.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TempDataProperties_PublicGetterSetter" xml:space="preserve">
|
||||||
|
<value>The '{0}.{1}' property with {2} is invalid. A property using {2} must have a public getter and setter.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -3,8 +3,11 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Rendering
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Properties with the <see cref="TempDataAttribute"/> are stored in the <see cref="TempDataDictionary"/>.
|
||||||
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||||
public sealed class TempDataAttribute : Attribute
|
public sealed class TempDataAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class TempDataPropertyTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||||
|
{
|
||||||
|
protected HttpClient Client { get; }
|
||||||
|
|
||||||
|
public TempDataPropertyTest(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||||
|
{
|
||||||
|
Client = fixture.Client;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TempDataPropertyAttribute_RetainsTempDataWithView()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var message = "Success (from Temp Data)";
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("FullName", "Bob"),
|
||||||
|
new KeyValuePair<string, string>("id", "1"),
|
||||||
|
};
|
||||||
|
var expected = $"{message} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}.";
|
||||||
|
var content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act 1
|
||||||
|
var redirectResponse = await Client.PostAsync("TempDataProperty/CreateForView", content);
|
||||||
|
|
||||||
|
// Assert 1
|
||||||
|
Assert.Equal(HttpStatusCode.Redirect, redirectResponse.StatusCode);
|
||||||
|
|
||||||
|
// Act 2
|
||||||
|
var response = await Client.SendAsync(GetRequest(redirectResponse.Headers.Location.ToString(), redirectResponse));
|
||||||
|
|
||||||
|
// Assert 2
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(expected, body.ToString().Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TempDataPropertyAttribute_RetainsTempDataWithoutView()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var message = "Success (from Temp Data)";
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("FullName", "Bob"),
|
||||||
|
new KeyValuePair<string, string>("id", "1"),
|
||||||
|
};
|
||||||
|
var expected = $"{message} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}.";
|
||||||
|
var content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act 1
|
||||||
|
var redirectResponse = await Client.PostAsync("TempDataProperty/Create", content);
|
||||||
|
|
||||||
|
// Assert 1
|
||||||
|
Assert.Equal(HttpStatusCode.Redirect, redirectResponse.StatusCode);
|
||||||
|
|
||||||
|
// Act 2
|
||||||
|
var response = await Client.SendAsync(GetRequest(redirectResponse.Headers.Location.ToString(), redirectResponse));
|
||||||
|
|
||||||
|
// Assert 2
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(expected, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TempDataPropertyAttribute_TempDataKept()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var message = "Success (from Temp Data)";
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("FullName", "Bob"),
|
||||||
|
new KeyValuePair<string, string>("id", "1"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var expected = $"{message} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}.";
|
||||||
|
var content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act 1
|
||||||
|
var response = await Client.PostAsync("TempDataProperty/CreateNoRedirect", content);
|
||||||
|
|
||||||
|
// Assert 1
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
// Act 2
|
||||||
|
response = await Client.SendAsync(GetRequest("TempDataProperty/TempDataKept", response));
|
||||||
|
|
||||||
|
// Assert 2
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(message, body);
|
||||||
|
|
||||||
|
// Act 3
|
||||||
|
response = await Client.SendAsync(GetRequest("TempDataProperty/ReadTempData", response));
|
||||||
|
|
||||||
|
// Assert 3
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
body = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(message, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TempDataPropertyAttribute_TempDataNotKept()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var message = "Success (from Temp Data)";
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("FullName", "Bob"),
|
||||||
|
new KeyValuePair<string, string>("id", "1"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var expected = $"{message} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}.";
|
||||||
|
var content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act 1
|
||||||
|
var response = await Client.PostAsync("TempDataProperty/CreateNoRedirect", content);
|
||||||
|
|
||||||
|
// Assert 1
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
// Act 2
|
||||||
|
response = await Client.SendAsync(GetRequest("TempDataProperty/ReadTempData", response));
|
||||||
|
|
||||||
|
// Assert 2
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(message, body);
|
||||||
|
|
||||||
|
// Act 3
|
||||||
|
response = await Client.SendAsync(GetRequest("TempDataProperty/ReadTempData", response));
|
||||||
|
|
||||||
|
// Assert 3
|
||||||
|
body = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Empty(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequestMessage GetRequest(string path, HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, path);
|
||||||
|
if (response.Headers.TryGetValues("Set-Cookie", out var values))
|
||||||
|
{
|
||||||
|
foreach (var cookie in SetCookieHeaderValue.ParseList(values.ToList()))
|
||||||
|
{
|
||||||
|
if (cookie.Expires == null || cookie.Expires >= DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
request.Headers.Add("Cookie", new CookieHeaderValue(cookie.Name, cookie.Value).ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -292,7 +292,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
filterDescriptor =>
|
filterDescriptor =>
|
||||||
{
|
{
|
||||||
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
|
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
|
||||||
Assert.IsType<SaveTempDataPropertyFilter>(filterDescriptor.Filter);
|
Assert.IsType<SaveTempDataPropertyFilterFactory>(filterDescriptor.Filter);
|
||||||
},
|
},
|
||||||
filterDescriptor =>
|
filterDescriptor =>
|
||||||
{
|
{
|
||||||
|
|
@ -343,7 +343,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
filterDescriptor =>
|
filterDescriptor =>
|
||||||
{
|
{
|
||||||
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
|
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
|
||||||
Assert.IsType<SaveTempDataPropertyFilter>(filterDescriptor.Filter);
|
Assert.IsType<SaveTempDataPropertyFilterFactory>(filterDescriptor.Filter);
|
||||||
},
|
},
|
||||||
filterDescriptor =>
|
filterDescriptor =>
|
||||||
{
|
{
|
||||||
|
|
@ -396,7 +396,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
filterDescriptor =>
|
filterDescriptor =>
|
||||||
{
|
{
|
||||||
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
|
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
|
||||||
Assert.IsType<SaveTempDataPropertyFilter>(filterDescriptor.Filter);
|
Assert.IsType<SaveTempDataPropertyFilterFactory>(filterDescriptor.Filter);
|
||||||
},
|
},
|
||||||
filterDescriptor =>
|
filterDescriptor =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||||
using Microsoft.AspNetCore.Razor.Evolution;
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(applicationModel.Filters,
|
Assert.Collection(applicationModel.Filters,
|
||||||
filter => Assert.IsType<SaveTempDataPropertyFilter>(filter),
|
filter => Assert.IsType<SaveTempDataPropertyFilterFactory>(filter),
|
||||||
filter => Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filter));
|
filter => Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -409,6 +409,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
typeof(DefaultApplicationModelProvider),
|
typeof(DefaultApplicationModelProvider),
|
||||||
typeof(CorsApplicationModelProvider),
|
typeof(CorsApplicationModelProvider),
|
||||||
typeof(AuthorizationApplicationModelProvider),
|
typeof(AuthorizationApplicationModelProvider),
|
||||||
|
typeof(TempDataApplicationModelProvider),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
// 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 System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
|
{
|
||||||
|
public class SaveTempDataPropertyFilterTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SaveTempDataPropertyFilter_PopulatesTempDataWithValuesFromControllerProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>())
|
||||||
|
{
|
||||||
|
["TempDataProperty-Test"] = "FirstValue"
|
||||||
|
};
|
||||||
|
|
||||||
|
var factory = new Mock<ITempDataDictionaryFactory>();
|
||||||
|
factory.Setup(f => f.GetTempData(httpContext))
|
||||||
|
.Returns(tempData);
|
||||||
|
|
||||||
|
var filter = new SaveTempDataPropertyFilter(factory.Object);
|
||||||
|
|
||||||
|
var controller = new TestController();
|
||||||
|
var controllerType = controller.GetType().GetTypeInfo();
|
||||||
|
|
||||||
|
var propertyHelper1 = new PropertyHelper(controllerType.GetProperty(nameof(TestController.Test)));
|
||||||
|
var propertyHelper2 = new PropertyHelper(controllerType.GetProperty(nameof(TestController.Test2)));
|
||||||
|
var propertyHelpers = new List<PropertyHelper>
|
||||||
|
{
|
||||||
|
propertyHelper1,
|
||||||
|
propertyHelper2,
|
||||||
|
};
|
||||||
|
|
||||||
|
filter.PropertyHelpers = propertyHelpers;
|
||||||
|
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 = "SecondValue";
|
||||||
|
filter.OnTempDataSaving(tempData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("SecondValue", controller.Test);
|
||||||
|
Assert.Equal("SecondValue", tempData["TempDataProperty-Test"]);
|
||||||
|
Assert.Equal(0, controller.Test2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SaveTempDataPropertyFilter_ReadsTempDataFromTempDataDictionary()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>())
|
||||||
|
{
|
||||||
|
["TempDataProperty-Test"] = "FirstValue"
|
||||||
|
};
|
||||||
|
|
||||||
|
var factory = new Mock<ITempDataDictionaryFactory>();
|
||||||
|
factory.Setup(f => f.GetTempData(httpContext))
|
||||||
|
.Returns(tempData);
|
||||||
|
|
||||||
|
var filter = new SaveTempDataPropertyFilter(factory.Object);
|
||||||
|
var controller = new TestController();
|
||||||
|
var controllerType = controller.GetType().GetTypeInfo();
|
||||||
|
|
||||||
|
var propertyHelper1 = new PropertyHelper(controllerType.GetProperty(nameof(TestController.Test)));
|
||||||
|
var propertyHelper2 = new PropertyHelper(controllerType.GetProperty(nameof(TestController.Test2)));
|
||||||
|
var propertyHelpers = new List<PropertyHelper>
|
||||||
|
{
|
||||||
|
propertyHelper1,
|
||||||
|
propertyHelper2,
|
||||||
|
};
|
||||||
|
|
||||||
|
filter.PropertyHelpers = propertyHelpers;
|
||||||
|
|
||||||
|
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);
|
||||||
|
filter.OnTempDataSaving(tempData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("FirstValue", controller.Test);
|
||||||
|
Assert.Equal(0, controller.Test2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestController : Controller
|
||||||
|
{
|
||||||
|
[TempData]
|
||||||
|
public string Test { get; set; }
|
||||||
|
|
||||||
|
[TempData]
|
||||||
|
public int Test2 { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
|
{
|
||||||
|
public class TempDataApplicationModelProviderTest
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(TestController_OneTempDataProperty))]
|
||||||
|
[InlineData(typeof(TestController_TwoTempDataProperties))]
|
||||||
|
public void AddsTempDataPropertyFilter_ForTempDataAttributeProperties(Type type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new TempDataApplicationModelProvider();
|
||||||
|
var defaultProvider = new DefaultApplicationModelProvider(new TestOptionsManager<MvcOptions>());
|
||||||
|
|
||||||
|
var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() });
|
||||||
|
defaultProvider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var controller = Assert.Single(context.Result.Controllers);
|
||||||
|
Assert.Single(controller.Filters, f => f is SaveTempDataPropertyFilterFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InitializeFilterFactory_WithExpectedPropertyHelpers_ForTempDataAttributeProperties()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new TempDataApplicationModelProvider();
|
||||||
|
var defaultProvider = new DefaultApplicationModelProvider(new TestOptionsManager<MvcOptions>());
|
||||||
|
|
||||||
|
var context = new ApplicationModelProviderContext(new[] { typeof(TestController_OneTempDataProperty).GetTypeInfo() });
|
||||||
|
defaultProvider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.OnProvidersExecuting(context);
|
||||||
|
var controller = context.Result.Controllers.SingleOrDefault();
|
||||||
|
var filter = controller.Filters.OfType<SaveTempDataPropertyFilterFactory>();
|
||||||
|
var saveTempDataPropertyFilterFactory = filter.SingleOrDefault();
|
||||||
|
var expected = typeof(TestController_OneTempDataProperty).GetProperty(nameof(TestController_OneTempDataProperty.Test2));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(saveTempDataPropertyFilterFactory);
|
||||||
|
var tempDataPropertyHelper = Assert.Single(saveTempDataPropertyFilterFactory.TempDataProperties);
|
||||||
|
Assert.Same(expected, tempDataPropertyHelper.Property);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DoesNotInitializeFilterFactory_ThrowsInvalidOperationException_NonPrimitiveType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new TempDataApplicationModelProvider();
|
||||||
|
var defaultProvider = new DefaultApplicationModelProvider(new TestOptionsManager<MvcOptions>());
|
||||||
|
|
||||||
|
var context = new ApplicationModelProviderContext(new[] { typeof(TestController_OneValid_OneInvalidProperty).GetTypeInfo() });
|
||||||
|
defaultProvider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
provider.OnProvidersExecuting(context));
|
||||||
|
|
||||||
|
Assert.Equal($"The '{typeof(TestController_OneValid_OneInvalidProperty).FullName}.{nameof(TestController_OneValid_OneInvalidProperty.Test2)}' property with {nameof(TempDataAttribute)} is invalid. A property using {nameof(TempDataAttribute)} must be of primitive or string type.", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ThrowsInvalidOperationException_PrivateSetter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new TempDataApplicationModelProvider();
|
||||||
|
var defaultProvider = new DefaultApplicationModelProvider(new TestOptionsManager<MvcOptions>());
|
||||||
|
|
||||||
|
var context = new ApplicationModelProviderContext(new[] { typeof(TestController_PrivateSet).GetTypeInfo() });
|
||||||
|
defaultProvider.OnProvidersExecuting(context);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
provider.OnProvidersExecuting(context));
|
||||||
|
|
||||||
|
Assert.Equal($"The '{typeof(TestController_PrivateSet).FullName}.{nameof(TestController_NonPrimitiveType.Test)}' property with {nameof(TempDataAttribute)} is invalid. A property using {nameof(TempDataAttribute)} must have a public getter and setter.", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ThrowsInvalidOperationException_NonPrimitiveType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new TempDataApplicationModelProvider();
|
||||||
|
var defaultProvider = new DefaultApplicationModelProvider(new TestOptionsManager<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. A property using {nameof(TempDataAttribute)} must be of primitive or string type.", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestController_OneTempDataProperty
|
||||||
|
{
|
||||||
|
public string Test { get; set; }
|
||||||
|
|
||||||
|
[TempData]
|
||||||
|
public string Test2 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestController_TwoTempDataProperties
|
||||||
|
{
|
||||||
|
[TempData]
|
||||||
|
public string Test { get; set; }
|
||||||
|
|
||||||
|
[TempData]
|
||||||
|
public int Test2 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestController_OneValid_OneInvalidProperty
|
||||||
|
{
|
||||||
|
[TempData]
|
||||||
|
public int Test { get; set; }
|
||||||
|
|
||||||
|
[TempData]
|
||||||
|
public IList<string> Test2 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestController_PrivateSet
|
||||||
|
{
|
||||||
|
[TempData]
|
||||||
|
public string Test { get; private set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestController_NonPrimitiveType
|
||||||
|
{
|
||||||
|
[TempData]
|
||||||
|
public object Test { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||||
|
{
|
||||||
|
public class TempDataPropertyProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void LoadAndTrackChanges_SetsPropertyValue()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new TempDataPropertyProvider();
|
||||||
|
|
||||||
|
var tempData = new TempDataDictionary(new DefaultHttpContext(), new NullTempDataProvider());
|
||||||
|
tempData["TempDataProperty-TestString"] = "Value";
|
||||||
|
tempData.Save();
|
||||||
|
|
||||||
|
var controller = new TestController()
|
||||||
|
{
|
||||||
|
TempData = tempData,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.LoadAndTrackChanges(controller, controller.TempData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Value", controller.TestString);
|
||||||
|
Assert.Null(controller.TestString2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LoadAndTrackChanges_ThrowsInvalidOperationException_PrivateSetter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new TempDataPropertyProvider();
|
||||||
|
|
||||||
|
var tempData = new TempDataDictionary(new DefaultHttpContext(), new NullTempDataProvider());
|
||||||
|
tempData["TempDataProperty-Test"] = "Value";
|
||||||
|
tempData.Save();
|
||||||
|
|
||||||
|
var controller = new TestController_PrivateSet()
|
||||||
|
{
|
||||||
|
TempData = tempData,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
provider.LoadAndTrackChanges(controller, controller.TempData));
|
||||||
|
|
||||||
|
Assert.Equal("TempData properties must have a public getter and setter.", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LoadAndTrackChanges_ThrowsInvalidOperationException_NonPrimitiveType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new TempDataPropertyProvider();
|
||||||
|
|
||||||
|
var tempData = new TempDataDictionary(new DefaultHttpContext(), new NullTempDataProvider());
|
||||||
|
tempData["TempDataProperty-Test"] = new object();
|
||||||
|
tempData.Save();
|
||||||
|
|
||||||
|
var controller = new TestController_NonPrimitiveType()
|
||||||
|
{
|
||||||
|
TempData = tempData,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
provider.LoadAndTrackChanges(controller, controller.TempData));
|
||||||
|
|
||||||
|
Assert.Equal("TempData properties must be declared as primitive types or string only.", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestController : Controller
|
||||||
|
{
|
||||||
|
[TempData]
|
||||||
|
public string TestString { get; set; }
|
||||||
|
|
||||||
|
[TempData]
|
||||||
|
public string TestString2 { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestController_PrivateSet : Controller
|
||||||
|
{
|
||||||
|
[TempData]
|
||||||
|
public string Test { get; private set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestController_NonPrimitiveType : Controller
|
||||||
|
{
|
||||||
|
[TempData]
|
||||||
|
public object Test { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NullTempDataProvider : ITempDataProvider
|
||||||
|
{
|
||||||
|
public IDictionary<string, object> LoadTempData(HttpContext context)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveTempData(HttpContext context, IDictionary<string, object> values)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
// 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 BasicWebSite.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace BasicWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class TempDataPropertyController : Controller
|
||||||
|
{
|
||||||
|
[TempData]
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult CreateForView(Person person)
|
||||||
|
{
|
||||||
|
Message = "Success (from Temp Data)";
|
||||||
|
return RedirectToAction("DetailsView", person);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult Create(Person person)
|
||||||
|
{
|
||||||
|
Message = "Success (from Temp Data)";
|
||||||
|
return RedirectToAction("Details", person);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult DetailsView(Person person)
|
||||||
|
{
|
||||||
|
ViewData["Message"] = Message;
|
||||||
|
return View(person);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Details(Person person)
|
||||||
|
{
|
||||||
|
return $"{Message} for person {person.FullName} with id {person.id}.";
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusCodeResult CreateNoRedirect(Person person)
|
||||||
|
{
|
||||||
|
Message = "Success (from Temp Data)";
|
||||||
|
return new OkResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TempDataKept()
|
||||||
|
{
|
||||||
|
TempData.Keep();
|
||||||
|
return Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadTempData()
|
||||||
|
{
|
||||||
|
return Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
@model BasicWebSite.Models.Person
|
||||||
|
@ViewData["Message"] for person @Model.FullName with id @Model.id.
|
||||||
Loading…
Reference in New Issue