Adding Input Formatters to MVC Options and using HeaderValueAbstractions

This commit is contained in:
harshgMSFT 2014-07-31 18:01:30 -07:00
parent 944fafb589
commit d97a427b3d
27 changed files with 440 additions and 241 deletions

View File

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc.OptionDescriptors
{
/// <inheritdoc />
public class DefaultInputFormattersProvider
: OptionDescriptorBasedProvider<IInputFormatter>, IInputFormattersProvider
{
/// <summary>
/// Initializes a new instance of the DefaultInputFormattersProvider class.
/// </summary>
/// <param name="options">An accessor to the <see cref="MvcOptions"/> configured for this application.</param>
/// <param name="typeActivator">An <see cref="ITypeActivator"/> instance used to instantiate types.</param>
/// <param name="serviceProvider">A <see cref="IServiceProvider"/> instance that retrieves services from the
/// service collection.</param>
public DefaultInputFormattersProvider(IOptionsAccessor<MvcOptions> optionsAccessor,
ITypeActivator typeActivator,
IServiceProvider serviceProvider)
: base(optionsAccessor.Options.InputFormatters, typeActivator, serviceProvider)
{
}
/// <inheritdoc />
public IReadOnlyList<IInputFormatter> InputFormatters
{
get
{
return Options;
}
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Provides an activated collection of <see cref="IInputFormatter"/> instances.
/// </summary>
public interface IInputFormattersProvider
{
/// <summary>
/// Gets a collection of activated InputFormatter instances.
/// </summary>
IReadOnlyList<IInputFormatter> InputFormatters { get; }
}
}

View File

@ -0,0 +1,52 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Globalization;
using System.Linq;
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc
{
public class TempInputFormatterProvider : IInputFormatterProvider
{
private IInputFormattersProvider _defaultFormattersProvider;
public TempInputFormatterProvider([NotNull] IInputFormattersProvider formattersProvider)
{
_defaultFormattersProvider = formattersProvider;
}
public IInputFormatter GetInputFormatter(InputFormatterProviderContext context)
{
var request = context.HttpContext.Request;
MediaTypeHeaderValue contentType;
if (!MediaTypeHeaderValue.TryParse(request.ContentType, out contentType))
{
// TODO: https://github.com/aspnet/Mvc/issues/458
throw new InvalidOperationException("400: Bad Request");
}
var formatters = _defaultFormattersProvider.InputFormatters;
foreach (var formatter in formatters)
{
var formatterMatched = formatter.SupportedMediaTypes
.Any(supportedMediaType =>
supportedMediaType.IsSubsetOf(contentType));
if (formatterMatched)
{
return formatter;
}
}
// TODO: https://github.com/aspnet/Mvc/issues/458
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
"415: Unsupported content type {0}",
contentType.RawValue));
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.OptionDescriptors;
using Microsoft.AspNet.Mvc.ReflectedModelBuilder;
@ -24,6 +25,7 @@ namespace Microsoft.AspNet.Mvc
ViewEngines = new List<ViewEngineDescriptor>();
ValueProviderFactories = new List<ValueProviderFactoryDescriptor>();
OutputFormatters = new List<OutputFormatterDescriptor>();
InputFormatters = new List<InputFormatterDescriptor>();
}
/// <summary>
@ -51,6 +53,8 @@ namespace Microsoft.AspNet.Mvc
public List<OutputFormatterDescriptor> OutputFormatters { get; private set; }
public List<InputFormatterDescriptor> InputFormatters { get; private set; }
/// <summary>
/// Provides programmatic configuration for the default <see cref="Rendering.IViewEngine" />.
/// </summary>

View File

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc.OptionDescriptors
{
/// <summary>
/// Encapsulates information that describes an <see cref="IInputFormatter"/>.
/// </summary>
public class InputFormatterDescriptor : OptionDescriptor<IInputFormatter>
{
/// <summary>
/// Creates a new instance of <see cref="InputFormatterDescriptor"/>.
/// </summary>
/// <param name="type">A <see cref="IOutputFormatter"/> type that the descriptor represents.
/// </param>
public InputFormatterDescriptor([NotNull] Type type)
: base(type)
{
}
/// <summary>
/// Creates a new instance of <see cref="InputFormatterDescriptor"/>.
/// </summary>
/// <param name="inputFormatter">An instance of <see cref="IInputFormatter"/>
/// that the descriptor represents.</param>
public InputFormatterDescriptor([NotNull] IInputFormatter inputFormatter)
: base(inputFormatter)
{
}
}
}

View File

@ -0,0 +1,84 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.OptionDescriptors;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Extension methods for adding Input formatters to a collection.
/// </summary>
public static class InputFormatterDescriptorExtensions
{
/// <summary>
/// Adds a type representing a <see cref="IInputFormatter"/> to a descriptor collection.
/// </summary>
/// <param name="descriptors">A list of InputFormatterDescriptors</param>
/// <param name="inputFormatterType">Type representing an <see cref="IInputFormatter"/>.</param>
/// <returns>InputFormatterDescriptor representing the added instance.</returns>
public static InputFormatterDescriptor Add([NotNull] this IList<InputFormatterDescriptor> descriptors,
[NotNull] Type inputFormatterType)
{
var descriptor = new InputFormatterDescriptor(inputFormatterType);
descriptors.Add(descriptor);
return descriptor;
}
/// <summary>
/// Inserts a type representing a <see cref="IInputFormatter"/> to a descriptor collection.
/// </summary>
/// <param name="descriptors">A list of InputFormatterDescriptors</param>
/// <param name="inputFormatterType">Type representing an <see cref="IInputFormatter"/>.</param>
/// <returns>InputFormatterDescriptor representing the inserted instance.</returns>
public static InputFormatterDescriptor Insert([NotNull] this IList<InputFormatterDescriptor> descriptors,
int index,
[NotNull] Type inputFormatterType)
{
if (index < 0 || index > descriptors.Count)
{
throw new ArgumentOutOfRangeException("index");
}
var descriptor = new InputFormatterDescriptor(inputFormatterType);
descriptors.Insert(index, descriptor);
return descriptor;
}
/// <summary>
/// Adds an <see cref="IInputFormatter"/> to a descriptor collection.
/// </summary>
/// <param name="descriptors">A list of InputFormatterDescriptors</param>
/// <param name="inputFormatter">An <see cref="IInputFormatter"/> instance.</param>
/// <returns>InputFormatterDescriptor representing the added instance.</returns>
public static InputFormatterDescriptor Add([NotNull] this IList<InputFormatterDescriptor> descriptors,
[NotNull] IInputFormatter inputFormatter)
{
var descriptor = new InputFormatterDescriptor(inputFormatter);
descriptors.Add(descriptor);
return descriptor;
}
/// <summary>
/// Insert an <see cref="IInputFormatter"/> to a descriptor collection.
/// </summary>
/// <param name="descriptors">A list of InputFormatterDescriptors</param>
/// <param name="inputFormatter">An <see cref="IInputFormatter"/> instance.</param>
/// <returns>InputFormatterDescriptor representing the added instance.</returns>
public static InputFormatterDescriptor Insert([NotNull] this IList<InputFormatterDescriptor> descriptors,
int index,
[NotNull] IInputFormatter inputFormatter)
{
if (index < 0 || index > descriptors.Count)
{
throw new ArgumentOutOfRangeException("index");
}
var descriptor = new InputFormatterDescriptor(inputFormatter);
descriptors.Insert(index, descriptor);
return descriptor;
}
}
}

View File

@ -12,8 +12,8 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions
public static new MediaTypeWithQualityHeaderValue Parse(string input)
{
var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(input);
if (mediaTypeHeaderValue == null)
MediaTypeHeaderValue mediaTypeHeaderValue;
if (!MediaTypeHeaderValue.TryParse(input, out mediaTypeHeaderValue))
{
return null;
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -34,35 +35,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return XmlDictionaryReaderQuotas.Max;
#endif
}
/// Internal because ContentTypeHeaderValue is internal.
internal static Encoding SelectCharacterEncoding(IList<Encoding> supportedEncodings,
ContentTypeHeaderValue contentType, Type callerType)
{
if (contentType != null)
{
// Find encoding based on content type charset parameter
var charset = contentType.CharSet;
if (!string.IsNullOrWhiteSpace(contentType.CharSet))
{
for (var i = 0; i < supportedEncodings.Count; i++)
{
var supportedEncoding = supportedEncodings[i];
if (string.Equals(charset, supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase))
{
return supportedEncoding;
}
}
}
}
if (supportedEncodings.Count > 0)
{
return supportedEncodings[0];
}
// No supported encoding was found so there is no way for us to start reading.
throw new InvalidOperationException(Resources.FormatMediaTypeFormatterNoEncoding(callerType.FullName));
}
}
}

