【问题标题】:ASP.Net Core MVC - Ajax form submission with ModelState validation on few ViewModel propertiesASP.Net Core MVC - Ajax 表单提交与几个 ViewModel 属性的 ModelState 验证
【发布时间】:2017-02-26 12:46:38
【问题描述】:

问题

我有一个带有一些必需属性和非必需属性的客户端视图模型。视图包含用于更新不同视图模型属性的不同部分。如果我使用所需的客户端详细信息(例如 FirstName、LastName、DOB 等)更新视图的一部分,那么我可以将输入类型包装到 ajax 表单中,控制器将从视图模型中获取这些属性并使用 ModelState 进行相应的验证.IsValid 和验证将成功。但是,如果我在同一个视图上有另一个部分需要更新视图模型(即注释)上的非必需属性并从 ajax 表单发布传递,那么 ModelState 验证失败,因为其他必需属性为空,因为它们从未提交过作为 ajax 表单的一部分。请注意,必填字段应始终在加载客户端详细信息页面之前填充数据,因此不应为空。

代码

视图模型

public class ClientDetailViewModel
{

    public int ID { get; set; }

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

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

    [Required]
    [Display(Name = "Date of Birth")]
    [DataType(DataType.Date)]
    public DateTime DOB { get; set; }

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

    public string Notes { get; set; }
}

查看

@model MSIC.Models.ClientViewModels.ClientDetailViewModel
@inject MSIC.Services.Custom.IGenderService GenderService;

<!-- tab-pane for updating core client details -->
<div class="tab-pane active" id="tab_1">
    <form asp-controller="Client" asp-action="Edit" class="form-horizontal">
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <div class="form-group">
            <label asp-for="FirstName" class="col-sm-2 control-label"></label>
            <div class="col-sm-10">
                <input asp-for="FirstName" class="form-control" />
                <span asp-validation-for="FirstName" class="text-danger" />
            </div>
        </div>
        <div class="form-group">
            <label asp-for="LastName" class="col-sm-2 control-label"></label>
            <div class="col-sm-10">
                <input asp-for="LastName" class="form-control" />
                <span asp-validation-for="LastName" class="text-danger" />
            </div>
        </div>
        <div class="form-group">
            <label asp-for="DOB" class="col-sm-2 control-label"></label>
            <div class="col-sm-10">
                <input asp-for="DOB" class="form-control" />
                <span asp-validation-for="DOB" class="text-danger" />
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Gender" class="col-sm-2 control-label"></label>
            <div class="col-sm-10">
                <select asp-for="Gender" asp-items="@(new SelectList(GenderService.GetAll(),"Code","Name"))" class="form-control">
                </select>
                <span asp-validation-for="Gender" class="text-danger" />
            </div>
        </div>
    </form>
</div>

<!-- different section for updating client notes -->
<div id="divNotes" class="center-block">@Model.Notes</div>
<a href="#" class="btn btn-danger btn-block" data-toggle="modal" data-target="#notesModal" role="button"><b>Edit Notes</b></a>
<form asp-controller="Client" asp-action="EditNotes" class="form-horizontal" data-ajax="true" data-ajax-method="POST" data-ajax-update="#divNotes" data-ajax-mode="replace" data-ajax-success="CloseModal('#notesModal')" data-ajax-failure="AjaxOnFailure(xhr, status, error)">
    <div class="modal fade" id="notesModal" tabindex="-1" role="dialog" aria-labelledby="notesModalLabel" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
                    <h4 class="modal-title" id="notesModalLabel">edit Reason</h4>
                </div>
                <div class="modal-body">
                    <input type="hidden" asp-for="ID" />
                    <div class="form-group">
                        <div class="col-sm-10">
                            <textarea asp-for="Notes" class="form-control" autofocus></textarea>
                            <span asp-validation-for="Notes" class="text-danger" />
                        </div>
                    </div>
                </div>
                <div class="modal-footer">
                    <div class="text-danger pull-left">
                        <i id="modalErrorIcon" class=""></i>
                        <span id="modalErrorText"></span>
                    </div>
                    <button type="submit" class="btn btn-primary">Save</button>
                    <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
                </div>
            </div>
        </div>
    </div>
