Added CanReadType to InputFormatter

This commit is contained in:
Ajay Bhargav Baaskaran 2015-02-12 17:37:25 -08:00
parent bb31e2aec5
commit d236d4ffde
9 changed files with 281 additions and 9 deletions

View File

@ -43,6 +43,11 @@ namespace Microsoft.AspNet.Mvc
/// <inheritdoc />
public virtual bool CanRead(InputFormatterContext context)
{
if (!CanReadType(context.ModelType))
{
return false;
}
var contentType = context.ActionContext.HttpContext.Request.ContentType;
MediaTypeHeaderValue requestContentType;
if (!MediaTypeHeaderValue.TryParse(contentType, out requestContentType))
@ -54,6 +59,16 @@ namespace Microsoft.AspNet.Mvc
.Any(supportedMediaType => supportedMediaType.IsSubsetOf(requestContentType));
}
/// <summary>
/// Returns a value indicating whether or not the given type can be read by this serializer.
/// </summary>
/// <param name="type">The type of object that will be read.</param>
/// <returns><c>true</c> if the type can be read, otherwise <c>false</c>.</returns>
protected virtual bool CanReadType(Type type)
{
return true;
}
/// <inheritdoc />
public virtual async Task<object> ReadAsync(InputFormatterContext context)
{

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
@ -19,6 +20,7 @@ namespace Microsoft.AspNet.Mvc.Xml
public class XmlDataContractSerializerInputFormatter : InputFormatter
{
private DataContractSerializerSettings _serializerSettings;
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
/// <summary>
@ -93,7 +95,7 @@ namespace Microsoft.AspNet.Mvc.Xml
{
var type = GetSerializableType(context.ModelType);
var serializer = CreateSerializer(type);
var serializer = GetCachedSerializer(type);
var deserializedObject = serializer.ReadObject(xmlReader);
@ -111,6 +113,12 @@ namespace Microsoft.AspNet.Mvc.Xml
}
}
/// <inheritdoc />
protected override bool CanReadType([NotNull] Type type)
{
return GetCachedSerializer(GetSerializableType(type)) != null;
}
/// <summary>
/// Called during deserialization to get the <see cref="XmlReader"/>.
/// </summary>
@ -142,7 +150,36 @@ namespace Microsoft.AspNet.Mvc.Xml
/// <returns>The <see cref="DataContractSerializer"/> used during deserialization.</returns>
protected virtual DataContractSerializer CreateSerializer([NotNull] Type type)
{
return new DataContractSerializer(type, _serializerSettings);
try
{
// If the serializer does not support this type it will throw an exception.
return new DataContractSerializer(type, _serializerSettings);
}
catch (Exception)
{
// We do not surface the caught exception because if CanRead returns
// false, then this Formatter is not picked up at all.
return null;
}
}
/// <summary>
/// Gets the cached serializer or creates and caches the serializer for the given type.
/// </summary>
/// <returns>The <see cref="DataContractSerializer"/> instance.</returns>
protected virtual DataContractSerializer GetCachedSerializer(Type type)
{
object serializer;
if (!_serializerCache.TryGetValue(type, out serializer))
{
serializer = CreateSerializer(type);
if (serializer != null)
{
_serializerCache.TryAdd(type, serializer);
}
}
return (DataContractSerializer)serializer;
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
@ -19,6 +20,7 @@ namespace Microsoft.AspNet.Mvc.Xml
public class XmlDataContractSerializerOutputFormatter : OutputFormatter
{
private DataContractSerializerSettings _serializerSettings;
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
/// <summary>
/// Initializes a new instance of <see cref="XmlDataContractSerializerOutputFormatter"/>
@ -115,8 +117,12 @@ namespace Microsoft.AspNet.Mvc.Xml
protected override bool CanWriteType(Type declaredType, Type runtimeType)
{
var type = ResolveType(declaredType, runtimeType);
if (type == null)
{
return false;
}
return CreateSerializer(GetSerializableType(type)) != null;
return GetCachedSerializer(GetSerializableType(type)) != null;
}
/// <summary>
@ -183,11 +189,30 @@ namespace Microsoft.AspNet.Mvc.Xml
obj = wrapperProvider.Wrap(obj);
}
var dataContractSerializer = CreateSerializer(wrappingType);
var dataContractSerializer = GetCachedSerializer(wrappingType);
dataContractSerializer.WriteObject(xmlWriter, obj);
}
return Task.FromResult(true);
}
/// <summary>
/// Gets the cached serializer or creates and caches the serializer for the given type.
/// </summary>
/// <returns>The <see cref="DataContractSerializer"/> instance.</returns>
protected virtual DataContractSerializer GetCachedSerializer(Type type)
{
object serializer;
if (!_serializerCache.TryGetValue(type, out serializer))
{
serializer = CreateSerializer(type);
if (serializer != null)
{
_serializerCache.TryAdd(type, serializer);
}
}
return (DataContractSerializer)serializer;
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
@ -18,6 +19,7 @@ namespace Microsoft.AspNet.Mvc.Xml
/// </summary>
public class XmlSerializerInputFormatter : InputFormatter
{
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
/// <summary>
@ -72,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.Xml
{
var type = GetSerializableType(context.ModelType);
var serializer = CreateSerializer(type);
var serializer = GetCachedSerializer(type);
var deserializedObject = serializer.Deserialize(xmlReader);
@ -90,6 +92,12 @@ namespace Microsoft.AspNet.Mvc.Xml
}
}
/// <inheritdoc />
protected override bool CanReadType([NotNull] Type type)
{
return GetCachedSerializer(GetSerializableType(type)) != null;
}
/// <summary>
/// Gets the type to which the XML will be deserialized.
/// </summary>
@ -120,7 +128,36 @@ namespace Microsoft.AspNet.Mvc.Xml
/// <returns>The <see cref="XmlSerializer"/> used during deserialization.</returns>
protected virtual XmlSerializer CreateSerializer(Type type)
{
return new XmlSerializer(type);
try
{
// If the serializer does not support this type it will throw an exception.
return new XmlSerializer(type);
}
catch (Exception)
{
// We do not surface the caught exception because if CanRead returns
// false, then this Formatter is not picked up at all.
return null;
}
}
/// <summary>
/// Gets the cached serializer or creates and caches the serializer for the given type.
/// </summary>
/// <returns>The <see cref="XmlSerializer"/> instance.</returns>
protected virtual XmlSerializer GetCachedSerializer(Type type)
{
object serializer;
if (!_serializerCache.TryGetValue(type, out serializer))
{
serializer = CreateSerializer(type);
if (serializer != null)
{
_serializerCache.TryAdd(type, serializer);
}
}
return (XmlSerializer) serializer;
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
@ -18,6 +19,8 @@ namespace Microsoft.AspNet.Mvc.Xml
/// </summary>
public class XmlSerializerOutputFormatter : OutputFormatter
{
private ConcurrentDictionary<Type, object> _serializerCache = new ConcurrentDictionary<Type, object>();
/// <summary>
/// Initializes a new instance of <see cref="XmlSerializerOutputFormatter"/>
/// with default XmlWriterSettings.
@ -93,8 +96,12 @@ namespace Microsoft.AspNet.Mvc.Xml
protected override bool CanWriteType(Type declaredType, Type runtimeType)
{
var type = ResolveType(declaredType, runtimeType);
if (type == null)
{
return false;
}
return CreateSerializer(GetSerializableType(type)) != null;
return GetCachedSerializer(GetSerializableType(type)) != null;
}
/// <summary>
@ -159,11 +166,30 @@ namespace Microsoft.AspNet.Mvc.Xml
obj = wrapperProvider.Wrap(obj);
}
var xmlSerializer = CreateSerializer(wrappingType);
var xmlSerializer = GetCachedSerializer(wrappingType);
xmlSerializer.Serialize(xmlWriter, obj);
}
return Task.FromResult(true);
}
/// <summary>
/// Gets the cached serializer or creates and caches the serializer for the given type.
/// </summary>
/// <returns>The <see cref="XmlSerializer"/> instance.</returns>
protected virtual XmlSerializer GetCachedSerializer(Type type)
{
object serializer;
if (!_serializerCache.TryGetValue(type, out serializer))
{
serializer = CreateSerializer(type);
if (serializer != null)
{
_serializerCache.TryAdd(type, serializer);
}
}
return (XmlSerializer)serializer;
}
}
}

