(edit at the bottom)
I'm building a questionnaire based on inputs from the user.
The questionnaire consists of n number of sections and each section can have n number of questions in it.
Each question is a type (FreeText, MultipleChoice, YesNo, Upload, etc.) used to determine behavior and which inputs to render on the page.
Some questions can have additional inputs for comments or additional info (whether or not this is the case is determined by the question type).
Having rendered the questions onto the page in javascript on a per-section basis, I'm struggling to allow capture of the answers.
The form renders dynamically when the user clicks on a section:
async function toggleSection(sectionId) {
const contiainer = document.querySelector("section")
// For ease, query the API to get the markup for the selected section.
// Building the markup from the basic data items on the backend was easier.
const sectionMarkupResult = await getSectionMarkup(sectionId)
if (sectionMarkupResult.length) {
container.innerHTML = sectionMarkupResult
}
else {
// In dev the API function will return an error if there's a problem.
container.innerHTML = `<p class="text-danger">
Unable to load section.
</p>
<p class="text-danger">${sectionMarkupResult}</p>`
}
// Hide the menu (.index) and show the section (container, above).
container.classList.remove("d-none")
document.querySelector(".index").classList.add("d-none")
}
The output is something like this:
<form method="post" action="/api/Provider" data-sectionid="1">
<p>Tell us about yourself.</p>
<p class="mb-0 mt-2">Yes / no (radio buttons)</p>
<div class="form-check">
<input class="form-check-input" type="radio" id="51e14e20-5a91-44b9-83a8-dca221d434e6" name="51e14e20-5a91-44b9-83a8-dca221d434e6" value="true" data-questionid="6">
<label class="form-check-label" for="51e14e20-5a91-44b9-83a8-dca221d434e6">Yes</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="radio" id="51e14e20-5a91-44b9-83a8-dca221d434e6" name="51e14e20-5a91-44b9-83a8-dca221d434e6" value="false" data-questionid="6">
<label class="form-check-label" for="51e14e20-5a91-44b9-83a8-dca221d434e6">No</label>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="d9480b44-7916-4228-8c23-aa20a9efef5b" value="1" data-questionid="3">
<label class="form-check-label" for="d9480b44-7916-4228-8c23-aa20a9efef5b">Choice 1</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="d9480b44-7916-4228-8c23-aa20a9efef5b" value="2" data-questionid="3">
<label class="form-check-label" for="d9480b44-7916-4228-8c23-aa20a9efef5b">Choice 2</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="d9480b44-7916-4228-8c23-aa20a9efef5b" value="3" data-questionid="3">
<label class="form-check-label" for="d9480b44-7916-4228-8c23-aa20a9efef5b">Choice 3</label>
</div>
</div>
<div class="form-group mb-3">
<label for="61336a34-9e03-4842-a843-741344fe9dbb">Name</label>
<input type="text" class="form-control" id="61336a34-9e03-4842-a843-741344fe9dbb" value="" data-questionid="1">
</div>
<div class="form-group mb-3">
<label for="c9653cd7-059f-4273-866b-de502dac6ec9">Surname</label>
<textarea class="form-control" id="c9653cd7-059f-4273-866b-de502dac6ec9" data-questionid="2"></textarea>
</div>
<div class="form-group mb-3">
<label for="00529ca6-07ab-4df8-ac55-6376d2e9fe4d">Options</label>
<select class="form-control" id="00529ca6-07ab-4df8-ac55-6376d2e9fe4d" data-questionid="4">
<option value="">Pick one</option>
<option value="Option 1">Option 1</option>
<option value="Option 2">Option 2</option>
<option value="Option 3">Option 3</option>
</select>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="ia93e83bc136b43eda28c47f54716b6f2_0" value="7" data-questionid="9" onchange="showComments('da93e83bc136b43eda28c47f54716b6f2_0')">
<label class="form-check-label" for="ia93e83bc136b43eda28c47f54716b6f2_0">Test 1</label>
<div class="input d-none da93e83bc136b43eda28c47f54716b6f2_0">
<textarea class="form-control" data-questionid="9"></textarea>
<small class="text-muted">Fee free to add any details.</small>
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="ia93e83bc136b43eda28c47f54716b6f2_1" value="8" data-questionid="9" onchange="showComments('da93e83bc136b43eda28c47f54716b6f2_1')">
<label class="form-check-label" for="ia93e83bc136b43eda28c47f54716b6f2_1">Test 2</label>
<div class="input d-none da93e83bc136b43eda28c47f54716b6f2_1">
<textarea class="form-control" data-questionid="9"></textarea>
<small class="text-muted">Fee free to add any details.</small>
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="ia93e83bc136b43eda28c47f54716b6f2_2" value="9" data-questionid="9" onchange="showComments('da93e83bc136b43eda28c47f54716b6f2_2')">
<label class="form-check-label" for="ia93e83bc136b43eda28c47f54716b6f2_2">Test 3</label>
<div class="input d-none da93e83bc136b43eda28c47f54716b6f2_2">
<textarea class="form-control" data-questionid="9"></textarea>
<small class="text-muted">Fee free to add any details.</small>
</div>
</div>
<div class="form-group mb-3">
<label for="1f751be5-09f7-464f-99c1-c2dd4dfc6c8c">Test</label>
<input type="date" class="form-control" id="1f751be5-09f7-464f-99c1-c2dd4dfc6c8c" value="" data-questionid="10">
</div>
<div class="form-group mb-3">
<p class="mb-0 mt-2">File <i data-questionid="5" class="uploadSpinner fas fa-circle-notch fa-spin"></i></p>
<input type="file" id="5e8fa2b6-cc20-4fcb-b6d1-6e4970463394" data-questionid="5" data-sectionid="1">
<div class="filesList mt-3" data-questionid="5" data-sectionid="1">
<h4>Files:</h4>
<ul>
</ul>
</div>
</div>
</form>
Because the form is dynamically rendered based on the questions captured for the selected section, it's impossible to model a POCO that I can use on the API as a method param for saving the user input so I thought about using a dynamic to represent the submitted form and figure something out from there.
This hasn't gone well, unfortunately as the param value comes back as object and I don't know what to do with it.
My API endpoint looks something like this:
[HttpPost]
public async Task<IActionResult> SaveSection([FromForm]dynamic sectionFormData)
{
var foo = "bar";
return Ok();
}
Submitting to this is just posting the <form>:
function saveSection() {
document.querySelector("form").submit()
}
I'm not able to discern what the form payload would be like before it gets sent to the API so I really have no idea how read this data into my API so that I can save it to my database.
How can I capture the form data from the submission (preferably without having to rebuild it in JS before it posts)?
Is there something clever I can possibly do with HttpContext to read the data as submitted?
EDIT
Evaluating HttpContext.Request.Form on the api allows me access to submitted values by looping through the FormCollection.
Unfortunately, this doesn't provide me enough information to save anything to my database as it doesn't include data about which question each key/value pair is for.
It also isn't submitting pairs for each input - seems to only be for the first one (there's only 1 item in the collection, relating to the first input in the <form> on the page).
I will need another solution.