我在一年多前发过这篇文章,我觉得更新它是有意义的,因为它得到了不少观点。
我要么缺少一些东西,要么微软已经搞砸了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等方法被引入,但文档在哪里?也许是时候停下来思考一下了?我希望我的代码始终保持一致,但是到目前为止我所看到的,我对这是正确的前进方式毫无信心。
我喜欢这个框架。有很多东西需要学习,它并没有比以往任何时候都更令人兴奋。应该发布关于网络表单的另一篇文章。我希望这会有所帮助。
答案 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,但是如果你包括如何解决那里的特定问题,它会让整个问题看起来不像是一个咆哮。
让我们清楚一些误解或误解:
答案 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应该失败。