如何从“编辑”视图中的表单传递DTO到“编辑POST操作”?

时间:2018-08-13 16:35:54

标签: c# asp.net-mvc

我正在将CRUD用户管理添加到我的ASP.NET MVC应用程序中。我正在使用身份。我正在使用DTO来处理ApplicationUser属性,并使用AutoMapper将UserDto映射到ApplicationUser,反之亦然。 当我尝试将DTO传递给Edit / POST操作时,从表单(从View)返回,DTO的所有属性都为null,除了ID

即在发布表单并调试应用程序时,除了ID正确之外,作为参数传递给Edit操作的DTO的所有属性都为空。

我可以将DTO传递回控制器吗?

UserDTO.cs

public class UserDto
{
    [Required]
    public string Id { get; set; }

    [Required]
    [StringLength(255)]
    [Display(Name = "First Name")]
    public string FirstName { get; set; }

    [Required]
    [StringLength(255)]
    public string Surname { get; set; }

    [Required]
    [EmailAddress]
    [Display(Name = "E-mail")]
    public string Email { get; set; }

    [Display(Name = "E-mail Confirmed")]
    public bool EmailConfirmed { get; set; }

    [Display(Name = "Lockout Enddate")]
    public DateTime? LockoutEndDateUtc { get; set; }

    [Display(Name = "Lockout Enabled")]
    public bool LockoutEnabled { get; set; }

    [Display(Name = "Access Failed Count")]
    public int AccessFailedCount { get; set; }

}

DTO包含在传递给View的ViewModel中。我使用相同的ViewModel查看用户的详细信息,并且工作正常:

UserDetailsViewModel.cs

public class UserDetailsViewModel
{        
    public UserDto User { get; set; }
    public UserRolesViewModel UserRolesViewModel { get; set; }

    public UserDetailsViewModel(UserDto user)
    {
        User = user;
        UserRolesViewModel = new UserRolesViewModel(user.Id);
    }
}

Users / Edit.cshtml

@model MyApp.ViewModels.UserDetailsViewModel

@{
    ViewBag.Title = Model.User.FirstName + " " + Model.User.Surname;
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<div class="col-md-6">
    <div class="card">
        <div class="card-block">

            @using (Html.BeginForm())
            {
                @Html.AntiForgeryToken()
                @Html.HiddenFor(model => model.User.Id)

            <div class="form-horizontal">

                @Html.ValidationSummary(true, "", new { @class = "has-error" })

                <div class="form-group">
                    @Html.LabelFor(model => model.User.FirstName, htmlAttributes: new { @class = "control-label" })
                    @Html.EditorFor(model => model.User.FirstName, new { htmlAttributes = new { @class = "form-control boxed" } })
                    @Html.ValidationMessageFor(model => model.User.FirstName, "", new { @class = "has-error" })
                </div>

                <div class="form-group">
                    @Html.LabelFor(model => model.User.Surname, htmlAttributes: new { @class = "control-label" })
                    @Html.EditorFor(model => model.User.Surname, new { htmlAttributes = new { @class = "form-control boxed" } })
                    @Html.ValidationMessageFor(model => model.User.Surname, "", new { @class = "has-error" })
                </div>

                <div class="form-group">
                    @Html.LabelFor(model => model.User.Email, htmlAttributes: new { @class = "control-label" })
                    @Html.EditorFor(model => model.User.Email, new { htmlAttributes = new { @class = "form-control boxed" } })
                    @Html.ValidationMessageFor(model => model.User.Email, "", new { @class = "has-error" })
                </div>

                <div class="form-group">
                    @Html.LabelFor(model => model.User.EmailConfirmed, htmlAttributes: new { @class = "control-label" })
                    @Html.EditorFor(model => model.User.EmailConfirmed, new { htmlAttributes = new { @class = "checkbox" } })
                    @Html.ValidationMessageFor(model => model.User.EmailConfirmed, "", new { @class = "has-error" })
                </div>

                <div class="form-group">
                    @Html.LabelFor(model => model.User.LockoutEndDateUtc, htmlAttributes: new { @class = "control-label" })
                    @Html.EditorFor(model => model.User.LockoutEndDateUtc, new { htmlAttributes = new { @class = "form-control boxed" } })
                    @Html.ValidationMessageFor(model => model.User.LockoutEndDateUtc, "", new { @class = "has-error" })
                </div>

                <div class="form-group">
                    @Html.LabelFor(model => model.User.LockoutEnabled, htmlAttributes: new { @class = "control-label" })
                    <div class="checkbox">
                        @Html.EditorFor(model => model.User.LockoutEnabled)
                        @Html.ValidationMessageFor(model => model.User.LockoutEnabled, "", new { @class = "has-error" })
                    </div>
                </div>

                <div class="form-group">
                    @Html.LabelFor(model => model.User.AccessFailedCount, htmlAttributes: new { @class = "control-label" })
                    @Html.EditorFor(model => model.User.AccessFailedCount, new { htmlAttributes = new { @class = "form-control boxed" } })
                    @Html.ValidationMessageFor(model => model.User.AccessFailedCount, "", new { @class = "has-error" })
                </div>

            </div>

                <div class="form-group">
                    <input type="submit" value="Save" class="btn btn-primary" />
                </div>
            }

            <div>
                @Html.ActionLink("Back to List", "Index")
            </div>
        </div>
    </div>
</div>

<div class="col-md-6">
    <div id="user-roles" class="card" data-user-id="@Model.User.Id">
        @Html.Partial("_UserRoles", Model.UserRolesViewModel)
    </div>
</div>

@section scripts
{
    @Scripts.Render("~/bundles/scripts-user-roles")
}

最后,是Edit操作的当前实现。由于userDto的值为空,因此ModelState始终无效:

UsersController.cs

// POST: Users/Edit/jhdjdkjdh-asuhhahf
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserDto userDto)
{
    if (ModelState.IsValid)
    {
        var applicationUser = _context.Users.Find(userDto.Id);
        applicationUser.FirstName = userDto.FirstName;
        applicationUser.Surname = userDto.Surname;
        applicationUser.Email = userDto.Email;
        applicationUser.EmailConfirmed = userDto.EmailConfirmed;
        applicationUser.LockoutEndDateUtc = userDto.LockoutEndDateUtc;
        applicationUser.LockoutEnabled = userDto.LockoutEnabled;
        applicationUser.AccessFailedCount = userDto.AccessFailedCount;
        _context.SaveChanges();
        return View(new UserDetailsViewModel(userDto));
    }

    return View("Details", new UserDetailsViewModel(userDto));
}

