WebForm

  隨著ASP.NET MVC的興起,WebForm已成昨日黃花,但我其實還很想為WebForm說幾句。

  沒有經歷過從ASP向ASP.NET轉變的同學,是很難理解當WebForm出現時,程序猿世界的歡呼雀躍的。事實上,我也是在Razor出現之后,才勉勉強強的轉向MVC,因為看見<% %>這個東西就怕。我曾經參加過一個升級ASP到ASP.NET的項目,ASP里面亂七八糟的代碼看得我眼睛又酸又脹紅通通的流淚,一輩子都記得!

  WebForm最后生成的html可能會臃腫難看,但其代碼頁面(.aspx)是相當清爽漂亮的。

  既然我們都已經決定采用MVC了,WebForm的不足就不用再多說了。但我們應該努力的學習和借鑒它優秀的地方,這些也是在MVC的開發中會用到的:

  • 呈現和頁面邏輯相分離。WebForm里由于它的框架本來就顯式的區分了aspx和aspx.cs,所以大多數時候我們不會擔心這個事情。但MVC里面,我們很容易就在view里面利用ViewModel數據進行運算,模糊Controller和View之間的邏輯界限。這個問題我們將在CurrentUser的時候詳細講解。

  • 良好的頁面封裝和重用。當我們發現頁面又反復出現的、大同小異的“部件”時,我們肯定就會想到重用。這就是考驗我們功力的時候。我先提一點我想到的:有時候我們寧愿重復不愿重用!這是我得出來的血淚教訓。應該是在創業家園項目的評論頁面部分,我曾經試圖重用所有評論的PartialView,結果慘不忍睹,最后放棄重用,反而海闊天空。其實有一個更好的例子就是WebForm中的GridView和Repeater,從實踐上看,反而是簡單封裝的Repeater更受歡迎,“大而全”的GridView卻少有人用。所以封裝和重用有一個度的問題。

  RouteTest

  Route功能是MVC的一個重大突破,也是一個重要缺陷。由于沒有良好的自動檢查機制,在實際的開發過程中,非常容易出錯!相信有過開發經驗的同學都有體會,有時候老半天都報錯:找不到View找不到Action,查來查去就一個拼寫錯誤;有時候新增一條RouteConfig,一會兒其他同事叫起來了,“考!原來是你的設置把我的覆蓋了。查了我一下午!”

  把時間浪費在這些地方實在是可惜,所以我們解決這個問題的辦法是使用單元測試,在PCTest的project中引入了RouteTest。每一次新增RouteConfig,跑一遍單元測試:自己的能過,也不影響別人的,就OK了。

  這是單元測試在我們項目的UI層最成功的例子。照理說,MVC的最大的一個好處就是“可測試”,其他地方也應該廣泛引入單元測試的,但本人偷懶,另外HttpContext的sealed限制也限制了單元測試的實施(MVC 5應該解決了這個問題),所以目前UI層的單元測試還沒有展開。但估計這個工作遲早都得做,現在已經出現了一些手工測試繁瑣費事易遺漏的問題了。

  URL/View層級

  MVC現目前的另一個問題是,View很難按多層級組織。比如,我可能需要的View是這樣組織的:

架構設計之路(八):MVC點滴

  注意Controller也有層級關系設置。我始終覺得這樣會更清晰整潔,但如果MVC的框架不能這樣進行“層級對應”。如果一定要這樣把View分層組織起來,在Action中就必須寫出View的全部路徑,比如:

