diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs
index 9962de7a39..769bad4837 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs
@@ -43,6 +43,11 @@ namespace Microsoft.AspNet.Mvc
///
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));
}
+ ///
+ /// Returns a value indicating whether or not the given type can be read by this serializer.
+ ///
+ /// The type of object that will be read.
+ /// true if the type can be read, otherwise false .
+ protected virtual bool CanReadType(Type type)
+ {
+ return true;
+ }
+
///
public virtual async Task ReadAsync(InputFormatterContext context)
{
diff --git a/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs
index 810fdabec5..62c4689336 100644
--- a/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs
@@ -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 _serializerCache = new ConcurrentDictionary();
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
///
@@ -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
}
}
+ ///
+ protected override bool CanReadType([NotNull] Type type)
+ {
+ return GetCachedSerializer(GetSerializableType(type)) != null;
+ }
+
///
/// Called during deserialization to get the .
///
@@ -142,7 +150,36 @@ namespace Microsoft.AspNet.Mvc.Xml
/// The used during deserialization.
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;
+ }
+ }
+
+ ///
+ /// Gets the cached serializer or creates and caches the serializer for the given type.
+ ///
+ /// The instance.
+ 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;
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerOutputFormatter.cs
index 285d8416d5..51dad77720 100644
--- a/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerOutputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerOutputFormatter.cs
@@ -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 _serializerCache = new ConcurrentDictionary();
///
/// Initializes a new instance of
@@ -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;
}
///
@@ -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);
}
+
+ ///
+ /// Gets the cached serializer or creates and caches the serializer for the given type.
+ ///
+ /// The instance.
+ 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;
+ }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerInputFormatter.cs
index bc661b4f91..e8c34b0587 100644
--- a/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerInputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerInputFormatter.cs
@@ -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
///
public class XmlSerializerInputFormatter : InputFormatter
{
+ private ConcurrentDictionary _serializerCache = new ConcurrentDictionary();
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
///
@@ -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
}
}
+ ///
+ protected override bool CanReadType([NotNull] Type type)
+ {
+ return GetCachedSerializer(GetSerializableType(type)) != null;
+ }
+
///
/// Gets the type to which the XML will be deserialized.
///
@@ -120,7 +128,36 @@ namespace Microsoft.AspNet.Mvc.Xml
/// The used during deserialization.
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;
+ }
+ }
+
+ ///
+ /// Gets the cached serializer or creates and caches the serializer for the given type.
+ ///
+ /// The instance.
+ 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;
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerOutputFormatter.cs
index d485d268f9..db7423ec13 100644
--- a/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerOutputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerOutputFormatter.cs
@@ -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
///
public class XmlSerializerOutputFormatter : OutputFormatter
{
+ private ConcurrentDictionary _serializerCache = new ConcurrentDictionary();
+
///
/// Initializes a new instance of
/// 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;
}
///
@@ -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);
}
+
+ ///
+ /// Gets the cached serializer or creates and caches the serializer for the given type.
+ ///
+ /// The instance.
+ 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;
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
index 0785d58fe4..8f09180515 100644
--- a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs
@@ -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 = "" +
+ "10 ";
+ 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);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs
index 95915ac472..111cd544f6 100644
--- a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs
@@ -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);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs
index 4f1dab7dc2..1e8103b3b0 100644
--- a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs
@@ -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), 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 = "" +
+ "10 ";
+ 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
diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerOutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerOutputFormatterTest.cs
index 4ba727cd2d..634c7d2940 100644
--- a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerOutputFormatterTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerOutputFormatterTest.cs
@@ -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);
+ }
+ }
}
}
\ No newline at end of file