MVC 3 - 这是如何运作的?

时间:2011-04-27 16:51:01

标签: c# asp.net-mvc asp.net-mvc-3 poco

我在一年多前发过这篇文章,我觉得更新它是有意义的,因为它得到了不少观点。

我要么缺少一些东西,要么微软已经搞砸了MVC。我从事过Java MVC项目,而且很简单。然而,这是一个完全混乱的IMO。在线示例如NerdDinner和ASP.Net上讨论的项目太基础,因此他们“简单”工作的原因。如果这听起来很消极,那么这是我迄今为止的经历。

我有一个存储库和一个与存储库对话的服务。控制器呼叫服务。

我的数据层不是持久性独立的,因为这些类是由SQL metal生成的。因此,我有很多不必要的功能。理想情况下我想要POCO,但我还没有找到实现这个目标的好方法。

* 更新:当然微软没有搞砸任何东西 - 我做到了。我没有完全理解我所掌握的工具。我所做的主要缺陷是,我选择了一种错误的技术来坚持我的实体。 LINQ to SQL在有状态应用程序中运行良好,因为可以轻松跟踪数据上下文。但是,这不是无状态环境中的情况。什么是正确的选择?实体框架代码首先或代码只能很好地工作,但更重要的是,它应该无关紧要。 MVC或前端应用程序必须不应该知道数据是如何持久化的。 *

创建entites时我可以使用对象绑定:

[HttpPost]
public ActionResult Create(Customer c)
{
    // Persistance logic and return view
}    

这很有效,MVC在幕后做了一些约束,一切都“快乐好”。

这不是“快乐的好”。客户是一个领域模型,更糟糕的是,它依赖于持久性介质,因为它是由SQL金属生成的。我现在要做的是设计我的域模型,它将独立于数据存储或表示层。然后我会从我的域模型创建视图模型并使用它。

我想做一些更复杂的事情,例如 - 保存与客户相关的订单似乎一切都在破坏:

    [HttpPost]
    public ActionResult Create(Order o)
    {
        // Persistance logic and return view
    }

要保留订单,我需要客户或至少是CustomerId。 CustomerId出现在视图中,但是当它到达Create方法时,它已经丢失了CustomerId。我不喜欢坐在调试MVC代码,因为我无法以任何方式在托管环境中更改它。

好的,有点呻吟,抱歉。我现在要做的是创建一个名为NewOrder,SaveOrder或EditOrder的视图模型,具体取决于我想要实现的目标。这个视图模型将包含我感兴趣的所有属性。顾名思义,开箱即用的自动绑定将绑定提交的值,不会丢失任何内容。如果我想要自定义行为,那么我可以实现我自己的“绑定”,它将完成这项工作。

替代方法是使用FormCollection:

[HttpPost]
public ActionResult Create(FormCollection collection)
{
   // Here I use the "magic" UpdateModel method which sometimes works and sometimes doesn't, at least for LINQ Entities.               
}

这在书籍和教程中使用,但我没有看到方法中有一个替代方法:TryUpdateModel - 如果崩溃或模型无效,它会尝试以任何方式更新它。你怎么能确定这会起作用?

使用视图模型进行自动绑定大部分时间都可以使用。如果没有,那么你可以覆盖它。你怎么知道它会一直有效?你对它进行单元测试并且你睡得很好。

我尝试过的另一种方法是使用ViewModel - 带有验证规则的包装器对象。这听起来是个好主意,除了我不想为Entity类添加注释。这种方法非常适合显示数据,但在编写数据时你会怎么做?

[HttpPost]
public ActionResult Create(CustomViewWrapper submittedObject)
{
    // Here I'd have to manually iterate through fields in submittedObject, map it to my Entities, and then, eventually, submit it to the service/repository.
}    

**查看模型是一个很好的前进方式。必须有一些从视图模型到域模型的映射代码,然后可以将其传递给相关服务。这不是一种正确的方法,但它是一种方法。自动映射工具是你最好的朋友,你应该找到一个适合你的要求,否则你将编写大量的样板代码。**

