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

Allow IFormFile parameters annotated with [FromForm] to be correctly … #8452

Merged
merged 2 commits into from
Sep 14, 2018

Conversation

pranavkm
Copy link
Contributor

…bound in ApiControllers

Fixes #8311

@@ -166,5 +170,12 @@ private bool IsComplexTypeParameter(ParameterModel parameter)
.GetMetadataForType(parameter.ParameterInfo.ParameterType);
return metadata.IsComplexType && !metadata.IsCollectionType;
}

private static bool IsFormFile(Type parameterType)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BindingSource.Form is non-greedy, while BindingSource.FormFile that's inferred from model metadata is greedy. The difference causes the parameter name to be reset by ApiController.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above code is intended to reset the name iff the binding source is non-greedy.

@@ -112,7 +114,8 @@ internal void InferBoundPropertyModelPrefixes(ControllerModel controllerModel)
if (property.BindingInfo != null &&
property.BindingInfo.BinderModelName == null &&
property.BindingInfo.BindingSource != null &&
!property.BindingInfo.BindingSource.IsGreedy)
!property.BindingInfo.BindingSource.IsGreedy &&
!IsFormFile(property.ParameterType))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something else is going on here because this line should be redundant. IFormFile and IEnumerable<IFormFile> parameters should be inferred to have BindingSource.FromFile. IsGreedy is true for that binding source. This line and line 139 should not be reachable for the parameters affected by #8311. (Modulo the #7770 fix for parameters that aren't exactly IFormFile or IFormFileCollection.)

@pranavkm did you debug the scenario and confirm these BinderModelName settings are the issue? If yes, why were property.BindingInfo.BindingSource.IsGreedy or parameter.BindingInfo.BindingSource.IsGreedy (below) false for an IFormFile property or parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue specifically manifests in this scenario: Post([FromForm] IFormFile formFile)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it now. Thanks for the offline conversation @pranavkm

private static bool IsFormFile(Type parameterType)
{
return typeof(IFormFile).IsAssignableFrom(parameterType) ||
typeof(IFormFileCollection).IsAssignableFrom(parameterType) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need to check for IFormFileCollection because that implements IEnumerable<IFormFile>.

@@ -166,5 +170,12 @@ private bool IsComplexTypeParameter(ParameterModel parameter)
.GetMetadataForType(parameter.ParameterInfo.ParameterType);
return metadata.IsComplexType && !metadata.IsCollectionType;
}

private static bool IsFormFile(Type parameterType)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above code is intended to reset the name iff the binding source is non-greedy.

@@ -720,6 +720,87 @@ public void InferParameterModelPrefixes_SetsModelPrefix_ForComplexTypeFromValueP
Assert.Equal(string.Empty, parameter.BindingInfo.BinderModelName);
}

[Fact]
public void InferParameterModelPrefixes_DoesNotSetModelPrefix_ForFormFileParametersAnnotatedWithFromForm()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new tests aren't really regression tests for #8311. Need integration or functional tests that include the default BindingSourceMetadataProviders. See

modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFile), BindingSource.FormFile));
modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormCollection), BindingSource.FormFile));
modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFileCollection), BindingSource.FormFile));
modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IEnumerable<IFormFile>), BindingSource.FormFile));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use TestModelMetadataProvider that's set up with these detail providers in these tests

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🆗

}

[Fact]
public void InferParameterModelPrefixes_DoesNotSetModelPrefix_ForFormFileCollectionParametersAnnotatedWithFromForm()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename. This test covers properties, not parameters.

@pranavkm
Copy link
Contributor Author

🆙 📅

Copy link
Member

@dougbu dougbu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The main question I have is whether we should similarly special-case CancellationToken, IFormCollection, and anything with a specified BindingInfo.BinderType to ignore BindingSource (as the binder providers do). Thoughts?

@dougbu
Copy link
Member

dougbu commented Sep 13, 2018

After another offline conversation, @pranavkm has agreed to file a couple of bugs to track separately. In addition, neither of us believe CancellationToken or IFormCollection will hit problems similar to this one -- the relevant binders ignore BinderModelName.

@pranavkm
Copy link
Contributor Author

@dougbu would you mind filing the issue with the BinderModelType? You'd be able to explain better.

@pranavkm pranavkm merged commit 233140c into release/2.2 Sep 14, 2018
@pranavkm pranavkm deleted the prkrishn/formfile branch September 14, 2018 16:06
@dougbu
Copy link
Member

dougbu commented Sep 14, 2018

@dougbu would you mind filing the issue with the BinderModelType? You'd be able to explain better.

@pranavkm after experimenting a bit, I don't think [ModelBinder] or [BindProperty] are likely to cause problems. Those are main ways BindingInfo.BinderType can be set. ApiBehaviorApplicationModelProvider will not override BinderModelName (or BindingSource) because those attributes implement IBindingSourceMetadata (through IBinderTypeProviderMetadata). The chosen BindingSource is BindingSource.Custom and BindingSource.Custom is greedy.

It's possible to subclass either attribute or implement IBinderTypeProviderMetadata directly and set BindingSource to something greedy. But, we're now in a dim corner of a corner case.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants