Use strongly typed MediaTypeHeaderValue for content type in action results.

This commit is contained in:
Kiran Challa 2015-04-23 15:55:34 -07:00
parent 38bd617778
commit 7e623258c0
24 changed files with 485 additions and 168 deletions

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
using Microsoft.Net.Http.Headers;
namespace MvcSample.Web
{
@ -15,7 +16,7 @@ namespace MvcSample.Web
context.Result = new ContentResult
{
ContentType = "text/plain",
ContentType = new MediaTypeHeaderValue("text/plain"),
Content = "Boom " + context.Exception.Message
};
}

View File

@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. 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 System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
@ -11,11 +13,20 @@ namespace Microsoft.AspNet.Mvc
{
public class ContentResult : ActionResult
{
private readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("text/plain")
{
Encoding = Encodings.UTF8EncodingWithoutBOM
};
/// <summary>
/// Gets or set the content representing the body of the response.
/// </summary>
public string Content { get; set; }
public Encoding ContentEncoding { get; set; }
public string ContentType { get; set; }
/// <summary>
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
/// </summary>
public MediaTypeHeaderValue ContentType { get; set; }
/// <summary>
/// Gets or sets the HTTP status code.
@ -26,17 +37,30 @@ namespace Microsoft.AspNet.Mvc
{
var response = context.HttpContext.Response;
MediaTypeHeaderValue contentTypeHeader;
if (string.IsNullOrEmpty(ContentType))
var contentTypeHeader = ContentType;
Encoding encoding;
if (contentTypeHeader == null)
{
contentTypeHeader = new MediaTypeHeaderValue("text/plain");
contentTypeHeader = DefaultContentType;
encoding = Encodings.UTF8EncodingWithoutBOM;
}
else
{
contentTypeHeader = new MediaTypeHeaderValue(ContentType);
if (contentTypeHeader.Encoding == null)
{
// 1. Do not modify the user supplied content type
// 2. Parse here to handle parameters apart from charset
contentTypeHeader = MediaTypeHeaderValue.Parse(contentTypeHeader.ToString());
contentTypeHeader.Encoding = Encodings.UTF8EncodingWithoutBOM;
encoding = Encodings.UTF8EncodingWithoutBOM;
}
else
{
encoding = contentTypeHeader.Encoding;
}
}
contentTypeHeader.Encoding = ContentEncoding ?? Encodings.UTF8EncodingWithoutBOM;
response.ContentType = contentTypeHeader.ToString();
if (StatusCode != null)
@ -46,7 +70,7 @@ namespace Microsoft.AspNet.Mvc
if (Content != null)
{
await response.WriteAsync(Content, contentTypeHeader.Encoding);
await response.WriteAsync(Content, encoding);
}
}
}

View File

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
@ -19,13 +20,14 @@ namespace Microsoft.AspNet.Mvc
/// <summary>
/// Creates a new <see cref="FileContentResult"/> instance with
/// the provided <paramref name="fileContents"/>.
/// the provided <paramref name="fileContents"/> and the
/// provided <paramref name="contentType"/>.
/// </summary>
/// <param name="fileContents">The bytes that represent the file contents.</param>
public FileContentResult([NotNull] byte[] fileContents)
: base(contentType: null)
/// <param name="contentType">The Content-Type header of the response.</param>
public FileContentResult([NotNull] byte[] fileContents, [NotNull] string contentType)
: this(fileContents, new MediaTypeHeaderValue(contentType))
{
FileContents = fileContents;
}
/// <summary>
@ -35,7 +37,7 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
/// <param name="fileContents">The bytes that represent the file contents.</param>
/// <param name="contentType">The Content-Type header of the response.</param>
public FileContentResult([NotNull] byte[] fileContents, string contentType)
public FileContentResult([NotNull] byte[] fileContents, [NotNull] MediaTypeHeaderValue contentType)
: base(contentType)
{
FileContents = fileContents;

View File

@ -11,6 +11,7 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
@ -27,15 +28,15 @@ namespace Microsoft.AspNet.Mvc
/// <summary>
/// Creates a new <see cref="FilePathResult"/> instance with
/// the provided <paramref name="fileName"/>
/// the provided <paramref name="fileName"/> and the
/// provided <paramref name="contentType"/>.
/// </summary>
/// <param name="fileName">The path to the file. The path must be an absolute
/// path. Relative and virtual paths are not supported.</param>
/// <param name="contentType">The Content-Type header of the response.</param>
public FilePathResult([NotNull] string fileName)
: base(contentType: null)
public FilePathResult([NotNull] string fileName, [NotNull] string contentType)
: this(fileName, new MediaTypeHeaderValue(contentType))
{
FileName = fileName;
}
/// <summary>
@ -46,7 +47,7 @@ namespace Microsoft.AspNet.Mvc
/// <param name="fileName">The path to the file. The path must be an absolute
/// path. Relative and virtual paths are not supported.</param>
/// <param name="contentType">The Content-Type header of the response.</param>
public FilePathResult([NotNull] string fileName, string contentType)
public FilePathResult([NotNull] string fileName, [NotNull] MediaTypeHeaderValue contentType)
: base(contentType)
{
FileName = fileName;

View File

@ -24,15 +24,25 @@ namespace Microsoft.AspNet.Mvc
/// the provided <paramref name="contentType"/>.
/// </summary>
/// <param name="contentType">The Content-Type header of the response.</param>
protected FileResult(string contentType)
protected FileResult([NotNull] string contentType)
: this(new MediaTypeHeaderValue(contentType))
{
}
/// <summary>
/// Creates a new <see cref="FileResult"/> instance with
/// the provided <paramref name="contentType"/>.
/// </summary>
/// <param name="contentType">The Content-Type header of the response.</param>
protected FileResult([NotNull] MediaTypeHeaderValue contentType)
{
ContentType = contentType;
}
/// <summary>
/// Gets or sets the Content-Type header value that will be written to the response.
/// Gets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
/// </summary>
public string ContentType { get; set; }
public MediaTypeHeaderValue ContentType { get; }
/// <summary>
/// Gets the file name that will be used in the Content-Disposition header of the response.
@ -47,11 +57,7 @@ namespace Microsoft.AspNet.Mvc
public override Task ExecuteResultAsync([NotNull] ActionContext context)
{
var response = context.HttpContext.Response;
if (ContentType != null)
{
response.ContentType = ContentType;
}
response.ContentType = ContentType.ToString();
if (!string.IsNullOrEmpty(FileDownloadName))
{

View File

@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
@ -23,13 +24,14 @@ namespace Microsoft.AspNet.Mvc
/// <summary>
/// Creates a new <see cref="FileStreamResult"/> instance with
/// the provided <paramref name="fileStream"/>.
/// the provided <paramref name="fileStream"/> and the
/// provided <paramref name="contentType"/>.
/// </summary>
/// <param name="fileStream">The stream with the file.</param>
public FileStreamResult([NotNull] Stream fileStream)
: base(contentType: null)
/// <param name="contentType">The Content-Type header of the response.</param>
public FileStreamResult([NotNull] Stream fileStream, [NotNull] string contentType)
: this(fileStream, new MediaTypeHeaderValue(contentType))
{
FileStream = fileStream;
}
/// <summary>
@ -39,7 +41,7 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
/// <param name="fileStream">The stream with the file.</param>
/// <param name="contentType">The Content-Type header of the response.</param>
public FileStreamResult([NotNull] Stream fileStream, string contentType)
public FileStreamResult([NotNull] Stream fileStream, [NotNull] MediaTypeHeaderValue contentType)
: base(contentType)
{
FileStream = fileStream;

View File

@ -7,6 +7,7 @@ using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
@ -45,6 +46,11 @@ namespace Microsoft.AspNet.Mvc
/// <c>ActionContext.HttpContext.RequestServices</c> is used.</remarks>
public IViewEngine ViewEngine { get; set; }
/// <summary>
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
/// </summary>
public MediaTypeHeaderValue ContentType { get; set; }
/// <inheritdoc />
public override async Task ExecuteResultAsync([NotNull] ActionContext context)
{
@ -74,7 +80,7 @@ namespace Microsoft.AspNet.Mvc
using (view as IDisposable)
{
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, contentType: null);
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, ContentType);
}
}
}

View File

@ -1,10 +1,13 @@
// Copyright (c) .NET Foundation. 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 System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
@ -14,7 +17,10 @@ namespace Microsoft.AspNet.Mvc
public static class ViewExecutor
{
private const int BufferSize = 1024;
private const string ContentType = "text/html; charset=utf-8";
private static readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("text/html")
{
Encoding = Encodings.UTF8EncodingWithoutBOM
};
/// <summary>
/// Asynchronously renders the specified <paramref name="view"/> to the response body.
@ -23,21 +29,43 @@ namespace Microsoft.AspNet.Mvc
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing action.</param>
/// <param name="viewData">The <see cref="ViewDataDictionary"/> for the view being rendered.</param>
/// <param name="tempData">The <see cref="ITempDataDictionary"/> for the view being rendered.</param>
/// <returns>A <see cref="Task"/> that represents the asychronous rendering.</returns>
/// <returns>A <see cref="Task"/> that represents the asynchronous rendering.</returns>
public static async Task ExecuteAsync([NotNull] IView view,
[NotNull] ActionContext actionContext,
[NotNull] ViewDataDictionary viewData,
[NotNull] ITempDataDictionary tempData,
string contentType)
MediaTypeHeaderValue contentType)
{
if (string.IsNullOrEmpty(contentType))
var response = actionContext.HttpContext.Response;
var contentTypeHeader = contentType;
Encoding encoding;
if (contentTypeHeader == null)
{
contentType = ContentType;
contentTypeHeader = DefaultContentType;
encoding = Encodings.UTF8EncodingWithoutBOM;
}
else
{
if (contentTypeHeader.Encoding == null)
{
// 1. Do not modify the user supplied content type
// 2. Parse here to handle parameters apart from charset
contentTypeHeader = MediaTypeHeaderValue.Parse(contentTypeHeader.ToString());
contentTypeHeader.Encoding = Encodings.UTF8EncodingWithoutBOM;
encoding = Encodings.UTF8EncodingWithoutBOM;
}
else
{
encoding = contentTypeHeader.Encoding;
}
}
actionContext.HttpContext.Response.ContentType = contentType;
var wrappedStream = new StreamWrapper(actionContext.HttpContext.Response.Body);
var encoding = Encodings.UTF8EncodingWithoutBOM;
response.ContentType = contentTypeHeader.ToString();
var wrappedStream = new StreamWrapper(response.Body);
using (var writer = new StreamWriter(wrappedStream, encoding, BufferSize, leaveOpen: true))
{
try

View File

@ -7,6 +7,7 @@ using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
@ -45,6 +46,11 @@ namespace Microsoft.AspNet.Mvc
/// <c>ActionContext.HttpContext.RequestServices</c> is used.</remarks>
public IViewEngine ViewEngine { get; set; }
/// <summary>
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
/// </summary>
public MediaTypeHeaderValue ContentType { get; set; }
/// <inheritdoc />
public override async Task ExecuteResultAsync([NotNull] ActionContext context)
{
@ -74,7 +80,7 @@ namespace Microsoft.AspNet.Mvc
using (view as IDisposable)
{
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, contentType: null);
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, ContentType);
}
}
}

View File

@ -14,6 +14,7 @@ using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
@ -330,7 +331,7 @@ namespace Microsoft.AspNet.Mvc
[NonAction]
public virtual ContentResult Content(string content)
{
return Content(content, contentType: null);
return Content(content, (MediaTypeHeaderValue)null);
}
/// <summary>
@ -356,11 +357,23 @@ namespace Microsoft.AspNet.Mvc
/// <returns>The created <see cref="ContentResult"/> object for the response.</returns>
[NonAction]
public virtual ContentResult Content(string content, string contentType, Encoding contentEncoding)
{
return Content(content, new MediaTypeHeaderValue(contentType) { Encoding = contentEncoding });
}
/// <summary>
/// Creates a <see cref="ContentResult"/> object by specifying a <paramref name="content"/>
/// string and a <paramref name="contentType"/>.
/// </summary>
/// <param name="content">The content to write to the response.</param>
/// <param name="contentType">The content type (MIME type).</param>
/// <returns>The created <see cref="ContentResult"/> object for the response.</returns>
[NonAction]
public virtual ContentResult Content(string content, MediaTypeHeaderValue contentType)
{
var result = new ContentResult
{
Content = content,
ContentEncoding = contentEncoding,
ContentType = contentType
};

View File

@ -1,11 +1,12 @@
// Copyright (c) .NET Foundation. 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 System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
@ -20,8 +21,7 @@ namespace Microsoft.AspNet.Mvc
var contentResult = new ContentResult
{
Content = "Test Content",
ContentType = "application/json",
ContentEncoding = null
ContentType = new MediaTypeHeaderValue("application/json")
};
var httpContext = GetHttpContext();
var actionContext = GetActionContext(httpContext);
@ -40,8 +40,10 @@ namespace Microsoft.AspNet.Mvc
var contentResult = new ContentResult
{
Content = "Test Content",
ContentType = "text/plain",
ContentEncoding = Encoding.ASCII
ContentType = new MediaTypeHeaderValue("text/plain")
{
Encoding = Encoding.ASCII
}
};
var httpContext = GetHttpContext();
var actionContext = GetActionContext(httpContext);
@ -54,14 +56,16 @@ namespace Microsoft.AspNet.Mvc
}
[Fact]
public async Task ContentResult_Response_NullContentType_SetsEncodingAndDefaultContentType()
public async Task ContentResult_Response_NullContent_SetsContentTypeAndEncoding()
{
// Arrange
var contentResult = new ContentResult
{
Content = "Test Content",
ContentType = null,
ContentEncoding = Encoding.UTF7
Content = null,
ContentType = new MediaTypeHeaderValue("text/plain")
{
Encoding = Encoding.UTF7
}
};
var httpContext = GetHttpContext();
var actionContext = GetActionContext(httpContext);
@ -73,45 +77,65 @@ namespace Microsoft.AspNet.Mvc
Assert.Equal("text/plain; charset=utf-7", httpContext.Response.ContentType);
}
[Fact]
public async Task ContentResult_Response_NullContent_SetsContentTypeAndEncoding()
public static TheoryData<MediaTypeHeaderValue, string, string, byte[]> ContentResultContentTypeData
{
get
{
return new TheoryData<MediaTypeHeaderValue, string, string, byte[]>
{
{
null,
"κόσμε",
"text/plain; charset=utf-8",
new byte[] { 206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181 } //utf-8 without BOM
},
{
new MediaTypeHeaderValue("text/foo"),
"κόσμε",
"text/foo; charset=utf-8",
new byte[] { 206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181 } //utf-8 without BOM
},
{
MediaTypeHeaderValue.Parse("text/foo;p1=p1-value"),
"κόσμε",
"text/foo; p1=p1-value; charset=utf-8",
new byte[] { 206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181 } //utf-8 without BOM
},
{
new MediaTypeHeaderValue("text/foo") { Encoding = Encoding.ASCII },
"abcd",
"text/foo; charset=us-ascii",
new byte[] { 97, 98, 99, 100 }
}
};
}
}
[Theory]
[MemberData(nameof(ContentResultContentTypeData))]
public async Task ContentResult_ExecuteResultAsync_SetContentTypeAndEncoding_OnResponse(
MediaTypeHeaderValue contentType,
string content,
string expectedContentType,
byte[] expectedContentData)
{
// Arrange
var contentResult = new ContentResult
{
Content = null,
ContentType = "application/json",
ContentEncoding = Encoding.UTF8
Content = content,
ContentType = contentType
};
var httpContext = GetHttpContext();
var memoryStream = new MemoryStream();
httpContext.Response.Body = memoryStream;
var actionContext = GetActionContext(httpContext);
// Act
await contentResult.ExecuteResultAsync(actionContext);
// Assert
Assert.Equal("application/json; charset=utf-8", httpContext.Response.ContentType);
}
[Fact]
public async Task ContentResult_Response_BadContentType_ThrowsFormatException()
{
// Arrange
var contentResult = new ContentResult
{
Content = "Test Content",
ContentType = "some-type",
ContentEncoding = null
};
var httpContext = GetHttpContext();
var actionContext = GetActionContext(httpContext);
// Act
var exception = await Assert.ThrowsAsync<FormatException>(
async () => await contentResult.ExecuteResultAsync(actionContext));
// Assert
Assert.Equal("Invalid media type 'some-type'.", exception.Message);
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
Assert.Equal(expectedContentData, memoryStream.ToArray());
}
private static ActionContext GetActionContext(HttpContext httpContext)

View File

@ -5,6 +5,7 @@ using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace Microsoft.AspNet.Mvc
@ -45,5 +46,29 @@ namespace Microsoft.AspNet.Mvc
// Assert
Assert.Equal(buffer, outStream.ToArray());
}
[Fact]
public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
{
// Arrange
var expectedContentType = "text/foo; charset=us-ascii";
var buffer = new byte[] { 1, 2, 3, 4, 5 };
var httpContext = new DefaultHttpContext();
var outStream = new MemoryStream();
httpContext.Response.Body = outStream;
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var result = new FileContentResult(buffer, MediaTypeHeaderValue.Parse(expectedContentType));
// Act
await result.ExecuteResultAsync(context);
// Assert
Assert.Equal(buffer, outStream.ToArray());
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.FileProviders;
@ -9,9 +10,9 @@ using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
using System.Text;
namespace Microsoft.AspNet.Mvc
{
@ -111,6 +112,36 @@ namespace Microsoft.AspNet.Mvc
sendFileMock.Verify();
}
[Fact]
public async Task ExecuteResultAsync_SetsSuppliedContentTypeAndEncoding()
{
// Arrange
var expectedContentType = "text/foo; charset=us-ascii";
// path will be C:/.../TestFiles/FilePathResultTestFile_ASCII.txt
var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile_ASCII.txt"));
path = path.Replace(@"\", "/");
// Point the FileProviderRoot to a subfolder
var result = new FilePathResult(path, MediaTypeHeaderValue.Parse(expectedContentType))
{
FileProvider = new PhysicalFileProvider(Path.GetFullPath("Utils")),
};
var httpContext = new DefaultHttpContext();
var memoryStream = new MemoryStream();
httpContext.Response.Body = memoryStream;
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
// Act
await result.ExecuteResultAsync(context);
// Assert
var contents = Encoding.ASCII.GetString(memoryStream.ToArray());
Assert.Equal("FilePathResultTestFile contents ASCII encoded", contents);
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
}
[Fact]
public async Task ExecuteResultAsync_WorksWithAbsolutePaths_UsingBackSlash()
{
@ -186,7 +217,7 @@ namespace Microsoft.AspNet.Mvc
nonDiskFileInfo.Setup(fi => fi.CreateReadStream()).Returns(sourceStream);
var nonDiskFileProvider = new Mock<IFileProvider>();
nonDiskFileProvider.Setup(fp => fp.GetFileInfo(It.IsAny<string>())).Returns(nonDiskFileInfo.Object);
var filePathResult = new FilePathResult("/SampleEmbeddedFile.txt")
var filePathResult = new FilePathResult("/SampleEmbeddedFile.txt", "text/plain")
{
FileProvider = nonDiskFileProvider.Object
};

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc
var result = new EmptyFileResult("text/plain");
// Assert
Assert.Equal("text/plain", result.ContentType);
Assert.Equal("text/plain", result.ContentType.ToString());
}
[Fact]

View File

@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
@ -87,5 +88,34 @@ namespace Microsoft.AspNet.Mvc
var outBytes = outStream.ToArray();
Assert.True(originalBytes.SequenceEqual(outBytes));
}
[Fact]
public async Task SetsSuppliedContentTypeAndEncoding()
{
// Arrange
var expectedContentType = "text/foo; charset=us-ascii";
// Generate an array of bytes with a predictable pattern
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12, 13
var originalBytes = Enumerable.Range(0, 0x1234)
.Select(b => (byte)(b % 20)).ToArray();
var originalStream = new MemoryStream(originalBytes);
var httpContext = new DefaultHttpContext();
var outStream = new MemoryStream();
httpContext.Response.Body = outStream;
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var result = new FileStreamResult(originalStream, MediaTypeHeaderValue.Parse(expectedContentType));
// Act
await result.ExecuteResultAsync(actionContext);
// Assert
var outBytes = outStream.ToArray();
Assert.True(originalBytes.SequenceEqual(outBytes));
Assert.Equal(expectedContentType, httpContext.Response.ContentType);
}
}
}