View File

@ -10,7 +10,6 @@ using System.Threading.Tasks;
using System.Xml;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Xml;
using Microsoft.AspNet.Testing;
using Moq;
using Xunit;
@ -79,6 +78,24 @@ namespace Microsoft.AspNet.Mvc.Xml
Assert.Equal(expectedCanRead, result);
}
[Fact]
public void XmlDataContractSerializer_CachesSerializerForType()
{
// Arrange
var input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<DummyClass><SampleInt>10</SampleInt></DummyClass>";
var formatter = new TestXmlDataContractSerializerInputFormatter();
var contentBytes = Encoding.UTF8.GetBytes(input);
var context = GetInputFormatterContext(contentBytes, typeof(DummyClass));
// Act
formatter.CanRead(context);
formatter.CanRead(context);
// Assert
Assert.Equal(1, formatter.createSerializerCalledCount);
}
[Fact]
public void HasProperSuppportedMediaTypes()
{
@ -485,5 +502,16 @@ namespace Microsoft.AspNet.Mvc.Xml
httpContext.SetupGet(c => c.Request).Returns(request.Object);
return httpContext.Object;
}
private class TestXmlDataContractSerializerInputFormatter : XmlDataContractSerializerInputFormatter
{
public int createSerializerCalledCount = 0;
protected override DataContractSerializer CreateSerializer(Type type)
{
createSerializerCalledCount++;
return base.CreateSerializer(type);
}
}
}
}

