做为设计模式的王者,MVC在众多领域都成为良好的模型的代名词,从前在ASP.NET下我们只能依靠Monorail来实现ASP.NET下无控件的MVC,但是现在ASP.NET 下的MVC已经成为现实。
本文只想让大家更直观地认知ASP.NET MVC,如果语言有所不当,还望大家海涵。当然,如果文中有所纰漏还希望大家及时指出,我也好做及时的修改。
在下本着对初学者负责的态度来书写本系列中的各篇文章,但期间的恒心与毅力相信过来的人更加明白,所以如果书写有误希望大家谅解。
ASP.NET MVC曾经是ASP.NET 3.5 Extensions Preview 的一个部分.如今历经五个Preview一个Beta两个RC版本终于走向我们。
最新版本:ASP.NET MVC正式版1.0
ASP.NET技术
===================
被称为一个漂亮的Asp.net MVC应用,从代码角度来看,我认为得满足这三点:
1. 使用依赖注入框架。
2. 不要直接依赖Cache, HttpContext等。
3. View中不要条件逻辑。
当然不只是这三点,还有很多。我个人拿它们出来,是认为这些很重要但是经常被忽视。
对于第1点,优点在于松耦合,可测试性很好。如果在Controller里面想要使用某些Service,要么new出来,要么用单例的形式,如UserService.Instance,这样想对Controller写单元测试都不容易,它和这些Service耦合太紧密,无法将这些Service替换成Stub实现。因此,松耦合是必须的。要实现这个功能,必须让依赖注入框架来创建Controller,才有可能注入依赖并组装对象。MVC里面有一个ControllerFactory的东西,可以使用来达到这个目的。
a. 写一个类,继承自DefaultControllerFactory,例如 UnityControllerFactory : DefaultControllerFactory
b. 覆盖方法GetControllerInstance,使用依赖注入框架来创建Conroller
c. 修改Global.asax.cs, 在Application_Start内注册使用自己的ControllerFactory,
ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory())
d. 对Controller进行构造函数注入,例如:
public class UserController : Controller
{
public UserController(IUserService, userService, IMessageService messageService)
…
}
从现在开始,所有的Controller都是通过依赖注入框架来创建的,新增的Service就在依赖注入框架里面注册,Controller要使用哪些Service就往构造函数里加,反正框架会注入进来。
第2点, 类如HttpContext.Current.Session[“xxx”] = …, HttpContext.Current.Cache.Insert…此样的代码充斥在各个角落。有问题吗?还是老问题,可测试性,可替换性。
像HttpContext这种东西,几乎是不能Mock或者Stub的,只要代码中使用了HttpContext,可以说它就没法做单元测试。为什么很多人都会这么用呢,一个原因是图方便图简单,二是没有写单元测试。要用Session直接用就好了,封装一下再用多麻烦啊,这就是图简单。
解法很简单,对HttpContext进行封装,例如ISessionProvider, ICacheProvider,然后通过依赖注入框架,注入到Controller中去,这样的结果是代码可测试性高,而且想改变Cache机制也很方便。
意识不够。什么意识?让代码松耦合的意识。使用静态方法,静态变量,直接new依赖的对象,这些都是松耦合的反例。我们写代码的时候要规避它们。即使我们不用TDD(极端一点来说,现在不写单元测试),脑子里也要清楚记得,面向对象的法则、松耦合的一些原则等等,然后反应到代码上去。
(推荐)asp.net mvc强大的分页控件MvcPager
继我的【Ajax服务端框架】完成后, 也花了些时间学习了一Asp.net MVC,感觉我的Ajax框架也能玩MVC开发, 于是乎,在又加了点功能后,现在也能像Asp.net MVC那样写aspx和ascx了。
先来点代码来看看,具体的页面呈现效果请参考: 通用数据访问层及Ajax服务端框架的综合示例,展示与下载
<%@ Page Title="商品管理" Language="C#" MasterPageFile="~/MasterPage.master" Inherits="FishWebLib.Mvc.MyPageView<ProductsPageData, ProductsModelLoader>" %> <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> <script type="text/javascript" src="/js/MyPage/Products.js"></script> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server"> <table cellpadding="4" border="0"> <tr><td>当前分类</td><td> <select id="ddlCurrentCategory" onchange="<%= UiHelper.DropDownListAutoRedir("ddlCurrentCategory")%>" combobox="not-editable" style="width:300px;"> <%= Model.Categories.ToHtmlOptions("categoryId", false, "searchWord", "page")%> </select> </td><td> <a id="btnCreateItem" href="#" class="easyui-linkbutton" iconCls="icon-add">添加商品</a> </td></tr> </table> <div style="height: 10px"></div> <table class="GridView" cellspacing="0" cellpadding="4" border="0" style="border-collapse:collapse;"> <tr align="left"> <th style="width:20px;"> </th> <th style="width:550px;">商品名称</th> <th style="width:120px;">单位</th> <th style="width:120px;">单价</th> <th style="width:50px;">数量</th> </tr> <% foreach( Product product in Model.Products ) { %> <tr> <td><a href="/AjaxProduct.Delete.cs?id=<%= product.ProductID %>&returnUrl=<%= this.CurrentRequestRawUrl %>" title="删除" class="easyui-linkbutton" plain="true"> <img src="/Images/delete.gif" alt="删除" /></a> </td> <td style="white-space:nowrap;"> <a href="#" rowId="<%= product.ProductID %>" class="easyui-linkbutton" plain="true" iconCls="icon-open" > <%= product.ProductName.HtmlEncode()%></a> </td><td> <span name="Unit"><%= product.Unit.HtmlEncode() %></span> </td><td> <span name="UnitPrice"><%= product.UnitPrice.ToText() %></span> </td><td> <input type="text" pid="<%= product.ProductID %>" value="<%= product.Quantity %>" class="quantityTextbox" /> </td> </tr> <% } %> <%= Model.PagingInfo.PaginationBar(5)%> </table> <input type="hidden" id="hfCurrentCategoryId" value="<%= Model.CurrentCategoryId %>" /> <div id="divProductInfo" title="商品" style="display: none"><div style="padding: 8px"> <fish:ProductInfo ID="ProductInfo1" runat="server" ModelDataName="Model.ProductInfoViewData" /> </div></div> </asp:Content>
再来个ascx
<%@ Control Language="C#" Inherits="FishWebLib.Mvc.MyUserControlView<OrderListViewData>" %> <table class="GridView" cellspacing="0" cellpadding="4" border="0" style="border-collapse:collapse; width: 99%"> <tr align="left"> <th style="width:100px;">订单编号</th> <th style="width:160px;">时间</th> <th style="width:300px;">客户</th> <th style="width:100px;">订单金额</th> <th style="width:60px;">已处理</th> </tr> <% foreach( var item in Model.List ) { %> <tr> <td> <a href="#" OrderNo="<%= item.OrderID %>" class="easyui-linkbutton" plain="true" iconCls="icon-open"> <%= item.OrderNo %></a> </td> <td><%= string.Format("{0:yyyy-MM-dd HH:mm:ss}", item.OrderDate) %></td> <td> <a href="#" Customer='<%= item.ValidCustomerId %>' class="easyui-linkbutton" plain="true" iconCls="icon-open"> <%= item.CustomerName.HtmlEncode() %></a> </td> <td><%= item.SumMoney.ToText() %></td> <td> <%= item.Finished.ToCheckBox(null, "chk_Finished", true) %> </td> </tr> <% } %> <%= Model.PagingInfo.PaginationBar(5) %> </table>
与Asp.net MVC的aspx,ascx一样:没有与之对应的CodeFile
再来看看"商品管理"页面加载数据的C#代码吧。注意类型的名称与方法的签名,它们在上面Page指令中有引用。
public class ProductsModelLoader { public static ProductsPageData LoadModel() { ProductsPageData result = new ProductsPageData(); result = new ProductsPageData(); result.Categories = BllFactory.GetCategoryBLL().GetList(); if( result.Categories.Count == 0 ) { //HttpContext.Current.Response.Redirect("/Categories.aspx", true); // 不建议使用这个方法,不利于测试 FishWebLib.HttpContextHelper.RequireRedirect("/Categories.aspx"); return null; } // 获取当前用户选择的商品分类ID result.CurrentCategoryId = FishWebLib.FishUrlHelper.GetIntegerFromQueryString("categoryId", 0); if( result.CurrentCategoryId == 0 ) result.CurrentCategoryId = result.Categories[0].CategoryID; result.ProductInfoViewData = new ProductInfoViewData(result.Categories, new Product { CategoryID = result.CurrentCategoryId }); result.PagingInfo.PageIndex = result.PagingInfo.GetPageIndex(); result.PagingInfo.PageSize = AppHelper.DefaultPageSize; // 加载商品列表,并显示 result.Products = BllFactory.GetProductBLL().GetProductByCategoryId(result.CurrentCategoryId, result.PagingInfo); return result; } } public class ProductsPageData { public PagingInfo PagingInfo; public List<Category> Categories; public int CurrentCategoryId; public List<Product> Products; public ProductInfoViewData ProductInfoViewData { get; set; } public ProductsPageData() { this.PagingInfo = new PagingInfo(); } }
上面那个ascx是供JS调用的,C#的实现代码如下:(注意是如何执行用户控件的)
/// <summary> /// 搜索订单,并以HTML代码的形式返回给客户端 /// </summary> /// <returns></returns> public static string Search() { // 从查询字符串中读取客户端发过的日期范围。 QueryDateRange range = QueryDateRange.GetDateRangeFromQueryString("StartDate", "EndDate"); OrderListViewData data = new OrderListViewData(); data.PagingInfo.PageIndex = data.PagingInfo.GetPageIndex(); data.PagingInfo.PageSize = AppHelper.DefaultPageSize; // 搜索数据库 data.List = BllFactory.GetOrderBLL().Search(range, data.PagingInfo); // 执行用户控件,并传递所需的呈现数据。 return FishWebLib.Ajax.UcExecutor.Execute("~/Controls/OrderList.ascx", data); } public class OrderListViewData { public List<OrderItem> List; public PagingInfo PagingInfo = new PagingInfo(); }
与Asp.net MVC框架一样,页面数据的加载和呈现彻底地分开了。
JS调用上面那个ascx的代码如下:
function btnQuery_Click(){ var dateRange = GetDateRange("txtStartDate", "txtEndDate"); if( dateRange == null ) return false; var url = '/AjaxOrder.Search.cs?' + $.param({StartDate: dateRange.StartDate, EndDate: dateRange.EndDate}); ShowPickerPage(url, 'divResultList_inner', ShowResultSuccess); return false; }
学过Asp.net MVC的朋友应该能发现,前面示例代码中的 aspx, ascx的写法,与Asp.net MVC的写法是100%的兼容的。 但我肯定没有使用Asp.net MVC,您可以下载示例代码去看。
当初学习Asp.net MVC是因为有些人把Asp.net MVC说得太过于神话了,好像用Asp.net做开发,非它不可。但是,学了之后才发现, 确实,这个东西有很多优点,但没那么神奇。我认它最出色的地方还是在于它的思想:MVC,这种思想能够让我们在开发时, 尽量去分离一些可分离的东西,比如:呈现与获取数据的过程,从而可以在一个地方只关注一件事情,也就是它所提倡的:分离关注点。
正如前面所说,我认为Asp.net MVC的优点在于它的设计体现了MVC的思想。但让人不解的是:微软在实现时,不知为何, 非要把MVC搞得与WebForms差别巨大。这种区别尤其是在加入了Route和一堆Html扩展之后。 对于Route和一堆Html扩展,我对它们没多大的兴趣,因为它们于MVC无关,对于代码的维护和性能根本没什么改善。 所以也不打算在这个山寨版本上采用,反而认为现在的代码更容易理解了。
至于在Ajax方面的实现,我的框架也完全是符合MVC思想的。 而且在生成HTML片段时,仍然可以使用MVC的思想:先准备数据,然后交给ascx来呈现。
我的MVC框架与Asp.net MVC的不同之处:
1. 简单了很多,抛弃了许多与MVC无关的东西。因此,学习成本更低。
2. Controller少了很多限制,可以在单独的类库中实现。为了方便安全检查,建议放在一个命名空间中,或者取个前缀或后缀,这也符合:约定胜于配置。
3. 由于没有Route,页面是由Asp.net框架所调用的,而不是先经过Controller,但页面的数据加载是由一个辅助的类来完成的, 这个类一定要实现一个名为“LoadModel”的方法(签名不限,框架会识别),同样,也是采用约定胜于配置的原则。
‘
----------------------------------------------
在我们建立Action时,有个原则,就是最好和页面名称相同,如果一个名为Index的Action,
那它的页面就应该是Index.aspx/cshtml,
如果你非要建立一个Index1.aspx,那路由是无法找到的,
当然,如果你想让路由找到,就用return View("Index1")来代替return View()就可以了
-------------------------------------------------------