diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs index 88573bf49d..3cbbc2524d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs @@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc var incomingAcceptHeader = HeaderParsingHelpers.GetAcceptHeaders( formatterContext.ActionContext.HttpContext.Request.Accept); var sortedAcceptHeaders = SortMediaTypeWithQualityHeaderValues(incomingAcceptHeader) - .Where(header => header.Quality != FormattingUtilities.NoMatch) + .Where(header => header.Quality != HttpHeaderUtilitites.NoMatch) .ToArray(); IOutputFormatter selectedFormatter = null; diff --git a/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs index 0c753ff0e3..f7215f0148 100644 --- a/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs @@ -241,18 +241,16 @@ namespace Microsoft.AspNet.Mvc modelAccessor: null, modelType: parameterType); var providerContext = new InputFormatterProviderContext( - actionBindingContext.ActionContext.HttpContext, + actionBindingContext.ActionContext, modelMetadata, modelState); var inputFormatter = actionBindingContext.InputFormatterProvider.GetInputFormatter( providerContext); - var formatterContext = new InputFormatterContext(actionBindingContext.ActionContext.HttpContext, - modelMetadata, - modelState); - await inputFormatter.ReadAsync(formatterContext); - parameterValues[parameter.Name] = formatterContext.Model; + var formatterContext = new InputFormatterContext(actionBindingContext.ActionContext, + modelMetadata.ModelType); + parameterValues[parameter.Name] = await inputFormatter.ReadAsync(formatterContext); } else { diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/DelegatingStream.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/DelegatingStream.cs similarity index 99% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/DelegatingStream.cs rename to src/Microsoft.AspNet.Mvc.Core/Formatters/DelegatingStream.cs index d807799f27..afc27c9430 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/DelegatingStream.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/DelegatingStream.cs @@ -6,7 +6,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.AspNet.Mvc.ModelBinding +namespace Microsoft.AspNet.Mvc { /// /// Stream that delegates to an inner stream. diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/FormattingUtilities.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormattingUtilities.cs similarity index 94% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/FormattingUtilities.cs rename to src/Microsoft.AspNet.Mvc.Core/Formatters/FormattingUtilities.cs index d8da31d762..d8f7cb52c5 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/FormattingUtilities.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormattingUtilities.cs @@ -5,9 +5,10 @@ using System; using System.Collections.Generic; using System.Text; using System.Xml; +using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; -namespace Microsoft.AspNet.Mvc.ModelBinding +namespace Microsoft.AspNet.Mvc { /// /// Contains methods which are used by input formatters. diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatter.cs new file mode 100644 index 0000000000..2c69347ee9 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatter.cs @@ -0,0 +1,33 @@ +// 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 System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.HeaderValueAbstractions; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Reads an object from the request body. + /// + public interface IInputFormatter + { + /// + /// Determines whether this can de-serialize + /// an object of the specified type. + /// + /// Input formatter context associated with this call. + /// True if this supports the passed in + /// request's content-type and is able to de-serialize the request body. + /// False otherwise. + bool CanRead(InputFormatterContext context); + + /// + /// Called during deserialization to read an object from the request. + /// + /// Input formatter context associated with this call. + /// A task that deserializes the request body. + Task ReadAsync(InputFormatterContext context); + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/IInputFormatterProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatterProvider.cs similarity index 91% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/IInputFormatterProvider.cs rename to src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatterProvider.cs index 5b12e99a1f..f46073dc07 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/IInputFormatterProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatterProvider.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Microsoft.AspNet.Mvc.ModelBinding +namespace Microsoft.AspNet.Mvc { public interface IInputFormatterProvider { diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatterContext.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatterContext.cs new file mode 100644 index 0000000000..96a798ca86 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatterContext.cs @@ -0,0 +1,37 @@ +// 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.Text; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Represents information used by an input formatter for + /// deserializing the request body into an object. + /// + public class InputFormatterContext + { + /// + /// Creates a new instance of . + /// + public InputFormatterContext([NotNull] ActionContext actionContext, + [NotNull] Type modelType) + { + ActionContext = actionContext; + ModelType = modelType; + } + + /// + /// Action context associated with the current call. + /// + public ActionContext ActionContext { get; private set; } + + /// + /// Represents the expected type of the model represented by the request body. + /// + public Type ModelType { get; private set; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/InputFormatterProviderContext.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatterProviderContext.cs similarity index 71% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/InputFormatterProviderContext.cs rename to src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatterProviderContext.cs index 90b105a9a3..68c4bf1ed7 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/InputFormatterProviderContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatterProviderContext.cs @@ -2,21 +2,22 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; -namespace Microsoft.AspNet.Mvc.ModelBinding +namespace Microsoft.AspNet.Mvc { public class InputFormatterProviderContext { - public InputFormatterProviderContext([NotNull] HttpContext httpContext, + public InputFormatterProviderContext([NotNull] ActionContext actionContext, [NotNull] ModelMetadata metadata, [NotNull] ModelStateDictionary modelState) { - HttpContext = httpContext; + ActionContext = actionContext; Metadata = metadata; ModelState = modelState; } - public HttpContext HttpContext { get; private set; } + public ActionContext ActionContext { get; private set; } public ModelMetadata Metadata { get; private set; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/JsonInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs similarity index 83% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/JsonInputFormatter.cs rename to src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs index 9bcaaab953..6cdff8e15d 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/JsonInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs @@ -8,10 +8,11 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; using Newtonsoft.Json; -namespace Microsoft.AspNet.Mvc.ModelBinding +namespace Microsoft.AspNet.Mvc { public class JsonInputFormatter : IInputFormatter { @@ -66,20 +67,34 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// /// Gets or sets if deserialization errors are captured. When set, these errors appear in - /// the instance of . + /// the instance of . /// public bool CaptureDeserilizationErrors { get; set; } /// - public async Task ReadAsync([NotNull] InputFormatterContext context) + public bool CanRead(InputFormatterContext context) { - var request = context.HttpContext.Request; + var contentType = context.ActionContext.HttpContext.Request.ContentType; + MediaTypeHeaderValue requestContentType; + if (!MediaTypeHeaderValue.TryParse(contentType, out requestContentType)) + { + return false; + } + + return SupportedMediaTypes + .Any(supportedMediaType => supportedMediaType.IsSubsetOf(requestContentType)); + } + + /// + public async Task ReadAsync([NotNull] InputFormatterContext context) + { + var request = context.ActionContext.HttpContext.Request; if (request.ContentLength == 0) { - var modelType = context.Metadata.ModelType; - context.Model = modelType.GetTypeInfo().IsValueType ? Activator.CreateInstance(modelType) : + var modelType = context.ModelType; + var model = modelType.GetTypeInfo().IsValueType ? Activator.CreateInstance(modelType) : null; - return; + return model; } MediaTypeHeaderValue requestContentType = null; @@ -89,7 +104,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Never non-null since SelectCharacterEncoding() throws in error / not found scenarios var effectiveEncoding = SelectCharacterEncoding(requestContentType); - context.Model = await ReadInternal(context, effectiveEncoding); + return await ReadInternal(context, effectiveEncoding); } /// @@ -118,8 +133,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private Task ReadInternal(InputFormatterContext context, Encoding effectiveEncoding) { - var type = context.Metadata.ModelType; - var request = context.HttpContext.Request; + var type = context.ModelType; + var request = context.ActionContext.HttpContext.Request; using (var jsonReader = CreateJsonReader(context, request.Body, effectiveEncoding)) { @@ -133,7 +148,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding errorHandler = (sender, e) => { var exception = e.ErrorContext.Error; - context.ModelState.AddModelError(e.ErrorContext.Path, e.ErrorContext.Error); + context.ActionContext.ModelState.AddModelError(e.ErrorContext.Path, e.ErrorContext.Error); // Error must always be marked as handled // Failure to do so can cause the exception to be rethrown at every recursive level and // overflow the stack for x64 CLR processes @@ -181,7 +196,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } // No supported encoding was found so there is no way for us to start reading. - throw new InvalidOperationException(Resources.FormatMediaTypeFormatterNoEncoding(GetType().FullName)); + throw new InvalidOperationException(Resources.FormatInputFormatterNoEncoding(GetType().FullName)); } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeWithQualityHeaderValueComparer.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeWithQualityHeaderValueComparer.cs index 65ecd3a84d..047ab24b14 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeWithQualityHeaderValueComparer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeWithQualityHeaderValueComparer.cs @@ -85,8 +85,8 @@ namespace Microsoft.AspNet.Mvc private static int CompareBasedOnQualityFactor(MediaTypeWithQualityHeaderValue mediaType1, MediaTypeWithQualityHeaderValue mediaType2) { - var mediaType1Quality = mediaType1.Quality ?? FormattingUtilities.Match; - var mediaType2Quality = mediaType2.Quality ?? FormattingUtilities.Match; + var mediaType1Quality = mediaType1.Quality ?? HttpHeaderUtilitites.Match; + var mediaType2Quality = mediaType2.Quality ?? HttpHeaderUtilitites.Match; var qualityDifference = mediaType1Quality - mediaType2Quality; if (qualityDifference < 0) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs index ff42bb55b8..383117c109 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs @@ -150,7 +150,7 @@ namespace Microsoft.AspNet.Mvc { var sortedAcceptCharsetHeaders = acceptCharsetHeaders .Where(acceptCharset => - acceptCharset.Quality != FormattingUtilities.NoMatch) + acceptCharset.Quality != HttpHeaderUtilitites.NoMatch) .OrderByDescending( m => m, StringWithQualityHeaderValueComparer.QualityComparer); diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/StringWithQualityHeaderValueComparer.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/StringWithQualityHeaderValueComparer.cs index 1f39c3962e..f1f9365c7f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/StringWithQualityHeaderValueComparer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/StringWithQualityHeaderValueComparer.cs @@ -42,8 +42,8 @@ namespace Microsoft.AspNet.Mvc public int Compare([NotNull] StringWithQualityHeaderValue stringWithQuality1, [NotNull] StringWithQualityHeaderValue stringWithQuality2) { - var quality1 = stringWithQuality1.Quality ?? FormattingUtilities.Match; - var quality2 = stringWithQuality2.Quality ?? FormattingUtilities.Match; + var quality1 = stringWithQuality1.Quality ?? HttpHeaderUtilitites.Match; + var quality2 = stringWithQuality2.Quality ?? HttpHeaderUtilitites.Match; var qualityDifference = quality1 - quality2; if (qualityDifference < 0) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/TempInputFormatterProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/TempInputFormatterProvider.cs index 681e1675ed..e91321d963 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/TempInputFormatterProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/TempInputFormatterProvider.cs @@ -2,13 +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.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 { @@ -23,20 +17,15 @@ namespace Microsoft.AspNet.Mvc 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 request = context.ActionContext.HttpContext.Request; + var formatterContext = new InputFormatterContext(context.ActionContext, + context.Metadata.ModelType); + // TODO: https://github.com/aspnet/Mvc/issues/1014 var formatters = _defaultFormattersProvider.InputFormatters; foreach (var formatter in formatters) { - var formatterMatched = formatter.SupportedMediaTypes - .Any(supportedMediaType => - supportedMediaType.IsSubsetOf(contentType)); + var formatterMatched = formatter.CanRead(formatterContext); if (formatterMatched) { return formatter; @@ -46,7 +35,7 @@ namespace Microsoft.AspNet.Mvc // TODO: https://github.com/aspnet/Mvc/issues/458 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "415: Unsupported content type {0}", - contentType.RawValue)); + request.ContentType)); } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/XmlDataContractSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerInputFormatter.cs similarity index 81% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/XmlDataContractSerializerInputFormatter.cs rename to src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerInputFormatter.cs index cf7751095b..29ce23cb7e 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/XmlDataContractSerializerInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerInputFormatter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Text; @@ -11,7 +12,7 @@ using System.Threading.Tasks; using System.Xml; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; -namespace Microsoft.AspNet.Mvc.ModelBinding +namespace Microsoft.AspNet.Mvc { /// /// This class handles deserialization of input XML data @@ -58,21 +59,34 @@ namespace Microsoft.AspNet.Mvc.ModelBinding get { return _readerQuotas; } } + /// + public bool CanRead(InputFormatterContext context) + { + var contentType = context.ActionContext.HttpContext.Request.ContentType; + MediaTypeHeaderValue requestContentType; + if (!MediaTypeHeaderValue.TryParse(contentType, out requestContentType)) + { + return false; + } + + return SupportedMediaTypes + .Any(supportedMediaType => supportedMediaType.IsSubsetOf(requestContentType)); + } + /// /// Reads the input XML. /// /// The input formatter context which contains the body to be read. /// Task which reads the input. - public async Task ReadAsync(InputFormatterContext context) + public async Task ReadAsync(InputFormatterContext context) { - var request = context.HttpContext.Request; + var request = context.ActionContext.HttpContext.Request; if (request.ContentLength == 0) { - context.Model = GetDefaultValueForType(context.Metadata.ModelType); - return; + return GetDefaultValueForType(context.ModelType); } - context.Model = await ReadInternal(context); + return await ReadInternal(context); } /// @@ -107,8 +121,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private Task ReadInternal(InputFormatterContext context) { - var type = context.Metadata.ModelType; - var request = context.HttpContext.Request; + var type = context.ModelType; + var request = context.ActionContext.HttpContext.Request; using (var xmlReader = CreateXmlReader(new DelegatingStream(request.Body))) { diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/XmlSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerInputFormatter.cs similarity index 80% rename from src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/XmlSerializerInputFormatter.cs rename to src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerInputFormatter.cs index 3013480a4d..dd910646a2 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/XmlSerializerInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerInputFormatter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -11,7 +12,7 @@ using System.Xml; using System.Xml.Serialization; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; -namespace Microsoft.AspNet.Mvc.ModelBinding +namespace Microsoft.AspNet.Mvc { /// /// This class handles deserialization of input XML data @@ -58,21 +59,34 @@ namespace Microsoft.AspNet.Mvc.ModelBinding get { return _readerQuotas; } } + /// + public bool CanRead(InputFormatterContext context) + { + var contentType = context.ActionContext.HttpContext.Request.ContentType; + MediaTypeHeaderValue requestContentType; + if (!MediaTypeHeaderValue.TryParse(contentType, out requestContentType)) + { + return false; + } + + return SupportedMediaTypes + .Any(supportedMediaType => supportedMediaType.IsSubsetOf(requestContentType)); + } + /// /// Reads the input XML. /// /// The input formatter context which contains the body to be read. /// Task which reads the input. - public async Task ReadAsync(InputFormatterContext context) + public async Task ReadAsync(InputFormatterContext context) { - var request = context.HttpContext.Request; + var request = context.ActionContext.HttpContext.Request; if (request.ContentLength == 0) { - context.Model = GetDefaultValueForType(context.Metadata.ModelType); - return; + return GetDefaultValueForType(context.ModelType); } - context.Model = await ReadInternal(context); + return await ReadInternal(context); } /// @@ -107,8 +121,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private Task ReadInternal(InputFormatterContext context) { - var type = context.Metadata.ModelType; - var request = context.HttpContext.Request; + var type = context.ModelType; + var request = context.ActionContext.HttpContext.Request; using (var xmlReader = CreateXmlReader(new DelegatingStream(request.Body))) { diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs index ba1b511050..b317439e0f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs @@ -51,8 +51,16 @@ namespace Microsoft.AspNet.Mvc } } + /// + /// Get a list of the which are used to construct + /// a list of by . + /// public List OutputFormatters { get; private set; } + /// + /// Get a list of the which are used to construct + /// a list of by . + /// public List InputFormatters { get; private set; } /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index 1efeb0c0f4..99db9b5234 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -1066,6 +1066,22 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("OutputFormatterNoEncoding"), p0); } + /// + /// No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content. + /// + internal static string InputFormatterNoEncoding + { + get { return GetString("InputFormatterNoEncoding"); } + } + + /// + /// No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content. + /// + internal static string FormatInputFormatterNoEncoding(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("InputFormatterNoEncoding"), p0); + } + /// /// No supported media type registered for output formatter '{0}'. There must be at least one supported media type registered in order for the output formatter to write content. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs index 85a385d344..d1840e7958 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ReflectedActionInvoker.cs @@ -81,4 +81,4 @@ namespace Microsoft.AspNet.Mvc }; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index ef973ac845..8225d5379f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -315,6 +315,9 @@ No encoding found for output formatter '{0}'. There must be at least one supported encoding registered in order for the output formatter to write content. + + No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content. + No supported media type registered for output formatter '{0}'. There must be at least one supported media type registered in order for the output formatter to write content. diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/FormattingUtilities.cs b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/HttpHeaderUtilitites.cs similarity index 92% rename from src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/FormattingUtilities.cs rename to src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/HttpHeaderUtilitites.cs index f36bb61559..cdb145a1d2 100644 --- a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/FormattingUtilities.cs +++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/HttpHeaderUtilitites.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions { - public static class FormattingUtilities + public static class HttpHeaderUtilitites { /// /// Quality factor to indicate a perfect match. diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeWithQualityHeaderValue.cs b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeWithQualityHeaderValue.cs index e6632d89f1..89fa195332 100644 --- a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeWithQualityHeaderValue.cs +++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeWithQualityHeaderValue.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions return null; } - var quality = FormattingUtilities.Match; + var quality = HttpHeaderUtilitites.Match; string qualityStringValue = null; if (mediaTypeHeaderValue.Parameters.TryGetValue("q", out qualityStringValue)) { diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/Microsoft.AspNet.Mvc.HeaderValueAbstractions.kproj b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/Microsoft.AspNet.Mvc.HeaderValueAbstractions.kproj index 7c7f516cc3..cf23e633b8 100644 --- a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/Microsoft.AspNet.Mvc.HeaderValueAbstractions.kproj +++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/Microsoft.AspNet.Mvc.HeaderValueAbstractions.kproj @@ -16,10 +16,5 @@ 2.0 - - - ResXFileCodeGenerator - - \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/StringWithQualityHeaderValue.cs b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/StringWithQualityHeaderValue.cs index 61621ddb9d..548b902a5e 100644 --- a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/StringWithQualityHeaderValue.cs +++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/StringWithQualityHeaderValue.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions var value = inputArray[0].Trim(); // Unspecified q factor value is equal to a match. - var quality = FormattingUtilities.Match; + var quality = HttpHeaderUtilitites.Match; if (inputArray.Length > 1) { var parameter = inputArray[1].Trim(); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/IInputFormatter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/IInputFormatter.cs deleted file mode 100644 index 8424e4fbe6..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/IInputFormatter.cs +++ /dev/null @@ -1,29 +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.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNet.Mvc.HeaderValueAbstractions; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - public interface IInputFormatter - { - /// - /// Gets the mutable collection of media types supported by this instance. - /// - IList SupportedMediaTypes { get; } - - /// - /// Gets the mutable collection of character encodings supported by this - /// instance. - /// - IList SupportedEncodings { get; } - - /// - /// Called during deserialization to read an object from the request. - /// - Task ReadAsync(InputFormatterContext context); - } -} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/InputFormatterContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/InputFormatterContext.cs deleted file mode 100644 index 823fe32b4f..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Formatters/InputFormatterContext.cs +++ /dev/null @@ -1,29 +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.Text; -using Microsoft.AspNet.Http; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - public class InputFormatterContext - { - public InputFormatterContext([NotNull] HttpContext httpContext, - [NotNull] ModelMetadata metadata, - [NotNull] ModelStateDictionary modelState) - { - HttpContext = httpContext; - Metadata = metadata; - ModelState = modelState; - } - - public HttpContext HttpContext { get; private set; } - - public ModelMetadata Metadata { get; private set; } - - public ModelStateDictionary ModelState { get; private set; } - - public object Model { get; set; } - } -} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs index 72e9396357..8f6da0b7ee 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs @@ -74,22 +74,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return GetString("JQuerySyntaxMissingClosingBracket"); } - /// - /// No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content. - /// - internal static string MediaTypeFormatterNoEncoding - { - get { return GetString("MediaTypeFormatterNoEncoding"); } - } - - /// - /// No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content. - /// - internal static string FormatMediaTypeFormatterNoEncoding(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("MediaTypeFormatterNoEncoding"), p0); - } - /// /// Property '{0}' on type '{1}' is invalid. Value-typed properties marked as [Required] must also be marked with [DataMember(IsRequired=true)] to be recognized as required. Consider attributing the declaring type with [DataContract] and the property with [DataMember(IsRequired=true)]. /// diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx b/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx index 5bde6c1dbe..5f322e7d89 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx @@ -129,9 +129,6 @@ The key is invalid JQuery syntax because it is missing a closing bracket. - - No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content. - Property '{0}' on type '{1}' is invalid. Value-typed properties marked as [Required] must also be marked with [DataMember(IsRequired=true)] to be recognized as required. Consider attributing the declaring type with [DataContract] and the property with [DataMember(IsRequired=true)]. diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs index a276309c27..e18bed1260 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public class FormValueProviderFactory : IValueProviderFactory { private static MediaTypeHeaderValue _formEncodedContentType = - MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded"); + MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded"); public IValueProvider GetValueProvider([NotNull] ValueProviderFactoryContext context) { @@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { MediaTypeHeaderValue requestContentType = null; return MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType) && - _formEncodedContentType.IsSubsetOf(requestContentType); + _formEncodedContentType.IsSubsetOf(requestContentType); } private static CultureInfo GetCultureInfo(HttpRequest request) diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/DelegatingStreamTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/DelegatingStreamTests.cs similarity index 94% rename from test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/DelegatingStreamTests.cs rename to test/Microsoft.AspNet.Mvc.Core.Test/Formatters/DelegatingStreamTests.cs index d19295db4d..334fea6dd6 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/DelegatingStreamTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/DelegatingStreamTests.cs @@ -5,7 +5,7 @@ using System.IO; using Xunit; -namespace Microsoft.AspNet.Mvc.ModelBinding.Test +namespace Microsoft.AspNet.Mvc { public class DelegatingStreamTests { diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/JsonInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonInputFormatterTest.cs similarity index 59% rename from test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/JsonInputFormatterTest.cs rename to test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonInputFormatterTest.cs index be4a915022..87f2da86b5 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/JsonInputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/JsonInputFormatterTest.cs @@ -8,14 +8,43 @@ using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; using Moq; using Newtonsoft.Json; using Xunit; -namespace Microsoft.AspNet.Mvc.ModelBinding +namespace Microsoft.AspNet.Mvc { public class JsonInputFormatterTest - { + { + + [Theory] + [InlineData("application/json", true)] + [InlineData("application/*", true)] + [InlineData("*/*", true)] + [InlineData("text/json", true)] + [InlineData("text/*", true)] + [InlineData("text/xml", false)] + [InlineData("application/xml", false)] + [InlineData("", false)] + [InlineData(null, false)] + [InlineData("invalid", false)] + public void CanRead_ReturnsTrueForAnySupportedContentType(string requestContentType, bool expectedCanRead) + { + // Arrange + var formatter = new JsonInputFormatter(); + var contentBytes = Encoding.UTF8.GetBytes("content"); + + var actionContext = GetActionContext(contentBytes, contentType: requestContentType); + var formatterContext = new InputFormatterContext(actionContext, typeof(string)); + + // Act + var result = formatter.CanRead(formatterContext); + + // Assert + Assert.Equal(expectedCanRead, result); + } + [Fact] public void DefaultMediaType_ReturnsApplicationJson() { @@ -48,16 +77,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var formatter = new JsonInputFormatter(); var contentBytes = Encoding.UTF8.GetBytes(content); - var httpContext = GetHttpContext(contentBytes); - var modelState = new ModelStateDictionary(); - var metadata = new EmptyModelMetadataProvider().GetMetadataForType(null, type); - var context = new InputFormatterContext(httpContext, metadata, modelState); + var actionContext = GetActionContext(contentBytes); + var context = new InputFormatterContext(actionContext, type); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.Equal(expected, context.Model); + Assert.Equal(expected, model); } [Fact] @@ -68,18 +95,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var formatter = new JsonInputFormatter(); var contentBytes = Encoding.UTF8.GetBytes(content); - var httpContext = GetHttpContext(contentBytes); - var modelState = new ModelStateDictionary(); + var actionContext = GetActionContext(contentBytes); var metadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(User)); - var context = new InputFormatterContext(httpContext, metadata, modelState); + var context = new InputFormatterContext(actionContext, metadata.ModelType); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - var model = Assert.IsType(context.Model); - Assert.Equal("Person Name", model.Name); - Assert.Equal(30, model.Age); + var userModel = Assert.IsType(model); + Assert.Equal("Person Name", userModel.Name); + Assert.Equal(30, userModel.Age); } [Fact] @@ -90,10 +116,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var formatter = new JsonInputFormatter(); var contentBytes = Encoding.UTF8.GetBytes(content); - var httpContext = GetHttpContext(contentBytes); - var modelState = new ModelStateDictionary(); + var httpContext = GetActionContext(contentBytes); var metadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(User)); - var context = new InputFormatterContext(httpContext, metadata, modelState); + var context = new InputFormatterContext(httpContext, metadata.ModelType); // Act and Assert await Assert.ThrowsAsync(() => formatter.ReadAsync(context)); @@ -107,17 +132,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var formatter = new JsonInputFormatter { CaptureDeserilizationErrors = true }; var contentBytes = Encoding.UTF8.GetBytes(content); - var httpContext = GetHttpContext(contentBytes); - var modelState = new ModelStateDictionary(); + var actionContext = GetActionContext(contentBytes); var metadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(User)); - var context = new InputFormatterContext(httpContext, metadata, modelState); + var context = new InputFormatterContext(actionContext, metadata.ModelType); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.Equal("Could not convert string to decimal: not-an-age. Path 'Age', line 1, position 39.", - modelState["Age"].Errors[0].Exception.Message); + Assert.Equal("Could not convert string to decimal: not-an-age. Path 'Age', line 1, position 39.", + actionContext.ModelState["Age"].Errors[0].Exception.Message); + } + + private static ActionContext GetActionContext(byte[] contentBytes, + string contentType = "application/xml") + { + return new ActionContext(GetHttpContext(contentBytes, contentType), + new AspNet.Routing.RouteData(), + new ActionDescriptor()); } private static HttpContext GetHttpContext(byte[] contentBytes, @@ -125,12 +157,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { var request = new Mock(); var headers = new Mock(); - headers.SetupGet(h => h["Content-Type"]).Returns(contentType); request.SetupGet(r => r.Headers).Returns(headers.Object); request.SetupGet(f => f.Body).Returns(new MemoryStream(contentBytes)); + request.SetupGet(f => f.ContentType).Returns(contentType); var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); + httpContext.SetupGet(c => c.Request).Returns(request.Object); return httpContext.Object; } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/XmlDataContractSerializerInputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerInputFormatterTests.cs similarity index 75% rename from test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/XmlDataContractSerializerInputFormatterTests.cs rename to test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerInputFormatterTests.cs index 110e69d33e..3094083fbe 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/XmlDataContractSerializerInputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerInputFormatterTests.cs @@ -10,10 +10,11 @@ using System.Text; using System.Threading.Tasks; using System.Xml; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc.ModelBinding +namespace Microsoft.AspNet.Mvc { public class DataContractSerializerInputFormatterTests { @@ -43,6 +44,33 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public TestLevelOne TestOne { get; set; } } + [Theory] + [InlineData("application/xml", true)] + [InlineData("application/*", true)] + [InlineData("*/*", true)] + [InlineData("text/xml", true)] + [InlineData("text/*", true)] + [InlineData("text/json", false)] + [InlineData("application/json", false)] + [InlineData("", false)] + [InlineData(null, false)] + [InlineData("invalid", false)] + public void CanRead_ReturnsTrueForAnySupportedContentType(string requestContentType, bool expectedCanRead) + { + // Arrange + var formatter = new XmlDataContractSerializerInputFormatter(); + var contentBytes = Encoding.UTF8.GetBytes("content"); + + var actionContext = GetActionContext(contentBytes, contentType: requestContentType); + var formatterContext = new InputFormatterContext(actionContext, typeof(string)); + + // Act + var result = formatter.CanRead(formatterContext); + + // Assert + Assert.Equal(expectedCanRead, result); + } + [Fact] public void XmlDataContractSerializerFormatterHasProperSuppportedMediaTypes() { @@ -85,15 +113,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var context = GetInputFormatterContext(contentBytes, typeof(TestLevelOne)); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - Assert.IsType(context.Model); + Assert.NotNull(model); + Assert.IsType(model); - var model = context.Model as TestLevelOne; - Assert.Equal(expectedInt, model.SampleInt); - Assert.Equal(expectedString, model.sampleString); + var levelOneModel = model as TestLevelOne; + Assert.Equal(expectedInt, levelOneModel.SampleInt); + Assert.Equal(expectedString, levelOneModel.sampleString); } [Fact] @@ -114,16 +142,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - Assert.IsType(context.Model); + Assert.NotNull(model); + Assert.IsType(model); - var model = context.Model as TestLevelTwo; - Assert.Equal(expectedLevelTwoString, model.SampleString); - Assert.Equal(expectedInt, model.TestOne.SampleInt); - Assert.Equal(expectedString, model.TestOne.sampleString); + var levelTwoModel = model as TestLevelTwo; + Assert.Equal(expectedLevelTwoString, levelTwoModel.SampleString); + Assert.Equal(expectedInt, levelTwoModel.TestOne.SampleInt); + Assert.Equal(expectedString, levelTwoModel.TestOne.sampleString); } [Fact] @@ -141,13 +169,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - Assert.IsType(context.Model); - var model = context.Model as DummyClass; - Assert.Equal(expectedInt, model.SampleInt); + Assert.NotNull(model); + Assert.IsType(model); + var dummyModel = model as DummyClass; + Assert.Equal(expectedInt, dummyModel.SampleInt); } [Fact] @@ -205,11 +233,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - Assert.True(context.HttpContext.Request.Body.CanRead); + Assert.NotNull(model); + Assert.True(context.ActionContext.HttpContext.Request.Body.CanRead); } [Fact] @@ -254,14 +282,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - var model = context.Model as TestLevelTwo; + Assert.NotNull(model); + var levelTwoModel = model as TestLevelTwo; Buffer.BlockCopy(sampleStringBytes, 0, expectedBytes, 0, sampleStringBytes.Length); Buffer.BlockCopy(bom, 0, expectedBytes, sampleStringBytes.Length, bom.Length); - Assert.Equal(expectedBytes, Encoding.UTF8.GetBytes(model.SampleString)); + Assert.Equal(expectedBytes, Encoding.UTF8.GetBytes(levelTwoModel.SampleString)); } [Fact] @@ -280,36 +308,43 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var context = GetInputFormatterContext(contentBytes, typeof(TestLevelOne)); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - Assert.IsType(context.Model); + Assert.NotNull(model); + Assert.IsType(model); - var model = context.Model as TestLevelOne; - Assert.Equal(expectedInt, model.SampleInt); - Assert.Equal(expectedString, model.sampleString); + var levelOneModel = model as TestLevelOne; + Assert.Equal(expectedInt, levelOneModel.SampleInt); + Assert.Equal(expectedString, levelOneModel.sampleString); } private InputFormatterContext GetInputFormatterContext(byte[] contentBytes, Type modelType) { - var httpContext = GetHttpContext(contentBytes); - var modelState = new ModelStateDictionary(); + var actionContext = GetActionContext(contentBytes); var metadata = new EmptyModelMetadataProvider().GetMetadataForType(null, modelType); - return new InputFormatterContext(httpContext, metadata, modelState); + return new InputFormatterContext(actionContext, metadata.ModelType); } + private static ActionContext GetActionContext(byte[] contentBytes, + string contentType = "application/xml") + { + return new ActionContext(GetHttpContext(contentBytes, contentType), + new AspNet.Routing.RouteData(), + new ActionDescriptor()); + } private static HttpContext GetHttpContext(byte[] contentBytes, - string contentType = "application/xml") + string contentType = "application/xml") { var request = new Mock(); var headers = new Mock(); - headers.SetupGet(h => h["Content-Type"]).Returns(contentType); request.SetupGet(r => r.Headers).Returns(headers.Object); request.SetupGet(f => f.Body).Returns(new MemoryStream(contentBytes)); + request.SetupGet(f => f.ContentType).Returns(contentType); var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); + httpContext.SetupGet(c => c.Request).Returns(request.Object); return httpContext.Object; } } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/XmlSerializerInputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerInputFormatterTests.cs similarity index 74% rename from test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/XmlSerializerInputFormatterTests.cs rename to test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerInputFormatterTests.cs index 4bc8b0a6e6..1ce7128da5 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Formatters/XmlSerializerInputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerInputFormatterTests.cs @@ -9,10 +9,11 @@ using System.Text; using System.Threading.Tasks; using System.Xml; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc.ModelBinding +namespace Microsoft.AspNet.Mvc { public class XmlSerializerInputFormatterTests { @@ -34,6 +35,33 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public TestLevelOne TestOne { get; set; } } + [Theory] + [InlineData("application/xml", true)] + [InlineData("application/*", true)] + [InlineData("*/*", true)] + [InlineData("text/xml", true)] + [InlineData("text/*", true)] + [InlineData("text/json", false)] + [InlineData("application/json", false)] + [InlineData("", false)] + [InlineData("invalid", false)] + [InlineData(null, false)] + public void CanRead_ReturnsTrueForAnySupportedContentType(string requestContentType, bool expectedCanRead) + { + // Arrange + var formatter = new XmlSerializerInputFormatter(); + var contentBytes = Encoding.UTF8.GetBytes("content"); + + var actionContext = GetActionContext(contentBytes, contentType: requestContentType); + var formatterContext = new InputFormatterContext(actionContext, typeof(string)); + + // Act + var result = formatter.CanRead(formatterContext); + + // Assert + Assert.Equal(expectedCanRead, result); + } + [Fact] public void XmlSerializerFormatterHasProperSuppportedMediaTypes() { @@ -78,16 +106,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var context = GetInputFormatterContext(contentBytes, typeof(TestLevelOne)); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - Assert.IsType(context.Model); + Assert.NotNull(model); + Assert.IsType(model); - var model = context.Model as TestLevelOne; - Assert.Equal(expectedInt, model.SampleInt); - Assert.Equal(expectedString, model.sampleString); - Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), model.SampleDate); + var levelOneModel = model as TestLevelOne; + Assert.Equal(expectedInt, levelOneModel.SampleInt); + Assert.Equal(expectedString, levelOneModel.sampleString); + Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), + levelOneModel.SampleDate); } [Fact] @@ -110,17 +139,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - Assert.IsType(context.Model); + Assert.NotNull(model); + Assert.IsType(model); - var model = context.Model as TestLevelTwo; - Assert.Equal(expectedLevelTwoString, model.SampleString); - Assert.Equal(expectedInt, model.TestOne.SampleInt); - Assert.Equal(expectedString, model.TestOne.sampleString); - Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), model.TestOne.SampleDate); + var levelTwoModel = model as TestLevelTwo; + Assert.Equal(expectedLevelTwoString, levelTwoModel.SampleString); + Assert.Equal(expectedInt, levelTwoModel.TestOne.SampleInt); + Assert.Equal(expectedString, levelTwoModel.TestOne.sampleString); + Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), + levelTwoModel.TestOne.SampleDate); } [Fact] @@ -138,13 +168,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - Assert.IsType(context.Model); - var model = context.Model as DummyClass; - Assert.Equal(expectedInt, model.SampleInt); + Assert.NotNull(model); + Assert.IsType(model); + var dummyModel = model as DummyClass; + Assert.Equal(expectedInt, dummyModel.SampleInt); } [Fact] @@ -206,11 +236,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var context = GetInputFormatterContext(contentBytes, typeof(DummyClass)); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - Assert.True(context.HttpContext.Request.Body.CanRead); + Assert.NotNull(model); + Assert.True(context.ActionContext.HttpContext.Request.Body.CanRead); } [Fact] @@ -255,14 +285,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var context = GetInputFormatterContext(contentBytes, typeof(TestLevelTwo)); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - var model = context.Model as TestLevelTwo; + Assert.NotNull(model); + var levelTwoModel = model as TestLevelTwo; Buffer.BlockCopy(sampleStringBytes, 0, expectedBytes, 0, sampleStringBytes.Length); Buffer.BlockCopy(bom, 0, expectedBytes, sampleStringBytes.Length, bom.Length); - Assert.Equal(expectedBytes, Encoding.UTF8.GetBytes(model.SampleString)); + Assert.Equal(expectedBytes, Encoding.UTF8.GetBytes(levelTwoModel.SampleString)); } [Fact] @@ -283,37 +313,44 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var context = GetInputFormatterContext(contentBytes, typeof(TestLevelOne)); // Act - await formatter.ReadAsync(context); + var model = await formatter.ReadAsync(context); // Assert - Assert.NotNull(context.Model); - Assert.IsType(context.Model); + Assert.NotNull(model); + Assert.IsType(model); - var model = context.Model as TestLevelOne; - Assert.Equal(expectedInt, model.SampleInt); - Assert.Equal(expectedString, model.sampleString); - Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), model.SampleDate); + var levelOneModel = model as TestLevelOne; + Assert.Equal(expectedInt, levelOneModel.SampleInt); + Assert.Equal(expectedString, levelOneModel.sampleString); + Assert.Equal(XmlConvert.ToDateTime(expectedDateTime, XmlDateTimeSerializationMode.Utc), levelOneModel.SampleDate); } private InputFormatterContext GetInputFormatterContext(byte[] contentBytes, Type modelType) { - var httpContext = GetHttpContext(contentBytes); - var modelState = new ModelStateDictionary(); + var actionContext = GetActionContext(contentBytes); var metadata = new EmptyModelMetadataProvider().GetMetadataForType(null, modelType); - return new InputFormatterContext(httpContext, metadata, modelState); + return new InputFormatterContext(actionContext, metadata.ModelType); } + private static ActionContext GetActionContext(byte[] contentBytes, + string contentType = "application/xml") + { + return new ActionContext(GetHttpContext(contentBytes, contentType), + new AspNet.Routing.RouteData(), + new ActionDescriptor()); + } private static HttpContext GetHttpContext(byte[] contentBytes, - string contentType = "application/xml") + string contentType = "application/xml") { var request = new Mock(); var headers = new Mock(); - headers.SetupGet(h => h["Content-Type"]).Returns(contentType); request.SetupGet(r => r.Headers).Returns(headers.Object); request.SetupGet(f => f.Body).Returns(new MemoryStream(contentBytes)); + request.SetupGet(f => f.ContentType).Returns(contentType); var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); + httpContext.SetupGet(c => c.Request).Returns(request.Object); return httpContext.Object; } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/InputFormatterDescriptorTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/InputFormatterDescriptorTest.cs index 027a75d1f8..298b766a49 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/InputFormatterDescriptorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/InputFormatterDescriptorTest.cs @@ -1,6 +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; +using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.AspNet.Mvc.OptionDescriptors; using Microsoft.AspNet.Testing; using Xunit; @@ -14,12 +17,53 @@ namespace Microsoft.AspNet.Mvc { // Arrange var expected = "The type 'System.String' must derive from " + - "'Microsoft.AspNet.Mvc.ModelBinding.IInputFormatter'."; + "'Microsoft.AspNet.Mvc.IInputFormatter'."; var type = typeof(string); // Act & Assert ExceptionAssert.ThrowsArgument(() => new InputFormatterDescriptor(type), "type", expected); } + + [Fact] + public void ConstructorSets_InputFormatterType() + { + // Arrange + var type = typeof(TestInputFormatter); + + // Act + var descriptor = new InputFormatterDescriptor(type); + + // Assert + Assert.Equal(type, descriptor.OptionType); + Assert.Null(descriptor.Instance); + } + + [Fact] + public void ConstructorSets_InputFormatterInstanceAndType() + { + // Arrange + var testFormatter = new TestInputFormatter(); + + // Act + var descriptor = new InputFormatterDescriptor(testFormatter); + + // Assert + Assert.Same(testFormatter, descriptor.Instance); + Assert.Equal(testFormatter.GetType(), descriptor.OptionType); + } + + private class TestInputFormatter : IInputFormatter + { + public bool CanRead(InputFormatterContext context) + { + throw new NotImplementedException(); + } + + public Task ReadAsync(InputFormatterContext context) + { + throw new NotImplementedException(); + } + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/OutputFormatterDescriptorTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/OutputFormatterDescriptorTest.cs index 5b395b41cc..3d50106498 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/OutputFormatterDescriptorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/OutputFormatterDescriptorTest.cs @@ -1,6 +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; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.HeaderValueAbstractions; using Microsoft.AspNet.Mvc.OptionDescriptors; using Microsoft.AspNet.Testing; using Xunit; @@ -21,5 +24,46 @@ namespace Microsoft.AspNet.Mvc.Core // Act & Assert ExceptionAssert.ThrowsArgument(() => new OutputFormatterDescriptor(type), "type", expected); } + + [Fact] + public void ConstructorSets_OutputFormatterType() + { + // Arrange + var type = typeof(TestOutputFormatter); + + // Act + var descriptor = new OutputFormatterDescriptor(type); + + // Assert + Assert.Equal(type, descriptor.OptionType); + Assert.Null(descriptor.Instance); + } + + [Fact] + public void ConstructorSets_OutputFormatterInsnaceAndType() + { + // Arrange + var testFormatter = new TestOutputFormatter(); + + // Act + var descriptor = new OutputFormatterDescriptor(testFormatter); + + // Assert + Assert.Same(testFormatter, descriptor.Instance); + Assert.Equal(testFormatter.GetType(), descriptor.OptionType); + } + + private class TestOutputFormatter : IOutputFormatter + { + public bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) + { + throw new NotImplementedException(); + } + + public Task WriteAsync(OutputFormatterContext context) + { + throw new NotImplementedException(); + } + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/InputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/InputFormatterTests.cs index 6438b8ef91..559bfb87e0 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/InputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/InputFormatterTests.cs @@ -37,8 +37,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(sampleInputInt.ToString(), await response.ReadBodyAsStringAsync()); } - [Fact] - public async Task JsonInputFormatter_IsSelectedForJsonRequest() + [Theory] + [InlineData("application/json")] + [InlineData("application/*")] + [InlineData("*/*")] + [InlineData("text/json")] + [InlineData("text/*")] + public async Task JsonInputFormatter_IsSelectedForJsonRequest(string requestContentType) { // Arrange var server = TestServer.Create(_services, _app); @@ -47,13 +52,33 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var input = "{\"SampleInt\":10}"; // Act - var response = await client.PostAsync("http://localhost/Home/Index", input, "application/json"); + var response = await client.PostAsync("http://localhost/Home/Index", input, requestContentType); //Assert Assert.Equal(200, response.StatusCode); Assert.Equal(sampleInputInt.ToString(), await response.ReadBodyAsStringAsync()); } + [Theory] + [InlineData("")] + [InlineData(null)] + [InlineData("invalid")] + public async Task JsonInputFormatter_IsNotSelectedForNonJsonRequests(string requestContentType) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.Handler; + var input = "{\"SampleInt\":10}"; + + // Act + var ex = await Assert.ThrowsAsync + (() => client.PostAsync("http://localhost/Home/CheckIfDummyIsNull", input, requestContentType)); + + //Assert + // TODO: Change the validation after https://github.com/aspnet/Mvc/issues/458 is fixed. + Assert.Equal("415: Unsupported content type " + requestContentType, ex.Message); + } + // TODO: By default XmlSerializerInputFormatter is called because of the order in which // the formatters are registered. Add a test to call into DataContractSerializerInputFormatter. } diff --git a/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/MediaTypeHeaderValueParsingTests.cs b/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/MediaTypeHeaderValueParsingTests.cs index 96779feb76..981e6b2af0 100644 --- a/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/MediaTypeHeaderValueParsingTests.cs +++ b/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/MediaTypeHeaderValueParsingTests.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions null, MediaTypeHeaderValueRange.AllMediaRange, new Dictionary(), - FormattingUtilities.Match, + HttpHeaderUtilitites.Match, "*/*" }; @@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions "utf-8", MediaTypeHeaderValueRange.SubtypeMediaRange, new Dictionary() { { "charset", "utf-8" }, { "foo", "bar" } }, - FormattingUtilities.Match, + HttpHeaderUtilitites.Match, "text/*;charset=utf-8;foo=bar", }; @@ -41,7 +41,7 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions "utf-8", MediaTypeHeaderValueRange.None, new Dictionary() { { "charset", "utf-8" }, { "foo", "bar" }, { "q", "0.0" } }, - FormattingUtilities.NoMatch, + HttpHeaderUtilitites.NoMatch, "text/plain;charset=utf-8;foo=bar;q=0.0", }; } diff --git a/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/StringWithQualityHeaderValueParsingTests.cs b/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/StringWithQualityHeaderValueParsingTests.cs index 6992c48e32..fa0c884441 100644 --- a/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/StringWithQualityHeaderValueParsingTests.cs +++ b/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/StringWithQualityHeaderValueParsingTests.cs @@ -8,9 +8,9 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions public class StringWithQualityHeaderValueParsingTests { [Theory] - [InlineData("*", FormattingUtilities.Match, "*")] + [InlineData("*", HttpHeaderUtilitites.Match, "*")] [InlineData("*", 0.7, "*;q=.7")] - [InlineData("iso-8859-5", FormattingUtilities.Match, "iso-8859-5")] + [InlineData("iso-8859-5", HttpHeaderUtilitites.Match, "iso-8859-5")] [InlineData("unicode-1-1", 0.8, "unicode-1-1;q=0.8")] [InlineData("unicode-1-1", 0.8, "unicode-1-1;q =0.8")] [InlineData("unicode-1-1", 0.8, "unicode-1-1;q = 0.8")] diff --git a/test/WebSites/FormatterWebSite/Controllers/HomeController.cs b/test/WebSites/FormatterWebSite/Controllers/HomeController.cs index e8d8f0cdaf..a15ed3820f 100644 --- a/test/WebSites/FormatterWebSite/Controllers/HomeController.cs +++ b/test/WebSites/FormatterWebSite/Controllers/HomeController.cs @@ -18,5 +18,11 @@ namespace FormatterWebSite.Controllers { return new DummyClass { SampleInt = sampleInput }; } + + [HttpPost] + public bool CheckIfDummyIsNull([FromBody] DummyClass dummy) + { + return dummy != null; + } } } \ No newline at end of file