Failed to load resource: the server responded with a status of 400 - using @Html.DropDownListFor

Viewed 244

I am trying to replicate the following Cascading DropDowns tutorial in my .net core application. I am using a WebAPI for my controllers and services... however to simplify and more closely match the tutorial, I have moved the controller to the client. The logic in the Controller is very similar to that of the tutorial, and clearly it is working well enough to at least populate the dropdowns initially on page load.

I am struggling a bit with the Javascript (which I believe is the cause of the 400 error), and I using a semi-custom model framework that is about 90% based on the tutorial model. The crazy thing is that the Javascript is running (I believe) since it stops at the breakpoints in the JS code as it initially populates the 3 drop downs on page load correctly. However, once I select any item in any of the 3 dropdown controls... instead of calling the 'on change' event, it triggers the following error:

Failed to load resource: the server responded with a status of 400 () [https://localhost:44317/]

I can provide more code if needed, but I have pasted below the crux of the logic that I believe is where the problem exists:

==== Index.cshtml code below ====

@page
@using Microsoft.Extensions.Configuration
@model MTGCardTracker.Client.Pages.IndexModel
<div id="dropDownListdiv">
    @using (Html.BeginForm("Index", "CascadingDropDownList", FormMethod.Post))
    {
        @Html.DropDownListFor(m => m.CascadingDropDown.FormatId, Model.CascadingDropDown.Formats, "Please select", new { @class = "form-control" })
        @Html.DropDownListFor(m => m.CascadingDropDown.EditionId, Model.CascadingDropDown.Editions, "Please select", new { @class = "form-control" })
        @Html.DropDownListFor(m => m.CascadingDropDown.CardListId, Model.CascadingDropDown.Cards, "Please select", new { @class = "form-control" })
        <input type="submit" value="Submit" />
    }
</div>
@section scripts {
    <script type="text/javascript">
         $(function () {
                         $("#dropDownListdiv").on("change", "select", function () {
                               var value = $(this).val();
                               var id = $(this).attr("id");
                               $.post("@Url.Action("SetDropDrownList")", { type: id, value: value }, function (data) {
                                     switch (id) {
                                         case "FormatId":
                                             PopulateDropDown("#CascadingDropDown_EditionId", data.CascadingDropDown.Editions);
                                             PopulateDropDown("#CascadingDropDown_CardListId", data.CascadingDropDown.Cards);
                                                 break;
                                         case "EditionId":
                                             PopulateDropDown("#CascadingDropDown_CardListId", data.CascadingDropDown.Cards);
                                                 break;
                                      }
                             });
                         });
                     });
        function PopulateDropDown(dropDownId, list) {
            $(dropDownId).empty();
            $(dropDownId).append("<option>Please select</option>")
            $.each(list, function (index, row) {
                if (index == 0) {
                    $(dropDownId).append("<option value='" + row.Value + "' selected='selected'>" + row.Text + "</option>");
                } else {
                    $(dropDownId).append("<option value='" + row.Value + "'>" + row.Text + "</option>")
                }
            });
        }
    </script>
}

Below is my model:

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace MTGTracker.DTO
{
    public class CascadingDropDownDTO
    {
        public string FormatId { get; set; }
        public string EditionId { get; set; }
        public string CardListId { get; set; }
        public SelectList Formats { get; set; }
        public SelectList Editions { get; set; }
        public SelectList Cards { get; set; }

        public IEnumerable<FormatDTO> FormatObject { get; set; }
        public IEnumerable<EditionDTO> EditionObject { get; set; }
        public IEnumerable<CardDetailDTO> CardObject { get; set; }

    }
}

Below (in _Layout.cshtm) is I beleive the only place I am referencing the JS and Boostrap files in my code now:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - MTGCardTracker</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />

    <script type="text/javascript" src="~/lib/jquery/dist/jquery.js"></script>
    @*<script src="~/lib/jquery/dist/jquery.min.js"></script>*@

    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">MTGCardTracker</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - MTGCardTracker - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>



    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

========== SetDropDownList ==========

[HttpPost]
public JsonResult SetDropDrownList(string type, int value)
{
    CascadingDropDownDTO model = GetCascadingDropDownListObject(CascadingDropDown);

    switch (type)
    {
        case "FormatId":
        var EditionsList = CascadingDropDown.EditionObject.Where(m => m.FormatId == value).ToList();
        model.Editions = new SelectList(EditionsList, "EditionId", "EditionName");
        var defaultEditionId = EditionsList.Select(m => m.EditionId).FirstOrDefault();
        model.Cards = new SelectList(CascadingDropDown.CardObject.Where(m => m.EditionId == defaultEditionId).ToList(), "CardListId", "Name");
        break;
        case "EditionId":
        model.Cards = new SelectList(CascadingDropDown.CardObject.Where(m => m.EditionId == value).ToList(), "CardListId", "Name");
        break;
    }
    return Json(model);
}

Token shown in code below even after I removed the submit button. So it seems it is simply tied to the page and/or the dropdowns.

Token is still there even with submit button removed.

Below is the Post. Not sure if I can drill down further for more details as I am still fairly new to trying to debug JS in the browser:

enter image description here

1 Answers

OK, so the answer for me was a couple things, but the below piece was possibly the most crucial. I needed to add the line "endpoints.MapControllerRoute ..." to my Startup.cs configure() method:

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapControllers();

        endpoints.MapControllerRoute(name: "CascadingDropDownList", pattern: "{controller=CascadingDropDownList}/{action=Index}/{id?}");
    });

This allowed my Controller to be seen.

I also needed to modify my JS to account for the control id name changes since I was using a semi-custom nested model class that differed from the tutorial, and changed the $.post call from an action to a URL with the controller name and method call:

<script>
    src = "https://code.jquery.com/jquery-3.5.1.min.js" >
        /*CODE FOR THE CASCADING DROP DOWNS*/
        $(function () {
            $("#dropDownListdiv").on("change", "select", function () {

                var id = $(this).attr("id");
                var value = $(this).val();
                alert($(this).attr("id"));
                alert($(this).val());
                alert("Executing POST...");
                $.post('/CascadingDropDownList/SetDropDrownList', { type: id, value: value }, function (data) {
                    switch (id) {
                        case "cascadingDropDown_FormatId":
                            PopulateDropDown("#cascadingDropDown_EditionId", data.Editions);
                            PopulateDropDown("#cascadingDropDown_CardListId", data.Cards
                            break;
                        case "cascadingDropDown_EditionId":
                            PopulateDropDown("#cascadingDropDown_CardListId", data.Cards);
                            break;
                    }
                });
            });
            alert("Exited Main Function");
        });

    function PopulateDropDown(dropDownId, list) {
        alert("Entered PopulateDropDown...");
        alert("dropDownId: " + dropDownId);
        $(dropDownId).empty();
        $(dropDownId).append("<option>Please select</option>")
        alert("Running ForEach...");
        $.each(list, function (index, row) {
            if (index == 0) {
                $(dropDownId).append("<option value='" + row.Value + "' selected='selected'>" + row.Text + "</option>");
            } else {
                $(dropDownId).append("<option value='" + row.Value + "'>" + row.Text + "</option>")
            }
        });
        alert("Exited PopulateDropDown...");
    }
</script>

Now my SetDropDownList method is being called correctly. And while my dropdowns are not being popoulated correctly, they are being reset to empty by the JS calls, so I know I am at least targeting them correctly. Figuring out why they aren't getting the correct new filtered subset list is next.

Hopefull this helps someone else. I think the main issue is my lack of familiarity with MVC vs Razor MVC and all the basic fundumentals and nuances that come with them.

Related