View File

@ -111,6 +111,22 @@ namespace Microsoft.AspNet.Mvc.Xml
Assert.True(outputFormatterContext.ActionContext.HttpContext.Response.Body.CanRead);
}
[Fact]
public void XmlDataContractSerializer_CachesSerializerForType()
{
// Arrange
var input = new DummyClass { SampleInt = 10 };
var formatter = new TestXmlDataContractSerializerOutputFormatter();
var context = GetOutputFormatterContext(input, typeof(DummyClass));
// Act
formatter.CanWriteResult(context, MediaTypeHeaderValue.Parse("application/xml"));
formatter.CanWriteResult(context, MediaTypeHeaderValue.Parse("application/xml"));
// Assert
Assert.Equal(1, formatter.createSerializerCalledCount);
}
[Fact]
public void DefaultConstructor_ExpectedWriterSettings_Created()
{
@ -552,5 +568,16 @@ namespace Microsoft.AspNet.Mvc.Xml
httpContext.SetupGet(c => c.Response).Returns(response.Object);
return new ActionContext(httpContext.Object, routeData: null, actionDescriptor: null);
}
private class TestXmlDataContractSerializerOutputFormatter : XmlDataContractSerializerOutputFormatter
{
public int createSerializerCalledCount = 0;
protected override DataContractSerializer CreateSerializer(Type type)
{
createSerializerCalledCount++;
return base.CreateSerializer(type);
}
}
}
}

View File

@ -3,11 +3,13 @@
#if ASPNET50
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Xml;
@ -64,6 +66,42 @@ namespace Microsoft.AspNet.Mvc.Xml
Assert.Equal(expectedCanRead, result);
}
[Theory]
[InlineData(typeof(Dictionary<string, object>), false)]
[InlineData(typeof(string), true)]
public void CanRead_ReturnsFalse_ForAnyUnsupportedModelType(Type modelType, bool expectedCanRead)
{
// Arrange
var formatter = new XmlSerializerInputFormatter();
var contentBytes = Encoding.UTF8.GetBytes("content");
var context = GetInputFormatterContext(contentBytes, modelType);
// Act
var result = formatter.CanRead(context);
// Assert
Assert.Equal(expectedCanRead, result);
}
[Fact]
public void XmlSerializer_CachesSerializerForType()
{
// Arrange
var input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<DummyClass><SampleInt>10</SampleInt></DummyClass>";
var formatter = new TestXmlSerializerInputFormatter();
var contentBytes = Encoding.UTF8.GetBytes(input);
var context = GetInputFormatterContext(contentBytes, typeof(DummyClass));
// Act
formatter.CanRead(context);
formatter.CanRead(context);
// Assert
Assert.Equal(1, formatter.createSerializerCalledCount);
}
[Fact]
public void HasProperSuppportedMediaTypes()
{
@ -374,6 +412,17 @@ namespace Microsoft.AspNet.Mvc.Xml
httpContext.SetupGet(c => c.Request).Returns(request.Object);
return httpContext.Object;
}
private class TestXmlSerializerInputFormatter : XmlSerializerInputFormatter
{
public int createSerializerCalledCount = 0;
protected override XmlSerializer CreateSerializer(Type type)
{
createSerializerCalledCount++;
return base.CreateSerializer(type);
}
}
}
}
#endif

View File

@ -13,6 +13,7 @@ using Microsoft.Net.Http.Headers;
using Microsoft.AspNet.Mvc.Xml;
using Moq;
using Xunit;
using System.Xml.Serialization;
namespace Microsoft.AspNet.Mvc.Xml
{
@ -68,6 +69,22 @@ namespace Microsoft.AspNet.Mvc.Xml
Assert.True(outputFormatterContext.ActionContext.HttpContext.Response.Body.CanRead);
}
[Fact]
public void XmlSerializer_CachesSerializerForType()
{
// Arrange
var input = new DummyClass { SampleInt = 10 };
var formatter = new TestXmlSerializerOutputFormatter();
var context = GetOutputFormatterContext(input, typeof(DummyClass));
// Act
formatter.CanWriteResult(context, MediaTypeHeaderValue.Parse("application/xml"));
formatter.CanWriteResult(context, MediaTypeHeaderValue.Parse("application/xml"));
// Assert
Assert.Equal(1, formatter.createSerializerCalledCount);
}
[Fact]
public void DefaultConstructor_ExpectedWriterSettings_Created()
{
@ -355,5 +372,16 @@ namespace Microsoft.AspNet.Mvc.Xml
httpContext.SetupGet(c => c.Response).Returns(response.Object);
return new ActionContext(httpContext.Object, routeData: null, actionDescriptor: null);
}
private class TestXmlSerializerOutputFormatter : XmlSerializerOutputFormatter
{
public int createSerializerCalledCount = 0;
protected override XmlSerializer CreateSerializer(Type type)
{
createSerializerCalledCount++;
return base.CreateSerializer(type);
}
}
}
}