5

I have a simple API I am writing which includes a POST method to insert a new record into a couple of database tables. The data is coming from form data.

One of the items is a collection which includes a few fields.

I am using Swagger to test and when I Post the form data the collection field is blank.

Controller - Post endpoint

[HttpPost("addboardmember")]
        public IActionResult PostBoardMember([FromForm] MemberForm memberData)
        {
            try
            {
                _memberRepository.PostBoardMember(memberData);
                
                var newMember = _memberRepository.GetMembers().SingleOrDefault(m => m.SSN == memberData.SSN);
                var newMemberId = newMember.Member_ID;

                var boardArray = memberData.BoardForms;


                return Ok();
               //return Ok() is temporary, will return 201 with appropriate data
            }

            catch (Exception ex)
            {
                return BadRequest("Couldn't Save record??" + ex.InnerException.ToString());
            }
            
        }

MemberForm class (Abbreviated to highlight the main fields and the field I am struggling with populating: BoardForms

public class MemberForm
    {
        [Required, NotNull]
        public string LastName { get; set; }
        
        [Required, NotNull]
        public string FirstName { get; set; }
       
        [Required, NotNull]
        public string SSN {get; set;}

       


        public virtual ICollection<BoardForm> BoardForms { get; set; }
    }
}

BoardForm class:

public class BoardForm
    {
        public int Board_ID { get; set; }

        public int StartYear { get; set; }

        public int EndYear { get; set; }
    }

When I test Post API endpoint using Swagger the BoardForms field has a count of zero. Board Forms Count Shows 0

Swagger input.

enter image description here

I will have additional business logic to create a record based on each record in the BoardForm array.

I do not know how I am supposed to access the array being sent in the Form Data which is setup as an ICollection.

Browser Developer Tools payload: Browser Payload

All the elements shown above in the payload screenshot are mapped to their correct elements an I can see the values with the exception of the 'BoardForms' array.

2
  • 1
    Q: Have you looked at the actual, HTTP-level requests/responses, in either/both the MSVS debugger or Chrome/Edge Developer Tools? Exactly what (if any) data is being received by your "Post" handler when you "Save" the form? Q: Have you looked in SSMS (or equivalent) to see what data is being saved to the DB? Commented Jan 17, 2022 at 22:57
  • I am receiving the data for all the other fields, and I verified my API can write those to the database table. In the included screenshot the email field shows the entered data. Most of my fields are currently options so I left those blank. When I have added a screenshot from the browser developer tools which shows the payload being sent. I can access all the fields with the exception of the BoardForms field. When I look at the values Visual Studio when the API is running I am shown a count of 0 for BoardForms. Commented Jan 17, 2022 at 23:03

1 Answer 1

8
  • You're using the wrong attribute and you're formatting your requests incorrectly.
    • Use [FromForm] when you want ASP.NET to bind HTML <form> data posted as application/x-www-form-urlencoded or multipart/form-data.
      • You cannot use [FromForm] for JSON data.
    • Use [FromBody] to have ASP.NET use some configured Content-Type-specific binder to populate an object.
      • ASP.NET Core supports application/json by default, I don't know what other types are supported in-box (e.g. XML, DataContract, etc) but it is extensible as you can also support gRPC, for example.
      • You probably should not be using either <form> content-types (multipart/form-data and application/x-www-form-urlencoded) for any "Web APIs" because those are specific to the idiosyncrasies of HTML's <form> and are unsuitable for describing structured data (such as arbitrary lists of data, or keyed collections), while ASP.NET does support binding <form> / FormData to objects it is unsuitable for JSON documents.
        • If you're using multipart/form-data to allow for binary uploads from client-libraries and headless clients (and not human-operated web-browsers) then I'd advise you to redesign your web-service's to accept binary file uploads as separate PUT/POST requests and to not combine DTOs with binary data in the same request.
          • While ASP.NET Core can bind objects to JSON (application/json) and IFormFile from other multipart/form-data request parts, this functionality isn't built-in nor turnkey: you'll need to write your own model-binder to handle this scenario - and I don't think it's worth the effort. YMMV.

so change this:

[HttpPost("addboardmember")]
public IActionResult PostBoardMember([FromForm] MemberForm memberData)
{
}

to this:

[HttpPost("addboardmember")]
public IActionResult PostBoardMember([FromBody] MemberForm memberData)
{
}

And you might want to ensure your Controller class has [ApiController] applied to it.

Sign up to request clarification or add additional context in comments.

4 Comments

I believe I am sending the data as a multipart/form-data. All the other fields are populated as expected in the API. Is it because the the BoardForms field is being sent as a JSON object?
@Tom You cannot have inline JSON in a multipart/form-data request body without specifically configuring a custom Model Binder to handle it. If this is an "API" as you describe, why aren't you using JSON consistently?
@Dai: I believe you hit the nail on the head. I love that frameworks like ASP.Net "abstract" away a lot of low-level "grunge" ... but I hate that means many developers never really get to understand the different ways HTTP response data can be formatted, or details about how "bindings" really work ... and the many potential "gotcha's you can encounter while using them.
@paulsm4 Wait until you come across my polemics about WebForms...

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.