Почему не приходят данные на сервер?

Делаю веб-приложение на ASP.NET. В разрабатываемом модуле необходимо сделать возможность добавлять заказ на имя клиента. Скину весь код. Вот код представления с формой

@model OrderFlow.Models.OrdersFlowDB.ViewModels.OrderCreateViewModel

@{
    ViewData["Title"] = "Create";
}

<h1>Создать</h1>

<h4>Заказ</h4>
<hr />

<form class="col-md-4" asp-action="Create" method="post">
    <div class="form-group">
        <label>Клиент</label>
        <input type="text" id="clientSearch" class="form-control" placeholder="Введите имя клиента..." />
        <input type="hidden" id="clientId" name="ClientId" />
    </div>

    <div class="form-group">
        <label>Юридическое лицо</label>
        <select id="legalEntitySelect" class="form-control">
            <option value="">Выберите юрлицо</option>
        </select>
        <input type="hidden" id="legalEntityId" name="LegalEntityId">

    </div>

    <div class="form-group">
        <label>Адрес</label>
        <input type="text" name="Address" class="form-control" required />
    </div>

    <div class="form-group">
        <label>Дата начала</label>
        <input type="date" name="DateOfStart" class="form-control" required />
    </div>

    <div class="form-group">
        <label>Дата окончания</label>
        <input type="date" name="DateOfEnd" class="form-control" required />
    </div>

    <div class="form-group">
        <label for="price">Стоимость</label>
        <input type="text" id="price" class="form-control" readonly>
        <input type="hidden" id="priceHidden" name="Price">
    </div>

    <div class="form-group">
        <label for="priceWithNDS">Стоимость с НДС</label>
        <input type="text" id="priceWithNDS" class="form-control" readonly>
        <input type="hidden" id="priceWithNDSHidden" name="PriceWithNDS">
    </div>

    <div class="form-group">
        <label for="productSelect">Товар:</label>
        <select id="productSelect" class="form-control"></select>

        <label for="productQuantity">Количество:</label>
        <input type="number" id="productQuantity" class="form-control" min="1" value="1">

        <button type="button" id="addProduct" class="btn btn-success">Добавить товар</button>

        <ul id="productList"></ul>

    </div>

    <button type="submit" class="btn btn-primary">Сохранить</button>
</form>


<div>
    <a class="text-white" asp-action="Index">К списку заказов</a>
</div>

Вот модель представления для формы

public class OrderCreateViewModel
{
    public string ClientId { get; set; }
    public int? LegalEntityId { get; set; }
    public string Address { get; set; }
    public string? Comment { get; set; }
    public DateOnly DateOfStart { get; set; }
    public DateOnly DateOfEnd { get; set; }
    public decimal Price { get; set; }
    public decimal PriceWithNDS { get; set; }

    public List<OrderProductViewModel> Products { get; set; } = new List<OrderProductViewModel>();
}

Код контроллера

[HttpGet]
public async Task<IActionResult> Create()
{
    return View();
}

[HttpPost]
public async Task<IActionResult> Create(OrderViewModel model, string products)
{
    Debug.WriteLine($"Received Price: {model.Price}");
    Debug.WriteLine($"Received PriceWithNDS: {model.PriceWithNDS}");
    if (!string.IsNullOrEmpty(products))
    {
        var productList = JsonConvert.DeserializeObject<List<OrderProductViewModel>>(products);

        var order = new Order
        {
            ClientId = model.ClientId,
            LegalEntityId = model.LegalEntityId,
            Address = model.Address,
            Comment = model.Comment,
            DateOfStart = model.DateOfStart,
            DateOfEnd = model.DateOfEnd,
            Price = model.Price,
            PriceWithNDS = model.PriceWithNDS,
            IsComplete = false,
            OrdersProducts = productList.Select(p => new OrdersProduct
            {
                ProductId = p.ProductId,
                Count = p.Quantity
            }).ToList()
        };

        _ordersContext.Orders.Add(order);
        await _ordersContext.SaveChangesAsync();
        return RedirectToAction("List");
    }

    return View(model);
}

