ASP.NET MVC-正确使用视图模型和命令模式 - c#

我已经写了一段时间的ASP.NET MVC应用程序,发现它们是使用命令模式的好地方:我们将每个用户请求都表示为命令-一组输入参数-然后处理此命令(处理包括验证和其他领域逻辑),然后将结果发送回用户。

我在应用程序中使用的另一件事是视图模型。我发现它们是将数据传递到视图的一种比使用域对象作为模型或填充ViewData / ViewBag更为方便的方法。

这两个概念非常适用于将显示给用户的数据与用户输入及其处理分开,但是在ASP.NET MVC中它们并不完全相同。

假设我要在开发一个简单的Web商店时使用命令并查看模型,在该商店中用户可以浏览产品并可以通过提供其名称和电子邮件地址来订购产品:

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. */ }
}

在阅读教程和SO时,我已经看到人们提出了几种实现此目的的方法。

选项1

控制器:

[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);
}

视图:

@using (Html.BeginForm())
{
    @Html.Hidden("ProductId", Model.Id)
    @Html.TextBox("Name")
    @Html.TextBox("Email")
    <input type="submit" value="Place order" />
}

优点:视图模型和命令彼此分开,HttpPost方法看起来很干净

缺点:我不能使用方便的HTML帮助程序,例如@Html.TextBoxFor(model => model.Email),我不能使用客户端验证(请参见my other question)

选项2

我们将IdNameEmail及其验证属性从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)
        // ...
}

视图:

@Html.TextBoxFor(m => m.Email)
...

优点:选项1的所有缺点都消失了

缺点:复制属性似乎很不方便(如果我有50个,该怎么办?),在视图模型中验证NameEmail(应该在其余域逻辑所在的command中完成),模型作为POST参数(请参见下文)

选项3

我们将command设置为viewModel的属性。

控制器:

[HttpPost]
public ActionResult Product(ProductViewModel viewModel)
{
        var command = viewModel.Command;
        if (ModelState.IsValid)
        // ...
}

视图:

@Html.TextBoxFor(m => m.Command.Email)
...

优点:选项1的所有缺点都消失了

缺点:视图模型应该只包含显示给用户的数据(并且不显示command),模型作为POST参数(请参见下文)

-

对于选项2和3,我不满意的是我们将视图模型用作POST方法参数。此方法用于处理用户输入(在这种情况下,仅2个字段+ 1个隐藏字段),并且该模型包含50个我将永远不会在此方法中使用的属性,并且该属性始终为空。更不用说为视图模型创建一个空的构造函数以处理该POST请求的必要性,以及为每个POST请求创建大型视图模型对象时不必要的内存消耗。

我的问题是(这是我所知道的最长的问题):是否存在秘密的选项4,用于正确使用命令并查看具有所有优点而又没有其他缺点的模型?还是我很偏执,这些缺点并不那么重要,可以忽略不计?

参考方案

似乎唯一可行的方法是使用部分视图来呈现表单,并使用OrderProductCommand作为视图模型。

Product.cshtml:

@model ProductViewModel
...
@Html.Partial("Product_OrderForm", new OrderProductCommand { ProductId = Model.Id })
...

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" />
}
...

这样,就无需在视图模型和业务对象之间创建数据映射,并且可以像在选项1中那样将控制器代码保持整洁。

[HttpGet]
public ActionResult Product(int id)
{
    return View(new ProductViewModel(id));
}

[HttpPost]
public ActionResult Product(OrderProductCommand command)
{
    // process command...
}

ASP.NET MVC 5自定义登录,无需脚手架,数据库优先 - c#

我对asp.net和mvc还是很陌生,所以我正在努力学习尽可能多的知识...为此,我从头开始编写博客网站,但是我对身份验证和授权有些困惑。由于我倾向于不真正使用任何脚手架的东西,所以我首先要使用数据库,所以不希望asp.net身份为我创建表。我对散列和加盐密码很酷,并对照数据库检查用户,我遇到的麻烦是将用户设置为登录状态并检查他们应该能够访问什么。我真的很想…

ASP.NET MVC ExecuteResult与ActionResult - c#

我见过它用来向响应添加标头,然后返回文件进行流传输。public override void ExecuteResult(ControllerContext context) { ... response.AddHeader("Accept-Ranges", "bytes"); response.AddHeader(&…

弹出表单中的ajax数据发布未命中控制器方法ASP.NET MVC - c#

我有一个弹出表单AddorEdit.cshtml,从index.cshtml页面调用。表单正在打开,但无法将数据发布到控制器方法。索引页// index.cshtml // <a class="btn btn-success" style="margin-bottom:10px;" onclick="P…

ASP.NET MVC Core 3.0 API将枚举序列化为字符串 - c#

如何在ASP.NET MVC Core 3.0中将Enum字段序列化为String而不是Int?我不能用旧的方式做。services.AddMvc().AddJsonOptions(opts => { opts.JsonSerializerOptions.Converters.Add(new StringEnumConverter()); }) 我收到…

ddl在服务器中未更新-asp.net - javascript

我在ASP.NET c#上工作。我有一个DropDownList。 (runat =“ server”)在$ {document).ready上,我更新了它的值:$(document).ready(function () { document.getElementById("ddl").value = "abc"; ……