View File

@ -2,11 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.Logging;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
@ -69,6 +71,58 @@ namespace Microsoft.AspNet.Mvc
viewEngine.Verify();
}
public static TheoryData<MediaTypeHeaderValue, string> PartialViewResultContentTypeData
{
get
{
return new TheoryData<MediaTypeHeaderValue, string>
{
{
null,
"text/html; charset=utf-8"
},
{
new MediaTypeHeaderValue("text/foo"),
"text/foo; charset=utf-8"
},
{
new MediaTypeHeaderValue("text/foo") { Encoding = Encoding.ASCII },
"text/foo; charset=us-ascii"
}
};
}
}
[Theory]
[MemberData(nameof(PartialViewResultContentTypeData))]
public async Task PartialViewResult_SetsContentTypeHeader(
MediaTypeHeaderValue contentType,
string expectedContentTypeHeaderValue)
{
// Arrange
var viewName = "myview";
var httpContext = GetHttpContext();
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var viewEngine = new Mock<IViewEngine>();
var view = Mock.Of<IView>();
viewEngine.Setup(e => e.FindPartialView(context, "myview"))
.Returns(ViewEngineResult.Found("myview", view));
var viewResult = new PartialViewResult
{
ViewName = viewName,
ViewEngine = viewEngine.Object,
ContentType = contentType
};
// Act
await viewResult.ExecuteResultAsync(context);
// Assert
Assert.Equal(expectedContentTypeHeaderValue, httpContext.Response.ContentType);
}
[Fact]
public async Task ExecuteResultAsync_UsesActionDescriptorName_IfViewNameIsNull()
{

View File

@ -4,11 +4,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
@ -19,12 +21,44 @@ namespace Microsoft.AspNet.Mvc
// The buffer size of the StreamWriter used in ViewResult.
private const int ViewResultStreamWriterBufferSize = 1024;
[Fact]
public async Task ExecuteAsync_WritesOutputWithoutBOM()
public static TheoryData<MediaTypeHeaderValue, string, byte[]> ViewExecutorSetsContentTypeAndEncodingData
{
get
{
return new TheoryData<MediaTypeHeaderValue, string, byte[]>
{
{
null,
"text/html; charset=utf-8",
new byte[] { 97, 98, 99, 100 }
},
{
new MediaTypeHeaderValue("text/foo"),
"text/foo; charset=utf-8",
new byte[] { 97, 98, 99, 100 }
},
{
MediaTypeHeaderValue.Parse("text/foo; p1=p1-value"),
"text/foo; p1=p1-value; charset=utf-8",
new byte[] { 97, 98, 99, 100 }
},
{
new MediaTypeHeaderValue("text/foo") { Charset = "us-ascii" },
"text/foo; charset=us-ascii",
new byte[] { 97, 98, 99, 100 }
}
};
}
}
[Theory]
[MemberData(nameof(ViewExecutorSetsContentTypeAndEncodingData))]
public async Task ExecuteAsync_SetsContentTypeAndEncoding(
MediaTypeHeaderValue contentType,
string expectedContentType,
byte[] expectedContentData)
{
// Arrange
var expected = new byte[] { 97, 98, 99, 100 };
var view = new Mock<IView>();
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
.Callback((ViewContext v) =>
@ -44,33 +78,11 @@ namespace Microsoft.AspNet.Mvc
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
// Act
await ViewExecutor.ExecuteAsync(view.Object, actionContext, viewData, null, contentType: null);
await ViewExecutor.ExecuteAsync(view.Object, actionContext, viewData, null, contentType);
// Assert
Assert.Equal(expected, memoryStream.ToArray());
Assert.Equal("text/html; charset=utf-8", context.Response.ContentType);
}
[Fact]
public async Task ExecuteAsync_UsesSpecifiedContentType()
{
// Arrange
var contentType = "some-content-type";
var view = Mock.Of<IView>();
var context = new DefaultHttpContext();
var memoryStream = new MemoryStream();
context.Response.Body = memoryStream;
var actionContext = new ActionContext(context,
new RouteData(),
new ActionDescriptor());
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
// Act
await ViewExecutor.ExecuteAsync(view, actionContext, viewData, null, contentType);
// Assert
Assert.Equal(contentType, context.Response.ContentType);
Assert.Equal(expectedContentType, context.Response.ContentType);
Assert.Equal(expectedContentData, memoryStream.ToArray());
}
public static IEnumerable<object[]> ExecuteAsync_DoesNotWriteToResponse_OnceExceptionIsThrownData

View File

@ -2,11 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.Logging;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
@ -70,6 +72,69 @@ namespace Microsoft.AspNet.Mvc
viewEngine.Verify();
}
public static TheoryData<MediaTypeHeaderValue, string> ViewResultContentTypeData
{
get
{
return new TheoryData<MediaTypeHeaderValue, string>
{
{
null,
"text/html; charset=utf-8"
},
{
new MediaTypeHeaderValue("text/foo"),
"text/foo; charset=utf-8"
},
{
MediaTypeHeaderValue.Parse("text/foo;p1=p1-value"),
"text/foo; p1=p1-value; charset=utf-8"
},
{
new MediaTypeHeaderValue("text/foo") { Encoding = Encoding.ASCII },
"text/foo; charset=us-ascii"
}
};
}
}
[Theory]
[MemberData(nameof(ViewResultContentTypeData))]
public async Task ViewResult_SetsContentTypeHeader(
MediaTypeHeaderValue contentType,
string expectedContentTypeHeaderValue)
{
// Arrange
var viewName = "myview";
var httpContext = GetHttpContext();
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var viewEngine = new Mock<IViewEngine>();
var view = Mock.Of<IView>();
var contentTypeBeforeViewResultExecution = contentType?.ToString();
viewEngine.Setup(e => e.FindView(context, "myview"))
.Returns(ViewEngineResult.Found("myview", view));
var viewResult = new ViewResult
{
ViewName = viewName,
ViewEngine = viewEngine.Object,
ContentType = contentType
};
// Act
await viewResult.ExecuteResultAsync(context);
// Assert
Assert.Equal(expectedContentTypeHeaderValue, httpContext.Response.ContentType);
// Check if the original instance provided by the user has not changed.
// Since we do not have access to the new instance created within the view executor,
// check if at least the content is the same.
var contentTypeAfterViewResultExecution = contentType?.ToString();
Assert.Equal(contentTypeBeforeViewResultExecution, contentTypeAfterViewResultExecution);
}
[Fact]
public async Task ExecuteResultAsync_UsesActionDescriptorName_IfViewNameIsNull()
{

View File

@ -627,12 +627,12 @@ namespace Microsoft.AspNet.Mvc.Test
var fileContents = new byte[0];
// Act
var result = controller.File(fileContents, "someContentType");
var result = controller.File(fileContents, "application/pdf");
// Assert
Assert.NotNull(result);
Assert.Same(fileContents, result.FileContents);
Assert.Equal("someContentType", result.ContentType);
Assert.Equal("application/pdf", result.ContentType.ToString());
Assert.Equal(string.Empty, result.FileDownloadName);
}
@ -644,12 +644,12 @@ namespace Microsoft.AspNet.Mvc.Test
var fileContents = new byte[0];
// Act
var result = controller.File(fileContents, "someContentType", "someDownloadName");
var result = controller.File(fileContents, "application/pdf", "someDownloadName");
// Assert
Assert.NotNull(result);
Assert.Same(fileContents, result.FileContents);
Assert.Equal("someContentType", result.ContentType);
Assert.Equal("application/pdf", result.ContentType.ToString());
Assert.Equal("someDownloadName", result.FileDownloadName);
}
@ -661,12 +661,12 @@ namespace Microsoft.AspNet.Mvc.Test
var path = Path.GetFullPath("somepath");
// Act
var result = controller.File(path, "someContentType");
var result = controller.File(path, "application/pdf");
// Assert
Assert.NotNull(result);
Assert.Equal(path, result.FileName);
Assert.Equal("someContentType", result.ContentType);
Assert.Equal("application/pdf", result.ContentType.ToString());
Assert.Equal(string.Empty, result.FileDownloadName);
}
@ -678,12 +678,12 @@ namespace Microsoft.AspNet.Mvc.Test
var path = Path.GetFullPath("somepath");
// Act
var result = controller.File(path, "someContentType", "someDownloadName");
var result = controller.File(path, "application/pdf", "someDownloadName");
// Assert
Assert.NotNull(result);
Assert.Equal(path, result.FileName);
Assert.Equal("someContentType", result.ContentType);
Assert.Equal("application/pdf", result.ContentType.ToString());
Assert.Equal("someDownloadName", result.FileDownloadName);
}
@ -700,12 +700,12 @@ namespace Microsoft.AspNet.Mvc.Test
var fileStream = Stream.Null;
// Act
var result = controller.File(fileStream, "someContentType");
var result = controller.File(fileStream, "application/pdf");
// Assert
Assert.NotNull(result);
Assert.Same(fileStream, result.FileStream);
Assert.Equal("someContentType", result.ContentType);
Assert.Equal("application/pdf", result.ContentType.ToString());
Assert.Equal(string.Empty, result.FileDownloadName);
}
@ -723,12 +723,12 @@ namespace Microsoft.AspNet.Mvc.Test
var fileStream = Stream.Null;
// Act
var result = controller.File(fileStream, "someContentType", "someDownloadName");
var result = controller.File(fileStream, "application/pdf", "someDownloadName");
// Assert
Assert.NotNull(result);
Assert.Same(fileStream, result.FileStream);
Assert.Equal("someContentType", result.ContentType);
Assert.Equal("application/pdf", result.ContentType.ToString());
Assert.Equal("someDownloadName", result.FileDownloadName);
mockHttpContext.Verify(
x => x.Response.OnResponseCompleted(It.IsAny<Action<object>>(), It.IsAny<object>()),
@ -980,7 +980,6 @@ namespace Microsoft.AspNet.Mvc.Test
// Assert
Assert.IsType<ContentResult>(actualContentResult);
Assert.Equal("TestContent", actualContentResult.Content);
Assert.Null(actualContentResult.ContentEncoding);
Assert.Null(actualContentResult.ContentType);
}
@ -996,8 +995,8 @@ namespace Microsoft.AspNet.Mvc.Test
// Assert
Assert.IsType<ContentResult>(actualContentResult);
Assert.Equal("TestContent", actualContentResult.Content);
Assert.Null(actualContentResult.ContentEncoding);
Assert.Equal("text/plain", actualContentResult.ContentType);
Assert.Null(actualContentResult.ContentType.Encoding);
Assert.Equal("text/plain", actualContentResult.ContentType.ToString());
}
[Fact]
@ -1012,8 +1011,8 @@ namespace Microsoft.AspNet.Mvc.Test
// Assert
Assert.IsType<ContentResult>(actualContentResult);
Assert.Equal("TestContent", actualContentResult.Content);
Assert.Same(Encoding.UTF8, actualContentResult.ContentEncoding);
Assert.Equal("text/plain", actualContentResult.ContentType);
Assert.Same(Encoding.UTF8, actualContentResult.ContentType.Encoding);
Assert.Equal("text/plain; charset=utf-8", actualContentResult.ContentType.ToString());
}
[Fact]

