I am using .NET Core 7.0. I have this endpoint in an API controller:
[HttpPost("add")]
public string Add([FromForm] LevelData level)
{
return "";
}
This is the class that this method receives:
public class LevelData
{
//
public List<IFormFile> NewImageFiles{ get; set; } = new();
public string HomePlanId { get; set; } = "";
public SecondLevel Level { get; set; } = new SecondLevel();
}
public class SecondLevel
{
public string HomePlanId2 { get; set; } = "";
}
No mater if I use the swagger page or use and ajax call, I always get all the properties empty
curl -X 'POST'
'https://localhost:7018/api/Level/add'
-H 'accept: text/plain'
-H 'Content-Type: multipart/form-data'
-F 'HomePlanId=1'
-F 'Level.HomePlanId2=2'
var formData = new FormData();
// Append the properties of the LevelData object
formData.append('HomePlanId', 'your_home_plan_id');
formData.append('Level.HomePlanId2', 'your_home_plan_id_2');
// Make the AJAX call to the C# API
$.ajax({
url: '/api/level/add',
type: 'POST',
contentType: false,
processData: false
data: formData,
success: function (data) {
// Handle the API response here if needed
console.log("API response:", data);
},
error: function (error) {
// Handle errors here if the API call fails
console.error("API error:", error);
}
});
I want to use a FromForm to be able to pass FormFile (images).
I did test commenting this part
public SecondLevel Level { get; set; } = new SecondLevel();
When is commented out, the code works.
What I need to change to make all the properties be setup correctly, including SecondLevel
2
Answers
modify the name of the parameter:
You have to avoid naming the parameter the same as the nested model property
Ah MVC binding, I really dislike this default behaviour.
When binding top level objects, eg controller properties or function arguments, the binder will first look for a submitted value with the same name.
If a value is found with that name, then the child properties or collections of that object will be bound with that prefix.
Since in your case, your argument is called
level
and you are submitting a value that starts withLevel
. Then the binder is expecting that you will submit;Then if no value is found with the same name, the binder will attempt to bind that object with no prefix. So if you renamed your argument to something else you could submit;
But if the client submits an extra value with the same name as your variable, then the binding will fail.
I find this behaviour to be particularly annoying when your top level objects include a
Dictionary<>
and you attempt to submit an empty collection. As in this case, any other value you submit for another top level object will be added as a key to the dictionary.You can override this behaviour by adding an explicit binding prefix to each parameter. Or globally by defining a custom
IBindingMetadataProvider
.