Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Added InputFormatter base type #1973

Merged
merged 1 commit into from
Feb 12, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// 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.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Reads an object from the request body.
/// </summary>
public abstract class InputFormatter : IInputFormatter
{
/// <summary>
/// Gets the mutable collection of character encodings supported by
/// this <see cref="InputFormatter"/>. The encodings are
/// used when reading the data.
/// </summary>
public IList<Encoding> SupportedEncodings { get; } = new List<Encoding>();

/// <summary>
/// Gets the mutable collection of <see cref="MediaTypeHeaderValue"/> elements supported by
/// this <see cref="InputFormatter"/>.
/// </summary>
public IList<MediaTypeHeaderValue> SupportedMediaTypes { get; } = new List<MediaTypeHeaderValue>();

protected object GetDefaultValueForType(Type modelType)
{
if (modelType.GetTypeInfo().IsValueType)
{
return Activator.CreateInstance(modelType);
}

return null;
}

/// <inheritdoc />
public virtual 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));
}

/// <inheritdoc />
public virtual async Task<object> ReadAsync(InputFormatterContext context)
{
var request = context.ActionContext.HttpContext.Request;
if (request.ContentLength == 0)
{
return GetDefaultValueForType(context.ModelType);
}

return await ReadRequestBodyAsync(context);
}

/// <summary>
/// Reads the request body.
/// </summary>
/// <param name="context">The <see cref="InputFormatterContext"/> associated with the call.</param>
/// <returns>A task which can read the request body.</returns>
public abstract Task<object> ReadRequestBodyAsync(InputFormatterContext context);

/// <summary>
/// Returns encoding based on content type charset parameter.
/// </summary>
protected Encoding SelectCharacterEncoding(MediaTypeHeaderValue contentType)
{
if (contentType != null)
{
var charset = contentType.Charset;
if (!string.IsNullOrWhiteSpace(contentType.Charset))
{
foreach (var supportedEncoding in SupportedEncodings)
{
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.FormatInputFormatterNoEncoding(GetType().FullName));
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you also move the SelectCharacterEncoding method to this base class?...Ideally I would like to see a test where you create a custom formatter using this base type just to see how the experience would be like...currently that picture is missing...please update the PR with it..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved. Will update tests shortly.

}
114 changes: 23 additions & 91 deletions src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,24 @@
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;

namespace Microsoft.AspNet.Mvc
{
public class JsonInputFormatter : IInputFormatter
public class JsonInputFormatter : InputFormatter
{
private const int DefaultMaxDepth = 32;
private JsonSerializerSettings _jsonSerializerSettings;

public JsonInputFormatter()
{
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"));

Expand All @@ -42,12 +37,6 @@ public JsonInputFormatter()
};
}

/// <inheritdoc />
public IList<MediaTypeHeaderValue> SupportedMediaTypes { get; private set; }

/// <inheritdoc />
public IList<Encoding> SupportedEncodings { get; private set; }

/// <summary>
/// Gets or sets the <see cref="JsonSerializerSettings"/> used to configure the <see cref="JsonSerializer"/>.
/// </summary>
Expand All @@ -72,70 +61,17 @@ public JsonSerializerSettings SerializerSettings
public bool CaptureDeserilizationErrors { get; set; }

/// <inheritdoc />
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));
}

/// <inheritdoc />
public async Task<object> ReadAsync([NotNull] InputFormatterContext context)
public override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
{
var type = context.ModelType;
var request = context.ActionContext.HttpContext.Request;
if (request.ContentLength == 0)
{
var modelType = context.ModelType;
var model = modelType.GetTypeInfo().IsValueType ? Activator.CreateInstance(modelType) :
null;
return model;
}

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(requestContentType);

return await ReadInternal(context, effectiveEncoding);
}

/// <summary>
/// Called during deserialization to get the <see cref="JsonReader"/>.
/// </summary>
/// <param name="context">The <see cref="InputFormatterContext"/> for the read.</param>
/// <param name="readStream">The <see cref="Stream"/> from which to read.</param>
/// <param name="effectiveEncoding">The <see cref="Encoding"/> to use when reading.</param>
/// <returns>The <see cref="JsonReader"/> used during deserialization.</returns>
public virtual JsonReader CreateJsonReader([NotNull] InputFormatterContext context,
[NotNull] Stream readStream,
[NotNull] Encoding effectiveEncoding)
{
return new JsonTextReader(new StreamReader(readStream, effectiveEncoding));
}

/// <summary>
/// Called during deserialization to get the <see cref="JsonSerializer"/>.
/// </summary>
/// <returns>The <see cref="JsonSerializer"/> used during serialization and deserialization.</returns>
public virtual JsonSerializer CreateJsonSerializer()
{
return JsonSerializer.Create(SerializerSettings);
}

private Task<object> ReadInternal(InputFormatterContext context,
Encoding effectiveEncoding)
{
var type = context.ModelType;
var request = context.ActionContext.HttpContext.Request;

using (var jsonReader = CreateJsonReader(context, request.Body, effectiveEncoding))
{
jsonReader.CloseInput = false;
Expand Down Expand Up @@ -172,31 +108,27 @@ private Task<object> ReadInternal(InputFormatterContext context,
}
}

private Encoding SelectCharacterEncoding(MediaTypeHeaderValue contentType)
/// <summary>
/// Called during deserialization to get the <see cref="JsonReader"/>.
/// </summary>
/// <param name="context">The <see cref="InputFormatterContext"/> for the read.</param>
/// <param name="readStream">The <see cref="Stream"/> from which to read.</param>
/// <param name="effectiveEncoding">The <see cref="Encoding"/> to use when reading.</param>
/// <returns>The <see cref="JsonReader"/> used during deserialization.</returns>
public virtual JsonReader CreateJsonReader([NotNull] InputFormatterContext context,
[NotNull] Stream readStream,
[NotNull] Encoding effectiveEncoding)
{
if (contentType != null)
{
// Find encoding based on content type charset parameter
var charset = contentType.Charset;
if (!string.IsNullOrWhiteSpace(contentType.Charset))
{
foreach (var supportedEncoding in SupportedEncodings)
{
if (string.Equals(charset, supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase))
{
return supportedEncoding;
}
}
}
}

if (SupportedEncodings.Count > 0)
{
return SupportedEncodings[0];
}
return new JsonTextReader(new StreamReader(readStream, effectiveEncoding));
}

// No supported encoding was found so there is no way for us to start reading.
throw new InvalidOperationException(Resources.FormatInputFormatterNoEncoding(GetType().FullName));
/// <summary>
/// Called during deserialization to get the <see cref="JsonSerializer"/>.
/// </summary>
/// <returns>The <see cref="JsonSerializer"/> used during serialization and deserialization.</returns>
public virtual JsonSerializer CreateJsonSerializer()
{
return JsonSerializer.Create(SerializerSettings);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ protected OutputFormatter()
/// this <see cref="OutputFormatter"/>. The encodings are
/// used when writing the data.
/// </summary>
public IList<Encoding> SupportedEncodings { get; private set; }
public IList<Encoding> SupportedEncodings { get; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same cleanup as InputFormatter


/// <summary>
/// Gets the mutable collection of <see cref="MediaTypeHeaderValue"/> elements supported by
Expand Down
Loading