我是否遗漏了某些内容,或者这是微软MVC3的工作方式?我不知道这是如何简化事情的,尤其是与Java MVC的比较。

如果这听起来很消极,我很抱歉,但这是我迄今为止的经历。我感谢框架不断改进,UpdateModel等方法被引入,但文档在哪里?也许是时候停下来思考一下了?我希望我的代码始终保持一致,但是到目前为止我所看到的,我对这是正确的前进方式毫无信心。

我喜欢这个框架。有很多东西需要学习,它并没有比以往任何时候都更令人兴奋。应该发布关于网络表单的另一篇文章。我希望这会有所帮助。

7 个答案:

答案 0 :(得分:17)

1)对于保存订单而不存在CustomerId的情况。如果Order上有一个CustomerId属性,并且您有一个强大的类型视图,那么您可以通过添加

将其保留回控制器操作
@Html.HiddenFor(model => model.CustomerId)

执行此操作将使默认模型绑定器为您填充内容。

2)关于使用视图模型,我建议采用这种方法。如果您使用AutoMapper之类的东西,您可以从冗余映射方案中消除一些痛苦。如果您使用Fluent Validation之类的内容,那么您可以很好地区分验证问题。

Here's a good link关于一般的ASP.NET MVC实现方法。

答案 1 :(得分:10)

我认为您的问题不在于asp.net MVC,而在于您选择使用的所有部分。

你想要它原始而简单吗?

全面使用POCO,并在您需要的地方实施存储库。

我没有使用过Java MVC,但是如果你包括如何解决那里的特定问题,它会让整个问题看起来不像是一个咆哮。

让我们清楚一些误解或误解:

  • 您可以通过帖子将复杂对象传递给视图。但是如果有意义的话,你只想这样做,见下一篇文章
  • 您在那里挑选的样品会发出一些警报。接受订单的客户数据或客户ID而不检查授权可能是一个很大的安全漏洞。根据您接受/允许的内容,订单也可以这样说。无论是POCO,LINQ,Asp.net MVC还是Java MVC,这都是使用ViewModel的一个巨大案例。
  • 您可以将未通过帖子显示的简单值传递给视图。它完成了隐藏字段(asp.net MVC非常简单地支持使用模型值),并且在某些情况下它会为您生成隐藏字段。
  • 您绝不会被迫使用linq2sql和Asp.net MVC。如果您发现它缺乏打算如何使用它,请远离它。注意我喜欢linq2sql,但它如何与你对asp.net mvc所能做的事情联系起来很奇怪。
  • “我参与了Java MVC项目,它们简洁明了”。处理项目与自己设计项目不同。设计技巧确实会影响你从中获得的东西。不是说你的情况,但只是想指出这一点,因为缺乏你在Java MVC中缺少的细节。
  • “我的数据层不是持久性独立的,因为这些类是由SQL metal生成的。因此我有很多不必要的功能。理想情况下我想要POCO,但我找不到好办法实现这一目标“。你选错了技术,linq2sql并不适合这个要求。它在我使用它的项目中并不是一个问题,但是所有东西都是以这样一种方式设计的,这种方式与你的细节相比没那么紧密。也就是说,转移到其他东西。顺便说一句,您应该与Java MVC共享您所使用的内容。
  • “CustomerId出现在视图中,但到创建方法时,它已经丢失了CustomerId。”如果该属性处于有序状态,您可以打赌您的代码有错误。现在,这是一个完全不同的真实问题,为什么它不使用CustomerId /这样的问题会伴随:您的Customer类,View,您传递给View的内容......答案将包括,但是不限于:检查浏览器中的HTML源代码以查看您使用源发布的实际值(或者使用fiddler查看相同的值),确保CustomerId在将其传递给View时确实具有该值。 / LI>
  • 你说:“”魔术“UpdateModel方法,有时可以工作,有时不会”。这不是魔术,你可以看到它做了什么,当然可以找到它的信息。您发布的信息中有一些内容,我的下注是非可选字段或解析信息的错误值...视图支持为此添加验证。没有验证,这可能是缺乏的。
  • 您在评论中说:“在调用UpdateModel之后,我无法显式设置CustomerId,我将不得不检索客户对象然后将其分配给订单,这似乎是一个开销,因为我所有需要是CustomerId“......您接受的是用户输入的CustomerId(即使它是隐藏字段),您真的想要验证该输入。此外,您自相矛盾,您声称只需要CustomerId,但之后您说您需要与订单绑定相关的完整客户对象。这就是它,如果您只绑定CustomerId,您仍然需要获得该客户并将其分配给该属性。除了场景之外没有任何魔力......
  • 同样在评论中:“更新模型是我现在完全避免的事情,因为我不知道它将如何与LINQ实体一起运行。在视图模型类中,我创建了将LINQ实体转换为我的视图模型的构造函数这减少了控制器中的代码量,但仍然感觉不对“。使用ViewModel(或EditModel)的原因不是因为它是linq2sql ...这是因为,在许多其他原因中,您正在公开一个模型,允许操作方式超出您实际想要允许用户修改的方式。如果原始模型具有不允许用户修改的字段,则公开原始模型是真正的问题。

