【问题标题】:ASP.Net Core MVC model property is nullified if model validation fails如果模型验证失败,则 ASP.Net Core MVC 模型属性无效
【发布时间】:2020-06-23 16:26:28
【问题描述】:

我正在开发一个 ASP.Net Core MVC 应用程序。我有一个模型配方,它有两个属性是其他对象的列表:

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

        [Required(ErrorMessage = "Title is required")]
        [StringLength(45, MinimumLength = 2)]
        [Display(Name = "Title", Prompt = "Title")]
        public string Title { get; set; }

        [Required(ErrorMessage = "Description is required")]
        [StringLength(500)]
        [Display(Name = "Description", Prompt = "Description")]
        public string Description { get; set; }

        [Required(ErrorMessage = "You must include at least one ingredient.")]
        public List<RecipeIngredient> Ingredients {get; set;}

        [Required(ErrorMessage = "You must include at least one instruction step.")]
        public List<Instruction> Directions { get; set; }

        public byte[] Picture { get; set; }
    }

RecipeIngredient.cs:

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

        public int IngredientID { get; set; }

        [Required]
        public string Measurement { get; set; }

        [Required]
        public string Ingredient { get; set; }
    }

指令.CS:

public class Instruction
    {
        [Required]
        public int Step { get; set; }

        [Required]
        [StringLength(200, MinimumLength = 2)]
        public string InstructionText { get; set; }
    }

这是我对创建新配方的看法:

@model TheKitchen.Models.Recipe


@section Scripts {
    <script src="@Url.Content("~/js/CreateRecipe.js")" type="text/javascript"></script>

    <script>
        $(document).ready(function () {
            autocomplete();
        });
    </script>
        <script>
            $('#addNewIngredient').click(function () {
                addIngredient();
            });
        </script>
    <script>
        $('#addNewInstruction').click(function () {
            addInstruction();
        });
    </script>

}

@section Styles{
    <link rel="stylesheet" href="~/css/CreateRecipe.css" />
}

@{
    ViewData["Title"] = "New Recipe";
}

<h3>New Recipe</h3>

<!-- New recipe form -->


@using (Html.BeginForm("Create", "Recipes", FormMethod.Post))
{
    <div class="form-group">
        <input type="text" class="form-control" id="recipeName" asp-for="Title" placeholder="Recipe Name">
        <span asp-validation-for="Title" class="text-danger"></span>
    </div>
    <div class="form-group">
        <textarea class="form-control" id="recipeDescription" asp-for="Description" rows="3" placeholder="Description"></textarea>
        <span asp-validation-for="Description" class="text-danger"></span>
    </div>
    <div class="form-group addIngredients">
        <h4>Ingredients</h4>
        <div id="ingredientSearchbar">
            <input type="search" class="form-control" id="ingredientSearch" placeholder="Enter ingredient name here.">
            <input type="button" class="btn btn-outline-secondary" id="addNewIngredient" value="Add" />
        </div>
        <br />
        <table class="table" id="ingredientTable" name="ingredientTable">
            <tbody>
            </tbody>
        </table>
        <span asp-validation-for="Ingredients" class="text-danger"></span>
    </div>

    <div class="form-group addInstructions">
        <h4>Instructions</h4>
        <div id="instructionTextBar">
            <input type="text" class="form-control" id="instructionText" placeholder="Enter instruction step here, then click Add Step.">
            <input type="button" class="btn btn-outline-secondary" id="addNewInstruction" value="Add" />
        </div>
        <br />

        <!-- model.Instructions is getting returned null if form validation fails -->

        <table class="table" id="instructionTable" name="instructionTable">
            <tbody>
            </tbody>
        </table>
        <span asp-validation-for="Directions" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary" id="newRecipeButton">Create Recipe</button>
}

而 jQuery 驱动将 RecipeIngredients 和 Instructions 添加到它们各自的模型列表和 html 表中以供显示:

/* Script for autocomplete in ingredient search box. */
function autocomplete() {
    $("#ingredientSearch").autocomplete({
        appendTo: ".addIngredients",
        source: function (request, response) {
            $.ajax({
                url: '/Recipe/IngredientSearch',
                type: 'GET',
                cache: false,
                data: request,
                dataType: 'json',
                success: function (data) {
                    response($.map(data, function (item) {
                        return {
                            label: item.label,
                            value: item.Value
                        }
                    }))
                }
            });
        }
    });
};