View File

@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -12,7 +13,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <summary>
/// Gets the mutable collection of media types supported by this <see cref="JsonInputFormatter"/> instance.
/// </summary>
IList<string> SupportedMediaTypes { get; }
IList<MediaTypeHeaderValue> SupportedMediaTypes { get; }
/// <summary>
/// Gets the mutable collection of character encodings supported by this <see cref="JsonInputFormatter"/>

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
using Newtonsoft.Json;
namespace Microsoft.AspNet.Mvc.ModelBinding
@ -15,23 +16,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public class JsonInputFormatter : IInputFormatter
{
private const int DefaultMaxDepth = 32;
private readonly List<Encoding> _supportedEncodings;
private readonly List<string> _supportedMediaTypes;
private JsonSerializerSettings _jsonSerializerSettings;
public JsonInputFormatter()
{
_supportedMediaTypes = new List<string>
{
"application/json",
"text/json"
};
_supportedEncodings = new List<Encoding>
{
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true),
new UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: true)
};
SupportedEncodings = new List<Encoding>();
SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM);
SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian);
SupportedMediaTypes = new List<MediaTypeHeaderValue>();
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/json"));
_jsonSerializerSettings = new JsonSerializerSettings
{
@ -48,16 +42,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
/// <inheritdoc />
public IList<string> SupportedMediaTypes
{
get { return _supportedMediaTypes; }
}
public IList<MediaTypeHeaderValue> SupportedMediaTypes { get; private set; }
/// <inheritdoc />
public IList<Encoding> SupportedEncodings
{
get { return _supportedEncodings; }
}
public IList<Encoding> SupportedEncodings { get; private set; }
/// <summary>
/// Gets or sets the <see cref="JsonSerializerSettings"/> used to configure the <see cref="JsonSerializer"/>.
@ -94,9 +82,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return;
}
MediaTypeHeaderValue requestContentType = null;
MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);
// Get the character encoding for the content
// Never non-null since SelectCharacterEncoding() throws in error / not found scenarios
var effectiveEncoding = SelectCharacterEncoding(request.GetContentType());
var effectiveEncoding = SelectCharacterEncoding(requestContentType);
context.Model = await ReadInternal(context, effectiveEncoding);
}
@ -124,12 +115,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return JsonSerializer.Create(SerializerSettings);
}
private bool IsSupportedContentType(ContentTypeHeaderValue contentType)
{
return contentType != null &&
_supportedMediaTypes.Contains(contentType.ContentType, StringComparer.OrdinalIgnoreCase);
}
private Task<object> ReadInternal(InputFormatterContext context,
Encoding effectiveEncoding)
{
@ -172,17 +157,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
private Encoding SelectCharacterEncoding(ContentTypeHeaderValue contentType)
private Encoding SelectCharacterEncoding(MediaTypeHeaderValue contentType)
{
if (contentType != null)
{
// Find encoding based on content type charset parameter
var charset = contentType.CharSet;
if (!string.IsNullOrWhiteSpace(contentType.CharSet))
var charset = contentType.Charset;
if (!string.IsNullOrWhiteSpace(contentType.Charset))
{
for (var i = 0; i < _supportedEncodings.Count; i++)
foreach (var supportedEncoding in SupportedEncodings)
{
var supportedEncoding = _supportedEncodings[i];
if (string.Equals(charset, supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase))
{
return supportedEncoding;
@ -191,9 +175,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
if (_supportedEncodings.Count > 0)
if (SupportedEncodings.Count > 0)
{
return _supportedEncodings[0];
return SupportedEncodings[0];
}
// No supported encoding was found so there is no way for us to start reading.

View File

@ -1,52 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Globalization;
using System.Linq;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class TempInputFormatterProvider : IInputFormatterProvider
{
private IInputFormatter[] _formatters;
public IInputFormatter GetInputFormatter(InputFormatterProviderContext context)
{
var request = context.HttpContext.Request;
var formatters = _formatters;
if (formatters == null)
{
formatters = context.HttpContext.RequestServices.GetService<IEnumerable<IInputFormatter>>()
.ToArray();
_formatters = formatters;
}
var contentType = request.GetContentType();
if (contentType == null)
{
// TODO: http exception?
throw new InvalidOperationException("400: Bad Request");
}
for (var i = 0; i < formatters.Length; i++)
{
var formatter = formatters[i];
if (formatter.SupportedMediaTypes.Contains(contentType.ContentType, StringComparer.OrdinalIgnoreCase))
{
return formatter;
}
}
// TODO: Http exception
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
"415: Unsupported content type {0}",
contentType));
}
}
}

View File

@ -9,6 +9,7 @@ using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -18,8 +19,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
public class XmlDataContractSerializerInputFormatter : IInputFormatter
{
private readonly IList<Encoding> _supportedEncodings;
private readonly IList<string> _supportedMediaTypes;
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
/// <summary>
@ -27,34 +26,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
public XmlDataContractSerializerInputFormatter()
{
_supportedMediaTypes = new List<string>
{
"application/xml",
"text/xml"
};
_supportedEncodings = new List<Encoding>
{
Encodings.UTF8EncodingWithoutBOM,
Encodings.UTF16EncodingLittleEndian
};
SupportedEncodings = new List<Encoding>();
SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM);
SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian);
SupportedMediaTypes = new List<MediaTypeHeaderValue>();
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml"));
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml"));
}
/// <summary>
/// Returns the list of supported encodings.
/// </summary>
public IList<Encoding> SupportedEncodings
{
get { return _supportedEncodings; }
}
/// <inheritdoc />
public IList<MediaTypeHeaderValue> SupportedMediaTypes { get; private set; }
/// <summary>
/// Returns the list of supported Media Types.
/// </summary>
public IList<string> SupportedMediaTypes
{
get { return _supportedMediaTypes; }
}
/// <inheritdoc />
public IList<Encoding> SupportedEncodings { get; private set; }
/// <summary>
/// Indicates the acceptable input XML depth.

View File

@ -9,6 +9,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -18,8 +19,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
public class XmlSerializerInputFormatter : IInputFormatter
{
private readonly IList<Encoding> _supportedEncodings;
private readonly IList<string> _supportedMediaTypes;
private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas();
/// <summary>
@ -27,34 +26,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
public XmlSerializerInputFormatter()
{
_supportedMediaTypes = new List<string>
{
"application/xml",
"text/xml"
};
_supportedEncodings = new List<Encoding>
{
Encodings.UTF8EncodingWithoutBOM,
Encodings.UTF16EncodingLittleEndian
};
SupportedEncodings = new List<Encoding>();
SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM);
SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian);
SupportedMediaTypes = new List<MediaTypeHeaderValue>();
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml"));
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml"));
}
/// <summary>
/// Returns the list of supported encodings.
/// </summary>
public IList<Encoding> SupportedEncodings
{
get { return _supportedEncodings; }
}
/// <inheritdoc />
public IList<MediaTypeHeaderValue> SupportedMediaTypes { get; private set; }
/// <summary>
/// Returns the list of supported Media Types.
/// </summary>
public IList<string> SupportedMediaTypes
{
get { return _supportedMediaTypes; }
}
/// <inheritdoc />
public IList<Encoding> SupportedEncodings { get; private set; }
/// <summary>
/// Indicates the acceptable input XML depth.
@ -105,7 +89,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <summary>
/// Called during deserialization to get the <see cref="XmlSerializer"/>.
/// </summary>
/// <returns>The <see cref="XmlSerializer"/> used during serialization and deserialization.</returns>
/// <returns>The <see cref="XmlSerializer"/> used during deserialization.</returns>
protected virtual XmlSerializer CreateXmlSerializer(Type type)
{
return new XmlSerializer(type);

View File

@ -1,19 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc.ModelBinding
{
internal class ContentTypeHeaderValue
{
public ContentTypeHeaderValue([NotNull] string contentType,
string charSet)
{
ContentType = contentType;
CharSet = charSet;
}
public string ContentType { get; private set; }
public string CharSet { get; set; }
}
}

View File

@ -1,31 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
internal static class HttpRequestExtensions
{
private const string ContentTypeHeader = "Content-Type";
private const string CharSetToken = "charset=";
public static ContentTypeHeaderValue GetContentType(this HttpRequest httpRequest)
{
var headerValue = httpRequest.Headers[ContentTypeHeader];
if (!string.IsNullOrEmpty(headerValue))
{
var tokens = headerValue.Split(new[] { ';' }, 2);
string charSet = null;
if (tokens.Length > 1 &&
tokens[1].TrimStart().StartsWith(CharSetToken, StringComparison.OrdinalIgnoreCase))
{
charSet = tokens[1].TrimStart().Substring(CharSetToken.Length);
}
return new ContentTypeHeaderValue(tokens[0], charSet);
}
return null;
}
}
}

View File

@ -4,12 +4,14 @@
using System;
using System.Globalization;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class FormValueProviderFactory : IValueProviderFactory
{
private const string FormEncodedContentType = "application/x-www-form-urlencoded";
private static MediaTypeHeaderValue _formEncodedContentType =
MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded");
public IValueProvider GetValueProvider([NotNull] ValueProviderFactoryContext context)
{
@ -26,9 +28,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private bool IsSupportedContentType(HttpRequest request)
{
var contentType = request.GetContentType();
return contentType != null &&
string.Equals(contentType.ContentType, FormEncodedContentType, StringComparison.OrdinalIgnoreCase);
MediaTypeHeaderValue requestContentType = null;
return MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType) &&
_formEncodedContentType.IsSubsetOf(requestContentType);
}
private static CultureInfo GetCultureInfo(HttpRequest request)

View File

@ -8,6 +8,7 @@
"Microsoft.AspNet.Mvc.Common": "",
"Microsoft.DataAnnotations": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
"Microsoft.AspNet.Mvc.HeaderValueAbstractions": "1.0.0-*",
"Newtonsoft.Json": "5.0.8"
},
"frameworks": {

View File

@ -1,7 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.OptionDescriptors;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.Framework.OptionsModel;
@ -43,6 +45,11 @@ namespace Microsoft.AspNet.Mvc
options.OutputFormatters.Add(
new XmlSerializerOutputFormatter(XmlOutputFormatter.GetDefaultXmlWriterSettings()));
// Set up default input formatters.
options.InputFormatters.Add(new JsonInputFormatter());
options.InputFormatters.Add(new XmlSerializerInputFormatter());
options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter());
// Set up ValueProviders
options.ValueProviderFactories.Add(new RouteValueValueProviderFactory());
options.ValueProviderFactories.Add(new QueryStringValueProviderFactory());

View File

@ -64,10 +64,8 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Transient<IModelMetadataProvider, DataAnnotationsModelMetadataProvider>();
yield return describe.Scoped<IActionBindingContextProvider, DefaultActionBindingContextProvider>();
yield return describe.Transient<IInputFormatter, JsonInputFormatter>();
yield return describe.Transient<IInputFormatter, XmlSerializerInputFormatter>();
yield return describe.Transient<IInputFormatter, XmlDataContractSerializerInputFormatter>();
yield return describe.Transient<IInputFormatterProvider, TempInputFormatterProvider>();
yield return describe.Transient<IInputFormattersProvider, DefaultInputFormattersProvider>();
yield return describe.Transient<IModelBinderProvider, DefaultModelBindersProvider>();
yield return describe.Scoped<ICompositeModelBinder, CompositeModelBinder>();

View File

@ -0,0 +1,73 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.OptionDescriptors;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.Core.Test
{
public class InputFormatterDescriptorExtensionTest
{
[Theory]
[InlineData(-1)]
[InlineData(5)]
public void Insert_WithType_ThrowsIfIndexIsOutOfBounds(int index)
{
// Arrange
var collection = new List<InputFormatterDescriptor>
{
new InputFormatterDescriptor(Mock.Of<IInputFormatter>()),
new InputFormatterDescriptor(Mock.Of<IInputFormatter>())
};
// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>("index",
() => collection.Insert(index, typeof(IInputFormatter)));
}
[Theory]
[InlineData(-2)]
[InlineData(3)]
public void Insert_WithInstance_ThrowsIfIndexIsOutOfBounds(int index)
{
// Arrange
var collection = new List<InputFormatterDescriptor>
{
new InputFormatterDescriptor(Mock.Of<IInputFormatter>()),
new InputFormatterDescriptor(Mock.Of<IInputFormatter>())
};
var formatter = Mock.Of<IInputFormatter>();
// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>("index", () => collection.Insert(index, formatter));
}
[InlineData]
public void InputFormatterDescriptors_AddsTypesAndInstances()
{
// Arrange
var formatter1 = Mock.Of<IInputFormatter>();
var formatter2 = Mock.Of<IInputFormatter>();
var type1 = typeof(JsonInputFormatter);
var type2 = typeof(IInputFormatter);
var collection = new List<InputFormatterDescriptor>();
// Act
collection.Add(formatter1);
collection.Insert(1, formatter2);
collection.Add(type1);
collection.Insert(2, type2);
// Assert
Assert.Equal(4, collection.Count);
Assert.Equal(formatter1, collection[0].Instance);
Assert.Equal(formatter2, collection[1].Instance);
Assert.Equal(type2, collection[2].OptionType);
Assert.Equal(type1, collection[3].OptionType);
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc.OptionDescriptors;
using Microsoft.AspNet.Testing;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class InputFormatterDescriptorTest
{
[Fact]
public void ConstructorThrows_IfTypeIsNotInputFormatter()
{
// Arrange
var expected = "The type 'System.String' must derive from " +
"'Microsoft.AspNet.Mvc.ModelBinding.IInputFormatter'.";
var type = typeof(string);
// Act & Assert
ExceptionAssert.ThrowsArgument(() => new InputFormatterDescriptor(type), "type", expected);
}
}
}

View File

@ -37,6 +37,23 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(sampleInputInt.ToString(), await response.ReadBodyAsStringAsync());
}
[Fact]
public async Task JsonInputFormatter_IsSelectedForJsonRequest()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.Handler;
var sampleInputInt = 10;
var input = "{\"SampleInt\":10}";
// Act
var response = await client.PostAsync("http://localhost/Home/Index", input, "application/json");
//Assert
Assert.Equal(200, response.StatusCode);
Assert.Equal(sampleInputInt.ToString(), await response.ReadBodyAsStringAsync());
}
// TODO: By default XmlSerializerInputFormatter is called because of the order in which
// the formatters are registered. Add a test to call into DataContractSerializerInputFormatter.
}