答案 2 :(得分:4)

如果您的视图已正确定义,那么您可以轻松地执行此操作>

    [HttpPost]
    public ActionResult Create(Order o, int CustomerId)
    {
        //you got the id, life back to jolly good (hopefully)
        // Persistance logic and return view
    }

编辑:

正如attadieni所提到的,通过正确的观点,我的意思是你在表格标签中有这样的东西>

@Html.HiddenFor(model => model.CustomerId)

ASP.NET MVC将自动绑定到相应的参数。

答案 3 :(得分:3)

我一定是错过了这个问题。

你有一个控制器订单,其行动为Create就像你说的那样:

public class OrderController()
{
    [HttpGet]
    public ViewResult Create()
    {
        var vm = new OrderCreateViewModel { 
            Customers = _customersService.All(),
            //An option, not the only solution; for simplicities sake
            CustomerId = *some value which you might already know*; 
            //If you know it set it, if you don't use another scheme.
        }                             
        return View(vm);    
    }

    [HttpPost]
    public ActionResult Create(OrderCreateViewModel model)
    {
        // Persistance logic and return view
    }  
}

“创建”操作会回发一个类似于OrderCreateViewModel的视图模型。

public class OrderCreateViewModel 
{
    // a whole bunch of order properties....
    public Cart OrderItems { get; set; }
    public int CustomerId { get; set; }

    // Different options
    public List<Customer> Customers { get; set; } // An option
    public string CustomerName { get; set; } // An option to use as a client side search
}

您的视图有一个客户下拉列表,您可以将其作为属性添加到viewmodel或文本框中,您可以通过JQuery连接到服务器端进行搜索,您可以在匹配时设置隐藏字段CustomerId但是你决定这样做。如果您已经提前知道了customerId(其他一些帖子似乎暗示了这一点),那么只需在viewmodel中设置它并绕过上述所有内容。

您拥有所有订单数据。您拥有此订单附加的客户的客户ID。你很高兴。

  

“要保留订单,我需要客户或至少是CustomerId。在视图中存在CustomerId,但是当它到达Create方法时,它已经丢失了CustomerId。”

什么?为什么?如果CustomerId在视图中,设置并回发,它就在HttpPost Create方法的模型中,这正是您需要它的地方。你错的是什么意思?

ViewModel被映射到order类型的Model对象。如建议的那样,使用AutoMapper很有帮助......