2 个答案:

答案 0 :(得分:1)

根据视图,您的模型为MyApp.ViewModels.UserDetailsViewModel,因此,当您提交表单时,它将发送UserDetailsViewModel模型进行操作。但是“编辑”操作需要UserDTO模型,因此由于模型不匹配,绑定将不起作用。您应该将UserDetailsViewModel作为“编辑”操作的输入参数,并从中读取UserDto,如下所示(更改了两行“编辑”操作)。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserDetailsViewModel model) // Changed the input parmater
{
    if (ModelState.IsValid)
    {
        var userDto = model.User; // read the user from view model
        var applicationUser = _context.Users.Find(userDto.Id);
        applicationUser.FirstName = userDto.FirstName;
        applicationUser.Surname = userDto.Surname;
        applicationUser.Email = userDto.Email;
        applicationUser.EmailConfirmed = userDto.EmailConfirmed;
        applicationUser.LockoutEndDateUtc = userDto.LockoutEndDateUtc;
        applicationUser.LockoutEnabled = userDto.LockoutEnabled;
        applicationUser.AccessFailedCount = userDto.AccessFailedCount;
        _context.SaveChanges();
        return View(new UserDetailsViewModel(userDto));
    }

    return View("Details", new UserDetailsViewModel(userDto));
}

答案 1 :(得分:0)

ASP.NET无法正确地反序列化 UserDto,因为HTTP请求变量与UserDto类中找到的属性不匹配。这很可能发生,因为在将cshtml页面转换为HTML时,请求值被标记为User.FirstName,而不是FirstName。问题的核心在于,您要将UserDetailViewModel与视图的模型相关联,而又想反序列化更平坦的UserDto类的实例。

发射的HTML

让我们仔细看看。首先,这是您表单中的摘录。

<div class="form-group">
  @Html.LabelFor(model => model.User.Email, htmlAttributes: new { @class = "control-label" })
  @Html.EditorFor(model => model.User.Email, new { htmlAttributes = new { @class = "form-control boxed" } })
  @Html.ValidationMessageFor(model => model.User.Email, "", new { @class = "has-error"})
</div>

将被渲染到该标记中

<div class="form-group">
  <label class="control-label" for="User_Email">Email</label>
  <input class="form-control boxed text-box single-line" id="User_Email" name="User.Email" type="text" value="">
  <span class="field-validation-valid has-error" data-valmsg-for="User.Email" data-valmsg-replace="true"></span>
</div>

在输入字段上方的标记中的通知具有name="User.Email"属性,使User.Email成为HTTP POST请求参数的键。我们可以更仔细地查看现代浏览器中的Web开发人员工具。我在这里使用Chrome。

enter image description here

服务器端故障排除

发生这种反序列化问题时,我想做的一件事就是检查服务器端的请求参数。通过在所需的Controller Action中设置一个断点并在Visual Studio中按F5,可以很容易地做到这一点。进入活动的调试会话后,请在监视窗口中检查Request.Params.AllKeys变量。

enter image description here

在您的情况下,这可能类似于User.Email。因此,ASP.NET将无法正确地将您的请求转换为UserDto,因为UserDto包含普通的Email属性而不是User.Email属性。

解决方案

有几种方法可以解决这种特殊的反序列化不匹配问题。可能最简单的方法就是简单地将UserDetailViewModel用作处理您请求的HTTP Post的控制器的参数。

另一种解决方案可能是将平面UserDto与您的Edit.cshtml视图相关联。

另一种解决方案是使所有内容保持与现在相同,但是手动指定@Html.EditorFor()方法的name属性,如下所示。根据您应用定位的ASP.NET版本,这可能对您不起作用。

@Html.EditorFor(model => model.User.Email, new { htmlAttributes = new { Name = "email", @class = "form-control boxed" } })

此外,请注意 上方语法中的Name属性区分大小写。