asp.netasp.net-mvcmediatrcontroller-action

I can't get data to my controller without trigger action twice


Hello I can get every data but I can't get the ProductIds, it gives me null in the Handle functions request parameter. SelectedIds is not null, I can get it without problem in my action. I hope someone can help.

EDIT:

After some debugging I see that my action gets triggered twice because of the cshtml. As you can see in

 <form class="form card-pretify" id="remaxForm" action="@Url.Action("EmployeeSessionPost","Product")" method="post" enctype="multipart/form-data">

I use the action as EmployeeSessionPost. Because of this it makes my action trigger twice, It was normally EmployeeSession before. But I see that I can't get the InstructorId and CsvFile from the controller.

With using EmployeeSessionPost as action parameter in this line, it triggers twice, in first trigger it gives me the selectedIds but not the CsvFile and InstructorId. In second trigger it gives me the CsvFile and InstructorId but not the selectedIds.

If I change the Url.Action parameter from EmployeeSessionPost to EmployeeSession, and it gives me the same result of first trigger.

EDIT2:

I removed the action of the form and changed the button type to 'button' here is the changes that I made from the comments. It prevents the double trigger but I can't get the CsvFile and InstructorId, I just get the selectedIds like this. Like I mentioned before

<!--begin::Container-->
<div class="container-fluid">
    <form class="form card-pretify" id="remaxForm" method="post" enctype="multipart/form-data">


<!--end::Licence-->
                <div class="card-footer">
                    <button type="button" class="btn btn-primary ml-3" id="btnUpdate">Update</button>
                </div>

I still have the same script block

[Authorize]
[HttpPost]
public async Task<IActionResult> EmployeeSessionPost(List<int> selectedIds)
{
    var csvFile = Request.Form.Files["CsvFile"];
    var instructorId = Convert.ToInt32(Request.Form["InstructorId"]);

    if (csvFile != null && csvFile.Length > 0)
    {
        using var stream = new MemoryStream();
        await csvFile.CopyToAsync(stream);
        await MediatrSend(new Services.Product.Queries.SetEmployeeSessionQuery
        {
            ProductIds = selectedIds,
            InstructorId = instructorId,
            CsvFile = csvFile,
        });
    }

    ModelState.AddModelError("CsvFile", "Please select a valid CSV file.");
    //return RedirectToAction("ParticipantList", "Product", new { culture = RouteData.Values["culture"].ToString(), selectedIds = selectedIds });

    return RedirectToAction("EmployeeSession", "Product", new { culture = RouteData.Values["culture"].ToString() });
}

