為什么需要分離?
我們知道MVC項目各部分職責比較清晰,相比較ASP.NET Webform而言,MVC項目的業務邏輯和頁面展現較好地分離開來,這樣的做法有許多優點,比如可測試,易擴展等等。但是在實際的開發中,隨着項目規模的不斷擴大,Controller控制器也隨之不斷增多。如果在Controllers文件夾下面有超過兩位數controller,即便采用良好的命名規范,或者用子文件夾的形式區分不同功能的控制器,還是會影響項目的可閱讀性和可維護性。因此,在一些場景下,如果能把與某功能相關的文件分離到一個獨立的項目中是非常有用的。Asp.Net MVC提供了Areas(區域)的概念達到這一目的。
一個典型的場景
Web應用通常會有前台(面向用戶)和后台(面向管理員)兩部分,我們希望以/locahost/Admin開始的URL都為后台管理地址,因此我們也許會有這樣的路由:
routes.MapRoute( //Admin Route "Admin", // Route name "Admin/{controller}/{action}/{id}", // URL with parameters new { controller = "Admin", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); routes.MapRoute( //Default Route "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults );
運行程序訪問locahost/Admin, 通過RouteDebugger(Asp.Net MVC路由調試的好幫手RouteDebugger)的輸出信息可以看到,第一個路由(Admin)匹配成功,AdminController的Index方法被調用了,然而再仔細想想,通過/localhost/Admin/Index可以匹配第二個路由(Default),同樣可以調用AdminController的Index方法!以此類推,后台用戶管理列表(/localhost/Admin/User/List)等同於(/localhost/User/List)。
第一次改進
我們要達到一種目的,那就是/localhost/Admin/Admin/Index正確匹配第一個路由,而/localhost/Admin/Index不匹配第二個路由。因此可以對Default路由進行條件限制,參考Stephen Walther的ASP.NET MVC Tip #30 – Create Custom Route Constraints,修改Default路由為:
routes.MapRoute( //Default Route "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults new { controller = new NotEqual("Admin")} );
上面的路由意思是AdminController不會匹配Default路由。現在分別運行之前的兩個URL,會發現直接訪問/Admin/Index已經不能匹配到任何路由。通過修改NotEqual類,可以很容易為Default路由添加多個“排除在外”的Controller限制條件。但是,你不覺得這樣很挫么?
第二次改進
這里進入正題,使用Areas這個功能來進行分離。新建一個項目"MyMvcAreasDemo",然后在項目上右鍵->添加->Areas,輸入"Admin",如下:
在Areas/Admin/Controllers文件夾下面新建HomeController並添加一個Index的方法和對應的View文件。這里可以發現Areas的另一個好處:你可以在不同Areas下面添加相同名稱的Controller。當然,如果你直接這么運行會得到一個錯誤:
這種情況需要修改一下AdminAreaRegistration.cs和Global.asax,分別為路由加上命名空間限制:
/Areas/Admin/AdminAreaRegistration.cs
context.MapRoute( "Admin_default", "Admin/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional }, new string[] { "MyMvcAreasDemo.Areas.Admin.Controllers" } );
/Global.asax.cs
routes.MapRoute( //Default Route "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults new string[] { "MyMvcAreasDemo.Controllers" } );
這樣,我們就可以把所有與后台管理相關的Controller和View文件放到/Areas/Admin下面,以此類推,可以添加諸如會員(Member),博客(Blog),論壇(Forum)等多個Areas。各部分都有自己的頂層文件夾,物理文件都分離開來,管理起來比較方便。
這種方式已經有了很大提高,但是所有的文件還是放在同一個項目里面。當項目規模較大的時候,比較好的開發方式是將不同功能模塊按需要獨立到不同項目里面,最后再整合成一個整體。這樣,每一個項目可以獨立開發,測試和發布。
第三次改進
我們需要對現有項目進行一下改造:
1. 在解決方案上面新建一個MyMvcAreasDemo.Admin的MVC3項目,並且刪除Global.asax和Web.config兩個文件
2. 在根目錄新建一個AdminAreaRegistration的類,輸入如下內容:
public class AdminAreaRegistration : AreaRegistration { public override string AreaName { get { return "Admin"; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "Admin_default", "Admin/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional } ); } }
3. 刪除MyMvcAreasDemo項目/Areas/Admin文件夾下面的AdminAreaRegistration.cs文件以及Controllers文件夾(包括HomeController)
4. 在MyMvcAreasDemo.Admin項目的Controllers里面新建一個HomeController
5. 記得保留MyMvcAreasDemo/Areas/Admin下面的Views,並且在MyMvcAreasDemo項目里面引用MyMvcAreasDemo.Admin項目,如圖:
現在運行程序並訪問/Admin/Home/Index可以發現Admin項目生效了。這樣,我們可以將所有的與后台管理相關的Controller都放到這個新的項目中來。但是很快你會發現,一個新的“問題”又出現了:
當你在MyMvcAreasDemo.Admin里面的HomeController添加新的Action(例如List),然后習慣性在上面右鍵-"Add View"后,你會發現新增的List.cshtml文件會出現在MyMvcAreasDemo.Admin/Views/Home下面,然后當你訪問/Admin/Home/List的時候瀏覽器會得到錯誤提示:"The view 'List' or its master was not found or no view engine supports the searched locations…"。原來它只會在主程序MyMvcAreasDemo中的對應目錄去尋找View。這么一來,MVC框架提供給我們的腳手架功能就大打折扣,當然你可以手動在MyMvcAreasDemo/Areas/Admin/Views中對應添加View,或者在MyMvcAreasDemo.Admin項目中自動生成了View之后再拷貝過去。有沒有更好的辦法呢?
第四次改進
為了使我們在MyMvcAreasDemo.Admin自動生成的View自動同步到MyMvcAreasDemo/Areas/Admin/Views文件夾中,可以使用“生成事件(Build Event)”里的“Post-Build Event”,打開MyMvcAreasDemo.Admin的屬性,修改如下圖所示:
我本地的生成事件為:
mkdir "$(SolutionDir)$(SolutionName)\Areas\Admin\Views" xcopy "$(ProjectDir)Views" "$(SolutionDir)$(SolutionName)\Areas\Admin\Views" /S /E /C /Y
意思其實很簡單,相信大家都能看得懂,就是完全復制MyMvcAreasDemo.Admin的Views文件夾下所有文件至MyMvcAreasDemo/Areas/Admin/Views中。
現在再次訪問/Admin/Home/List就可以得到正確結果了,並且你可以發現List.cshtml已經被復制到MyMvcAreasDemo/Areas/Admin/Views/Home目錄里。
至此大功告成!我們已經將面向前台和面向后台的模塊成功分離到兩個獨立的項目中,希望能對您有所幫助!
===update===
另外一種分離方式:使用MvcContrib分離ASP.NET MVC項目