Implement StreamOutputFormatter

Include Functional tests and unit tests

Resolves #1653
This commit is contained in:
Yishai Galatzer 2015-02-03 15:42:04 -08:00
parent 4691823a50
commit 5a3863d562
7 changed files with 274 additions and 11 deletions

View File

@ -69,6 +69,14 @@ namespace Microsoft.AspNet.Mvc
public virtual IOutputFormatter SelectFormatter(OutputFormatterContext formatterContext,
IEnumerable<IOutputFormatter> formatters)
{
if (ContentTypes.Count == 1)
{
// There is only one content type specified so we can skip looking at the accept headers.
return SelectFormatterUsingAnyAcceptableContentType(formatterContext,
formatters,
ContentTypes);
}
var incomingAcceptHeaderMediaTypes = formatterContext.ActionContext.HttpContext.Request.GetTypedHeaders().Accept ??
new MediaTypeHeaderValue[] { };
@ -143,14 +151,6 @@ namespace Microsoft.AspNet.Mvc
}
}
}
else if (ContentTypes.Count == 1)
{
// There is only one value that can be supported.
selectedFormatter = SelectFormatterUsingAnyAcceptableContentType(
formatterContext,
formatters,
ContentTypes);
}
else
{
if (respectAcceptHeader)

View File

@ -0,0 +1,72 @@
// 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.IO;
using System.Threading.Tasks;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Always copies the stream to the response, regardless of requested content type.
/// </summary>
public class StreamOutputFormatter : IOutputFormatter
{
/// <summary>
/// Echos the <paramref name="contentType"/> if the <paramref name="runtimeType"/> implements
/// <see cref="Stream"/> and <paramref name="contentType"/> is not <c>null</c>.
/// </summary>
/// <param name="declaredType">The declared type for which the supported content types are desired.</param>
/// <param name="runtimeType">The runtime type for which the supported content types are desired.</param>
/// <param name="contentType">
/// The content type for which the supported content types are desired, or <c>null</c> if any content
/// type can be used.
/// </param>
/// <returns>Content types which are supported by this formatter.</returns>
public IReadOnlyList<MediaTypeHeaderValue> GetSupportedContentTypes(
Type declaredType,
Type runtimeType,
MediaTypeHeaderValue contentType)
{
if (contentType != null &&
runtimeType != null &&
typeof(Stream).IsAssignableFrom(runtimeType))
{
return new[] { contentType };
}
return null;
}
/// <inheritdoc />
public bool CanWriteResult([NotNull] OutputFormatterContext context, MediaTypeHeaderValue contentType)
{
// Ignore the passed in content type, if the object is a Stream.
if (context.Object is Stream)
{
context.SelectedContentType = contentType;
return true;
}
return false;
}
/// <inheritdoc />
public async Task WriteAsync([NotNull] OutputFormatterContext context)
{
using (var valueAsStream = ((Stream)context.Object))
{
var response = context.ActionContext.HttpContext.Response;
if (context.SelectedContentType != null)
{
response.ContentType = context.SelectedContentType.ToString();
}
await valueAsStream.CopyToAsync(response.Body);
}
}
}
}

View File

@ -44,6 +44,7 @@ namespace Microsoft.AspNet.Mvc
// Set up default output formatters.
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
options.OutputFormatters.Add(new StringOutputFormatter());
options.OutputFormatters.Add(new StreamOutputFormatter());
options.OutputFormatters.Add(new JsonOutputFormatter());
// Set up default mapping for json extensions to content type

View File