[HttpGet]
public async Task<IActionResult> SearchUsers(string term)
{
    if (string.IsNullOrEmpty(term))
    {
        return Json(new { });
    }

    var users = await _userManager.Users
        .Where(u => u.UserName.Contains(term) || u.Email.Contains(term) || u.PhoneNumber.Contains(term))
        .Select(u => new { id = u.Id, userName = u.UserName, email = u.Email, phone = u.PhoneNumber })
        .Take(10) // Ограничиваем кол-во результатов
        .ToListAsync();

    return Json(users);
}


[HttpGet]
public async Task<IActionResult> GetLegalEntities(string userId)
{
    if (string.IsNullOrEmpty(userId))
    {
        return Json(new { });
    }

    var legalEntities = await _identityContext.LegalEntities
        .Where(le => le.UserId == userId)
        .Select(le => new { id = le.Id, companyName = le.CompanyName, inn = le.INN })
        .ToListAsync();

    return Json(legalEntities);
}

[HttpGet]
public async Task<IActionResult> GetProducts()
{
    var products = await _ordersContext.Products
        .Select(p => new { id = p.Id, name = p.Name, price = p.Price, nds = p.NDS })
        .ToListAsync();

    return Json(products);
}

В контроллере есть методы для получения списка пользователей, юридических лиц и товаров, т.к. они получаются через AJAX запросы. Вот, собственно, код js

