Implement StreamOutputFormatter
Include Functional tests and unit tests Resolves #1653
This commit is contained in:
parent
4691823a50
commit
5a3863d562
|
|
@ -69,6 +69,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
public virtual IOutputFormatter SelectFormatter(OutputFormatterContext formatterContext,
|
public virtual IOutputFormatter SelectFormatter(OutputFormatterContext formatterContext,
|
||||||
IEnumerable<IOutputFormatter> formatters)
|
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 ??
|
var incomingAcceptHeaderMediaTypes = formatterContext.ActionContext.HttpContext.Request.GetTypedHeaders().Accept ??
|
||||||
new MediaTypeHeaderValue[] { };
|
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
|
else
|
||||||
{
|
{
|
||||||
if (respectAcceptHeader)
|
if (respectAcceptHeader)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
// Set up default output formatters.
|
// Set up default output formatters.
|
||||||
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
|
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
|
||||||
options.OutputFormatters.Add(new StringOutputFormatter());
|
options.OutputFormatters.Add(new StringOutputFormatter());
|
||||||
|
options.OutputFormatters.Add(new StreamOutputFormatter());
|
||||||
options.OutputFormatters.Add(new JsonOutputFormatter());
|
options.OutputFormatters.Add(new JsonOutputFormatter());
|
||||||
|
|
||||||
// Set up default mapping for json extensions to content type
|
// Set up default mapping for json extensions to content type
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
public class MvcOptionSetupTest
|
public class MvcOptionsSetupTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Setup_SetsUpViewEngines()
|
public void Setup_SetsUpViewEngines()
|
||||||
|
|
@ -83,10 +83,11 @@ namespace Microsoft.AspNet.Mvc
|
||||||
setup.Configure(mvcOptions);
|
setup.Configure(mvcOptions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(3, mvcOptions.OutputFormatters.Count);
|
Assert.Equal(4, mvcOptions.OutputFormatters.Count);
|
||||||
Assert.IsType<HttpNoContentOutputFormatter>(mvcOptions.OutputFormatters[0].Instance);
|
Assert.IsType<HttpNoContentOutputFormatter>(mvcOptions.OutputFormatters[0].Instance);
|
||||||
Assert.IsType<StringOutputFormatter>(mvcOptions.OutputFormatters[1].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]
|
[Fact]
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue