-
Notifications
You must be signed in to change notification settings - Fork 10k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JSON "null" request body rejected as an empty request body #41002
Conversation
remove null coalescing since bodylength is null for null value in json
I think this should do it. If anyone has better idea how to check whether request was empty or with null value please let me know. Also the error message is up for debate I dont think the one I used is clear at all |
bindingContext.ModelState.AddModelError(modelBindingKey, message); | ||
|
||
// If request content length is 0 then request had empty body, if not null was provided in json | ||
if (httpContext.Request.ContentLength == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel that this is the right place to do this. Maybe an option is change the json-based inpur formatters to AddModelError and return Failure instead
return InputFormatterResult.NoValue(); |
return InputFormatterResult.NoValue(); |
Please check if we do have any side effects with this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do need to detect the presence of a request body, we should use the feature. See: https://github.com/dotnet/aspnetcore/pull/39917/files#diff-f137ebb8705f77c3c09cf21ca959b40735794ec71b8fdec0c7d4f0a0b3a7fb34R106
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for feedback! I agree with suggestions and am working on them
So I think there should be some sort of ModelState enum in InputFormatterResult class indicating whether model is set null or undefined. |
I made commit with changes that I think would improve API here. I have added enum describing state of model binding (set/null/empty body). All the logic checking if model is in these states is broken so don't look into that. I haven't made any api changes proposal yet so I dont entirely know how the process goes. That's why I would appreciate here feedback if this is the way I should finish this issue. |
@senbar That is an interesting change, however, as you mentioned that will be a large change to how the states are checked. Since the goal is to just provide an more descriptive error message, for JSON "null", but keep rejecting the request as before. I still feel you could try something easier. If you take a look where the if (model == null && !context.TreatEmptyInputAsDefaultValue)
{
if ( [check for JSON-encoded value "null"])
{
bindingContext.ModelState.AddModelError(context.ModelName, "The new message");
return InputFormatterResult.Failure();
}
// Some nonempty inputs might deserialize as null, for example whitespace.
// The upstream BodyModelBinder needs to
// be notified that we don't regard this as a real input so it can register
// a model binding error.
return InputFormatterResult.NoValue();
} |
This reverts commit c960a75.
To check if request is null here would need reading Request.Body stream in if (model == null && !context.TreatEmptyInputAsDefaultValue)
{
var bodyStreamReader = new StreamReader(httpContext.Request.Body);
bodyStreamReader.BaseStream.Seek(0, SeekOrigin.Begin);
string rawJson = await bodyStreamReader.ReadToEndAsync();
if(rawJson=="null")
{
context.ModelState.TryAddModelError(context.ModelName, "null value not supported");
return InputFormatterResult.Failure();
}
// Some nonempty inputs might deserialize as null, for example whitespace,
// or the JSON-encoded value "null". The upstream BodyModelBinder needs to
// be notified that we don't regard this as a real input so it can register
// a model binding error.
return InputFormatterResult.NoValue();
} which doesn't work because Request.Body stream is not seekable in general. Any ideas what should I do instead? Buffering whole body stream would have big performance impact. |
I'm not sure we want to report failure at such a low layer. After all, We probably should be changing this to return It's a literal default value, and the whole problem here seems to be that both empty input and null literals are being collapsed into a single I don't think it's necessary to check if there was an empty request body or aspnetcore/src/Mvc/Mvc.Core/src/Formatters/InputFormatter.cs Lines 102 to 116 in 3921939
And if the Unfortunately, Json.NET turns an empty body into @brunolins16 Is there anything wrong with just removing the I imagine we'll have to be more careful with the Json.NET input formatter for back compat. It's always reliably treated |
So isn't this a problem with entire handling of model binding of // If the input formatter gives a "no value" result, that's always a model state error, From what I understand
But we do know the type of deserialized object since we are getting it from // If the input formatter gives a "no value" result, that's always a model state error,
// because BodyModelBinder implicitly regards input as being required for model binding.
// If instead the input formatter wants to treat the input as optional, it must do so by
// returning InputFormatterResult.Success(defaultForModelType), because input formatters
// are responsible for choosing a default value for the model type. So I think it is implied that formatter should be handling dealing with optional arguments and stuff like that. As for checking if json is null literal I was thinking about the case described in the comment: |
I do not think that the condition is wrong just because we did some improvements (.NET 7) in how we are treating the AllowEmptyBody based on the Nullabity in I feel only very specific scenarios will allow
@halter73 As we discussed, "null" and empty body are different, however, MVC treat them as the same for STJ for a long time and change might cause some breaking change. We changed to make the empty body better and most of the cases when a "null" is accepted will be covered by this new mechanism, as example: public ActionResult([FromBody] Contact? contact) {} or public ActionResult([FromBody] Contact contact = null) {} Until .NET 6 they used to be blocked for both (null and empty body) because we were not using the optionality to allow them. Now, .NET 7, both actions will be accepted (null and empty body). Since the mechanism will behave differently, as I mentioned, not change it right now might be the best and going back to the original idea of just update the error message to differentiate between
I think the comment about if (model == null && !context.TreatEmptyInputAsDefaultValue)
{
context.ModelState.TryAddModelError(context.ModelName, "null value not supported");
return InputFormatterResult.Failure();
} That will produce a better error message, without change the current behavior. And the new mechanism or the user option to allow empty body will be used to decide if the failure will happen or we will treat as Also, I think we should keep the @halter73 anything to add based on our discussion? |
I am not sure if I understand correctly how it deals with nullable types. When I run test with (/contact is simple endpoint with Contact class object argument from body): var response = await Client.PostAsJsonAsync<Contact>("/contact", default); model binding returns error even though |
We changed the behavior in .NET 7 and a nullable |
remove logic from BodyModelBinder; Fix messgaes on ApiBehaviortest
@brunolins16 I have made suggested changes to SytemTextJsonInputFormatter and added tests for it. Edit: I see some tests are failing, will fix that shortly |
Let me do some investigation and will reply to you soon. Thanks for your patience and contribution. |
@senbar I just took a look and i think the easiest way is to add the new Functional Tests to https://github.com/brunolins16/aspnetcore/blob/main/src/Mvc/test/Mvc.FunctionalTests/SystemTextJsonInputFormatterTest.cs , instead |
// be notified that we don't regard this as a real input so it can register | ||
// a model binding error. | ||
return InputFormatterResult.NoValue(); | ||
context.ModelState.TryAddModelError(context.ModelName, "null value not supported"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about something like The supplied value 'null' is invalid
or The value 'null' is invalid
?
@@ -86,6 +86,28 @@ public async Task ReadAsync_SingleError() | |||
}); | |||
} | |||
|
|||
[Fact] | |||
public async Task ReadAsync_WithJsonNullLiteralWithoutTreatEmptyInputAsDefault_ShouldSetNullValueNotSupportedError() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you could add a test with treatEmptyInputAsDefaultValue = true
?
var httpContext = GetHttpContext(contentBytes); | ||
|
||
var formatterContext = CreateInputFormatterContext(typeof(int?), httpContext); | ||
// TreatEmptyInputAsDefaultValue is set to false so even if type is nullable we should gget 'null value not supported' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// TreatEmptyInputAsDefaultValue is set to false so even if type is nullable we should gget 'null value not supported' | |
// TreatEmptyInputAsDefaultValue is set to false so even if type is nullable the formatter should report a failure. |
} | ||
|
||
[Fact] | ||
public virtual async Task ActionWithNonOptionalParameterReturnsBodyCannotBeEmpty_WhenInvokedWithEmptyBody() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public virtual async Task ActionWithNonOptionalParameterReturnsBodyCannotBeEmpty_WhenInvokedWithEmptyBody() | |
public Task JsonInputFormatter_ReturnsBadRequest_ForEmptyRequestBodyAction() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned, maybe you should move it to https://github.com/brunolins16/aspnetcore/blob/main/src/Mvc/test/Mvc.FunctionalTests/SystemTextJsonInputFormatterTest.cs
@@ -82,6 +83,73 @@ public virtual async Task ActionsReturnBadRequest_WhenModelStateIsInvalid() | |||
} | |||
} | |||
|
|||
[Fact] | |||
public virtual async Task ActionWithNonOptionalParameterReturnsNullIsNotPermited_WhenInvokedWithNullParameter() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public virtual async Task ActionWithNonOptionalParameterReturnsNullIsNotPermited_WhenInvokedWithNullParameter() | |
public Task JsonInputFormatter_ReturnsBadRequest_ForLiteralNull() |
Sorry have been busy last weeks, I will be finishing PR soon |
@senbar are you still planning to work on it? Can I help you on anything? |
Yeah sorry aobut that I was changing job lately and was swamped with work. I will try to finish it next two weeks. As for help I think I got it it should be pretty straightforward, if there are any problems I will let You know, thanks |
Don't worry about it, take your time. I was just checking if I could do anything to help. |
JSON "null" response body rejected as an empty response body
I have added check if request body has 0 bytes; If yes then body was actually empty and proper error message is returned; If not then json contained null and different message is returned.
Should I create new error message for this null case in resources? I have changed it to ValueMustNotBeNullAccessor but it doesn't seem appropriate since it was for a bit different cases elsewhere.
Fixes #40415