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,
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
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]
|
||||
|
|
@ -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