My query file

   public class SetEmployeeSessionQuery : IRequest<int>
    {
        public List<int> ProductIds { get; set; }
        public int ProductId { get; set; }
        public int ProductEmployeeId { get; set; }
        public int InstructorId { get; set; }
        public ClaimModel ClaimModel { get; set; }
        public IFormFile CsvFile { get; set; }

    }

    public class SetEmployeeSessionQueryHandler : IRequestHandler<SetEmployeeSessionQuery, int>
    {
        private readonly IProductEmployeeSessionRepository _productEmployeeSessionRepository;
        private readonly IProductEmployeeRepository _productEmployeeRepository;

        public SetEmployeeSessionQueryHandler(IProductEmployeeSessionRepository productEmployeeSessionRepository, IProductEmployeeRepository productEmployeeRepository)
        {
            _productEmployeeSessionRepository = productEmployeeSessionRepository;
            _productEmployeeRepository = productEmployeeRepository;
        }

        public async Task<int> Handle(SetEmployeeSessionQuery request, CancellationToken cancellationToken)
        {
          .
          ..
          ...
          .... goes on

here is my cshtml

@model Remax.Services.DTOs.EmployeeSessionDTO;
@{
    ViewData["Title"] = "EmployeeSession";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="content d-flex flex-column flex-column-fluid" id="kt_content">
    <!--begin::Entry-->
    <div class="d-flex flex-column-fluid">
        <!--begin::Container-->
        <div class="container-fluid">
            <form class="form card-pretify" id="remaxForm" action="@Url.Action("EmployeeSession","Product")" method="post" enctype="multipart/form-data">
                <!--begin::Licence-->
                <div class="card card-custom" data-card="true" data-card-tooltips="false">
                    <div class="card-header">
                        <div class="card-header-inner" data-card-tool="toggle">
                            <div class="card-title">
                                <h3 class="card-label">@Html.Raw(Localizer["Form"])</h3>
                            </div>
                            <div class="card-toolbar">
                                <a href="#" class="btn btn-icon btn-sm btn-primary mr-1" data-toggle="tooltip" data-placement="top" title="@Localizer["OpenClose"]">
                                    <i class="ki ki-arrow-down icon-nm"></i>
                                </a>
                            </div>
                        </div>
                    </div>
                    <div class="card-body">
                        <div class="form-group row">
                            <div class="col-lg-4">
                                <label>@Html.Raw(Localizer["Instructor"]) *:</label>
                                <select class="form-control kt-selectpicker mr-1" data-actions-box="true" data-live-search="true" id="InstructorId" name="InstructorId">
                                    <option value="">@Html.Raw(Localizer["Select"])</option>
                                    @foreach (var o in Model.Instructors)
                                    {
                                        <option value="@(o.InstructorId)">@Html.Raw(o.NameSurname)</option>
                                    }
                                </select>
                            </div>
                            <div class="col-lg-4">
                                <label>@Html.Raw(Localizer["ZoomFile"]) *:</label>
                                <div class="custom-file">
                                    <input type="file" class="custom-file-input" id="CsvFile" name="CsvFile" accept=".csv">
                                    <label class="custom-file-label">@Html.Raw(Localizer["ZoomFile"])</label>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <!--begin::Card-->
                <div class="card card-custom">
                    <div class="card-header flex-wrap border-0 pt-6 pb-0">
                        <div class="card-title">
                            <h3 class="card-label">
                                @Html.Raw(Localizer["Products"])
                            </h3>
                        </div>
                        <div class="card-toolbar">
                            <!--begin::Button-->
                            <a href="javascript:;" class="btn btn btn-light-primary mr-2 f-filter-btn">
                                <i class="la la-filter"></i>@Html.Raw(Localizer["Filter"])
                            </a>
                            <!--end::Button-->
                        </div>
                    </div>
                    <div class="card-body">
                        <!--begin::Search Form-->
                        <div class="mb-7 f-filter-container" id="searchForm">
                            <div class="row align-items-center">
                                <div class="col-lg-12">
                                    <div class="row align-items-center mt-1">
                                        <div class="col-md-4 form-group">
                                            <label class="mr-3 mb-0 d-none d-md-block">@Html.Raw(Localizer["StartDate"]):</label>
                                            <div class="input-group-prepend">
                                                <select data-filter="1" class="form-control kt-selectpicker" data-actions-box="true" data-live-search="true" data-selected-text-format="count>1" id="Month" name="Month">
                                                    <option value="">@Html.Raw(Localizer["Select"])</option>
                                                    @for (byte i = 1; i < 13; i++)
                                                    {
                                                        <option value="@i">@(i.ToString().PadLeft(2, '0')) - @Html.Raw(Localizer[String.Format("Month{0}", i)])</option>
                                                    }
                                                </select>
                                                <input type="number" id="Year" name="Year" placeholder="@(Html.Raw(Localizer["Year"]))" class="form-control ml-1" data-filter="1" />
                                            </div>
                                        </div>
                                        <div class="col-md-4 form-group">
                                            <label class="mr-3 mb-0 d-none d-md-block">@Html.Raw(Localizer["Category"]):</label>
                                            <select data-filter="1" class="form-control kt-selectpicker" multiple="multiple" data-actions-box="true" data-live-search="true" data-selected-text-format="count>1" id="ProductCategory" name="ProductCategory">
                                                @for (byte i = 1; i <= 3; i++)
                                                {
                                                    <option selected value="@i">@Html.Raw(Localizer[String.Format("ProductType{0}", i)])</option>
                                                }
                                            </select>
                                        </div>
                                        <div class="col-md-4 form-group">
                                            <label class="mr-3 mb-0 d-none d-md-block">@Html.Raw(Localizer["ProductName"]):</label>
                                            <input type="text" class="form-control" id="ProductName" name="ProductName" placeholder="@Html.Raw(Localizer["ProductName"])" data-filter="1" />
                                        </div>
                                    </div>
                                    <div class="row justify-content-end mt-3">
                                        <div class="col-md-4 form-group text-right">
                                            <label class="mr-3 mb-0 d-none d-md-block">&nbsp;</label>
                                            <a href="javascript:;" class="btn btn-danger px-6 font-weight-bold" id="ktSearch">
                                                <i class="fa fa-search icon-nm"></i>
                                                <span>@Html.Raw(Localizer["Search"])</span>
                                            </a>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <!--end::Search Form-->
                        <!--begin: Datatable-->
                        <div class="datatable datatable-bordered datatable-head-custom" id="kt_datatable" data-url="@Url.Action("EmployeeSessionList","Product",new { culture=Model.Language})"></div>
                        <!--end: Datatable-->
                    </div>
                </div>
                <!--end::Card-->
                <!--end::Licence-->
                <div class="card-footer">
                    <button class="btn btn-primary ml-3" id="btnUpdate" name="btnUpdate">@Html.Raw(Localizer["Update"])</button>
                </div>
            </form>
        </div>
        <!--end::Container-->
    </div>
    <!--begin::Entry-->
</div>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

@section scripts{
    <script src="~/assets/js/pages/crud/ktdatatable/base/KtDatatable.js?v=@(AppSettings.Version)"></script>
    <script src="~/assets/js/pages/crud/ktdatatable/base/KtDatatable_tr.js?v=@(AppSettings.Version)"></script>

    <script src="~/assets/js/pages/crud/ktdatatable/base/KtDatatable_@(Model.Language.Substring(0,2)).js?v=@(AppSettings.Version)"></script>
    <script src="~/assets/js/pages/forms/product/productlist.js?v=@(AppSettings.Version)"></script>

    <script src="~/assets/js/pages/crud/ktdatatable/base/KtDatatable.min.js?v=@(AppSettings.Version)"></script>
    <script src="~/assets/js/pages/crud/ktdatatable/base/KtDatatable_@(Model.Language.Substring(0,2)).min.js?v=@(AppSettings.Version)"></script>

    <script>
        var remaxTableScroll = true;
        $.checkboxSingleClick = function () { };
        var selectedIds = [];

        var datatableCols = [
            {
                selector: true,
                field: 'Id',
                title: '@Html.Raw(Localizer["Code"])',
                width: 35,
            },
            {
                field: 'ProductName',
                title: '@Html.Raw(Localizer["Product"])',
                width: 235,
            },
            {
                field: 'CategoryName',
                title: '@Html.Raw(Localizer["Category"])',
                width: 150,
            },
            {
                field: 'StartDateStr',
                title: '@Html.Raw(Localizer["StartDate"])',
                sortable: 'desc',
                width: 90,
            },
        ];
        $(document).ready(function () {
            datatable.on('datatable-on-check',
                function (e, args) {
                    $.each(args, function (i, a) {
                        if (selectedIds.indexOf(a) == -1)
                            selectedIds.push(a);
                    })
                    if (selectedIds.length > 0)
                        $('#setMultiple').show();
                    console.log(selectedIds);
                });
            datatable.on('datatable-on-uncheck',
                function (e, args) {
                    $.each(args, function (i, a) {
                        selectedIds.pop(a);
                    })
                    if (selectedIds.length == 0)
                        $('#setMultiple').hide();
                    console.log(selectedIds);
                });
            $('#btnUpdate').on('click', function () {
                $.ajax({
                    type: "POST",
                    dataType: 'json',
                    cache: false,
                    url: '@Url.Action("EmployeeSessionPost", "Product")',
                    cache: false,
                    data: {
                        selectedIds: selectedIds
                    },
                    success: function (data) {
                    }, async: true,
                });
            });
        });

    </script>
}

Solution

  • Thanks for all the comments, here is my solution. Instead of posting seperate ( both form and ajax ) I wanted to collect all of the data in form and retrieve the data from same FormData via ajax. Here is the modified code

    controller/action

    [Authorize]
            [HttpPost]
            public async Task<IActionResult> EmployeeSessionPost()
            {
    
                var csvFile = Request.Form.Files.GetFile("CsvFile");
                var instructorId = int.Parse(Request.Form["InstructorId"]);
                var selectedIds = Request.Form["SelectedIds"].ToString().Split(',').Select(int.Parse).ToList();
    
                if (csvFile != null && csvFile.Length > 0)
                {
                    using var stream = new MemoryStream();
                    await csvFile.CopyToAsync(stream);
                    await MediatrSend(new Services.Product.Queries.SetEmployeeSessionQuery
                    {
                        ProductIds = selectedIds,
                        InstructorId = instructorId,
                        CsvFile = csvFile,
                    });
                }
    
                ModelState.AddModelError("CsvFile", "Please select a valid CSV file.");
                //return RedirectToAction("ParticipantList", "Product", new { culture = RouteData.Values["culture"].ToString(), selectedIds = selectedIds });
                return RedirectToAction("EmployeeSession", "Product", new { culture = RouteData.Values["culture"].ToString()});
            }
    

    cshtml

    <form class="form card-pretify" id="remaxForm" action="@Url.Action("EmployeeSessionPost","Product")" method="post" enctype="multipart/form-data">
    
    
     <div class="card-footer">
                        <button type="button" class="btn btn-primary ml-3" id="btnUpdate" name="btnUpdate">@Html.Raw(Localizer["Update"])</button>
                    </div>
    
    
    <script>
            var remaxTableScroll = true;
            $.checkboxSingleClick = function () { };
    
            var selectedIds = [];
            var formdata = new FormData();
    
            $(document).ready(function () {
                datatable.on('datatable-on-check',
                    function (e, args) {
                        $.each(args, function (i, a) {
                            if (selectedIds.indexOf(a) == -1)
                                selectedIds.push(a);
                        })
                        if (selectedIds.length > 0)
                            $('#setMultiple').show();
                        console.log(selectedIds);
                    });
                datatable.on('datatable-on-uncheck',
                    function (e, args) {
                        $.each(args, function (i, a) {
                            selectedIds.splice(selectedIds.indexOf(a), 1);
                        })
                        if (selectedIds.length == 0)
                            $('#setMultiple').hide();
                        console.log(selectedIds);
                    });
    
                $('#btnUpdate').on('click', function () {
    
                    var csvFile = $('#CsvFile').prop('files')[0];
                    var instructorId = $('#InstructorId').val();
    
                    formdata.append('CsvFile', csvFile);
                    formdata.append('InstructorId', instructorId);
                    formdata.append('SelectedIds', selectedIds)
    
                    $.ajax({
                        type: "POST",
                        dataType: 'json',
                        cache: false,
                        url: '@Url.Action("EmployeeSessionPost", "Product")',
                        data: formdata,
                        processData: false,
                        contentType: false,
                        success: function (data) {
                            console.log(data);
                        },
                        error: function (xhr, status, error) {
                            console.log(xhr.responseText);
                        }
                    });
                });
            });
        </script>