</form>

控制器

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult EditNotes(ClientDetailViewModel model)
{
    //validation fails because required fields in model are null since they were not submitted with this ajax form
    if (ModelState.IsValid)
    {
        //update database with client notes and return notes to screen
        return Content(model.Notes);
    }

    return Content("i haven't coded this yet");
}

问题

  1. 如何在不重复代码的情况下有效地在控制器内仅对某些字段执行 ModelState 验证?我搜索了这个问题并知道诸如 ModelState[].Errors.Clear(); 之类的选项。但由于这将是一个大视图,有许多不同的部分可供更新,我想避免在我需要执行的所有小型 ajax 帖子的不同 Action 方法中重复这些语句。基本上我想使用 ModelState 验证,所以我可以将我的验证逻辑存储在 ViewModel 中,而不是分离和/或复制控制器内部的任何验证逻辑。

  2. 我可以看到的另一个选项是将所有必需的属性作为隐藏输入类型包含在我拥有的每个 ajax 表单中,但这似乎非常不必要,并且肯定会成为维护的噩梦。有没有更好的方法来传递 ajax 帖子的所有 ViewModel 属性,如果是这样,发送除了利用 ModelState.IsValid 检查之外未使用的 ViewModel 属性是否会很昂贵?

  3. 我是 asp.net 的新手,从 asp.net core mvc 开始(我非常喜欢),并且一直在通过所有教程、SO 问题等进行学习。但是有可能我正在以错误的方式处理这个问题,如果是这样,使用 asp.net 核心和 Microsoft.jQuery.Unobtrusive.Ajax 或其他一些 ajax 工具解决这个问题的正确方法是什么?请注意,我只发布了这个问题,因为发布的其他类似问题似乎都没有涉及在 View 上共享 ViewModel 属性,但只提交了一些属性,但利用了开箱即用的验证。

提前致谢。

【问题讨论】:

    标签: c# ajax validation asp.net-core-mvc


    【解决方案1】:

    虽然我无法获得建议的行为方式,但我确实使用了关于分离视图模型以进行渲染和提交的建议。我想我被 Microsoft ASP.NET Core 网站上的电影教程误导了,该教程对详细信息和编辑页面使用相同的电影模型。

    为了解决我的问题,我为详细信息页面采用了一个大视图模型,并且我将对页面中需要更新的各个部分使用视图组件。这允许我为每个视图组件拥有一个单独的视图模型,并且我的控制器发布操作可以只接受与相应视图组件相关的视图模型,因此它只能执行必要的 ModelState 验证。

    这个来自 jQuery ajax 帖子的服务器端验证让我遇到了下一个问题,即将 ModelState 错误返回给客户端进行显示,但我已经为此提交了一个新问题...

    Returning BadRequest in ASP.Net Core MVC to Microsoft jQuery Unobtrusive Ajax post has ModelState undefined

    【讨论】:

      【解决方案2】:

      为所有调用(通过使用隐藏字段)发回所有内容并不昂贵,但它很老套,不是直接的解决方案。

      MVC 中的视图模型不必是用于呈现页面和提交表单的同一类。在您的情况下,您可以定义一个用于渲染的视图模型,如下所示:

      public class ClientDetailViewModel {
          public ClientBasicViewModel Basics {get;set;}
          public ClientNotesViewModel Notes {get;set;}
      }
      

      然后将字段(姓名、性别等)分别放入两个新类中。使用两个单独的控制器方法来处理表单提交。跟踪 FirstName 的发送和接收方式:

      • 渲染时,您将使用 Model.Basics.FirstName 来显示现有值。
      • 表单中的字段名,设为input name="FirstName"...
      • 返回表单时,控制器方法接受 ClientBasicViewModel 类型的参数。

      【讨论】:

      • 这是一个有趣的概念。我尝试按照您的建议将视图模型分解为单独的子模型,然后在我的控制器上我传入其中一个子模型而不是父视图模型,但属性值为 null
      • 调试可能很痛苦。检查浏览器中的页面源以确保表单字段名称(name 属性,而不是 id 属性)是处理表单提交的控制器方法所期望的。
      猜你喜欢
      • 1970-01-01
      • 2011-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多