/* Script to add ingredient search bar text to ingredient list if not already present. */
function addIngredient() {
    var listIndex = $('.recipeIngredientRow').length;
    var measurement = '<input type="text" asp-for="Ingredients[' + listIndex + '].Measurement" name="Ingredients[' + listIndex +
        '].Measurement" class="measurementTextBox" id="measurementTextBox' + listIndex + '" placeholder="Enter measurement">';
    var ingredient = '<input type="text" class="noTextborder" asp-for="Ingredients[' + listIndex + '].Ingredient" name="Ingredients[' + listIndex +
        '].Ingredient" value="' + $('#ingredientSearch').val() + '" readonly>';
    var deleteButton = '<input type="button" class="cancel" value="X">';
    var newRow = '<tr class="recipeIngredientRow"><td>' + measurement + '</td><td>' + ingredient +
        '</td><td>' + deleteButton + '</td></tr>';

    var hasDuplicates = false;
    var hasEmptyMeasurements = false;
    $("#ingredientTable td").each(function () {
        var measurementContent = $(this).find('input').val().length;

        var ingredientContent = $(this).find('input').val() == $('#ingredientSearch').val();

        if (ingredientContent) {
            hasDuplicates = true;
            return;
        }
        if (measurementContent == 0) {
            hasEmptyMeasurements = true;
            return;
        }
    });

    if (hasDuplicates)
        alert('This ingredient is already on the ingredient list.');
    else if ($('#ingredientSearch').val().length == 0)
        alert("You can't add a blank ingredient.");
    else if (hasEmptyMeasurements)
        alert("You must provide a measurement before adding a new ingredient.");
    else
        $('#ingredientTable').append(newRow);
    $('#ingredientSearch').val("");
};

/* Script for removing ingredient table row when 'X' button is clicked. */
$('#ingredientTable').on('click', 'tr input.cancel', function () {
    $(this).closest('tr').remove();
});

/* Script for adding instruction step to instruction list */
function addInstruction() {
    var listIndex = $('.recipeInstructionRow').length;
    var stepNumber = '<input type="number" readonly="true" asp-for="Directions" name="Directions[' + listIndex +
        '].Step" class="noTextborder stepNumber" value="' + (listIndex + 1) + '">';
    var instruction = '<textarea asp-for="Directions" name="Directions[' + listIndex +
        '].InstructionText" id="Directions[' + listIndex + ']" class="noTextborder">' + $('#instructionText').val() + '</textarea>';
    var deleteButton = '<input type="button" class="cancel" value="X"';
    var newRow = '<tr class="recipeInstructionRow"><td class="ten">' + stepNumber + ' </td><td class="eighty">' + instruction + '</td><td class="ten">' + deleteButton + '</td></tr>';

    if ($('#instructionText').val().length == 0)
        alert("You can't add a blank instruction.");
    else
        $('#instructionTable').append(newRow);
    $('#instructionText').val("");
}

/* Script for removing instruction table row when 'X' button is clicked */
$('#instructionTable').on('click', 'tr input.cancel', function () {
    $(this).closest('tr').remove();

    $('#instructionTable tr').each(function () {
        var rowIndex = $(this).index() + 1;
        $('td:first-child', this).find('input').val(rowIndex);
    });
});

最后,控制器方法:

[Authorize]
        [HttpPost("Recipe/Create")]
        public IActionResult Create(Recipe recipe)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    var userEmail = User.FindFirst(ClaimTypes.Email).Value;
                    var user = userData.GetUser(userEmail);

                    this.recipeData.CreateRecipe(recipe, user.UserId, DateTime.Now);
                    return RedirectToAction("Index");
                }
                else
                    return View("Create", recipe);
            }
            catch (MySqlException mySqlEx)
            {
                throw new Exception(mySqlEx.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return StatusCode(500, "Something went wrong. Please try again");
            }
        }

如果您提交包含所有必填字段的有效表单,则模型将传递给控制器​​并且一切正常。

但是,如果您提交了无效的表单并返回到视图,则食谱标题和描述将传递回表单,但两个列表都为空(例如,此列表属性本身为空)。在我的一生中,我无法弄清楚在从 View 到 Controller 再回到 View 的过程中,列表属性在哪里或为什么会变为 null。

有什么想法吗?

【问题讨论】:

    标签: c# jquery asp.net-mvc asp.net-core


    【解决方案1】:

    将以下内容添加到视图中:

    @if (Model != null && Model.Ingredients != null)
      {
        @for (int i = 0; i < Model.Ingredients.Count; i++)
        {
          <tr>
            <td>@Html.EditorFor(model => model.Ingredients[i].Measurement)</td>
            <td>@Html.EditorFor(model => model.Ingredients[i].Ingredient, new { htmlAttributes = new { @class = "noTextborder", @readonly = "true" } })</td>
            <td><input type="button" class="cancel" value="X"></td>
          </tr>
        }
      }
    

    会成功的。

    必须使用 EditorFor,因为 DisplayFor 仅简单地显示内容,因此它在您的屏幕上,但未绑定到模型。如果需要,html 属性会将内容保持为只读。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-28
      • 2015-02-26
      • 1970-01-01
      相关资源
      最近更新 更多