我跟着达林的帖子 multi-step registration process issues in asp.net mvc (splitted viewmodels, single model)
它是一个非常优雅的解决方案,但是我很难看到如何使用数据填充各个步骤视图模型。我试图模仿亚马逊结账步骤系统,首先选择一个地址,然后选择送货方式,然后是付款信息。
对于我的第一个viewmodel,我需要一个当前登录用户的地址列表,我将轮询数据库以显示在屏幕上
在我看来,这是对我有意义的视图模型。
[Serializable]
public class ShippingAddressViewModel : IStepViewModel
{
public List<AddressViewModel> Addresses { get; set; }
[Required(ErrorMessage="You must select a shipping address")]
public Int32? SelectedAddressId { get; set; }
#region IStepViewModel Members
private const Int32 stepNumber = 1;
public int GetStepNumber()
{
return stepNumber;
}
#endregion
}
然而,似乎没有好的方法来填充控制器中的地址。
public class WizardController : Controller
{
public ActionResult Index()
{
var wizard = new WizardViewModel();
wizard.Initialize();
return View(wizard);
}
[HttpPost]
public ActionResult Index(
[Deserialize] WizardViewModel wizard,
IStepViewModel step)
{
wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
if (!string.IsNullOrEmpty(Request["next"]))
{
wizard.CurrentStepIndex++;
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
else
{
// TODO: we have finished: all the step partial
// view models have passed validation => map them
// back to the domain model and do some processing with
// the results
return Content("thanks for filling this form", "text/plain");
}
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
// Even if validation failed we allow the user to
// navigate to previous steps
wizard.CurrentStepIndex--;
}
return View(wizard);
}
}
所以我删除了地址视图模型列表
[Serializable]
public class ShippingAddressViewModel : IStepViewModel
{
[Required(ErrorMessage="You must select a shipping address")]
public Int32? SelectedAddressId { get; set; }
#region IStepViewModel Members
private const Int32 stepNumber = 1;
public int GetStepNumber()
{
return stepNumber;
}
#endregion
}
这就是我想出的视图模型的自定义编辑器模板。它调用Html.RenderAction,它从我的用户控制器返回所有地址的部分视图,并使用Jquery为视图模型的必需SelectedAddressId属性填充隐藏的输入字段。
@model ViewModels.Checkout.ShippingAddressViewModel
<script src="../../Scripts/jquery-1.7.1.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
//Check to see if the shipping id is already set
var shippingID = $("#SelectedAddressId").val();
if (shippingID != null) {
$("#address-id-" + shippingID.toString()).addClass("selected");
}
$(".address-id-link").click(function () {
var shipAddrId = $(this).attr("data-addressid").valueOf();
$("#SelectedAddressId").val(shipAddrId);
$(this).parent("", $("li")).addClass("selected").siblings().removeClass("selected");
});
});
</script>
<div>
@Html.ValidationMessageFor(m => m.SelectedAddressId)
@Html.HiddenFor(s => s.SelectedAddressId)
<div id="ship-to-container">
@{Html.RenderAction("UserAddresses", "User", null);}
</div>
</div>
用户控制器操作
[ChildActionOnly]
public ActionResult UserAddresses()
{
var user = db.Users.Include("Addresses").FirstOrDefault(
u => u.UserID == WebSecurity.CurrentUserId);
if (user != null)
{
return PartialView("UserAddressesPartial",
Mapper.Map<List<AddressViewModel>>(user.Addresses));
}
return Content("An error occured");
}
部分视图
@model IEnumerable<AddressViewModel>
<ul>
@foreach (var item in Model)
{
<li id="address-id-@item.AddressID">
@Html.DisplayFor(c => item)
<a class="address-id-link" href="#" data-addressid="@item.AddressID">Ship To this Address
</a></li>
}
</ul>
对我来说,我的解决方案似乎超出了方式/草率,是否有更简洁的方式来填充视图模型而不是使用来自不同控制器的部分视图?
答案 0 :(得分:1)
使用像这样的子操作来填充用户的地址没有任何问题。事实上,我认为这实际上是最佳方法。你已经完全分离了关注点和单一责任。仅仅因为某些东西需要更多的“碎片”(额外的动作,观点等)并不会使它变得草率或其他错误。
处理此问题的唯一方法是依赖注入。也就是说,您的ShippingAddressViewModel
需要当前登录用户的依赖关系,以便它可以填充其构造函数中的地址列表。但是,由于ShippingAddressViewModel
未在您的视图中公开,因此您必须通过Wizard
传递依赖关系,这有点代码味道。 Wizard
不依赖于用户,但是由于将视图模型抽象出来,它会依赖于用户。
长而短,虽然有一种方式你可以在没有儿童动作和部分视图的情况下做到这一点,但它实际上会比使用它们更糟糕,更邋。。