1

I have started to take advantage of Axios header option multipart/form-data so I can go utilize Typescript objects for my requests to bind to my ASP.NET Core models, however List<IFormFile> properties do not seem to bind and stay as null, while my other properties bind fine, including a single IFormFile property.

My Typescript interface is as follows:

export interface AddReview {
    primaryDoc: File; // Required, PDF
    supportingDocuments: File[] | null;
    stateId: number; // Required
}

And my axios request is:

const addReview = async (submissionData :  SDP.AddReview) : Promise<any | null> => {

const data = api.post(`/sdp/Review/`, submissionData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  }
})
.then(response => { 
  const apiResponse : API.Response = response.data
  const review : any = apiResponse.data
  return review
}).catch(error => {
  if(error.response.status == 400) {
    router.push({ name: 'Error'})
  }
  return null
})
return data
}

Then this should bind to my AddReviewDTO model class in C#:

public class AddReviewDTO
{
    [Required]
    public required IFormFile PrimaryDoc { get; set; }

    public List<IFormFile>? SupportingDocuments { get; set; }

    [Required]
    public required int StateId { get; set; }
}

The controller action that is trying to achieve this action is as follows:

public async Task<IActionResult> AddReviewAsync([FromForm] AddReviewDTO addDTO)
{
    string json = JsonSerializer.Serialize(addReviewDTO);
}

I know there are instances where custom model binders are required in ASP.NET Core but this seems so basic so I am hoping this can be done without that sort of solution as I want to reuse this approach on many scenarios with different models. In addition, I do not want to go back to the approaching of looping through my entire object to add to FormData manually as this was tedious.

Here is a similar question but no answer without switching to complete FormData approach which I use to use and want to get away from:

.Net 7 API won't accept List<IFormFile>

My only solution so far is this utility function I had ChatGPT Create:

const objectToFormData = (
  obj: Record<string, any>,
  form: FormData = new FormData(),
  namespace: string = ''
): FormData => {
  Object.keys(obj).forEach((key) => {
    const value = obj[key];
    if (value === undefined || value === null) return;

    const formKey = namespace ? `${namespace}.${key}` : key;

    if (value instanceof Date) {
      form.append(formKey, value.toISOString());
    } else if (value instanceof File || value instanceof Blob) {
      form.append(formKey, value, value.name);
    } else if (Array.isArray(value)) {
      value.forEach((element) => {
        if (element instanceof File || element instanceof Blob) {
          // ✅ Multiple files should use the same key (no index)
          form.append(formKey, element, element.name);
        } else if (typeof element === 'object' && element !== null) {
          // Use dot notation or any custom naming here
          objectToFormData(element, form, formKey);
        } else {
          form.append(formKey, String(element));
        }
      });
    } else if (typeof value === 'object') {
      objectToFormData(value, form, formKey);
    } else {
      form.append(formKey, String(value));
    }
  });

  return form;
}

This will convert the object to FormData.

3
  • Total stab in the dark but try setting the Axios form serialiser indexes option to true, ie, just after headers... formSerializer: { indexes: true }. See github.com/axios/…. null may be another viable option, I can't remember how .Net expects to receive repeated form-data fields Commented May 27 at 23:54
  • 1
    Could you replace List<IFormFile> with List<FormFile>? I'm curious - how would the serializer know which concrete type to instantiate? Commented May 28 at 8:27
  • 1
    @Phil setting formSerializer: {indexes: null} solves the problem. What a great feature set axios has built in. If you want to put this in an answer il accept it. Thanks! Commented May 28 at 11:10

1 Answer 1

1

ASP.NET expects repeated fields in multipart/form-data payloads to have no [] or [n] suffixes whereas Axios by default opts for the PHP-style [] suffix.

You can disable the index suffixes by configuring the formSerializer option, best applied at your instance creation...

const api = axios.create({
  // ...
  formSerializer: {
    indexes: null,
  }
})

or inline with your request

const data = api.post(`/sdp/Review/`, submissionData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  },
  formSerializer: {
    index: null,
  },
});

See Automatic serialization to FormData

  • indexes: null|false|true = false - controls how indexes will be added to unwrapped keys of flat array-like objects.
    • null - don't add brackets (arr: 1, arr: 2, arr: 3)
    • false(default) - add empty brackets (arr[]: 1, arr[]: 2, arr[]: 3)
    • true - add brackets with indexes (arr[0]: 1, arr[1]: 2, arr[2]: 3)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.