[Fixes #6591] TempData should support nullable types

This commit is contained in:
Kiran Challa 2017-12-01 15:47:45 -08:00
parent 629f87181a
commit e09ea40551
7 changed files with 64 additions and 21 deletions

View File

@ -96,7 +96,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
Resources.FormatTempDataProperties_PublicGetterSetter(property.DeclaringType.FullName, property.Name, nameof(TempDataAttribute))); Resources.FormatTempDataProperties_PublicGetterSetter(property.DeclaringType.FullName, property.Name, nameof(TempDataAttribute)));
} }
if (!(property.PropertyType.GetTypeInfo().IsPrimitive || property.PropertyType == typeof(string))) var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
if (!(propertyType.GetTypeInfo().IsPrimitive || propertyType == typeof(string)))
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
Resources.FormatTempDataProperties_PrimitiveTypeOrString(property.DeclaringType.FullName, property.Name, nameof(TempDataAttribute))); Resources.FormatTempDataProperties_PrimitiveTypeOrString(property.DeclaringType.FullName, property.Name, nameof(TempDataAttribute)));

View File

@ -795,7 +795,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
=> string.Format(CultureInfo.CurrentCulture, GetString("ViewEnginesAreRequired"), p0, p1, p2); => string.Format(CultureInfo.CurrentCulture, GetString("ViewEnginesAreRequired"), p0, p1, p2);
/// <summary> /// <summary>
/// The '{0}.{1}' property with {2} is invalid. A property using {2} must be of primitive or string type. /// The '{0}.{1}' property with {2} is invalid. A property using {2} must be a primitive or string type.
/// </summary> /// </summary>
internal static string TempDataProperties_PrimitiveTypeOrString internal static string TempDataProperties_PrimitiveTypeOrString
{ {
@ -803,7 +803,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
} }
/// <summary> /// <summary>
/// The '{0}.{1}' property with {2} is invalid. A property using {2} must be of primitive or string type. /// The '{0}.{1}' property with {2} is invalid. A property using {2} must be a primitive or string type.
/// </summary> /// </summary>
internal static string FormatTempDataProperties_PrimitiveTypeOrString(object p0, object p1, object p2) internal static string FormatTempDataProperties_PrimitiveTypeOrString(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("TempDataProperties_PrimitiveTypeOrString"), p0, p1, p2); => string.Format(CultureInfo.CurrentCulture, GetString("TempDataProperties_PrimitiveTypeOrString"), p0, p1, p2);

View File

@ -287,7 +287,7 @@
<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"> <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> <value>The '{0}.{1}' property with {2} is invalid. A property using {2} must be a primitive or string type.</value>
</data> </data>
<data name="TempDataProperties_PublicGetterSetter" xml:space="preserve"> <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> <value>The '{0}.{1}' property with {2} is invalid. A property using {2} must have a public getter and setter.</value>

View File

