Add a System.Text.Json based TempDataSerializer (#8874)

* Add a System.Text.Json based TempDataSerializer

* Update DefaultTempDataSerializer
* Add common tests for DefaultTempDataSerializer & BsonTempDataSerializer
* Remove uses of NewtonsoftJson in tests solely required for temp-data support

Fixes https://github.com/aspnet/AspNetCore/issues/7255
This commit is contained in:
Pranav K 2019-04-02 10:34:50 -07:00 committed by GitHub
parent 8499a27c7f
commit 1b868323e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 813 additions and 100 deletions

View File

@ -149,7 +149,7 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
}
else
{
return new byte[0];
return Array.Empty<byte>();
}
}
@ -165,6 +165,8 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
}
}
public override bool CanSerializeType(Type type) => CanSerializeType(type, out _);
private static bool CanSerializeType(Type typeToSerialize, out string errorMessage)
{
typeToSerialize = typeToSerialize ?? throw new ArgumentNullException(nameof(typeToSerialize));
@ -208,6 +210,8 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
}
actualType = actualType ?? typeToSerialize;
actualType = Nullable.GetUnderlyingType(actualType) ?? actualType;
if (!IsSimpleType(actualType))
{
errorMessage = Resources.FormatTempData_CannotSerializeType(

View File

@ -3,12 +3,15 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
{
public class BsonTempDataSerializerTest
public class BsonTempDataSerializerTest : TempDataSerializerTestBase
{
protected override TempDataSerializer GetTempDataSerializer() => new BsonTempDataSerializer();
public static TheoryData<object, Type> InvalidTypes
{
get
@ -93,6 +96,112 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
}
}
[Fact]
public void RoundTripTest_ArrayOfIntegers()
{
// Arrange
var key = "test-key";
var value = new[] { 1, -2, 3 };
var testProvider = GetTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, value },
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = Assert.IsType<int[]>(values[key]);
Assert.Equal(value, roundTripValue);
}
[Fact]
public void RoundTripTest_DateTime()
{
// Arrange
var key = "test-key";
var value = new DateTime(2007, 1, 1);
var testProvider = GetTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, value },
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = Assert.IsType<DateTime>(values[key]);
Assert.Equal(value, roundTripValue);
}
[Fact]
public void RoundTripTest_Guid()
{
// Arrange
var key = "test-key";
var value = Guid.NewGuid();
var testProvider = GetTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, value },
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = Assert.IsType<Guid>(values[key]);
Assert.Equal(value, roundTripValue);
}
[Theory]
[InlineData(2147483648)]
[InlineData(-2147483649)]
public void RoundTripTest_LongValue(long value)
{
// Arrange
var key = "test-key";
var testProvider = GetTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, value },
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = Assert.IsType<long>(values[key]);
Assert.Equal(value, roundTripValue);
}
[Fact]
public void RoundTripTest_Double()
{
// Arrange
var key = "test-key";
var value = 10d;
var testProvider = GetTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, value },
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = (double)values[key];
Assert.Equal(value, roundTripValue);
}
[Theory]
[MemberData(nameof(ValidTypes))]
public void EnsureObjectCanBeSerialized_DoesNotThrow_OnValidType(object value)
@ -104,40 +213,6 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
testProvider.EnsureObjectCanBeSerialized(value);
}
[Fact]
public void DeserializeTempData_ReturnsEmptyDictionary_DataIsEmpty()
{
// Arrange
var serializer = new BsonTempDataSerializer();
// Act
var tempDataDictionary = serializer.Deserialize(new byte[0]);
// Assert
Assert.NotNull(tempDataDictionary);
Assert.Empty(tempDataDictionary);
}
[Fact]
public void SerializeAndDeserialize_NullValue_RoundTripsSuccessfully()
{
// Arrange
var key = "NullKey";
var testProvider = new BsonTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, null }
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
Assert.True(values.ContainsKey(key));
Assert.Null(values[key]);
}
private class TestItem
{
public int DummyInt { get; set; }

View File

@ -13,6 +13,7 @@
<ItemGroup>
<Compile Include="..\..\Mvc.Core\test\Formatters\JsonInputFormatterTestBase.cs" />
<Compile Include="..\..\Mvc.Core\test\Formatters\JsonOutputFormatterTestBase.cs" />
<Compile Include="..\..\Mvc.ViewFeatures\test\Infrastructure\TempDataSerializerTestBase.cs" />
</ItemGroup>
</Project>

View File

@ -5,9 +5,9 @@ using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
@ -44,6 +44,23 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
Assert.Equal(expected, ex.Message);
}
[Fact]
public void OnProvidersExecuting_ThrowsIfThePropertyTypeIsUnsupported()
{
// Arrange
var type = typeof(TestPageModel_InvalidProperties);
var expected = $"TempData serializer '{typeof(DefaultTempDataSerializer)}' cannot serialize property '{type}.ModelState' of type '{typeof(ModelStateDictionary)}'." +
Environment.NewLine +
$"TempData serializer '{typeof(DefaultTempDataSerializer)}' cannot serialize property '{type}.TimeZone' of type '{typeof(TimeZoneInfo)}'.";
var provider = CreateProvider();
var context = CreateProviderContext(type);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context));
Assert.Equal(expected, ex.Message);
}
[Fact]
public void AddsTempDataPropertyFilter_ForTempDataAttributeProperties()
{
@ -114,18 +131,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
return context;
}
private static CompiledPageActionDescriptor CreateDescriptor(Type type)
{
return new CompiledPageActionDescriptor(new PageActionDescriptor())
{
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
HandlerTypeInfo = type.GetTypeInfo(),
};
}
private static TempDataFilterPageApplicationModelProvider CreateProvider()
{
var tempDataSerializer = Mock.Of<TempDataSerializer>(s => s.CanSerializeType(It.IsAny<Type>()) == true);
var tempDataSerializer = new DefaultTempDataSerializer();
return new TempDataFilterPageApplicationModelProvider(tempDataSerializer);
}
@ -160,5 +168,17 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
[TempData]
public string Test { get; private set; }
}
public class TestPageModel_InvalidProperties
{
[TempData]
public ModelStateDictionary ModelState { get; set; }
[TempData]
public int SomeProperty { get; set; }
[TempData]
public TimeZoneInfo TimeZone { get; set; }
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure;
@ -86,6 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Filters
Type type)
{
List<LifecycleProperty> results = null;
var errorMessages = new List<string>();
var propertyHelpers = PropertyHelper.GetVisibleProperties(type: type);
for (var i = 0; i < propertyHelpers.Length; i++)
@ -93,9 +95,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Filters
var propertyHelper = propertyHelpers[i];
var property = propertyHelper.Property;
var tempDataAttribute = property.GetCustomAttribute<TempDataAttribute>();
if (tempDataAttribute != null)
if (tempDataAttribute != null && ValidateProperty(tempDataSerializer, errorMessages, propertyHelper.Property))
{
ValidateProperty(tempDataSerializer, propertyHelper.Property);
if (results == null)
{
results = new List<LifecycleProperty>();
@ -111,28 +112,41 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Filters
}
}
if (errorMessages.Count > 0)
{
throw new InvalidOperationException(string.Join(Environment.NewLine, errorMessages));
}
return results;
}
private static void ValidateProperty(TempDataSerializer tempDataSerializer, PropertyInfo property)
private static bool ValidateProperty(TempDataSerializer tempDataSerializer, List<string> errorMessages, PropertyInfo property)
{
if (!(property.SetMethod != null &&
property.SetMethod.IsPublic &&
property.GetMethod != null &&
property.GetMethod.IsPublic))
{
throw new InvalidOperationException(
errorMessages.Add(
Resources.FormatTempDataProperties_PublicGetterSetter(property.DeclaringType.FullName, property.Name, nameof(TempDataAttribute)));
return false;
}
if (!tempDataSerializer.CanSerializeType(property.PropertyType))
{
throw new InvalidOperationException(Resources.FormatTempDataProperties_InvalidType(
var errorMessage = Resources.FormatTempDataProperties_InvalidType(
tempDataSerializer.GetType().FullName,
TypeNameHelper.GetTypeDisplayName(property.DeclaringType),
property.Name,
TypeNameHelper.GetTypeDisplayName(property.PropertyType)));
TypeNameHelper.GetTypeDisplayName(property.PropertyType));
errorMessages.Add(errorMessage);
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,180 @@
// 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.Buffers;
using System.Diagnostics;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure
{
// Note: this is currently an internal class that will be replaced with a shared version.
internal sealed class ArrayBufferWriter<T> : IBufferWriter<T>, IDisposable
{
private T[] _rentedBuffer;
private int _index;
private const int MinimumBufferSize = 256;
public ArrayBufferWriter()
{
_rentedBuffer = ArrayPool<T>.Shared.Rent(MinimumBufferSize);
_index = 0;
}
public ArrayBufferWriter(int initialCapacity)
{
if (initialCapacity <= 0)
throw new ArgumentException(nameof(initialCapacity));
_rentedBuffer = ArrayPool<T>.Shared.Rent(initialCapacity);
_index = 0;
}
public ReadOnlyMemory<T> WrittenMemory
{
get
{
CheckIfDisposed();
return _rentedBuffer.AsMemory(0, _index);
}
}
public int WrittenCount
{
get
{
CheckIfDisposed();
return _index;
}
}
public int Capacity
{
get
{
CheckIfDisposed();
return _rentedBuffer.Length;
}
}
public int FreeCapacity
{
get
{
CheckIfDisposed();
return _rentedBuffer.Length - _index;
}
}
public void Clear()
{
CheckIfDisposed();
ClearHelper();
}
private void ClearHelper()
{
Debug.Assert(_rentedBuffer != null);
_rentedBuffer.AsSpan(0, _index).Clear();
_index = 0;
}
// Returns the rented buffer back to the pool
public void Dispose()
{
if (_rentedBuffer == null)
{
return;
}
ClearHelper();
ArrayPool<T>.Shared.Return(_rentedBuffer);
_rentedBuffer = null;
}
private void CheckIfDisposed()
{
if (_rentedBuffer == null)
{
throw new ObjectDisposedException(nameof(ArrayBufferWriter<T>));
}
}
public void Advance(int count)
{
CheckIfDisposed();
if (count < 0)
throw new ArgumentException(nameof(count));
if (_index > _rentedBuffer.Length - count)
ThrowInvalidOperationException(_rentedBuffer.Length);
_index += count;
}
public Memory<T> GetMemory(int sizeHint = 0)
{
CheckIfDisposed();
CheckAndResizeBuffer(sizeHint);
return _rentedBuffer.AsMemory(_index);
}
public Span<T> GetSpan(int sizeHint = 0)
{
CheckIfDisposed();
CheckAndResizeBuffer(sizeHint);
return _rentedBuffer.AsSpan(_index);
}
private void CheckAndResizeBuffer(int sizeHint)
{
Debug.Assert(_rentedBuffer != null);
if (sizeHint < 0)
throw new ArgumentException(nameof(sizeHint));
if (sizeHint == 0)
{
sizeHint = MinimumBufferSize;
}
int availableSpace = _rentedBuffer.Length - _index;
if (sizeHint > availableSpace)
{
int growBy = Math.Max(sizeHint, _rentedBuffer.Length);
int newSize = checked(_rentedBuffer.Length + growBy);
T[] oldBuffer = _rentedBuffer;
_rentedBuffer = ArrayPool<T>.Shared.Rent(newSize);
Debug.Assert(oldBuffer.Length >= _index);
Debug.Assert(_rentedBuffer.Length >= _index);
Span<T> previousBuffer = oldBuffer.AsSpan(0, _index);
previousBuffer.CopyTo(_rentedBuffer);
previousBuffer.Clear();
ArrayPool<T>.Shared.Return(oldBuffer);
}
Debug.Assert(_rentedBuffer.Length - _index > 0);
Debug.Assert(_rentedBuffer.Length - _index >= sizeHint);
}
private static void ThrowInvalidOperationException(int capacity)
{
throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {capacity}.");
}
}
}

View File

@ -3,30 +3,157 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using System.Globalization;
using System.Text.Json;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure
{
internal class DefaultTempDataSerializer : TempDataSerializer
{
public override IDictionary<string, object> Deserialize(byte[] unprotectedData)
public override IDictionary<string, object> Deserialize(byte[] value)
{
throw new InvalidOperationException(Core.Resources.FormatReferenceToNewtonsoftJsonRequired(
Resources.DeserializingTempData,
"Microsoft.AspNetCore.Mvc.NewtonsoftJson",
nameof(IMvcBuilder),
"AddNewtonsoftJson",
"ConfigureServices(...)"));
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
if (value.Length == 0)
{
return new Dictionary<string, object>();
}
using var jsonDocument = JsonDocument.Parse(value);
var rootElement = jsonDocument.RootElement;
return DeserializeDictionary(rootElement);
}
private IDictionary<string, object> DeserializeDictionary(JsonElement rootElement)
{
var deserialized = new Dictionary<string, object>(StringComparer.Ordinal);
foreach (var item in rootElement.EnumerateObject())
{
object deserializedValue;
switch (item.Value.Type)
{
case JsonValueType.False:
case JsonValueType.True:
deserializedValue = item.Value.GetBoolean();
break;
case JsonValueType.Number:
deserializedValue = item.Value.GetInt32();
break;
case JsonValueType.String:
var stringValue = item.Value.GetString();
// BsonTempDataSerializer will parse certain types of string values. We'll attempt to imitiate it.
if (DateTime.TryParseExact(stringValue, "r", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dateTime))
{
deserializedValue = dateTime;
}
else if (Guid.TryParseExact(stringValue, "B", out var guid))
{
deserializedValue = guid;
}
else
{
deserializedValue = stringValue;
}
break;
case JsonValueType.Null:
deserializedValue = null;
break;
default:
throw new InvalidOperationException(Resources.FormatTempData_CannotDeserializeType(item.Value.Type));
}
deserialized[item.Name] = deserializedValue;
}
return deserialized;
}
public override byte[] Serialize(IDictionary<string, object> values)
{
throw new InvalidOperationException(Core.Resources.FormatReferenceToNewtonsoftJsonRequired(
Resources.SerializingTempData,
"Microsoft.AspNetCore.Mvc.NewtonsoftJson",
nameof(IMvcBuilder),
"AddNewtonsoftJson",
"ConfigureServices(...)"));
if (values == null || values.Count == 0)
{
return Array.Empty<byte>();
}
using (var bufferWriter = new ArrayBufferWriter<byte>())
{
var writer = new Utf8JsonWriter(bufferWriter);
writer.WriteStartObject();
foreach (var (key, value) in values)
{
if (value == null)
{
writer.WriteNull(key);
continue;
}
// We want to allow only simple types to be serialized.
if (!CanSerializeType(value.GetType()))
{
throw new InvalidOperationException(
Resources.FormatTempData_CannotSerializeType(
typeof(DefaultTempDataSerializer).FullName,
value.GetType()));
}
switch (value)
{
case Enum _:
writer.WriteNumber(key, (int)value);
break;
case string stringValue:
writer.WriteString(key, stringValue);
break;
case int intValue:
writer.WriteNumber(key, intValue);
break;
case bool boolValue:
writer.WriteBoolean(key, boolValue);
break;
case DateTime dateTime:
writer.WriteString(key, dateTime.ToString("r", CultureInfo.InvariantCulture));
break;
case Guid guid:
writer.WriteString(key, guid.ToString("B", CultureInfo.InvariantCulture));
break;
}
}
writer.WriteEndObject();
writer.Flush();
return bufferWriter.WrittenMemory.ToArray();
}
}
public override bool CanSerializeType(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
type = Nullable.GetUnderlyingType(type) ?? type;
return
type.IsEnum ||
type == typeof(int) ||
type == typeof(string) ||
type == typeof(bool) ||
type == typeof(DateTime) ||
type == typeof(Guid);
}
}
}

View File

@ -808,6 +808,34 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
internal static string FormatSerializingTempData()
=> GetString("SerializingTempData");
/// <summary>
/// The '{0}' cannot serialize an object of type '{1}'.
/// </summary>
internal static string TempData_CannotSerializeType
{
get => GetString("TempData_CannotSerializeType");
}
/// <summary>
/// The '{0}' cannot serialize an object of type '{1}'.
/// </summary>
internal static string FormatTempData_CannotSerializeType(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("TempData_CannotSerializeType"), p0, p1);
/// <summary>
/// Unsupported data type '{0}'.
/// </summary>
internal static string TempData_CannotDeserializeType
{
get => GetString("TempData_CannotDeserializeType");
}
/// <summary>
/// Unsupported data type '{0}'.
/// </summary>
internal static string FormatTempData_CannotDeserializeType(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("TempData_CannotDeserializeType"), p0);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -289,4 +289,10 @@
<data name="SerializingTempData" xml:space="preserve">
<value>Serializing TempDataDictionary</value>
</data>
<data name="TempData_CannotSerializeType" xml:space="preserve">
<value>The '{0}' cannot serialize an object of type '{1}'.</value>
</data>
<data name="TempData_CannotDeserializeType" xml:space="preserve">
<value>Unsupported data type '{0}'.</value>
</data>
</root>

View File

@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Filters
@ -48,20 +47,20 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Filters
}
[Fact]
public void AddsTempDataPropertyFilter_ForTempDataAttributeProperties()
public void OnProvidersExecuting_ThrowsIfThePropertyTypeIsUnsupported()
{
// Arrange
var type = typeof(TestController_NullableNonPrimitiveTempDataProperty);
var type = typeof(TestController_InvalidProperties);
var expected = $"TempData serializer '{typeof(DefaultTempDataSerializer)}' cannot serialize property '{type}.ModelState' of type '{typeof(ModelStateDictionary)}'." +
Environment.NewLine +
$"TempData serializer '{typeof(DefaultTempDataSerializer)}' cannot serialize property '{type}.TimeZone' of type '{typeof(TimeZoneInfo)}'.";
var provider = CreateProvider();
var context = GetContext(type);
// Act
provider.OnProvidersExecuting(context);
// Assert
var controller = Assert.Single(context.Result.Controllers);
Assert.IsType<ControllerSaveTempDataPropertyFilterFactory>(Assert.Single(controller.Filters));
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => provider.OnProvidersExecuting(context));
Assert.Equal(expected, ex.Message);
}
[Fact]
@ -109,7 +108,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Filters
private static TempDataApplicationModelProvider CreateProvider()
{
var tempDataSerializer = Mock.Of<TempDataSerializer>(s => s.CanSerializeType(It.IsAny<Type>()) == true);
var tempDataSerializer = new DefaultTempDataSerializer();
return new TempDataApplicationModelProvider(tempDataSerializer);
}
@ -129,12 +128,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Filters
public DateTime? DateTime { get; set; }
}
public class TestController_NullableNonPrimitiveTempDataProperty
{
[TempData]
public DateTime? DateTime { get; set; }
}
public class TestController_OneTempDataProperty
{
public string Test { get; set; }
@ -148,5 +141,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Filters
[TempData]
public string Test { get; private set; }
}
public class TestController_InvalidProperties
{
[TempData]
public ModelStateDictionary ModelState { get; set; }
[TempData]
public int SomeProperty { get; set; }
[TempData]
public TimeZoneInfo TimeZone { get; set; }
}
}
}