[HttpPost]
public ActionResult Create(OrderCreateViewModel model)
{
    if(!ModelState.IsValid)
    {
      return View(model);
    }   

    // Persistance logic and return view

    var orderToCreate = new Order();

    //Build an AutoMapper map
    Mapper.CreateMap<OrderCreateViewModel, Order>();

    //Map View Model to object(s)
    Mapper.Map(model, orderToCreate);       

    //Other specialized mapping and logic

    _orderService.Create(orderToCreate);

    //Handle outcome. return view, spit out error, etc.
}  

这不是必需品,您可以手动映射,但它只会让事情变得更容易。

你已经定下来了。如果您不想使用数据注释进行验证,那么可以在服务层中进行,使用提到的流畅验证库,无论您选择什么。一旦使用所有数据调用服务层的Create()方法,就可以了。 断开的地方在哪里?我们缺少什么?

ataddeini的回答是正确的,我只是想展示一些代码。 Upvote ataddeini

答案 4 :(得分:2)

如果客户ID已经在订单模型中(在此示例中),则应该可以在不扩展方法签名的情况下使用它。如果在渲染视图上查看源,是否在表单中的隐藏字段中正确发出了客户ID?您是否在订单模型类上使用[Bind]属性并无意中阻止了客户ID的填充?

答案 5 :(得分:1)

我认为Order表会包含一个CustomerID字段,如果是这样,唯一的问题是你可能没有在视图中包含任何控件以保持该值,然后丢失。

尝试按照这个例子。

1)在发送到View之前执行GET操作,假设您在此时分配了CustomerID。

public ActionResult Create()
        {
            var o = new Order();
            o.CustomerID = User.Identity.Name; // or any other wher you store the customerID
            return View(o);
        }

2)View,如果您不对CustomerID使用任何控件,如文本框,组合框等,则必须使用隐藏字段来保留该值。

@using (Html.BeginForm())
    {
        @Html.HiddenFor(m => m.CustomerID)

        <label>Requested Date:</label>
        @Html.TextBoxFor(m => m.DateRequested)

        ...
    }

3)最后,POST动作来获取并保持订单。在这里,由于CustomerID保存在隐藏值中,Model Binder会自动将所有Form值放入Order对象o中,然后您只需要使用CRUD方法并保留它。

[HttpPost]
public ActionResult Create(Order o)
        {
            return View();
        }

可以有两种方法,一种是隐式保存所有Model值,即使未在View中使用,另一种是仅保留使用的值。我认为MVC正在做正确的事情来跟随后者,避免不必要为更大的模型保留大量垃圾,当唯一的想法是,一个名字,一个CustomerName,不知何故它可以让你控制通过什么数据保持通过整个循环动作 - 视图 - 动作并节省内存。

对于更复杂的方案,并非所有字段都在同一模型中,您需要使用ViewModels。例如,对于mater-detail场景,您将创建一个OrderViewModel,它具有两个属性:Order o和IEnumerable&lt; OrderDetail&gt; od,但同样,您需要明确使用视图中的值,或使用隐藏字段。

在最近的版本中,您现在可以使用POCO类和Code-First使所有内容更清晰,更容易,您可能需要尝试使用EF4 + CTP5。

答案 6 :(得分:1)

如果您正在使用服务(也就是服务层,业务外观),要处理我们说OrderModel,您可以提取一个接口,并让您的ViewModel / DTO实现它,以便您可以传回ViewModel / DTO服务。

如果您使用存储库直接管理控制器中的数据(没有服务层),那么您可以使用从存储库加载对象然后在其上执行UpdateModel的旧方法。

[HttpPost]
public ActionResult Create(string customerCode, int customerId, Order order)
{
    var cust = _customerRepository.Get(customerId);
    cust.AddOrder(order);//this should carry the customerId to the order.CustomerId
}

此外,URL可能会有所帮助,我的意思是您可以在网址中添加客户标识符来创建订单。

UpdateModel应该可以工作,如果你的FormCollection具有非可空属性的值并且它们在FormCollection中为空/ null,则UpdateModel应该失败。