View File

@ -71,11 +71,13 @@ namespace Microsoft.AspNet.Mvc
}
}
[Theory]
[MemberData(nameof(TestabilityContentTestData))]
public void ControllerContent_InvokedInUnitTests(string content, string contentType, Encoding encoding)
[Fact]
public void ControllerContent_InvokedInUnitTests()
{
// Arrange
var content = "Content_1";
var contentType = "text/asp";
var encoding = Encoding.ASCII;
var controller = new TestabilityController();
// Act
@ -86,8 +88,8 @@ namespace Microsoft.AspNet.Mvc
var contentResult = Assert.IsType<ContentResult>(result);
Assert.Equal(content, contentResult.Content);
Assert.Equal(contentType, contentResult.ContentType);
Assert.Equal(encoding, contentResult.ContentEncoding);
Assert.Equal("text/asp; charset=us-ascii", contentResult.ContentType.ToString());
Assert.Equal(encoding, contentResult.ContentType.Encoding);
}
[Theory]
@ -110,12 +112,13 @@ namespace Microsoft.AspNet.Mvc
Assert.Equal(StatusCodes.Status201Created, createdResult.StatusCode);
}
[Theory]
[InlineData("<html>CreatedBody</html>", "text/html", "Created.html")]
[InlineData("<html>CreatedBody</html>", null, null)]
public void ControllerFileContent_InvokedInUnitTests(string content, string contentType, string fileName)
[Fact]
public void ControllerFileContent_InvokedInUnitTests()
{
// Arrange
var content = "<html>CreatedBody</html>";
var contentType = "text/html";
var fileName = "Created.html";
var controller = new TestabilityController();
// Act
@ -125,7 +128,7 @@ namespace Microsoft.AspNet.Mvc
Assert.NotNull(result);
var fileContentResult = Assert.IsType<FileContentResult>(result);
Assert.Equal(contentType, fileContentResult.ContentType);
Assert.Equal(contentType, fileContentResult.ContentType.ToString());
Assert.Equal(fileName ?? string.Empty, fileContentResult.FileDownloadName);
if (content == null)
@ -138,12 +141,13 @@ namespace Microsoft.AspNet.Mvc
}
}
[Theory]
[InlineData("<html>CreatedBody</html>", "text/html", "Created.html")]
[InlineData("<html>CreatedBody</html>", null, null)]
public void ControllerFileStream_InvokedInUnitTests(string content, string contentType, string fileName)
[Fact]
public void ControllerFileStream_InvokedInUnitTests()
{
// Arrange
var content = "<html>CreatedBody</html>";
var contentType = "text/html";
var fileName = "Created.html";
var mockHttpContext = new Mock<DefaultHttpContext>();
mockHttpContext.Setup(x => x.Response.OnResponseCompleted(It.IsAny<Action<object>>(), It.IsAny<object>()));
var controller = new TestabilityController()
@ -158,7 +162,7 @@ namespace Microsoft.AspNet.Mvc
Assert.NotNull(result);
var fileStreamResult = Assert.IsType<FileStreamResult>(result);
Assert.Equal(contentType, fileStreamResult.ContentType);
Assert.Equal(contentType, fileStreamResult.ContentType.ToString());
Assert.Equal(fileName ?? string.Empty, fileStreamResult.FileDownloadName);
if (content == null)
@ -547,26 +551,6 @@ namespace Microsoft.AspNet.Mvc
}
}
public static IEnumerable<object[]> TestabilityContentTestData
{
get
{
yield return new object[]
{
null,
null,
null
};
yield return new object[]
{
"Content_1",
"text/asp",
Encoding.ASCII
};
}
}
private class TestabilityController : Controller
{
public IActionResult View_Action(string viewName, object data)

View File

@ -0,0 +1 @@
FilePathResultTestFile contents ASCII encoded

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
using Microsoft.Net.Http.Headers;
namespace FiltersWebSite
{
@ -12,7 +13,7 @@ namespace FiltersWebSite
context.Result = new ContentResult()
{
Content = "4",
ContentType = "text/plain"
ContentType = new MediaTypeHeaderValue("text/plain")
};
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
using Microsoft.Net.Http.Headers;
namespace FiltersWebSite
{
@ -12,7 +13,7 @@ namespace FiltersWebSite
context.Result = new ContentResult
{
Content = "The Action was never executed",
ContentType = "text/plain"
ContentType = new MediaTypeHeaderValue("text/plain")
};
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
using Microsoft.Net.Http.Headers;
namespace FiltersWebSite
{
@ -20,7 +21,7 @@ namespace FiltersWebSite
return new ContentResult()
{
Content = content,
ContentType = "text/plain",
ContentType = new MediaTypeHeaderValue("text/plain"),
};
}
}