View File

@ -0,0 +1,86 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure
{
public class DefaultTempDataSerializerTest : TempDataSerializerTestBase
{
protected override TempDataSerializer GetTempDataSerializer() => new DefaultTempDataSerializer();
[Fact]
public void RoundTripTest_StringThatLooksLikeCompliantDateTime()
{
// This is an unintentional side-effect of trying to support a compat with JSON.NET.
// Any string that looks like a compliant DateTime object will be parsed as a DateTime.
// This test documents this behavior.
// Arrange
var key = "test-key";
var testProvider = GetTempDataSerializer();
var value = new DateTime(2009, 1, 1, 12, 37, 43);
var input = new Dictionary<string, object>
{
{ key, value.ToString("r") }
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = Assert.IsType<DateTime>(values[key]);
Assert.Equal(value, roundTripValue);
}
[Fact]
public void RoundTripTest_StringThatIsNotCompliantDateTime()
{
// This is an unintentional side-effect of trying to support a compat with JSON.NET.
// Any string that looks like a compliant DateTime object will be parsed as a DateTime.
// This test documents this behavior.
// Arrange
var key = "test-key";
var testProvider = GetTempDataSerializer();
var value = new DateTime(2009, 1, 1, 12, 37, 43);
var input = new Dictionary<string, object>
{
{ key, value.ToString() }
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = Assert.IsType<string>(values[key]);
Assert.Equal(value.ToString(), roundTripValue);
}
[Fact]
public void RoundTripTest_StringThatIsNotCompliantGuid()
{
// This is an unintentional side-effect of trying to support a compat with JSON.NET.
// Any string that looks like a compliant DateTime object will be parsed as a DateTime.
// This test documents this behavior.
// Arrange
var key = "test-key";
var testProvider = GetTempDataSerializer();
var value = Guid.NewGuid();
var input = new Dictionary<string, object>
{
{ key, value.ToString() }
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = Assert.IsType<string>(values[key]);
Assert.Equal(value.ToString(), roundTripValue);
}
}
}

View File

@ -0,0 +1,179 @@
// 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 Xunit;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure
{
public abstract class TempDataSerializerTestBase
{
[Fact]
public void DeserializeTempData_ReturnsEmptyDictionary_DataIsEmpty()
{
// Arrange
var serializer = GetTempDataSerializer();
// Act
var tempDataDictionary = serializer.Deserialize(new byte[0]);
// Assert
Assert.NotNull(tempDataDictionary);
Assert.Empty(tempDataDictionary);
}
[Fact]
public void RoundTripTest_NullValue()
{
// Arrange
var key = "NullKey";
var testProvider = GetTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, null }
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
Assert.True(values.ContainsKey(key));
Assert.Null(values[key]);
}
[Theory]
[InlineData(-10)]
[InlineData(3340)]
[InlineData(int.MaxValue)]
[InlineData(int.MinValue)]
public void RoundTripTest_IntValue(int value)
{
// Arrange
var key = "test-key";
var testProvider = GetTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, value },
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = Assert.IsType<int>(values[key]);
Assert.Equal(value, roundTripValue);
}
[Theory]
[InlineData(null)]
[InlineData(10)]
public void RoundTripTest_NullableInt(int? value)
{
// Arrange
var key = "test-key";
var testProvider = GetTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, value },
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = (int?)values[key];
Assert.Equal(value, roundTripValue);
}
[Fact]
public void RoundTripTest_StringValue()
{
// Arrange
var key = "test-key";
var value = "test-value";
var testProvider = GetTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, value },
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = Assert.IsType<string>(values[key]);
Assert.Equal(value, roundTripValue);
}
[Fact]
public void RoundTripTest_Enum()
{
// Arrange
var key = "test-key";
var value = DayOfWeek.Friday;
var testProvider = GetTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, value },
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = (DayOfWeek)values[key];
Assert.Equal(value, roundTripValue);
}
[Fact]
public void RoundTripTest_DateTimeValue()
{
// Arrange
var key = "test-key";
var value = new DateTime(2009, 1, 1, 12, 37, 43);
var testProvider = GetTempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, value },
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = Assert.IsType<DateTime>(values[key]);
Assert.Equal(value, roundTripValue);
}
[Fact]
public void RoundTripTest_GuidValue()
{
// Arrange
var key = "test-key";
var testProvider = GetTempDataSerializer();
var value = Guid.NewGuid();
var input = new Dictionary<string, object>
{
{ key, value }
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
var roundTripValue = Assert.IsType<Guid>(values[key]);
Assert.Equal(value, roundTripValue);
}
protected abstract TempDataSerializer GetTempDataSerializer();
}
}

