我正在将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));
}
答案 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
类的实例。
让我们仔细看看。首先,这是您表单中的摘录。
<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。
发生这种反序列化问题时,我想做的一件事就是检查服务器端的请求参数。通过在所需的Controller Action中设置一个断点并在Visual Studio中按F5,可以很容易地做到这一点。进入活动的调试会话后,请在监视窗口中检查Request.Params.AllKeys
变量。
在您的情况下,这可能类似于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
属性区分大小写。