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

Commit 7e62325

Browse files
committed
Use strongly typed MediaTypeHeaderValue for content type in action results.
1 parent 38bd617 commit 7e62325

24 files changed

+482
-165
lines changed

samples/MvcSample.Web/Filters/ErrorMessagesAttribute.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using Microsoft.AspNet.Mvc;
5+
using Microsoft.Net.Http.Headers;
56

67
namespace MvcSample.Web
78
{
@@ -15,7 +16,7 @@ public override void OnActionExecuted(ActionExecutedContext context)
1516

1617
context.Result = new ContentResult
1718
{
18-
ContentType = "text/plain",
19+
ContentType = new MediaTypeHeaderValue("text/plain"),
1920
Content = "Boom " + context.Exception.Message
2021
};
2122
}

src/Microsoft.AspNet.Mvc.Core/ActionResults/ContentResult.cs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
5+
using System.IO;
46
using System.Text;
57
using System.Threading.Tasks;
68
using Microsoft.AspNet.Http;
@@ -11,11 +13,20 @@ namespace Microsoft.AspNet.Mvc
1113
{
1214
public class ContentResult : ActionResult
1315
{
14-
public string Content { get; set; }
16+
private readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("text/plain")
17+
{
18+
Encoding = Encodings.UTF8EncodingWithoutBOM
19+
};
1520

16-
public Encoding ContentEncoding { get; set; }
21+
/// <summary>
22+
/// Gets or set the content representing the body of the response.
23+
/// </summary>
24+
public string Content { get; set; }
1725

18-
public string ContentType { get; set; }
26+
/// <summary>
27+
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
28+
/// </summary>
29+
public MediaTypeHeaderValue ContentType { get; set; }
1930

2031
/// <summary>
2132
/// Gets or sets the HTTP status code.
@@ -26,17 +37,30 @@ public override async Task ExecuteResultAsync([NotNull] ActionContext context)
2637
{
2738
var response = context.HttpContext.Response;
2839

29-
MediaTypeHeaderValue contentTypeHeader;
30-
if (string.IsNullOrEmpty(ContentType))
40+
var contentTypeHeader = ContentType;
41+
Encoding encoding;
42+
if (contentTypeHeader == null)
3143
{
32-
contentTypeHeader = new MediaTypeHeaderValue("text/plain");
44+
contentTypeHeader = DefaultContentType;
45+
encoding = Encodings.UTF8EncodingWithoutBOM;
3346
}
3447
else
3548
{
36-
contentTypeHeader = new MediaTypeHeaderValue(ContentType);
49+
if (contentTypeHeader.Encoding == null)
50+
{
51+
// 1. Do not modify the user supplied content type
52+
// 2. Parse here to handle parameters apart from charset
53+
contentTypeHeader = MediaTypeHeaderValue.Parse(contentTypeHeader.ToString());
54+
contentTypeHeader.Encoding = Encodings.UTF8EncodingWithoutBOM;
55+
56+
encoding = Encodings.UTF8EncodingWithoutBOM;
57+
}
58+
else
59+
{
60+
encoding = contentTypeHeader.Encoding;
61+
}
3762
}
3863

39-
contentTypeHeader.Encoding = ContentEncoding ?? Encodings.UTF8EncodingWithoutBOM;
4064
response.ContentType = contentTypeHeader.ToString();
4165

4266
if (StatusCode != null)
@@ -46,7 +70,7 @@ public override async Task ExecuteResultAsync([NotNull] ActionContext context)
4670

4771
if (Content != null)
4872
{
49-
await response.WriteAsync(Content, contentTypeHeader.Encoding);
73+
await response.WriteAsync(Content, encoding);
5074
}
5175
}
5276
}

src/Microsoft.AspNet.Mvc.Core/ActionResults/FileContentResult.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading.Tasks;
77
using Microsoft.AspNet.Http;
88
using Microsoft.Framework.Internal;
9+
using Microsoft.Net.Http.Headers;
910

1011
namespace Microsoft.AspNet.Mvc
1112
{
@@ -19,13 +20,14 @@ public class FileContentResult : FileResult
1920

2021
/// <summary>
2122
/// Creates a new <see cref="FileContentResult"/> instance with
22-
/// the provided <paramref name="fileContents"/>.
23+
/// the provided <paramref name="fileContents"/> and the
24+
/// provided <paramref name="contentType"/>.
2325
/// </summary>
2426
/// <param name="fileContents">The bytes that represent the file contents.</param>
25-
public FileContentResult([NotNull] byte[] fileContents)
26-
: base(contentType: null)
27+
/// <param name="contentType">The Content-Type header of the response.</param>
28+
public FileContentResult([NotNull] byte[] fileContents, [NotNull] string contentType)
29+
: this(fileContents, new MediaTypeHeaderValue(contentType))
2730
{
28-
FileContents = fileContents;
2931
}
3032

3133
/// <summary>
@@ -35,7 +37,7 @@ public FileContentResult([NotNull] byte[] fileContents)
3537
/// </summary>
3638
/// <param name="fileContents">The bytes that represent the file contents.</param>
3739
/// <param name="contentType">The Content-Type header of the response.</param>
38-
public FileContentResult([NotNull] byte[] fileContents, string contentType)
40+
public FileContentResult([NotNull] byte[] fileContents, [NotNull] MediaTypeHeaderValue contentType)
3941
: base(contentType)
4042
{
4143
FileContents = fileContents;

src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.AspNet.Mvc.Core;
1212
using Microsoft.Framework.DependencyInjection;
1313
using Microsoft.Framework.Internal;
14+
using Microsoft.Net.Http.Headers;
1415

1516
namespace Microsoft.AspNet.Mvc
1617
{
@@ -27,15 +28,15 @@ public class FilePathResult : FileResult
2728

2829
/// <summary>
2930
/// Creates a new <see cref="FilePathResult"/> instance with
30-
/// the provided <paramref name="fileName"/>
31+
/// the provided <paramref name="fileName"/> and the
32+
/// provided <paramref name="contentType"/>.
3133
/// </summary>
3234
/// <param name="fileName">The path to the file. The path must be an absolute
3335
/// path. Relative and virtual paths are not supported.</param>
3436
/// <param name="contentType">The Content-Type header of the response.</param>
35-
public FilePathResult([NotNull] string fileName)
36-
: base(contentType: null)
37+
public FilePathResult([NotNull] string fileName, [NotNull] string contentType)
38+
: this(fileName, new MediaTypeHeaderValue(contentType))
3739
{
38-
FileName = fileName;
3940
}
4041

4142
/// <summary>
@@ -46,7 +47,7 @@ public FilePathResult([NotNull] string fileName)
4647
/// <param name="fileName">The path to the file. The path must be an absolute
4748
/// path. Relative and virtual paths are not supported.</param>
4849
/// <param name="contentType">The Content-Type header of the response.</param>
49-
public FilePathResult([NotNull] string fileName, string contentType)
50+
public FilePathResult([NotNull] string fileName, [NotNull] MediaTypeHeaderValue contentType)
5051
: base(contentType)
5152
{
5253
FileName = fileName;

src/Microsoft.AspNet.Mvc.Core/ActionResults/FileResult.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,25 @@ public abstract class FileResult : ActionResult
2424
/// the provided <paramref name="contentType"/>.
2525
/// </summary>
2626
/// <param name="contentType">The Content-Type header of the response.</param>
27-
protected FileResult(string contentType)
27+
protected FileResult([NotNull] string contentType)
28+
: this(new MediaTypeHeaderValue(contentType))
29+
{
30+
}
31+
32+
/// <summary>
33+
/// Creates a new <see cref="FileResult"/> instance with
34+
/// the provided <paramref name="contentType"/>.
35+
/// </summary>
36+
/// <param name="contentType">The Content-Type header of the response.</param>
37+
protected FileResult([NotNull] MediaTypeHeaderValue contentType)
2838
{
2939
ContentType = contentType;
3040
}
3141

3242
/// <summary>
33-
/// Gets or sets the Content-Type header value that will be written to the response.
43+
/// Gets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
3444
/// </summary>
35-
public string ContentType { get; set; }
45+
public MediaTypeHeaderValue ContentType { get; }
3646

3747
/// <summary>
3848
/// Gets the file name that will be used in the Content-Disposition header of the response.
@@ -47,11 +57,7 @@ public string FileDownloadName
4757
public override Task ExecuteResultAsync([NotNull] ActionContext context)
4858
{
4959
var response = context.HttpContext.Response;
50-
51-
if (ContentType != null)
52-
{
53-
response.ContentType = ContentType;
54-
}
60+
response.ContentType = ContentType.ToString();
5561

5662
if (!string.IsNullOrEmpty(FileDownloadName))
5763
{

src/Microsoft.AspNet.Mvc.Core/ActionResults/FileStreamResult.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Threading.Tasks;
88
using Microsoft.AspNet.Http;
99
using Microsoft.Framework.Internal;
10+
using Microsoft.Net.Http.Headers;
1011

1112
namespace Microsoft.AspNet.Mvc
1213
{
@@ -23,13 +24,14 @@ public class FileStreamResult : FileResult
2324

2425
/// <summary>
2526
/// Creates a new <see cref="FileStreamResult"/> instance with
26-
/// the provided <paramref name="fileStream"/>.
27+
/// the provided <paramref name="fileStream"/> and the
28+
/// provided <paramref name="contentType"/>.
2729
/// </summary>
2830
/// <param name="fileStream">The stream with the file.</param>
29-
public FileStreamResult([NotNull] Stream fileStream)
30-
: base(contentType: null)
31+
/// <param name="contentType">The Content-Type header of the response.</param>
32+
public FileStreamResult([NotNull] Stream fileStream, [NotNull] string contentType)
33+
: this(fileStream, new MediaTypeHeaderValue(contentType))
3134
{
32-
FileStream = fileStream;
3335
}
3436

3537
/// <summary>
@@ -39,7 +41,7 @@ public FileStreamResult([NotNull] Stream fileStream)
3941
/// </summary>
4042
/// <param name="fileStream">The stream with the file.</param>
4143
/// <param name="contentType">The Content-Type header of the response.</param>
42-
public FileStreamResult([NotNull] Stream fileStream, string contentType)
44+
public FileStreamResult([NotNull] Stream fileStream, [NotNull] MediaTypeHeaderValue contentType)
4345
: base(contentType)
4446
{
4547
FileStream = fileStream;

src/Microsoft.AspNet.Mvc.Core/ActionResults/PartialViewResult.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.Framework.DependencyInjection;
88
using Microsoft.Framework.Internal;
99
using Microsoft.Framework.Logging;
10+
using Microsoft.Net.Http.Headers;
1011

1112
namespace Microsoft.AspNet.Mvc
1213
{
@@ -45,6 +46,11 @@ public class PartialViewResult : ActionResult
4546
/// <c>ActionContext.HttpContext.RequestServices</c> is used.</remarks>
4647
public IViewEngine ViewEngine { get; set; }
4748

49+
/// <summary>
50+
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
51+
/// </summary>
52+
public MediaTypeHeaderValue ContentType { get; set; }
53+
4854
/// <inheritdoc />
4955
public override async Task ExecuteResultAsync([NotNull] ActionContext context)
5056
{
@@ -74,7 +80,7 @@ public override async Task ExecuteResultAsync([NotNull] ActionContext context)
7480

7581
using (view as IDisposable)
7682
{
77-
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, contentType: null);
83+
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, ContentType);
7884
}
7985
}
8086
}

src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewExecutor.cs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.IO;
6+
using System.Text;
57
using System.Threading.Tasks;
68
using Microsoft.AspNet.Mvc.Rendering;
79
using Microsoft.Framework.Internal;
10+
using Microsoft.Net.Http.Headers;
811

912
namespace Microsoft.AspNet.Mvc
1013
{
@@ -14,7 +17,10 @@ namespace Microsoft.AspNet.Mvc
1417
public static class ViewExecutor
1518
{
1619
private const int BufferSize = 1024;
17-
private const string ContentType = "text/html; charset=utf-8";
20+
private static readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("text/html")
21+
{
22+
Encoding = Encodings.UTF8EncodingWithoutBOM
23+
};
1824

1925
/// <summary>
2026
/// Asynchronously renders the specified <paramref name="view"/> to the response body.
@@ -23,21 +29,43 @@ public static class ViewExecutor
2329
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing action.</param>
2430
/// <param name="viewData">The <see cref="ViewDataDictionary"/> for the view being rendered.</param>
2531
/// <param name="tempData">The <see cref="ITempDataDictionary"/> for the view being rendered.</param>
26-
/// <returns>A <see cref="Task"/> that represents the asychronous rendering.</returns>
32+
/// <returns>A <see cref="Task"/> that represents the asynchronous rendering.</returns>
2733
public static async Task ExecuteAsync([NotNull] IView view,
2834
[NotNull] ActionContext actionContext,
2935
[NotNull] ViewDataDictionary viewData,
3036
[NotNull] ITempDataDictionary tempData,
31-
string contentType)
37+
MediaTypeHeaderValue contentType)
3238
{
33-
if (string.IsNullOrEmpty(contentType))
39+
var response = actionContext.HttpContext.Response;
40+
41+
var contentTypeHeader = contentType;
42+
Encoding encoding;
43+
if (contentTypeHeader == null)
3444
{
35-
contentType = ContentType;
45+
contentTypeHeader = DefaultContentType;
46+
encoding = Encodings.UTF8EncodingWithoutBOM;
3647
}
48+
else
49+
{
50+
if (contentTypeHeader.Encoding == null)
51+
{
52+
// 1. Do not modify the user supplied content type
53+
// 2. Parse here to handle parameters apart from charset
54+
contentTypeHeader = MediaTypeHeaderValue.Parse(contentTypeHeader.ToString());
55+
contentTypeHeader.Encoding = Encodings.UTF8EncodingWithoutBOM;
56+
57+
encoding = Encodings.UTF8EncodingWithoutBOM;
58+
}
59+
else
60+
{
61+
encoding = contentTypeHeader.Encoding;
62+
}
63+
}
64+
65+
response.ContentType = contentTypeHeader.ToString();
66+
67+
var wrappedStream = new StreamWrapper(response.Body);
3768

38-
actionContext.HttpContext.Response.ContentType = contentType;
39-
var wrappedStream = new StreamWrapper(actionContext.HttpContext.Response.Body);
40-
var encoding = Encodings.UTF8EncodingWithoutBOM;
4169
using (var writer = new StreamWriter(wrappedStream, encoding, BufferSize, leaveOpen: true))
4270
{
4371
try

src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.Framework.DependencyInjection;
88
using Microsoft.Framework.Internal;
99
using Microsoft.Framework.Logging;
10+
using Microsoft.Net.Http.Headers;
1011

1112
namespace Microsoft.AspNet.Mvc
1213
{
@@ -45,6 +46,11 @@ public class ViewResult : ActionResult
4546
/// <c>ActionContext.HttpContext.RequestServices</c> is used.</remarks>
4647
public IViewEngine ViewEngine { get; set; }
4748

49+
/// <summary>
50+
/// Gets or sets the <see cref="MediaTypeHeaderValue"/> representing the Content-Type header of the response.
51+
/// </summary>
52+
public MediaTypeHeaderValue ContentType { get; set; }
53+
4854
/// <inheritdoc />
4955
public override async Task ExecuteResultAsync([NotNull] ActionContext context)
5056
{
@@ -74,7 +80,7 @@ public override async Task ExecuteResultAsync([NotNull] ActionContext context)
7480

7581
using (view as IDisposable)
7682
{
77-
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, contentType: null);
83+
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, ContentType);
7884
}
7985
}
8086
}

0 commit comments

Comments
 (0)