View File

@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var mediaType = formatter.SupportedMediaTypes[0];
// Assert
Assert.Equal("application/json", mediaType);
Assert.Equal("application/json", mediaType.RawValue);
}
public static IEnumerable<object[]> JsonFormatterReadSimpleTypesData

View File

@ -50,8 +50,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var formatter = new XmlDataContractSerializerInputFormatter();
// Assert
Assert.True(formatter.SupportedMediaTypes.Contains("application/xml"));
Assert.True(formatter.SupportedMediaTypes.Contains("text/xml"));
Assert.True(formatter.SupportedMediaTypes
.Select(content => content.RawValue)
.Contains("application/xml"));
Assert.True(formatter.SupportedMediaTypes
.Select(content => content.RawValue)
.Contains("text/xml"));
}
[Fact]

View File

@ -41,8 +41,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var formatter = new XmlSerializerInputFormatter();
// Assert
Assert.True(formatter.SupportedMediaTypes.Contains("application/xml"));
Assert.True(formatter.SupportedMediaTypes.Contains("text/xml"));
Assert.True(formatter.SupportedMediaTypes
.Select(content => content.RawValue)
.Contains("application/xml"));
Assert.True(formatter.SupportedMediaTypes
.Select(content => content.RawValue)
.Contains("text/xml"));
}
[Fact]