$(document).ready(function () {
    // Поиск клиентов
    $("#clientSearch").autocomplete({
        source: function (request, response) {
            $.ajax({
                url: "/Orders/SearchUsers",
                type: "GET",
                dataType: "json",
                data: { term: request.term }, // Текст из инпута
                success: function (data) {
                    response($.map(data, function (item) {
                        return {
                            label: item.userName + " (" + item.email + ")" + " (" + item.phone + ")",
                            value: item.userName,
                            id: item.id
                        };
                    }));
                }
            });
        },
        minLength: 2,
        select: function (event, ui) {
            $("#clientSearch").val(ui.item.value); // Имя пользователя в инпуте
            $("#clientId").val(ui.item.id).trigger("change");; // ID пользователя в скрытое поле
            return false;
        }
    });

    // Загрузка юрлиц при выборе клиента
    $("#clientId").change(function () {
        var userId = $("#clientId").val();
        var client = $("#clientSearch").val();
        console.log("Выбран клиент ID:", userId)
        if (userId && client) {
            $.getJSON("/Orders/GetLegalEntities", { userId: userId }, function (data) {
                console.log("Полученные юрлица:", data);
                var options = '<option value="">Выберите юрлицо</option>';
                $.each(data, function (index, item) {
                    options += `<option value="${item.id}">${item.companyName} (ИНН: ${item.inn})</option>`;
                });
                $("#legalEntitySelect").html(options);
            });
        } else {
            $("#legalEntitySelect").html('<option value="">Выберите клиента</option>');
        }
    });

    $("#legalEntitySelect").change(function () {
        let selectedValue = $(this).val();
        $("#legalEntityId").val(selectedValue);
    });


    // Добавление товара
    $.ajax({
        url: "/Orders/GetProducts",
        type: "GET",
        success: function (data) {
            let productSelect = $("#productSelect");
            productSelect.empty();
            productSelect.append('<option value="">Выберите товар</option>');

            data.forEach(function (product) {
                productSelect.append(
                    `<option value="${product.id}" data-price="${product.price}" data-nds="${product.nds}">
                        ${product.name} (Цена: ${product.price}, НДС: ${product.nds}%)
                    </option>`
                );
            });
        },
        error: function () {
            console.error("Ошибка загрузки товаров");
        }
    });

    $("#addProduct").click(function (event) {
        event.preventDefault();

        let selectedProduct = $("#productSelect option:selected");
        let productId = selectedProduct.val();
        let productName = selectedProduct.text();
        let productQuantity = $("#productQuantity").val();
        let productPrice = parseFloat(selectedProduct.attr("data-price")) || 0;
        let productNDS = parseFloat(selectedProduct.attr("data-nds")) || 0;

        if (!productId) {
            alert("Выберите товар!");
            return;
        }

        if (productQuantity <= 0) {
            alert("Введите корректное количество!");
            return;
        }

        let totalPrice = productPrice * productQuantity;
        let totalNDS = (totalPrice * productNDS) / 100;
        let totalPriceWithNDS = totalPrice + totalNDS;

        // Проверяем, есть ли уже этот товар в списке
        let existingItem = $(`#productList li[data-id='${productId}']`);
        if (existingItem.length > 0) {
            let currentQuantity = parseInt(existingItem.attr("data-quantity"));
            let newQuantity = currentQuantity + parseInt(productQuantity);
            existingItem.attr("data-quantity", newQuantity);
            existingItem.attr("data-total-price", totalPrice);
            existingItem.attr("data-total-price-nds", totalPriceWithNDS);
            existingItem.find(".product-info").text(`${productName} - ${newQuantity} шт.`);
        } else {
            $("#productList").append(
                `<li data-id="${productId}" data-quantity="${productQuantity}" data-total-price="${totalPrice}" data-total-price-nds="${totalPriceWithNDS}">
                <span class="product-info">${productName} - ${productQuantity} шт.</span> 
                <button type="button" class="removeProduct btn btn-danger btn-sm">Удалить</button>
            </li>`
            );
        }

        updateTotalPrice();
    });


    // Удаление товара
    $(document).on("click", ".removeProduct", function () {
        $(this).parent().remove();
        updateTotalPrice();
    });

    // Отправка формы
    $("form").submit(function () {
        let products = [];
        let price = $("#priceHidden").val();
        let priceWithNDS = $("#priceWithNDSHidden").val();
        let legalEntity = $("#legalEntityId").val();
        $("#productList li").each(function () {
            let productId = $(this).attr("data-id");
            let quantity = $(this).attr("data-quantity");
            products.push({ productId, quantity });
        });

        // Записываем товары в скрытое поле формы
        $("<input>").attr({
            type: "hidden",
            name: "products",
            value: JSON.stringify(products)
        }).appendTo("form");

        if (!price || price <= 0) {
            alert("Ошибка: Итоговая стоимость равна 0. Добавьте товары!");
            return false;
        }

        console.log("Цена:", price);
        console.log("Цена с НДС:", priceWithNDS);
        console.log("Юрлицо:", legalEntity);
    });
});

function updateTotalPrice() {
    let totalPrice = 0;
    let totalPriceWithNDS = 0;

    $("#productList li").each(function () {
        totalPrice += parseFloat($(this).attr("data-total-price"));
        totalPriceWithNDS += parseFloat($(this).attr("data-total-price-nds"));
    });

    $("#price").val(totalPrice.toFixed(2));
    $("#priceHidden").val(totalPrice.toFixed(2));

    $("#priceWithNDS").val(totalPriceWithNDS.toFixed(2));
    $("#priceWithNDSHidden").val(totalPriceWithNDS.toFixed(2));

    console.log("Обновлено в форме: Цена =", totalPrice, "Цена с НДС =", totalPriceWithNDS);
}

И тут проблема в том, что все данные после отправки записываются в БД, кроме стоимости и стоимости с НДС(Price, PriceWithNDS). Точнее, всегда записываются нули вместо реальных чисел. Как видно, в коде js я создал функцию автозаполнения этих полей. Я также сделал проверку на то, что они обновляются в этой же функции

console.log("Обновлено в форме: Цена =", totalPrice, "Цена с НДС =", totalPriceWithNDS);

