ASP.Net Core MVC - 在几个ViewModel属性

时间:2016-10-17 13:40:29

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

问题

我有一个客户端视图模型,其中包含一些必需属性和非必需属性。该视图包含用于更新不同视图模型属性的不同部分。如果我使用所需的客户端详细信息(如FirstName,LastName,DOB等)更新视图的一部分,那么我可以将输入类型包装为ajax形式,控制器将从视图模型中获取这些属性并使用ModelState进行相应的验证.IsValid和验证将成功。但是,如果我在同一个视图上有另一个部分需要更新viewmodel上的非必需属性(即Notes)并从ajax表单发布,那么ModelState验证失败,因为其他必需属性为null,因为它们从未提交过作为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属性,只是提交了一些属性,但却利用了开箱即用的验证。

  4. 提前致谢。

2 个答案:

答案 0 :(得分:1)

虽然我无法按照建议采用推荐的方法,但我确实使用了有关分离视图模型以进行渲染和提交的建议。我认为我被微软ASP.NET核心网站上的电影教程误入歧途,该网站对细节和编辑页面使用相同的电影模型。

为了解决我的问题,我已经为详细信息页面采用了一个大视图模型,我将使用View Components来查看需要更新的页面的各个部分。这允许我为每个View Component提供单独的View Model,我的控制器post action只接受与相应View Component相关的View Model,这样它就只能执行必要的ModelState验证。

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

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

答案 1 :(得分:0)

将所有内容发送回所有电话(通过使用隐藏字段)并不昂贵,但它很骇人听闻,而不是直接解决方案。

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

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

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

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