C#代碼
  1. public class LogController : Controller    
  2. {    
  3.       //    
  4.       // GET: /Account/Log/On    
  5.       public ActionResult On()    
  6.       {    
  7.           return View("~/Views/Account/Log/On.cshtml");    
  8.       }    
  9. }    

  還得專門配置RouteConfig,這也太麻煩了一點。所以,我們就還是盡量按MVC的框架,從URL的設計開始,就盡量是/{Controller}/{action}/{route-parameter}的樣式,View也同樣,放在Contoller對應的文件夾下即可。

  Partial/ChildAction/EditorTemplate

  當我們需要重用某些“頁面片段”時,我們就面臨了以上這幾種選擇。切入的點有很多,我們就只結合我們項目,抽取其最鮮明最容易辨認的特點,直接講述他們的使用場景:

  首先是EditorTemplate。它的特點最明顯,是和Post相關的。也就是,當一個“頁面片段”的數據,還需要再Post回服務器的時候,我們就必須使用EditorTemplate;如果不使用EditorTemplate,ViewModel的數據就無法傳回。為什么呢?和MVC的ViewModel綁定機制有關,EditorTemplate中的html控件呈現時,會在其name上加上所屬父Model的前綴,以便于MVC自動解析post數據并綁定到ViewModel。

  如果“頁面片段”不需要POST,只負責呈現即可,又該如何選擇呢?我們的原則是:

  • 如果“頁面片段”不需要和服務器端交互,所需要的數據都能從父Model中獲得,使用Partial;

  • 否則,如果“頁面片段”說需要的數據還需要從服務器獲得,那就只能使用ChildAction了。

  HtmlHelper

  除了上述幾種頁面片段的重用,還有通過創建HtmlHelper的擴展方法,自定義一種“頁面片段”的呈現方式。這種方式一般是PartialView的一種替代方式,我們通常把“很小很小”(比如一個鏈接、一個下拉列表等),用處“很多很多”(甚至于跨項目)的可重用html片段用HtmlHelper封裝起來。

  AJAX

  觀察我們的Action就可以發現,我們為Ajax提供的Action始終是返回的ActionResult,而不是使用“更先進”的WebApi機制(直接返回int等簡單類型)。這主要是因為我們使用了SessionPerRequest機制(主要是為了提高性能),我們讓一個Request請求只使用一個session(可先簡單的理解為一個數據庫連接),亦即:

  1. 當MVC獲得一個Request,需要使用session時,Service生成一個session;

  2. 然后,在這個Request的整個請求過程中,使用的都將是這個已經生成的session(類似于“單例模式”);

  3. 當Request結束后,釋放這個session,將所有改動同步到數據庫

  好了,這里我們的關鍵點就是什么時候算“Request結束”?我們更進一步的定義它為View呈現完畢的時候,所以利用了Filter機制,在OnResultExecuted()時同步數據庫,代碼如下:  所以,即使Ajax調用,也必須經歷一個“View呈現完畢”的過程,才能完成數據同步。

  UIDevService切換

  進行前臺開發,不需要連接后臺數據庫的同學,只需要在MVC項目編譯時,輸入UIDEV即可(如果要真正的連接數據庫,使用PROD),如下所示:

架構設計之路(八):MVC點滴

  那么,這究竟是如何實現的呢?

  總體上來說,我們借用了autofac這個類庫,實現了所謂的“依賴倒置”

架構設計之路(八):MVC點滴

  所以,在MVC的Controller中,我們只使用ServiceInterface而不管其具體實現,如下所示:

C#代碼
  1. private IAuthroizationService _authService;    
  2. public AuthController(IAuthroizationService authService)    
  3. {    
  4.     _authService = authService;    
  5. }    

  最后,在Global.asax.cs中我們通過條件編譯符if...else來確定究竟使用哪一種Service實現:ProdServiceModule,或者UIDevServicemodule

C#代碼
  1.         void ResolveDependency()    
  2.         {    
  3.             var builder = new ContainerBuilder();    
  4.     
  5.             builder.RegisterControllers(Assembly.GetExecutingAssembly());    
  6.             builder.RegisterFilterProvider();    
  7.    
  8. #if PROD    
  9.             builder.RegisterModule(new ProdServiceModule());    
  10. #endif    
  11. #if UIDEV    
  12.             builder.RegisterModule(new UIDevServicemodule());       
  13. #endif    
  14.             container = builder.Build();    
  15.             DependencyResolver.SetResolver(new AutofacDependencyResolver(container));    
  16.         }   

  最后,不要忘了,新引入一個Service時,在ProdServiceModule.cs或者UIDevServicemodule.cs中添加:

C#代碼
  1. builder.RegisterType<RegisterService>().As<IRegisterService>();    

  這一章就差不多了吧。下一章我們再講CurrentUser,并由此引出我們的原則:如何在View、Controller、Service和ViewModel之間劃分邏輯(或者責任)。

除非特別注明,雞啄米文章均為原創
轉載請標明本文地址:http://www.vkzldl.live/software/481.html
2015年12月14日
作者:雞啄米 分類:軟件開發 瀏覽: 評論:0