В консоль выводятся правильные значения. введите сюда описание изображения Далее, в методе отправки формы создал проверку на то, чтобы стоимость не была равна нулю и вывел в консоль отправляемые значения.

if (!price || price <= 0) {
            alert("Ошибка: Итоговая стоимость равна 0. Добавьте товары!");
            return false;
        }

        console.log("Цена:", price);
        console.log("Цена с НДС:", priceWithNDS);
        console.log("Юрлицо:", legalEntity);

Тут тоже всё выводит правильно. Кроме того я запрещаю отправку формы, если цена меньше или равна нулю. Но форма отправляется. Захожу в СУБД и вижу, что всё записалось правильно, кроме этих двух значений (Price и PriceWithNDS) Тогда подумал, что проблема в серверной части, но не совсем так. В начале метода Create в контроллере проверил какие значения приходят на сервер при отправке формы

[HttpPost]
public async Task<IActionResult> Create(OrderViewModel model, string products)
{
    Debug.WriteLine($"Received Price: {model.Price}");
    Debug.WriteLine($"Received PriceWithNDS: {model.PriceWithNDS}");

И тут выводит нули. введите сюда описание изображения Но почему, ведь при отправке формы выводились правильные числа? Заметил то, что когда поле заполняется автоматически, то на сервер приходит 0. Но в случае, когда в input нет атрибута readonly и пользователь сам вписывает данные, то они отправляются на сервер.


Ответы (1 шт):

Автор решения: Дмитрий Хороший

Насколько я помню, чтобы данные получить в контроллер из формы нужно в контроллере указать атрибут [FromForm], правда на счет string products не уверен, что будет приходить, но надо попробовать

Create([FromForm]OrderViewModel model, string products)

  • лучше в HTML (в input откуда необходимо будет брать данные для БД) использовать не просто атрибут name, а хелпер asp-for. Так и читаемости больше на мой взгляд и ASP.NET Core точно должен правильно разобрать пришедшие ему данные и десериализовать в объект C#

еще в коде отправки формы

// Отправка формы
    $("form").submit(function () {
        let products = [];
        let price = $("#priceHidden").val();
        let priceWithNDS = $("#priceWithNDSHidden").val();
        let legalEntity = $("#legalEntityId").val();
        $("#productList li").each(function () {
            let productId = $(this).attr("data-id");
            let quantity = $(this).attr("data-quantity");
            products.push({ productId, quantity });
        });

        // Записываем товары в скрытое поле формы
        $("<input>").attr({
            type: "hidden",
            name: "products",
            value: JSON.stringify(products)
        }).appendTo("form");

        if (!price || price <= 0) {
            alert("Ошибка: Итоговая стоимость равна 0. Добавьте товары!");
            return false;
        }

        console.log("Цена:", price);
        console.log("Цена с НДС:", priceWithNDS);
        console.log("Юрлицо:", legalEntity);
    });

ты отправляешь форму до своих махинаций. в submit ты передаешь функцию которая выполнится вместе с отправкой, а не до нее. Попробуй сделать так:

// Отправка формы
let products = [];
        let price = $("#priceHidden").val();
        let priceWithNDS = $("#priceWithNDSHidden").val();
        let legalEntity = $("#legalEntityId").val();
        $("#productList li").each(function () {
            let productId = $(this).attr("data-id");
            let quantity = $(this).attr("data-quantity");
            products.push({ productId, quantity });
        });

        // Записываем товары в скрытое поле формы
        $("<input>").attr({
            type: "hidden",
            name: "products",
            value: JSON.stringify(products)
        }).appendTo("form");

        if (!price || price <= 0) {
            alert("Ошибка: Итоговая стоимость равна 0. Добавьте товары!");
            return false;
        }

        console.log("Цена:", price);
        console.log("Цена с НДС:", priceWithNDS);
        console.log("Юрлицо:", legalEntity);

$("form").submit();
→ Ссылка