View File

@ -52,10 +52,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var collection = Mock.Of<IReadableStringCollection>();
var request = new Mock<HttpRequest>();
request.Setup(f => f.GetFormAsync(CancellationToken.None)).Returns(Task.FromResult(collection));
var mockHeader = new Mock<IHeaderDictionary>();
mockHeader.Setup(h => h["Content-Type"]).Returns(contentType);
request.SetupGet(r => r.Headers).Returns(mockHeader.Object);
request.SetupGet(r => r.ContentType).Returns(contentType);
var context = new Mock<HttpContext>();
context.SetupGet(c => c.Request).Returns(request.Object);

View File

@ -80,5 +80,22 @@ namespace Microsoft.AspNet.Mvc
Assert.IsType<XmlDataContractSerializerOutputFormatter>(mvcOptions.OutputFormatters[3].Instance);
Assert.IsType<XmlSerializerOutputFormatter>(mvcOptions.OutputFormatters[4].Instance);
}
[Fact]
public void Setup_SetsUpInputFormatters()
{
// Arrange
var mvcOptions = new MvcOptions();
var setup = new MvcOptionsSetup();
// Act
setup.Setup(mvcOptions);
// Assert
Assert.Equal(3, mvcOptions.InputFormatters.Count);
Assert.IsType<JsonInputFormatter>(mvcOptions.InputFormatters[0].Instance);
Assert.IsType<XmlSerializerInputFormatter>(mvcOptions.InputFormatters[1].Instance);
Assert.IsType<XmlDataContractSerializerInputFormatter>(mvcOptions.InputFormatters[2].Instance);
}
}
}