@ -0,0 +1,89 @@
// 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.IO;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class StreamOutputFormatterTest
{
[Theory]
[InlineData(typeof(Stream), typeof(FileStream), "text/plain", "text/plain")]
[InlineData(typeof(object), typeof(FileStream), "text/plain", "text/plain")]
[InlineData(typeof(object), typeof(MemoryStream), "text/plain", "text/plain")]
[InlineData(typeof(object), typeof(object), "text/plain", null)]
[InlineData(typeof(object), typeof(string), "text/plain", null)]
[InlineData(typeof(object), null, "text/plain", null)]
[InlineData(typeof(IActionResult), null, "text/plain", null)]
[InlineData(typeof(IActionResult), typeof(IActionResult), "text/plain", null)]
public void GetSupportedContentTypes_ReturnsAppropriateValues(Type declaredType,
Type runtimeType,
string contentType,
string expected)
{
// Arrange
var formatter = new StreamOutputFormatter();
var contentTypeHeader = contentType == null ? null : new MediaTypeHeaderValue(contentType);
// Act
var contentTypes = formatter.GetSupportedContentTypes(declaredType, runtimeType, contentTypeHeader);
// Assert
if (expected == null)
{
Assert.Null(contentTypes);
}
else
{
Assert.Equal(1, contentTypes.Count);
Assert.Equal(expected, contentTypes[0].ToString());
}
}
[Theory]
[InlineData(typeof(object))]
[InlineData(typeof(SimplePOCO))]
[InlineData(null)]
public void CanWriteResult_OnlyActsOnStreams(Type type)
{
// Arrange
var formatter = new StreamOutputFormatter();
var context = new OutputFormatterContext();
var contentType = new MediaTypeHeaderValue("text/plain");
context.Object = type != null ? Activator.CreateInstance(type) : null;
// Act
var result = formatter.CanWriteResult(context, contentType);
// Assert
Assert.False(result);
Assert.Null(context.SelectedContentType);
}
[Fact]
public void CanWriteResult_SetsContentType()
{
// Arrange
var formatter = new StreamOutputFormatter();
var contentType = new MediaTypeHeaderValue("text/plain");
var context = new OutputFormatterContext();
context.Object = new MemoryStream();
// Act
var result = formatter.CanWriteResult(context, contentType);
// Assert
Assert.True(result);
Assert.Same(contentType, context.SelectedContentType);
}
private class SimplePOCO
{
public int Id { get; set; }
}
}
}

View File

@ -0,0 +1,40 @@
// 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 FormatterWebSite;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.TestHost;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class StreamOutputFormatterTest
{
private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(FormatterWebSite));
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
[Theory]
[InlineData("SimpleMemoryStream", null)]
[InlineData("MemoryStreamWithContentType", "text/html")]
[InlineData("MemoryStreamWithContentTypeFromProduces", "text/plain")]
[InlineData("MemoryStreamWithContentTypeFromProducesWithMultipleValues", "text/html")]
[InlineData("MemoryStreamOverridesContentTypeWithProduces", "text/plain")]
public async Task StreamOutputFormatter_ReturnsAppropriateContentAndContentType(string actionName, string contentType)
{
// Arrange
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/Stream/" + actionName);
var body = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(contentType, response.Content.Headers.ContentType?.ToString());
Assert.Equal("Sample text from a stream", body);
}
}
}

View File

@ -10,7 +10,7 @@ using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class MvcOptionSetupTest
public class MvcOptionsSetupTest
{
[Fact]
public void Setup_SetsUpViewEngines()
@ -83,10 +83,11 @@ namespace Microsoft.AspNet.Mvc
setup.Configure(mvcOptions);
// Assert
Assert.Equal(3, mvcOptions.OutputFormatters.Count);
Assert.Equal(4, mvcOptions.OutputFormatters.Count);
Assert.IsType<HttpNoContentOutputFormatter>(mvcOptions.OutputFormatters[0].Instance);
Assert.IsType<StringOutputFormatter>(mvcOptions.OutputFormatters[1].Instance);
Assert.IsType<JsonOutputFormatter>(mvcOptions.OutputFormatters[2].Instance);
Assert.IsType<StreamOutputFormatter>(mvcOptions.OutputFormatters[2].Instance);
Assert.IsType<JsonOutputFormatter>(mvcOptions.OutputFormatters[3].Instance);
}
[Fact]

View File

@ -0,0 +1,60 @@
// 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.IO;
using Microsoft.AspNet.Mvc;
namespace FormatterWebSite
{
public class StreamController : Controller
{
[HttpGet]
public Stream SimpleMemoryStream()
{
return CreateDefaultStream();
}
[HttpGet]
public Stream MemoryStreamWithContentType()
{
Response.ContentType = "text/html";
return CreateDefaultStream();
}
[HttpGet]
[Produces("text/plain")]
public Stream MemoryStreamWithContentTypeFromProduces()
{
return CreateDefaultStream();
}
[HttpGet]
[Produces("text/html", "text/plain")]
public Stream MemoryStreamWithContentTypeFromProducesWithMultipleValues()
{
return CreateDefaultStream();
}
[HttpGet]
[Produces("text/plain")]
public Stream MemoryStreamOverridesContentTypeWithProduces()
{
// Produces will set a ContentType on the implicit ObjectResult and
// ContentType on response are overriden by content types from ObjectResult.
Response.ContentType = "text/html";
return CreateDefaultStream();
}
private static Stream CreateDefaultStream()
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write("Sample text from a stream");
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
}
}