ASP.NET MVC4 IN ACTION –View Models
--這是本書(shū)的第二部分,前三波是第一部分,這波的知識(shí)真的很重要
--在這一波博客里,我會(huì)加入一些vs2010的編程技巧,這是在別人那里學(xué)不到的
--我會(huì)加入一些自己總結(jié)的EF方面技巧,這是這本書(shū)中暫時(shí)還沒(méi)看到的
--內(nèi)容我已經(jīng)不打算按書(shū)中原汁原味的講,因?yàn)檫@波開(kāi)始,你將要真正的掌握MVC
--每個(gè)細(xì)節(jié)我都會(huì)抓的很細(xì),講書(shū)中省略的很多東西,但后期我可能不會(huì)這樣去做了,因?yàn)橐岣咝?/span>
--所以學(xué)習(xí)本系列MVC博客的人,可能不能跳著學(xué)習(xí)了,因?yàn)槲抑v過(guò)的就不會(huì)去再講了
本人能力有限,盡量將書(shū)中的知識(shí)濃縮去講,仔細(xì)學(xué)過(guò)后,然后你再學(xué)習(xí)其他語(yǔ)言的MVC框架也就大同小異了
本次覆蓋知識(shí)點(diǎn):
備注:controller action指controller中的action(也就是你們經(jīng)常寫的方法而已)
controller actions指controller中全部actions
action名字加action表示 某某名字的action,例如有個(gè)叫Index的action,我就會(huì)這樣表示
Index action.知道了嗎?這方便我寫博客,也更方便理解
view model指頁(yè)面(view)中用到model,我上篇博客說(shuō)的視圖模型就是view model
action method指 返回值是ActionResult那個(gè)方法,比如Index那個(gè)action方法
user interface,用戶接口(UI的全稱),你可以意會(huì)理解成頁(yè)面,也就是要跟用戶交互的東西,比如我們給用提供一個(gè)頁(yè)面,讓用戶錄入數(shù)據(jù),這就是一個(gè)interface,一個(gè)接口,你暫且在這里就是view,就是頁(yè)面
business logic,商業(yè)邏輯,類似于數(shù)據(jù)訪問(wèn)層中業(yè)務(wù)實(shí)現(xiàn)的邏輯
domain,你可以理解數(shù)據(jù)訪問(wèn)層
domain model,數(shù)據(jù)訪問(wèn)層要使用到的model
以后我想直接寫英文了,好方便理解,在第二波的博客里我是吃過(guò)苦了,文章讀起來(lái)好拗口
關(guān)于本書(shū)的第一部分(前三波內(nèi)容)講了很多點(diǎn)到即止的知識(shí),大致地概括了一下框架的某些部分。
現(xiàn)在我們要更深入的研究這些細(xì)枝末節(jié)
在本章我們將要討論這個(gè)model,怎樣為ASP.NET MVC框架特意地去設(shè)計(jì)一些更好結(jié)構(gòu)的model。我們還要研究一下Model-View-Controller模式,model通常是很難理解的,因?yàn)閙odel在很多地方用到的,說(shuō)實(shí)話,view model,domain model,頁(yè)面上的Model對(duì)象,@model等等。
model中定義了很多字段,例如Student這個(gè)model定義了Name,id,classname,我們可以想象一下Student是個(gè)圓,這個(gè)大圓里面有很多小圓
假如程序運(yùn)行了,會(huì)給程序一塊內(nèi)存用來(lái)存數(shù)據(jù),Student這個(gè)圓可以理解為一整塊,一個(gè)整體,然后細(xì)分Name一塊,Class一塊,Id一塊,我感覺(jué)這就是數(shù)據(jù)結(jié)構(gòu)了,不知道對(duì)不對(duì)。如果圓更多,是不是可以抽象理解成一個(gè)煤球形狀的數(shù)據(jù)結(jié)構(gòu),如果數(shù)據(jù)是這樣分布的,這里隨便說(shuō)著玩的,我是這樣理解的。
干嘛說(shuō)這些,因?yàn)檫@些數(shù)據(jù)都有意義,比如Name代表學(xué)生姓名,那么在view中我就可以直接model.Name就可以輸出學(xué)生名字了。上一波我們也講到了關(guān)于 view model要設(shè)計(jì)的更有意義和技巧,比如顯示用戶名 和 這個(gè)用戶所發(fā)的所有comment數(shù),view model中就2個(gè)字段,但是如果我們用domain model,那么顯示不太方便。所以上面說(shuō)要特意地去設(shè)計(jì)view model。
我感覺(jué)這句話挺好的:
When you work with object-oriented languages (such as C#), you create classes that define this representation. You can create your representation so that when you use it you’re working in a more natural language that allows you to talk about the concepts represented by the software instead of using programming language constructs like Booleans, strings, and integers.
當(dāng)你在使用面向?qū)ο蟮恼Z(yǔ)言,比如C#,你定義了一個(gè)類,這個(gè)類定義了頁(yè)面上該怎么顯示,你可以創(chuàng)建你的表現(xiàn)形式,不需要再使用domain model(它里面可能含有很多boolean類型,string類型,int類型結(jié)構(gòu)的數(shù)據(jù)),用更少的代碼就可以表達(dá)出你想要顯示的數(shù)據(jù)的形式,table顯示或者div顯示等等
一句話,domain model能滿足view顯示,你就顯示,你覺(jué)得不方便,你可以新建一個(gè)model,我們稱為 view model(或者presentation model),這個(gè)view model用來(lái)在頁(yè)面更容易顯示數(shù)據(jù)
通過(guò)本章,有些顯示數(shù)據(jù)的問(wèn)題你可以使用view model去簡(jiǎn)化在頁(yè)面上呈現(xiàn)的邏輯,我們將要看一下view models和input models(把數(shù)據(jù)從view傳遞給controller,相當(dāng)于添加數(shù)據(jù)等等)
5.1 什么是view model
view model的目的很直接--它就是一個(gè)model,在一個(gè)view里面,為了方便操作數(shù)據(jù)而特意設(shè)計(jì)的一個(gè)model ,感覺(jué)如果還不懂,需要到項(xiàng)目中去意會(huì)了。
在本節(jié)中,我們做一個(gè)簡(jiǎn)單的online store(在線商店)例子來(lái)講解view model怎么工作的。了解一下,把view model傳遞給view時(shí)候的時(shí)候,domain model和view model傳遞的區(qū)別,最后我們看一下,input models是如何把view上的數(shù)據(jù)傳遞給controller的。
5.1.1 Online Store例子
準(zhǔn)備工作(書(shū)中沒(méi)有,我大致寫下讓你復(fù)習(xí)一下)
修改這個(gè)類,讓它繼承DbContext,按Shift+Alt+F10快速導(dǎo)入命名空間
添加一個(gè)構(gòu)造函數(shù),參數(shù),是我們的數(shù)據(jù)庫(kù)名字
接下來(lái),我順便教大家快捷方式吧,寫代碼就要快才好,下次我就不寫了
直接在這個(gè)類繼續(xù)編寫,寫下如下代碼
將光標(biāo)置入 Customer中去,按下Ctrl+Shift+F10,然后回車鍵,可以快速生成對(duì)應(yīng)名稱的類
同理我們繼續(xù),完成后的樣子
然后我們?nèi)サ?那個(gè)ServiceLevel的DbSet
接下來(lái)我們向?qū)嶓w類中添加代碼
Customer.cs
1: using System;
2: using System.Collections.Generic;
3: using System.ComponentModel.DataAnnotations;
4: using System.Linq;
5: using System.Text;
6:
7: namespace OnlineStore.Models
8: {
9: public class Customer
10: {
11: [Key]
12: public int ID { get; set; }
13: public int Number { get; set; }
14: public string FirstName { get; set; }
15: public string LastName { get; set; }
16: public bool Active { get; set; }
17: public ServiceLevel ServiceLevel { get; set; }
18: public ICollection<Order> Orders { get; set; }
19: }
20: }
ServiceLevel.cs
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace OnlineStore.Models
7: {
8: public enum ServiceLevel
9: {
10: Standard,
11: Premier
12: }
13: }
Order.cs
1: using System;
2: using System.Collections.Generic;
3: using System.ComponentModel.DataAnnotations;
4: using System.Linq;
5: using System.Text;
6:
7: namespace OnlineStore.Models
8: {
9: public class Order
10: {
11: [Key]
12: public int ID { get; set; }
13: public DateTime Date { get; set; }
14: public ICollection<Product> Product { get; set; }
15: public decimal TotalAmount { get; set; }
16: }
17: }
Product.cs
1: using System;
2: using System.Collections.Generic;
3: using System.ComponentModel.DataAnnotations;
4: using System.Linq;
5: using System.Text;
6:
7: namespace OnlineStore.Models
8: {
9: public class Product
10: {
11: [Key]
12: public int ID { get; set; }
13: public string Name { get; set; }
14: public decimal Cost { get; set; }
15: }
16: }
接下來(lái)我們打開(kāi)HomeController 添加一個(gè)新的action,添加好后,我們便來(lái)添加好對(duì)應(yīng)的試圖
1: StoreOnlineContext _db = new StoreOnlineContext();
2:
3: public ActionResult Create()
4: {
5: //添加測(cè)試數(shù)據(jù)
6: //1.添加產(chǎn)品
7: Product pro1 = new Product
8: {
9: ID=1,
10: Name = "大話設(shè)計(jì)模式",
11: Cost = 39.98M
12: };
13: Product pro2 = new Product
14: {
15: ID = 2,
16: Name = " WCF服務(wù)編程:.NET開(kāi)發(fā)者決戰(zhàn)SOA的制勝利劍(第3版) [平裝]",
17: Cost = 88.5M
18: };
19: Product pro3 = new Product
20: {
21: ID = 3,
22: Name = " WCF全面解析(套裝上下冊(cè))",
23: Cost = 126M
24: };
25: ICollection<Product> pros = new List<Product> { pro1,pro2,pro3};
26: //2.添加訂單
27: Order order = new Order();
28: order.ID = 1;
29: order.Date = DateTime.Now;
30: order.Product = pros;
31: order.TotalAmount = 254.48M;
32: //3.添加客戶
33: Customer customer = new Customer();
34: customer.FirstName = "楊";
35: customer.LastName = "洋";
36: customer.Number = 10000;
37: customer.ServiceLevel = ServiceLevel.Standard;
38: customer.Active = true;
39: ICollection<Order> orders = new List<Order> { order };
40: customer.Orders = orders;
41: _db.Customers.Add(customer);
42: _db.SaveChanges();
43: return Content("添加成功!");
44: }
對(duì)應(yīng)的Create頁(yè)面
接下來(lái)按下F5運(yùn)行,默認(rèn)只有 localhost:端口號(hào),顯示的Home中的Index action的內(nèi)容
修改瀏覽器地址欄:localhost:你的端口號(hào)/Home/Create,過(guò)一會(huì),會(huì)顯示添加成功
關(guān)閉窗口,關(guān)掉調(diào)試,我們就會(huì)發(fā)現(xiàn)生成了一個(gè)sdf文件,這個(gè)是我們第一波博客里面說(shuō)的SqlServer Compact
右鍵該數(shù)據(jù)庫(kù),將它包含到項(xiàng)目中去,我們右鍵打開(kāi)該文件,就會(huì)發(fā)現(xiàn)生成的表文件了
到這里,我們的數(shù)據(jù)基本初始化完成了,數(shù)據(jù)雖然少,但是我們主要是體現(xiàn)的編程思想,下面開(kāi)始吧!
我們現(xiàn)在有一個(gè)這樣的需求:
這個(gè)系統(tǒng)的管理員,想要總結(jié)Customer的 名字,在線狀態(tài),等級(jí)狀態(tài),Order數(shù)量,最近的訂單時(shí)間
一種方案是:我們用domain model完成,將數(shù)據(jù)處理后放到頁(yè)面展示。我們從數(shù)據(jù)庫(kù)中取出Customers,然后把它傳給view,在view里面,我們循環(huán)這個(gè)customer集合,然后構(gòu)造個(gè)table展示,在最后一列(最近訂單日期),在view中又要遍歷Customers中的Orders集合,處理一下,然后找出最近的訂單日期。
這種方法會(huì)感覺(jué)在view上很復(fù)雜,秉著盡可能地設(shè)計(jì)可維護(hù)性好的程序的理念,這種方法就要放棄----復(fù)雜的循環(huán)和計(jì)算應(yīng)該在更高級(jí)的一層去處理,view要做的事情就是展示處理后的結(jié)果。(這里我又添加了一條數(shù)據(jù),但是這次我把Id手動(dòng)賦值給去掉了,因?yàn)閿?shù)據(jù)庫(kù)中我給Id那個(gè)屬性上面加了Key特性,說(shuō)明它是個(gè)主鍵,自動(dòng)增長(zhǎng)列,ICollection會(huì)自動(dòng)理解為外鍵)
我們創(chuàng)建一個(gè)view model來(lái)解決這個(gè)問(wèn)題。
5.1.2 創(chuàng)建一個(gè)View Model
在Customer Summary頁(yè)面上使用一個(gè)view model是很正確的。我們這樣設(shè)計(jì)我們?cè)贛odels文件中添加一個(gè)CustomerSummary.cs文件)
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5:
6: namespace OnlineStore.Models
7: {
8: public class CustomerSummary
9: {
10: public string Name { get; set; }
11: public string Active { get; set; }
12: public string ServiceLevel { get; set; }
13: public string OrderCount { get; set; }
14: public string MostRecentOrderDate { get; set; }
15: }
16: }
這里面的結(jié)構(gòu)很簡(jiǎn)單,都是string類型的。對(duì)的,我們所要做的就是在頁(yè)面上顯示文本(text)。這樣看起來(lái),簡(jiǎn)單直接,很容易見(jiàn)看懂了,不是嗎?presentation model設(shè)計(jì)理念,就是在一個(gè)view里面,presentation model的設(shè)計(jì),要將view要呈現(xiàn)的model的難度降到最低。
這里我解釋一下:presentation model、view model、domain model的區(qū)別:
presentation model是頁(yè)面上最終要使用的一類model的總稱,可以是view model,也可以是domain model(當(dāng)然,domain model顯示的數(shù)據(jù)比較簡(jiǎn)單,已經(jīng)不需要在專門去設(shè)計(jì)一個(gè)view model了,它也可以充當(dāng)presentation model)。view model是為了方便view顯示數(shù)據(jù)而有意,有技巧的而設(shè)計(jì)的model。
打個(gè)比方:presentation 對(duì)應(yīng) 一場(chǎng)展覽晚會(huì),model對(duì)應(yīng)服裝,平時(shí)domain風(fēng)格的model也可以去參加這場(chǎng)晚會(huì),有時(shí)場(chǎng)合合適,但是有時(shí)不適合,在晚會(huì)里會(huì)遇到很多坎坷,所以我們有必要去換一個(gè)風(fēng)格的服裝,比如view風(fēng)格的model,它也可以去參加這場(chǎng)晚會(huì),而且精心設(shè)計(jì)的,我相信view風(fēng)格的在presentation 表現(xiàn)上更出色。所以我會(huì)去選擇view model
不知道你們有沒(méi)有聽(tīng)懂?(哎,這三個(gè)英文單詞的區(qū)別,我是搞懂了,不知道,你有沒(méi)有搞懂?)
5.1.3 把數(shù)據(jù)傳到 Presentation Model
在我們的應(yīng)用程序中,我們等下會(huì)建立presentation model,這個(gè)model結(jié)構(gòu)和domain model的結(jié)構(gòu)有時(shí)幾乎一模一樣的,你可能會(huì)懷疑,有必要再建立一個(gè)model嗎?用domain model不就夠了嗎,寫兩個(gè)model不麻煩嗎?通常如果你用Entity framework框架生成的實(shí)體已經(jīng)夠做domain model了,但有時(shí)你真的不能怕麻煩,而不去再建立presentation model,寧愿你把ef中生成的實(shí)體,你拷貝過(guò)去都行,都隨便了,只要有都行,我們真正要在頁(yè)面上使用的model都是presentation model,這樣比如 domain model 滿足不了頁(yè)面要顯示的額外數(shù)據(jù),我們就可以在presentation model中拓展要顯示的屬性就行了,view中使用@model關(guān)鍵字 聲明一個(gè)強(qiáng)類型的視圖的時(shí)候,只能是一個(gè)model,假如一個(gè)頁(yè)面要顯示的數(shù)據(jù)來(lái)自兩個(gè)model怎么辦?view中@model只能使用一次,所以你就只能把要顯示的所有內(nèi)容封裝到一個(gè)model中去,而這些數(shù)據(jù)都來(lái)自domain model中的,有的在domain層,可能都是要通過(guò)邏輯計(jì)算后才能得到的結(jié)果,比如 用戶名 發(fā)帖數(shù) 最近訪問(wèn)時(shí)間,這個(gè)發(fā)帖數(shù)使用通過(guò)計(jì)算得到的,除非你數(shù)據(jù)庫(kù)設(shè)計(jì)時(shí),就一張表,字段就這個(gè)3個(gè),那無(wú)所謂了,隨便了,而view中就只是要顯示這3個(gè)字段,所以我們可以建立一個(gè)presentation model,里面就這3個(gè)字段,全string型的,方便顯示了不是嗎?而view中@model 關(guān)鍵字引入的model就是presentation model類型的,就行了.所有復(fù)雜的問(wèn)題都解決了.
每次都要寫presentation model的確很煩,不過(guò)不用擔(dān)心,這個(gè)問(wèn)題,已經(jīng)有人考慮了,我們可以使用AutoMapper工具幫我們對(duì)presentation model和domain model的相互映射,因?yàn)榧偃?domain model中有很多屬性要賦值到presentation model中去,一行一行寫代碼確實(shí)很煩,所以使用AutoMapper就能很快幫我們解決,一行代碼就行了.(我會(huì)在第11章講)
(這段內(nèi)容都是我自己加的,這一波開(kāi)始,我發(fā)現(xiàn)書(shū)中把看書(shū)者的地位已經(jīng)放在你已經(jīng)做過(guò)ASP.NET MVC項(xiàng)目了,內(nèi)容簡(jiǎn)寫了好多)
后期你寫項(xiàng)目的時(shí)候,就應(yīng)該能感受到方便了.我們把要顯示的數(shù)據(jù)使用Presentation model思想封裝的那么好,頁(yè)面上很容易就顯示數(shù)據(jù)了,所以我們就能把開(kāi)發(fā)的重點(diǎn)放在HTML和css樣式,布局前端上面去了,難道不是嗎?
這樣做,反而使的程序更容易測(cè)試,維護(hù),開(kāi)發(fā).寫到這里,我想到一個(gè)想法,團(tuán)隊(duì)開(kāi)發(fā)中,我們可以先寫好Presentation model,domain model直接利用工具生成,寫好后,后臺(tái)程序員直接經(jīng)過(guò)邏輯,將結(jié)果封裝成presentation model去就行了.前端只要Model點(diǎn)屬性不就夠了,真的很簡(jiǎn)單.
在controller中創(chuàng)建presentation model是不好的,因?yàn)閏ontroller負(fù)責(zé)的職責(zé)已經(jīng)很多了,它要決定哪個(gè)view被呈現(xiàn),還有很多其他的工作,所以不要給controller添麻煩了,把這個(gè)model提取出去,新建一個(gè)類也可以
我們新建一個(gè)CustomerSummaryController
代碼:
1: using OnlineStore.Models;
2: using System;
3: using System.Collections.Generic;
4: using System.Linq;
5: using System.Web;
6: using System.Web.Mvc;
7:
8: namespace OnlineStore.Controllers
9: {
10: public class CustomerSummaryController : Controller
11: {
12: //
13: // GET: /CustomerSummary/
14: StoreOnlineContext _db = new StoreOnlineContext();
15: public ActionResult Index()
16: {
17: IEnumerable<CustomerSummary> model = from o in _db.Customers.ToList<Customer>()
18: select new CustomerSummary
19: {
20: Name = o.FirstName + o.LastName,
21: Active = o.Active? "是" : "否",
22: ServiceLevel = Enum.GetName(typeof(ServiceLevel), o.ServiceLevel).ToString(),
23: OrderCount = o.Orders == null ? "0" : o.Orders.Count().ToString(),
24: MostRecentOrderDate=(from sub in o.Orders
25: orderby sub.Date descending
26: select sub).Count()>0?(from sub in o.Orders
27: orderby sub.Date descending
28: select sub).FirstOrDefault().Date.ToString("yyyy年MM月dd日"):"還沒(méi)有訂單"
29: };
30: return View(model);
31: }
在這里,你發(fā)現(xiàn)了,我_db.Customers.ToList<Customer>()
本來(lái)是linq to entity操作,被我轉(zhuǎn)成了linq to object操作,因?yàn)槲以趌inq to entity遇到一個(gè)問(wèn)題,就是ToString的問(wèn)題,linq to entity不支持ToString,不信你可以試試
在這個(gè)方法中,我們返回的是一個(gè) View(對(duì)象),這個(gè)對(duì)象還是一個(gè)集合
5.1.4 ViewData.Model
Controller和View都共享一個(gè)名叫ViewData.ViewData(鍵值對(duì)形式的一個(gè)很正常的集合),類型叫ViewDataDictionary的一個(gè)object
在上個(gè)代碼中,我們最后使用了return View(model),這個(gè)ViewData.Model就會(huì)自動(dòng)的被賦值了,這里被賦值了一個(gè)IEnumerable<CustomerSummary>類型的集合,這個(gè)對(duì)象在view被呈現(xiàn)之前,就已經(jīng)全部準(zhǔn)備好了,所以你在view中就可以使用這個(gè)對(duì)象,并使用它里面的值了.這個(gè)Model屬性是強(qiáng)類型的,所以我們的view知道怎樣去預(yù)測(cè),開(kāi)發(fā)者就可以在vs中就會(huì)有智能提示,得到這個(gè)好處.這些內(nèi)部的工作,大部分的Razor視圖引擎都會(huì)幫我們做好了,我們使用@model關(guān)鍵字
@model IEnumerable<OnlineStore.Models.CustomerSummary>
接著上面的那個(gè)action,我們添加對(duì)應(yīng)的視圖,代碼如下:
1: @{
2: ViewBag.Title = "Index";
3: }
4: @model IEnumerable<OnlineStore.Models.CustomerSummary>
5: <style>
6: td,th{ height:30px; text-align:center;width:20%}
7: table td,th{ border: #0094ff solid 1px ;}
8: table {width:100%;}
9: </style>
10: <h2>Custommer Summary</h2>
11: <table>
12: <tr>
13: <th>Name</th>
14: <th>Active?</th>
15: <th>Service Level</th>
16: <th>Order Count</th>
17: <th>Most Recent Order Date</th>
18: </tr>
19: @foreach (var summary in Model)
20: {
21: <tr>
22: <td>@summary.Name</td>
23: <td>@summary.Active</td>
24:
25: <td>@summary.ServiceLevel</td>
26:
27: <td>@summary.OrderCount</td>
28: <td>@summary.MostRecentOrderDate</td>
29: </tr>
30: }
31: </table>
估計(jì)你也看懂了,怎么去使用了.
5.2 表現(xiàn)層的用戶輸入(user input)
就像我們精心設(shè)計(jì)一個(gè)presentation model來(lái)呈現(xiàn)數(shù)據(jù)一樣,我們也要精心設(shè)計(jì)一個(gè)model來(lái)獲得用戶輸入的數(shù)據(jù).在view里,我們使用很厲害的presentation model使工作變得很簡(jiǎn)單,當(dāng)然一個(gè)很厲害的input model也可以在解決獲得用戶輸入的數(shù)據(jù)后添加到數(shù)據(jù)庫(kù)里這個(gè)問(wèn)題上變得很簡(jiǎn)單.以前在ASP.NET里面我們都是通過(guò)request請(qǐng)求上key獲得對(duì)應(yīng)的view來(lái)獲得數(shù)據(jù)然后來(lái)處理的。這里我們使用input model
5.2.1 設(shè)計(jì)model
我們新建一個(gè)類
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5:
6: namespace OnlineStore.Models
7: {
8: public class NewCustomerInput
9: {
10: public string FirstName { get; set; }
11: public string LastName { get; set; }
12: public bool Active { get; set; }
13: }
14: }
這個(gè)model主要接受用戶輸入的數(shù)據(jù),跟view都是一一對(duì)應(yīng)的
5.2.2 設(shè)計(jì)model
接下來(lái)我們?cè)贑ustomerSummaryController中添加一個(gè)action
1: public ViewResult Save(NewCustomerInput input)
2: {
3: return View(input);
4: }
添加完NewCustomerInput,F5運(yùn)行下程序,然后我們?cè)儆益ISave名稱,添加對(duì)應(yīng)的view
當(dāng)然我們可以不選擇模型類,自己添加對(duì)應(yīng)的view后,自己手動(dòng)加代碼,這里只是列出一個(gè)技巧
在這個(gè)Save action中,我們傳了一個(gè)NewCustomerInput類型對(duì)象的參數(shù),這個(gè)值就會(huì)被ASP.NET MVC中的DefaultModelBinder相互綁定,view中就可以把值封裝好后傳過(guò)來(lái),在ASP.NET MVC默認(rèn)是這樣定義的.
我們添加好view后,我們就成功把一個(gè)view變成一個(gè)強(qiáng)類型的view,ViewPage<T>,其中T已經(jīng)被指定為NewCustomerInput對(duì)象了,也就意味著,ViewData.Model對(duì)象已經(jīng)是NewCustomerInput類型的了。然后我們?cè)诰脑O(shè)計(jì)一個(gè)form(表單)就可以完成了.
1: @model OnlineStore.Models.NewCustomerInput
2:
3: @{
4: ViewBag.Title = "Save";
5: }
6: <div>
7: <form action="@Url.Action("Save")" method="post">
8: <fieldset>
9: <div>
10: @Html.LabelFor(x => x.FirstName)
11: @Html.TextBoxFor(x => x.FirstName)
12: </div>
13: <div>
14: @Html.LabelFor(x => x.LastName)
15: @Html.TextBoxFor(x => x.LastName)
16: </div>
17: <div>
18: @Html.LabelFor(x => x.Active)
19: @Html.CheckBoxFor(x => x.Active)
20: </div>
21: <div>
22: <button name="save">
23: 保存</button>
24: </div>
25: </fieldset>
26: </form>
27: </div>
還有我們使用了lambda表達(dá)式,綁定NewCustomerInput中的屬性
例如,@Html.TextBoxFor(x=> x.LastName)等同于代碼
<input type="text" name="LastName" />
你寫這行代碼和C#風(fēng)格的綁定效果都是一樣的.
Lambda在重構(gòu)(refactoring)中的幫助
不要小看view中的lambda表達(dá)式,它們?cè)谀銓懘a的時(shí)候就在編譯,所以你改了action,這個(gè)代碼就會(huì)在編譯的時(shí)候報(bào)錯(cuò),所以在你的view中寫代碼時(shí)候,對(duì)比下你視圖中引用的類,還有返回string的方法--你只有在運(yùn)行的時(shí)候會(huì)發(fā)現(xiàn)這些錯(cuò)誤.
在refactoring中當(dāng)然還有其他的強(qiáng)類型視圖數(shù)據(jù)引用助手,我們可以使用JetBrains ReSharper(www.jetbrains.com/resharper),它將幫助你重構(gòu)代碼,所有的視圖都可以使用它,一個(gè)很強(qiáng)大的工具
再使用強(qiáng)類型助手之前,我們還是依靠 magic(魔力的)strings,編程者們要手動(dòng)地去確保它們和input form一致,使用強(qiáng)類型助手,就像上面view中的代碼,ASP.NET MVC已經(jīng)為我們處理了,所以把input model中的屬性改名了,也不會(huì)在屏幕上顯示錯(cuò)誤,程序順利運(yùn)行,但是打開(kāi)錯(cuò)誤的頁(yè)面,還是會(huì)顯示綁定錯(cuò)誤的.
按下F5的時(shí)候,程序可以運(yùn)行,無(wú)影響,但是指定到使用頁(yè)面的時(shí)候,就會(huì)顯示這個(gè)綁定的錯(cuò)誤.就是這個(gè)效果
5.2.3 處理提交的input model
在上面那個(gè)view中,我們post到Save action,ASP.NET MVC提供了一個(gè)很簡(jiǎn)單的方式,把HTTP請(qǐng)求轉(zhuǎn)換成一個(gè)model,這個(gè)過(guò)程我們叫model binding,第十章我們會(huì)仔細(xì)去講,我們看一下這個(gè)action
接下來(lái)我們新添一個(gè)ActionResult Save2,上面那個(gè)action返回的是ViewResult
修改對(duì)應(yīng)的view中post請(qǐng)求的action,為Save2
按下F5運(yùn)行項(xiàng)目,輸入新地址
然后輸入http://localhost:<你的port>/CustomerSummary/Index
我們?cè)傩薷南耂ave2 action,return 新的action---Index,這樣添加成功后,就會(huì)跳轉(zhuǎn)到Index action,而Index action返回的是一個(gè)view,也就是Customer Summary列表
小提示:修改view中的代碼的時(shí)候,修改好后,保存,不必重新運(yùn)行項(xiàng)目,直接刷新瀏覽器就可以立即看到刷新后的效果
如果程序的服務(wù)器沒(méi)有關(guān),但是vs中的代碼已經(jīng)停止運(yùn)行了,此時(shí)修改view中的代碼,然后保存,也不必重新運(yùn)行項(xiàng)目,直接刷新瀏覽器就可以立即看到刷新后的效果.就等同于修改一個(gè)html一樣,很方便,但是修改后臺(tái)的代碼,就不行了,就必須要重新運(yùn)行項(xiàng)目.
許多view不會(huì)去顯示或者一個(gè)input forms,而是整合很多elements(網(wǎng)頁(yè)上一級(jí)一級(jí)的節(jié)點(diǎn)),來(lái)達(dá)到一個(gè)富客戶端的體驗(yàn).在一下節(jié)里,我么將會(huì)應(yīng)用這些我們?cè)诒静ɡ镆呀?jīng)學(xué)到的concept(概念或者思想),來(lái)實(shí)現(xiàn)一個(gè)更復(fù)雜的view
5.3 更復(fù)雜的model,display和input并存
在這一節(jié)里面,我們將構(gòu)造一個(gè)view model,既顯示數(shù)據(jù),也把input model中最后修改的active狀態(tài)數(shù)據(jù)發(fā)送到服務(wù)器上,做修改工作
5.3.1 設(shè)計(jì)展示和Input都存在的model
我們新建一個(gè)CustomerSummary2類
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5:
6: namespace OnlineStore.Models
7: {
8: public class CustomerSummary2
9: {
10: public string Name { get; set; }
11: public string ServiceLevel { get; set; }
12: public string OrderCount { get; set; }
13: public string MostRecentOrderDate { get; set; }
14: public CustomerSummaryInput inputs { get; set; }
15: public class CustomerSummaryInput {
16: public int Number { get; set; }
17: public bool Active { get; set; }
18: }
19: }
20: }
這里我們?cè)谝粋€(gè)類中嵌套另一個(gè)類,之后,在user interface上,在顯示的時(shí)候,input elements就會(huì)被嵌套,把CustomerSummaryInput這個(gè)Input model寫在這里面,方便以后的維護(hù),因?yàn)樗瓦@一個(gè)view在一起顯示,在其他view中也沒(méi)有用到啊
注意在CustomerSummaryInput中的Number屬性--它是每一個(gè)customer中的編號(hào)(Id),現(xiàn)在我們凍結(jié)叫楊洋的這個(gè)customer
5.3.2 讓Input Model開(kāi)始工作
我們?cè)傩陆ㄒ粋€(gè)action,MoreSave action,Save3 action
1: public ViewResult MoreSave()
2: {
3: IEnumerable<CustomerSummary2> model = from o in _db.Customers.ToList<Customer>()
4: select new CustomerSummary2
5: {
6: Name = o.FirstName + o.LastName,
7: ServiceLevel = Enum.GetName(typeof(ServiceLevel), o.ServiceLevel).ToString(),
8: OrderCount = o.Orders == null ? "0" : o.Orders.Count().ToString(),
9: MostRecentOrderDate = (from sub in o.Orders
10: orderby sub.Date descending
11: select sub).Count() > 0 ? (from sub in o.Orders
12: orderby sub.Date descending
13: select sub).FirstOrDefault().Date.ToString("yyyy年MM月dd日") : "還沒(méi)有訂單",
14: inputs=new CustomerSummary2.CustomerSummaryInput{
15: Active=o.Active,
16: Number=o.Number
17: }
18: };
19: return View(model);
20: }
21:
22: [HttpPost]
23: public ActionResult Save3(List<CustomerSummary2.CustomerSummaryInput> input)
24: {
25: foreach (var item in input)
26: {
27: Customer cus = _db.Customers.Where(x => x.Number == item.Number).FirstOrDefault();
28: cus.Active = item.Active;
29: _db.SaveChanges();
30: }
31: return RedirectToAction("MoreSave");
32: }
我們添加對(duì)應(yīng)的視圖:
1: @model IEnumerable<OnlineStore.Models.CustomerSummary2>
2: @{
3: ViewBag.Title = "MoreSave";
4: }
5: <style>
6: td, th {
7: height: 30px;
8: text-align: center;
9: width: 20%;
10: }
11:
12: table td, th {
13: border: #0094ff solid 1px;
14: }
15:
16: table {
17: width: 100%;
18: }
19: </style>
20: <h2>Custommer Summary</h2>
21: <form action="@Url.Action("Save3")" method="post">
22: <table>
23: <tr>
24: <th>Name</th>
25: <th>Service Level</th>
26: <th>Order Count</th>
27: <th>Most Recent Order Date</th>
28: <th>Active?</th>
29: </tr>
30: @foreach (var summary in Model)
31: {
32: <tr>
33: <td>@summary.Name</td>
34: <td>@summary.ServiceLevel</td>
35:
36: <td>@summary.OrderCount</td>
37: <td>@summary.MostRecentOrderDate</td>
38: <td>
39: <input type="checkbox" name="Active" checked="@summary.inputs.Active"/>
40: <input type="hidden" name="Number" value="@summary.inputs.Number" />
41: </td>
42: </tr>
43: }
44: </table>
45: <div>
46: <button name="save">
47: 修改Active</button>
48: </div>
49: </form>
按下F5運(yùn)行,就可以看到一個(gè)列表的效果,Active列已經(jīng)自動(dòng)幫我們勾選上了
接下來(lái)修改active,關(guān)于這個(gè),這里的Save3沒(méi)有成功實(shí)現(xiàn),還有問(wèn)題,如果讓我實(shí)現(xiàn)這個(gè)功能,我不會(huì)采用嵌入類的形式的,我自己試著用嵌入類的方式去寫了,但是沒(méi)有成功,你們能幫我解決嗎?
在這一波教程里面,書(shū)里面省略了很多的代碼,而且都簡(jiǎn)化了,博客寫到這里,還有點(diǎn)可惜,因?yàn)榘研薷暮蟮臓顟B(tài)保存回去,還沒(méi)有完成.
這一波的代碼我就不上傳了,手寫練習(xí)吧,如果你有利用嵌入類完成了最后的這個(gè)效果,你能告訴我思路嗎?
在下一波的教程里,我們主要講Validation ,驗(yàn)證,再下下一波里,我們主要講AJAX方面的,這兩波內(nèi)容都很多,也都是很重要的部分
關(guān)于ASP.NET MVC4 IN ACTION系列目錄地址已經(jīng)生成:點(diǎn)擊查看目錄
聯(lián)系客服