parent
ce28117b8b
commit
1197657e5b
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.Razor;
|
|||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc.Abstractions;
|
|||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
var index = parentDirectoryPath.LastIndexOf('/');
|
||||
if (index == -1)
|
||||
{
|
||||
parentDirectoryPath = string.Empty;
|
||||
parentDirectoryPath = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -357,7 +357,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
{
|
||||
propertyFilter.OriginalValues = originalValues;
|
||||
propertyFilter.Subject = _page;
|
||||
propertyFilter.Prefix = TempDataPropertyProvider.Prefix;
|
||||
}
|
||||
|
||||
IActionResult result = null;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
}
|
||||
|
||||
// Support for [TempData] on properties
|
||||
options.ConfigureFilter(new SaveTempDataPropertyFilter());
|
||||
options.ConfigureFilter(new SaveTempDataPropertyFilterFactory());
|
||||
// Always require an antiforgery token on post
|
||||
options.ConfigureFilter(new AutoValidateAntiforgeryTokenAttribute());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Buffers;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
|
@ -197,6 +198,11 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
//
|
||||
// Temp Data
|
||||
//
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IApplicationModelProvider, TempDataApplicationModelProvider>());
|
||||
services.TryAddSingleton<SaveTempDataFilter>();
|
||||
services.TryAddTransient<SaveTempDataPropertyFilter>();
|
||||
|
||||
// This does caching so it should stay singleton
|
||||
services.TryAddSingleton<ITempDataProvider, CookieTempDataProvider>();
|
||||
|
||||
|
|
@ -208,8 +214,6 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
// These are stateless so their lifetime isn't really important.
|
||||
services.TryAddSingleton<ITempDataDictionaryFactory, TempDataDictionaryFactory>();
|
||||
services.TryAddSingleton<SaveTempDataFilter>();
|
||||
|
||||
services.TryAddSingleton(ArrayPool<ViewBufferValue>.Shared);
|
||||
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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||
|
|
|
|||
|
|
@ -1,14 +1,26 @@
|
|||
// 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.Filters;
|
||||
using Microsoft.Extensions.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; }
|
||||
|
||||
|
|
@ -24,12 +36,46 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
|||
var originalValue = kvp.Value;
|
||||
|
||||
var newValue = property.GetValue(Subject);
|
||||
if (newValue != null && newValue != originalValue)
|
||||
if (newValue != null && !newValue.Equals(originalValue))
|
||||
{
|
||||
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.Linq;
|
||||
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
|
||||
{
|
||||
|
|
@ -18,6 +16,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
private ConcurrentDictionary<Type, IEnumerable<PropertyInfo>> _subjectProperties =
|
||||
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)
|
||||
{
|
||||
var properties = GetSubjectProperties(subject);
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -286,4 +286,10 @@
|
|||
<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>
|
||||
</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>
|
||||
|
|
@ -3,8 +3,11 @@
|
|||
|
||||
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)]
|
||||
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 =>
|
||||
{
|
||||
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
|
||||
Assert.IsType<SaveTempDataPropertyFilter>(filterDescriptor.Filter);
|
||||
Assert.IsType<SaveTempDataPropertyFilterFactory>(filterDescriptor.Filter);
|
||||
},
|
||||
filterDescriptor =>
|
||||
{
|
||||
|
|
@ -343,7 +343,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
filterDescriptor =>
|
||||
{
|
||||
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
|
||||
Assert.IsType<SaveTempDataPropertyFilter>(filterDescriptor.Filter);
|
||||
Assert.IsType<SaveTempDataPropertyFilterFactory>(filterDescriptor.Filter);
|
||||
},
|
||||
filterDescriptor =>
|
||||
{
|
||||
|
|
@ -396,7 +396,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
filterDescriptor =>
|
||||
{
|
||||
Assert.Equal(FilterScope.Action, filterDescriptor.Scope);
|
||||
Assert.IsType<SaveTempDataPropertyFilter>(filterDescriptor.Filter);
|
||||
Assert.IsType<SaveTempDataPropertyFilterFactory>(filterDescriptor.Filter);
|
||||
},
|
||||
filterDescriptor =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Evolution;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.Razor;
|
|||
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
|||
|
||||
// Assert
|
||||
Assert.Collection(applicationModel.Filters,
|
||||
filter => Assert.IsType<SaveTempDataPropertyFilter>(filter),
|
||||
filter => Assert.IsType<SaveTempDataPropertyFilterFactory>(filter),
|
||||
filter => Assert.IsType<AutoValidateAntiforgeryTokenAttribute>(filter));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -409,6 +409,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
typeof(DefaultApplicationModelProvider),
|
||||
typeof(CorsApplicationModelProvider),
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
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