hol*_*hen 5 asp.net-mvc design-patterns command-pattern
我已经编写ASP.NET MVC应用程序一段时间了,我发现它们是使用命令模式的好地方:我们将每个用户请求表示为命令 - 一组输入参数 - 然后处理此命令(处理包括验证和其他域逻辑),结果将发送回用户.
我在我的应用程序中使用的另一件事是视图模型.我发现它们是将数据传递给视图比使用域对象作为模型或填充ViewData/ViewBag更方便的方式.
这两个概念非常适合将显示给用户的数据与用户输入及其处理分开,但它们在ASP.NET MVC中并不完全一致.
假设我想在开发一个简单的网上商店时使用命令和视图模型,用户可以在其中查看产品,并可以通过提供产品名称和电子邮件地址来订购产品:
class ProductViewModel
{
public ProductViewModel(int id) { /* init */ }
public int Id { get; set; }
public string Name { get; set; }
// a LOT of other properties (let's say 50)
}
class OrderProductCommand
{
public int ProductId { get; set; }
[Required(ErrorMessage = "Name not specified")]
public string Name { get; set; }
[Required(ErrorMessage ="E-Mail not specified")]
public string Email { get; set; }
public CommandResult Process() { /* validate, save to DB, send email, etc. */ }
}
Run Code Online (Sandbox Code Playgroud)
在浏览教程时,我看到人们提出了几种方法.
控制器:
[HttpGet]
public ActionResult Product(int id)
{
return View(new ProductViewModel(id));
}
[HttpPost]
public ActionResult Product(OrderProductCommand command)
{
if (ModelState.IsValid)
{
var result = command.Process();
if(result.Success)
return View("ThankYou");
else
result.CopyErrorsToModelState(ModelState);
}
return Product(command.Id);
}
Run Code Online (Sandbox Code Playgroud)
视图:
@using (Html.BeginForm())
{
@Html.Hidden("ProductId", Model.Id)
@Html.TextBox("Name")
@Html.TextBox("Email")
<input type="submit" value="Place order" />
}
Run Code Online (Sandbox Code Playgroud)
优点:视图模型和命令相互分离,HttpPost方法看起来很干净
缺点:我不能使用方便的HTML帮助器@Html.TextBoxFor(model => model.Email),我不能使用客户端验证(参见我的其他问题)
我们复制Id, Name并Email与他们的验证属性command一起viewModel.
控制器:
[HttpPost]
public ActionResult Product(ProductViewModel viewModel)
{
var command = new OrderProductCommand();
command.Id = viewModel.Id;
command.Name = viewModel.Name;
command.Email = viewModel.Email;
if (ModelState.IsValid)
// ...
}
Run Code Online (Sandbox Code Playgroud)
视图:
@Html.TextBoxFor(m => m.Email)
...
Run Code Online (Sandbox Code Playgroud)
优点:所有选项1缺点都消失了
缺点:复制属性似乎不方便(如果我有50个?),验证Name和Email视图模型(它应该command在域逻辑的其余部分所在的位置完成),模型作为POST参数(见下文)
我们制作command了一个属性viewModel.
控制器:
[HttpPost]
public ActionResult Product(ProductViewModel viewModel)
{
var command = viewModel.Command;
if (ModelState.IsValid)
// ...
}
Run Code Online (Sandbox Code Playgroud)
视图:
@Html.TextBoxFor(m => m.Command.Email)
...
Run Code Online (Sandbox Code Playgroud)
优点:所有选项1缺点都消失了
缺点:视图模型应该只包含显示给用户的数据(并且command不显示),模型作为POST参数(见下文)
-
我不喜欢选项2和3的是我们使用视图模型作为POST方法参数.此方法用于处理用户输入(在这种情况下只有2个字段+ 1隐藏),并且该模型包含50个我将永远不会在此方法中使用的属性,并且始终为空.更不用说为视图模型创建一个空构造函数的必要性,只是为了处理这个POST请求以及为每个POST请求创建大型视图模型对象时不必要的内存消耗.
我的问题是(这就像有史以来最长的问题,我知道):是否存在一个秘密的选项4,用于正确使用命令和查看模型,这些模型具有所有优点,而没有其他优点?或者我是偏执狂,这些缺点并不重要,可以忽略不计?
似乎唯一其他不错的方法是使用部分视图来渲染表单并用作OrderProductCommand视图模型。
产品.cshtml:
@model ProductViewModel
...
@Html.Partial("Product_OrderForm", new OrderProductCommand { ProductId = Model.Id })
...
Run Code Online (Sandbox Code Playgroud)
Product_OrderForm.cshtml:
@model OrderProductCommand
...
@using (Html.BeginForm("Product", "Home"))
{
@Html.HiddenFor(cmd => cmd.ProductId)
@Html.TextBoxFor(cmd => cmd.Name)
@Html.TextBoxFor(cmd => cmd.Email)
<input type="submit" value="Place order" />
}
...
Run Code Online (Sandbox Code Playgroud)
这样就不需要在视图模型和业务对象之间创建数据映射,并且控制器代码可以像选项 1中那样保持干净:
[HttpGet]
public ActionResult Product(int id)
{
return View(new ProductViewModel(id));
}
[HttpPost]
public ActionResult Product(OrderProductCommand command)
{
// process command...
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1431 次 |
| 最近记录: |