View File

@ -507,7 +507,7 @@ Products: Book1, Book2 (1)";
// Act - 3
// Trigger an expiration of the nested content.
var content = @"[{ productName: ""Music Systems"" },{ productName: ""Televisions"" }]";
var content = @"[{ ""ProductName"": ""Music Systems"" },{ ""ProductName"": ""Televisions"" }]";
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/categories/Electronics");
requestMessage.Content = new StringContent(content, Encoding.UTF8, "application/json");
(await Client.SendAsync(requestMessage)).EnsureSuccessStatusCode();

View File

@ -784,7 +784,7 @@ Hello from /Pages/WithViewStart/Index.cshtml!";
Assert.Equal(expected, content);
}
[Fact]
[Fact(Skip = "https://github.com/dotnet/corefx/issues/36024")]
public async Task PolymorphicPropertiesOnPageModelsAreValidated()
{
// Arrange

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
@ -8,7 +8,6 @@
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Mvc" />
<Reference Include="Microsoft.AspNetCore.Mvc.Formatters.Xml" />
<Reference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" />
<Reference Include="Microsoft.AspNetCore.Cors" />
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />

View File

@ -13,7 +13,6 @@ namespace CorsWebSite
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(ConfigureMvcOptions)
.AddNewtonsoftJson()
.SetCompatibilityVersion(CompatibilityVersion.Latest);
services.Configure<CorsOptions>(options =>
{

View File

@ -11,8 +11,6 @@
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Mvc" />
<Reference Include="Microsoft.AspNetCore.Mvc.Formatters.Xml" />
<Reference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" />
<Reference Include="Microsoft.AspNetCore.Localization.Routing" />
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />

View File

@ -1,11 +1,8 @@
// 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 Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
@ -24,9 +21,7 @@ namespace GenericHostWebSite
// Remove when all URL generation tests are passing - https://github.com/aspnet/Routing/issues/590
options.EnableEndpointRouting = false;
})
.SetCompatibilityVersion(CompatibilityVersion.Latest)
.AddNewtonsoftJson()
.AddXmlDataContractSerializerFormatters();
.SetCompatibilityVersion(CompatibilityVersion.Latest);
services.AddLogging();
services.AddHttpContextAccessor();

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
@ -8,7 +8,6 @@
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Mvc" />
<Reference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" />
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.AspNetCore.StaticFiles" />

View File

@ -19,7 +19,6 @@ namespace HtmlGenerationWebSite
// null which is interpreted as true unless element includes an action attribute.
services.AddMvc(ConfigureMvcOptions)
.InitializeTagHelper<FormTagHelper>((helper, _) => helper.Antiforgery = false)
.AddNewtonsoftJson()
.SetCompatibilityVersion(CompatibilityVersion.Latest);
services.AddSingleton(typeof(ISignalTokenProviderService<>), typeof(SignalTokenProviderService<>));

View File

@ -17,7 +17,6 @@ namespace RazorPagesWebSite
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => options.LoginPath = "/Login");
services.AddMvc(options => options.EnableEndpointRouting = false)
.AddMvcLocalization()
.AddNewtonsoftJson()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/HelloWorldWithAuth");