Skip to content

Commit 39fe063

Browse files
committed
Fix aspnet#2330 - Reimagine *FormatterContext
This change simplifies InputFormatterContext/OutputFormatterContext by swapping ActionContext for HttpContext. This change is important especially for InputFormatterContext as it decouples ModelState from ActionContext - allowing us to fix a related bug where the _wrong_ ModelState can be passed in for a TryUpdateModel operation.
1 parent fcf7b15 commit 39fe063

36 files changed

+402
-370
lines changed

src/Microsoft.AspNet.Mvc.Abstractions/InputFormatterContext.cs

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

44
using System;
5+
using Microsoft.AspNet.Http;
6+
using Microsoft.AspNet.Mvc.ModelBinding;
57
using Microsoft.Framework.Internal;
68

79
namespace Microsoft.AspNet.Mvc
810
{
911
/// <summary>
10-
/// Represents information used by an input formatter for
11-
/// deserializing the request body into an object.
12+
/// A context object used by an input formatter for deserializing the request body into an object.
1213
/// </summary>
1314
public class InputFormatterContext
1415
{
1516
/// <summary>
1617
/// Creates a new instance of <see cref="InputFormatterContext"/>.
1718
/// </summary>
18-
public InputFormatterContext([NotNull] ActionContext actionContext,
19-
[NotNull] Type modelType)
19+
/// <param name="httpContext">
20+
/// The <see cref="Http.HttpContext"/> for the current operation.
21+
/// </param>
22+
/// <param name="modelState">
23+
/// The <see cref="ModelStateDictionary"/> for recording errors.
24+
/// </param>
25+
/// <param name="modelType">
26+
/// The <see cref="Type"/> of the model to deserialize.
27+
/// </param>
28+
public InputFormatterContext(
29+
[NotNull] HttpContext httpContext,
30+
[NotNull] ModelStateDictionary modelState,
31+
[NotNull] Type modelType)
2032
{
21-
ActionContext = actionContext;
33+
HttpContext = httpContext;
34+
ModelState = modelState;
2235
ModelType = modelType;
2336
}
2437

2538
/// <summary>
26-
/// Action context associated with the current call.
39+
/// Gets the <see cref="Http.HttpContext"/> associated with the current operation.
2740
/// </summary>
28-
public ActionContext ActionContext { get; private set; }
41+
public HttpContext HttpContext { get; private set; }
2942

3043
/// <summary>
31-
/// Represents the expected type of the model represented by the request body.
44+
/// Gets the <see cref="ModelStateDictionary"/> associated with the current operation.
45+
/// </summary>
46+
public ModelStateDictionary ModelState { get; }
47+
48+
/// <summary>
49+
/// Gets the expected <see cref="Type"/> of the model represented by the request body.
3250
/// </summary>
3351
public Type ModelType { get; private set; }
3452
}

src/Microsoft.AspNet.Mvc.Abstractions/OutputFormatterContext.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Text;
6+
using Microsoft.AspNet.Http;
67
using Microsoft.Net.Http.Headers;
78

89
namespace Microsoft.AspNet.Mvc
@@ -24,9 +25,9 @@ public class OutputFormatterContext
2425
public Type DeclaredType { get; set; }
2526

2627
/// <summary>
27-
/// Action context associated with the current call.
28+
/// Gets or sets the <see cref="HttpContext"/> context associated with the current operation.
2829
/// </summary>
29-
public ActionContext ActionContext { get; set; }
30+
public HttpContext HttpContext { get; set; }
3031

3132
/// <summary>
3233
/// The encoding which is chosen by the selected formatter.

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public override async Task ExecuteResultAsync([NotNull] ActionContext context)
101101

102102
var formatterContext = new OutputFormatterContext()
103103
{
104-
ActionContext = context,
104+
HttpContext = context.HttpContext,
105105
DeclaredType = objectResult.DeclaredType,
106106
Object = Value,
107107
};
@@ -125,7 +125,6 @@ private IOutputFormatter SelectFormatter(ObjectResult objectResult, OutputFormat
125125
{
126126
// If no formatter was provided, then run Conneg with the formatters configured in options.
127127
var formatters = formatterContext
128-
.ActionContext
129128
.HttpContext
130129
.RequestServices
131130
.GetRequiredService<IOptions<MvcOptions>>()
@@ -140,7 +139,6 @@ private IOutputFormatter SelectFormatter(ObjectResult objectResult, OutputFormat
140139
// If the available user-configured formatters can't write this type, then fall back to the
141140
// 'global' one.
142141
formatter = formatterContext
143-
.ActionContext
144142
.HttpContext
145143
.RequestServices
146144
.GetRequiredService<JsonOutputFormatter>();

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public override async Task ExecuteResultAsync(ActionContext context)
4949
var formatterContext = new OutputFormatterContext()
5050
{
5151
DeclaredType = DeclaredType,
52-
ActionContext = context,
52+
HttpContext = context.HttpContext,
5353
Object = Value,
5454
StatusCode = StatusCode
5555
};
@@ -83,8 +83,7 @@ public virtual IOutputFormatter SelectFormatter(
8383
OutputFormatterContext formatterContext,
8484
IEnumerable<IOutputFormatter> formatters)
8585
{
86-
var logger = formatterContext.ActionContext.HttpContext.RequestServices
87-
.GetRequiredService<ILogger<ObjectResult>>();
86+
var logger = formatterContext.HttpContext.RequestServices.GetRequiredService<ILogger<ObjectResult>>();
8887

8988
// Check if any content-type was explicitly set (for example, via ProducesAttribute
9089
// or Url path extension mapping). If yes, then ignore content-negotiation and use this content-type.
@@ -108,7 +107,7 @@ public virtual IOutputFormatter SelectFormatter(
108107
// which can write the type.
109108
MediaTypeHeaderValue requestContentType = null;
110109
MediaTypeHeaderValue.TryParse(
111-
formatterContext.ActionContext.HttpContext.Request.ContentType,
110+
formatterContext.HttpContext.Request.ContentType,
112111
out requestContentType);
113112
if (!sortedAcceptHeaderMediaTypes.Any() && requestContentType == null)
114113
{
@@ -240,17 +239,18 @@ public virtual IOutputFormatter SelectFormatterUsingAnyAcceptableContentType(
240239
private IEnumerable<MediaTypeHeaderValue> GetSortedAcceptHeaderMediaTypes(
241240
OutputFormatterContext formatterContext)
242241
{
243-
var request = formatterContext.ActionContext.HttpContext.Request;
242+
var request = formatterContext.HttpContext.Request;
244243
var incomingAcceptHeaderMediaTypes = request.GetTypedHeaders().Accept ?? new MediaTypeHeaderValue[] { };
245244

246245
// By default we want to ignore considering accept headers for content negotiation when
247246
// they have a media type like */* in them. Browsers typically have these media types.
248247
// In these cases we would want the first formatter in the list of output formatters to
249248
// write the response. This default behavior can be changed through options, so checking here.
250-
var options = formatterContext.ActionContext.HttpContext
251-
.RequestServices
252-
.GetRequiredService<IOptions<MvcOptions>>()
253-
.Options;
249+
var options = formatterContext
250+
.HttpContext
251+
.RequestServices
252+
.GetRequiredService<IOptions<MvcOptions>>()
253+
.Options;
254254

255255
var respectAcceptHeader = true;
256256
if (options.RespectBrowserAcceptHeader == false

src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue
3333

3434
public Task WriteAsync(OutputFormatterContext context)
3535
{
36-
var response = context.ActionContext.HttpContext.Response;
36+
var response = context.HttpContext.Response;
3737
response.ContentLength = 0;
3838
response.StatusCode = context.StatusCode ?? StatusCodes.Status204NoContent;
3939
return Task.FromResult(true);

src/Microsoft.AspNet.Mvc.Core/Formatters/HttpNotAcceptableOutputFormatter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue
2121
/// <inheritdoc />
2222
public Task WriteAsync(OutputFormatterContext context)
2323
{
24-
var response = context.ActionContext.HttpContext.Response;
24+
var response = context.HttpContext.Response;
2525
response.StatusCode = StatusCodes.Status406NotAcceptable;
2626
return Task.FromResult(true);
2727
}

src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public virtual bool CanRead(InputFormatterContext context)
4848
return false;
4949
}
5050

51-
var contentType = context.ActionContext.HttpContext.Request.ContentType;
51+
var contentType = context.HttpContext.Request.ContentType;
5252
MediaTypeHeaderValue requestContentType;
5353
if (!MediaTypeHeaderValue.TryParse(contentType, out requestContentType))
5454
{
@@ -72,7 +72,7 @@ protected virtual bool CanReadType(Type type)
7272
/// <inheritdoc />
7373
public virtual async Task<object> ReadAsync(InputFormatterContext context)
7474
{
75-
var request = context.ActionContext.HttpContext.Request;
75+
var request = context.HttpContext.Request;
7676
if (request.ContentLength == 0)
7777
{
7878
return GetDefaultValueForType(context.ModelType);

src/Microsoft.AspNet.Mvc.Core/Formatters/JsonInputFormatter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public JsonSerializerSettings SerializerSettings
5252
public override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContext context)
5353
{
5454
var type = context.ModelType;
55-
var request = context.ActionContext.HttpContext.Request;
55+
var request = context.HttpContext.Request;
5656
MediaTypeHeaderValue requestContentType = null;
5757
MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);
5858

@@ -70,7 +70,7 @@ public override Task<object> ReadRequestBodyAsync([NotNull] InputFormatterContex
7070
errorHandler = (sender, e) =>
7171
{
7272
var exception = e.ErrorContext.Error;
73-
context.ActionContext.ModelState.TryAddModelError(e.ErrorContext.Path, e.ErrorContext.Error);
73+
context.ModelState.TryAddModelError(e.ErrorContext.Path, e.ErrorContext.Error);
7474

7575
// Error must always be marked as handled
7676
// Failure to do so can cause the exception to be rethrown at every recursive level and

src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ private JsonSerializer CreateJsonSerializer()
7272

7373
public override Task WriteResponseBodyAsync(OutputFormatterContext context)
7474
{
75-
var response = context.ActionContext.HttpContext.Response;
75+
var response = context.HttpContext.Response;
7676
var selectedEncoding = context.SelectedEncoding;
7777

7878
using (var writer = new HttpResponseStreamWriter(response.Body, selectedEncoding))

src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public virtual IReadOnlyList<MediaTypeHeaderValue> GetSupportedContentTypes(
104104
/// <returns>The <see cref="Encoding"/> to use when reading the request or writing the response.</returns>
105105
public virtual Encoding SelectCharacterEncoding([NotNull] OutputFormatterContext context)
106106
{
107-
var request = context.ActionContext.HttpContext.Request;
107+
var request = context.HttpContext.Request;
108108
var encoding = MatchAcceptCharacterEncoding(request.GetTypedHeaders().AcceptCharset);
109109
if (encoding == null)
110110
{
@@ -195,7 +195,7 @@ public virtual void WriteResponseHeaders([NotNull] OutputFormatterContext contex
195195
selectedMediaType.Charset = selectedEncoding.WebName;
196196

197197
context.SelectedContentType = context.SelectedContentType ?? selectedMediaType;
198-
var response = context.ActionContext.HttpContext.Response;
198+
var response = context.HttpContext.Response;
199199
response.ContentType = selectedMediaType.ToString();
200200
}
201201

0 commit comments

Comments
 (0)