skip to Main Content

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);
            }
});

enter image description here

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


  1. modify the name of the parameter:

    [HttpPost("add")]
     //level=>levelData
     public string Add([FromForm] LevelData levelData)
     {
                return "";
     }
    

    You have to avoid naming the parameter the same as the nested model property

    Login or Signup to reply.
  2. 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 with Level. Then the binder is expecting that you will submit;

    Level.HomePlanId=your_home_plan_id&Level.Level.HomePlanId2=your_home_plan_id_2
    

    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;

    HomePlanId=your_home_plan_id&Level.HomePlanId2=your_home_plan_id_2
    

    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.

    public class BindingProvider : IBindingMetadataProvider {
        public void CreateBindingMetadata(BindingMetadataProviderContext context)
        {
            IModelNameProvider nameAttrib = null;
            if (context.BindingMetadata.BinderModelName == null 
                && context.BindingMetadata.IsBindingAllowed
                && (nameAttrib = context.Attributes.OfType<IModelNameProvider>().FirstOrDefault())!=null
                && nameAttrib.Name == null)
                context.BindingMetadata.BinderModelName = 
                    context.Key.PropertyInfo?.Name
                    ?? context.Key.ParameterInfo?.Name;
        }
    }
    services.AddMvc(o => o.ModelMetadataDetailsProviders.Add(new BindingProvider()));
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search