-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Avoid allocating FormFeature in HttpRequest.HasFormContentType #50830
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
Conversation
{ | ||
return true; | ||
} | ||
|
||
var contentType = ContentType; | ||
return HasApplicationFormContentType(contentType) || HasMultipartFormContentType(contentType); | ||
return HttpExtensions.HasApplicationFormContentType(_request.ContentType) || HttpExtensions.HasMultipartFormContentType(_request.ContentType); |
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.
This is done a couple of times.
Add a HasFormContentType(...)
to HttpExtensions that calls these methods for you? Eliminates the chance of logic going out of sync.
{ | ||
var encoding = FilterEncoding(contentType.Encoding); | ||
var encoding = FilterEncoding(ContentType!.Encoding); |
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.
nit: I don't like that the ContentType property allocates a MediaTypeHeaderValue
. IMO it should be a method.
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.
The type is more reliable, methods just re-invent the parsers.
{ | ||
get { return FormFeature.HasFormContentType; } | ||
} | ||
public override bool HasFormContentType => HttpExtensions.HasApplicationFormContentType(ContentType) || HttpExtensions.HasMultipartFormContentType(ContentType); |
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.
Use new HasFormContentType(...)
method here.
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.
This is a behavior change mentioned here: #50016 (comment)
Before if the FormFeature.Form
was set manually this would also return true
independent of the headers.
Now it would return false
because only the headers are checked.
DefaultHttpRequest.HasFormContentType
needs to be exactly the same as FormFeature.HasFormContentType
for backward compatibility.
I guess this should also check if the form is already set e.g.
_features.Cache.Form?._form is not null || ...
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.
Now it would return false because only the headers are checked.
DefaultHttpRequest.HasFormContentType needs to be exactly the same as FormFeature.HasFormContentType for backward compatibility.
Yeah, this is a total drag. I almost considered a breaking change here. However, it seems like there's a few components in MVC's model binding that rely on the behavior of form set = form content type is true. At best, here we can avoid constructing a new FormFeature
if it has already been set.
Looks like this PR hasn't been active for some time and the codebase could have been changed in the meantime. |
internal static bool HasApplicationFormContentType([NotNullWhen(true)] string? contentType) | ||
{ | ||
// Content-Type: application/x-www-form-urlencoded; charset=utf-8 | ||
return contentType != null && contentType.Contains(UrlEncodedFormContentType, StringComparison.OrdinalIgnoreCase); |
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.
This is less precise / robust than the prior implementation. It's ok in the normal scenario, but it can cause strange random bugs for other inputs. E.g. this now matches "web-application/x-www-form-urlencoded-foo".
We have tools that can get the same precision as before without allocating:
https://github.com/dotnet/aspnetcore/blob/main/src/Shared/MediaType/ReadOnlyMediaTypeHeaderValue.cs
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.
We have tools that can get the same precision as before without allocating:
Ah, perfect! This is exactly what I was looking for.
get { return FormFeature.HasFormContentType; } | ||
} | ||
public override bool HasFormContentType => HttpExtensions.HasAnyFormContentType(ContentType) | ||
|| _features.Fetch(ref _features.Cache.Form, _features.Collection, collection => collection.Get<IFormFeature>()) != null; |
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.
Isn't this just checking for the presence of the form feature? If present, it should check the form feature's HasFormContentType.
The current form feature should also take precedence if it exists.
Closes #50153.
Baseline
With Changeset