@ -25,13 +25,13 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
public async Task TempDataPropertyAttribute_RetainsTempDataWithView() public async Task TempDataPropertyAttribute_RetainsTempDataWithView()
{ {
// Arrange // Arrange
var message = "Success (from Temp Data)"; var tempDataContent = "Success (from Temp Data)100";
var nameValueCollection = new List<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
{ {
new KeyValuePair<string, string>("FullName", "Bob"), new KeyValuePair<string, string>("FullName", "Bob"),
new KeyValuePair<string, string>("id", "1"), new KeyValuePair<string, string>("id", "1"),
}; };
var expected = $"{message} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}."; var expected = $"{tempDataContent} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}.";
var content = new FormUrlEncodedContent(nameValueCollection); var content = new FormUrlEncodedContent(nameValueCollection);
// Act 1 // Act 1
@ -53,13 +53,13 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
public async Task TempDataPropertyAttribute_RetainsTempDataWithoutView() public async Task TempDataPropertyAttribute_RetainsTempDataWithoutView()
{ {
// Arrange // Arrange
var message = "Success (from Temp Data)"; var tempDataContent = "Success (from Temp Data)100";
var nameValueCollection = new List<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
{ {
new KeyValuePair<string, string>("FullName", "Bob"), new KeyValuePair<string, string>("FullName", "Bob"),
new KeyValuePair<string, string>("id", "1"), new KeyValuePair<string, string>("id", "1"),
}; };
var expected = $"{message} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}."; var expected = $"{tempDataContent} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}.";
var content = new FormUrlEncodedContent(nameValueCollection); var content = new FormUrlEncodedContent(nameValueCollection);
// Act 1 // Act 1
@ -81,14 +81,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
public async Task TempDataPropertyAttribute_TempDataKept() public async Task TempDataPropertyAttribute_TempDataKept()
{ {
// Arrange // Arrange
var message = "Success (from Temp Data)"; var tempDataContent = "Success (from Temp Data)100";
var nameValueCollection = new List<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
{ {
new KeyValuePair<string, string>("FullName", "Bob"), new KeyValuePair<string, string>("FullName", "Bob"),
new KeyValuePair<string, string>("id", "1"), new KeyValuePair<string, string>("id", "1"),
}; };
var expected = $"{message} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}."; var expected = $"{tempDataContent} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}.";
var content = new FormUrlEncodedContent(nameValueCollection); var content = new FormUrlEncodedContent(nameValueCollection);
// Act 1 // Act 1
@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Assert 2 // Assert 2
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync(); var body = await response.Content.ReadAsStringAsync();
Assert.Equal(message, body); Assert.Equal(tempDataContent, body);
// Act 3 // Act 3
response = await Client.SendAsync(GetRequest("TempDataProperty/ReadTempData", response)); response = await Client.SendAsync(GetRequest("TempDataProperty/ReadTempData", response));
@ -111,21 +111,21 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Assert 3 // Assert 3
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
body = await response.Content.ReadAsStringAsync(); body = await response.Content.ReadAsStringAsync();
Assert.Equal(message, body); Assert.Equal(tempDataContent, body);
} }
[Fact] [Fact]
public async Task TempDataPropertyAttribute_TempDataNotKept() public async Task TempDataPropertyAttribute_TempDataNotKept()
{ {
// Arrange // Arrange
var message = "Success (from Temp Data)"; var tempDataContent = "Success (from Temp Data)100";
var nameValueCollection = new List<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
{ {
new KeyValuePair<string, string>("FullName", "Bob"), new KeyValuePair<string, string>("FullName", "Bob"),
new KeyValuePair<string, string>("id", "1"), new KeyValuePair<string, string>("id", "1"),
}; };
var expected = $"{message} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}."; var expected = $"{tempDataContent} for person {nameValueCollection[0].Value} with id {nameValueCollection[1].Value}.";
var content = new FormUrlEncodedContent(nameValueCollection); var content = new FormUrlEncodedContent(nameValueCollection);
// Act 1 // Act 1
@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
// Assert 2 // Assert 2
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync(); var body = await response.Content.ReadAsStringAsync();
Assert.Equal(message, body); Assert.Equal(tempDataContent, body);
// Act 3 // Act 3
response = await Client.SendAsync(GetRequest("TempDataProperty/ReadTempData", response)); response = await Client.SendAsync(GetRequest("TempDataProperty/ReadTempData", response));

View File

@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{ {
[Theory] [Theory]
[InlineData(typeof(TestController_OneTempDataProperty))] [InlineData(typeof(TestController_OneTempDataProperty))]
[InlineData(typeof(TestController_OneNullableTempDataProperty))]
[InlineData(typeof(TestController_TwoTempDataProperties))] [InlineData(typeof(TestController_TwoTempDataProperties))]
public void AddsTempDataPropertyFilter_ForTempDataAttributeProperties(Type type) public void AddsTempDataPropertyFilter_ForTempDataAttributeProperties(Type type)
{ {
@ -71,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
var exception = Assert.Throws<InvalidOperationException>(() => var exception = Assert.Throws<InvalidOperationException>(() =>
provider.OnProvidersExecuting(context)); 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); 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 a primitive or string type.", exception.Message);
} }
[Fact] [Fact]
@ -105,7 +106,32 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
var exception = Assert.Throws<InvalidOperationException>(() => var exception = Assert.Throws<InvalidOperationException>(() =>
provider.OnProvidersExecuting(context)); 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); Assert.Equal($"The '{typeof(TestController_NonPrimitiveType).FullName}.{nameof(TestController_NonPrimitiveType.Test)}' property with {nameof(TempDataAttribute)} is invalid. A property using {nameof(TempDataAttribute)} must be a primitive or string type.", exception.Message);
}
[Fact]
public void ThrowsInvalidOperationException_ForNullableNonPrimitiveType()
{
// Arrange
var provider = new TempDataApplicationModelProvider();
var defaultProvider = new DefaultApplicationModelProvider(Options.Create(new MvcOptions()));
var controllerType = typeof(TestController_NullableNonPrimitiveTempDataProperty);
var context = new ApplicationModelProviderContext(new[] { controllerType.GetTypeInfo() });
defaultProvider.OnProvidersExecuting(context);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
provider.OnProvidersExecuting(context));
Assert.Equal($"The '{controllerType.FullName}.{nameof(TestController_NullableNonPrimitiveTempDataProperty.DateTime)}'"
+ $" property with {nameof(TempDataAttribute)} is invalid. A property using {nameof(TempDataAttribute)} "
+ $"must be a primitive or string type.", exception.Message);
}
public class TestController_NullableNonPrimitiveTempDataProperty
{
[TempData]
public DateTime? DateTime { get; set; }
} }
public class TestController_OneTempDataProperty public class TestController_OneTempDataProperty
@ -125,6 +151,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
public int Test2 { get; set; } public int Test2 { get; set; }
} }
public class TestController_OneNullableTempDataProperty
{
public string Test { get; set; }
[TempData]
public int? Test2 { get; set; }
}
public class TestController_OneValid_OneInvalidProperty public class TestController_OneValid_OneInvalidProperty
{ {
[TempData] [TempData]

View File

@ -13,10 +13,14 @@ namespace BasicWebSite.Controllers
[TempData] [TempData]
public string Message { get; set; } public string Message { get; set; }
[TempData]
public int? NullableInt { get; set; }
[HttpPost] [HttpPost]
public IActionResult CreateForView(Person person) public IActionResult CreateForView(Person person)
{ {
Message = "Success (from Temp Data)"; Message = "Success (from Temp Data)";
NullableInt = 100;
return RedirectToAction("DetailsView", person); return RedirectToAction("DetailsView", person);
} }
@ -24,35 +28,38 @@ namespace BasicWebSite.Controllers
public IActionResult Create(Person person) public IActionResult Create(Person person)
{ {
Message = "Success (from Temp Data)"; Message = "Success (from Temp Data)";
NullableInt = 100;
return RedirectToAction("Details", person); return RedirectToAction("Details", person);
} }
public IActionResult DetailsView(Person person) public IActionResult DetailsView(Person person)
{ {
ViewData["Message"] = Message; ViewData["Message"] = Message;
ViewData["NullableInt"] = NullableInt;
return View(person); return View(person);
} }
public string Details(Person person) public string Details(Person person)
{ {
return $"{Message} for person {person.FullName} with id {person.id}."; return $"{Message}{NullableInt} for person {person.FullName} with id {person.id}.";
} }
public StatusCodeResult CreateNoRedirect(Person person) public StatusCodeResult CreateNoRedirect(Person person)
{ {
Message = "Success (from Temp Data)"; Message = "Success (from Temp Data)";
NullableInt = 100;
return new OkResult(); return new OkResult();
} }
public string TempDataKept() public string TempDataKept()
{ {
TempData.Keep(); TempData.Keep();
return Message; return Message + NullableInt;
} }
public string ReadTempData() public string ReadTempData()
{ {
return Message; return Message + NullableInt;
} }
} }
} }

View File

@ -1,2 +1,2 @@
@model BasicWebSite.Models.Person @model BasicWebSite.Models.Person
@ViewData["Message"] for person @Model.FullName with id @Model.id. @ViewData["Message"]@ViewData["NullableInt"] for person @Model.FullName with id @Model.id.