Search code examples
javascriptc#asp.net-core-mvc

When using fetch, the json object is not converted to the model in the controller. Why is this happening?


I am new to ASP.NET MVC, I am trying to send data to the server, through the javascript - fetch method, and not through the usual asp-controller, asp-action, in order to interact with the response from the server, so that depending on the status response display different pop-up windows. My form, with my fetch method, looks like this:

<form id="personalProfileFormId">
    <input class="info-user-input" asp-for="Id" hidden="hidden" />
    <input class="info-user-input" asp-for="IsExamCompleted" hidden="hidden" />
    <input class="info-user-input" asp-for="FinalGrade" hidden="hidden" />
    <div class="mb-3">
        Логин : <input class="info-user-input personal-profile-input-disabled" asp-for="Login" type="text">
    </div>
    <div class="mb-3">
        Имя: <input class="info-user-input" asp-for="Name" class="form-control" type="text">
    </div>
    <div class="mb-3">
        Фамилия: <input class="info-user-input" asp-for="Surname" class="form-control" type="text">
    </div>
    <div class="mb-3">
        <div class="d-flex justify-content-between card-buttons-group">
            <button type="submit" class="btn btn-primary" id='updateInfoId'>Обновить информацию</button>
        </div>
    </div>
    <p>
        Окончательный результат: @if (Model.IsExamCompleted == false)
        {
            <text>не получен</text>
        }
        else
        {
            <text>@Model.FinalGrade баллов из 100</text>
        }
    </p>
    <button type="button" align="center" class="btn btn-gradient text-white"
            onclick="openModal({ url: '/PersonalProfile/GetLessonRecords',  modalId: 'modalWindow'})"
            data-toggle="ajax-modal" data-target="Modal">
        Список оценок
    </button>
    <br />
    <p>Лекций пройдено: <input asp-for="LessonsCompleted" class="personal-profile-input-disabled info-user-input" disabled="disabled" /> </p>
</form>

Here is my fetch code:

document.getElementById("personalProfileFormId").addEventListener("submit", async (event) => {
    event.preventDefault();

    var updateFormInputs = document.getElementsByClassName('info-user-input');
    var updateFormData = {};

    for (let i = 0; i < updateFormInputs.length; i++) {
        let input = updateFormInputs[i];
        updateFormData[input.id] = input.value;
    }
    var jsonData = JSON.stringify(updateFormData);

    const response = await fetch('/PersonalProfile/UpdateInfo', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: jsonData
    });
    if (response.ok) {
        Swal.fire({
            title: 'Информация',
            text: response.description,
            icon: 'success',
            confirmButtonText: 'Окей'
        })
    }
    else {
        Swal.fire({
            title: 'Информация',
            text: response.description,
            icon: 'error',
            confirmButtonText: 'Окей'
        })
    }
});

When i use fetch to send data, in the controller the model gets null, here is the controller code:

[HttpPost]
        public async Task<IActionResult> UpdateInfo([FromBody] ProfileViewModel model)
        {
            //return View();
            if (ModelState.IsValid)
            {
                var response = await _personalProfileService.UpdateInfo(model);
                if (response.StatusCode == Domain.Enum.StatusCode.OK)
                {
                    return Json(new { description = response.Description });
                }
            }
            return StatusCode(StatusCodes.Status500InternalServerError);
        }

Here is the model code:

public class ProfileViewModel
    {
        public int Id { get; set; } 

        public string Login { get; set; }

        [Required(ErrorMessage = "Введите имя")]
        [MinLength(2, ErrorMessage = "Длина имени должна быть больше двух символов")]
        [MaxLength(25, ErrorMessage = "Длина имени должна быть меньше 25 символов")]
        public string Name { get; set; }

        [Required(ErrorMessage = "Введите фамилию")]
        [MinLength(2, ErrorMessage = "Длина фамилии должна быть больше двух символов")]
        [MaxLength(25, ErrorMessage = "Длина фамилии должна быть меньше 25 символов")]
        public string Surname { get; set; }

        public float FinalGrade { get; set; }

        public bool IsExamCompleted { get; set;}

        public int LessonsCompleted { get; set; }
    }

But. When I put the type object in the controller parameter instead of ProfileViewModel, something like this ends up in object:

    ValueKind = Object : "     {"Id":"2","IsExamCompleted":"true","FinalGrade":"26,5","Login":"CommonUser02","Name":"maximka","Surname":"maximoff","LessonsCompleted":"5"}"

My question is how can I convert this to a ProfileViewModel? I have seen that in other examples it is converted. What am I doing wrong, and another question, is it possible to interact with the response from the form without using fetch, but using a standard form in the manner of ASP.NET, with asp-controller and asp-action, and display swal.fire pop-ups like I do this with fetch in the example.


Solution

  • The correct json should be like below:

    { "Id": 1, "IsExamCompleted": true, "FinalGrade": 26.5, "Login": "aa", "Name": "bbb", "Surname": "ccc", "LessonsCompleted": 5 }
    

    Modify your js code to:

    var updateFormData = {
        Id:parseInt($("#Id").val()),
        IsExamCompleted: ($("#IsExamCompleted").val() === 'true'),
    
       //parseFloat("26,5") will return 26 because `,` is not recognized as decimal separator, while `.` is.
        FinalGrade: parseFloat($("#FinalGrade").val().replace(/,/g, '.')),
        Login: $("#Login").val(),
        Name: $("#Name").val(),
        Surname: $("#Surname").val(),
        LessonsCompleted: parseInt($("#LessonsCompleted").val()),
    };