每周源代码17 – ASP.NET MVC共同代码版

[原文发表地址]The Weekly Source Code 17 - ASP.NET MVC Community Code Edition

[原文发表时间] 2008-02-27 1:24 AM

我正在给ASP.NET MVC和Mix会议做新的视频,想看看同行们用ASP.NET MVC的December CTP做出来的代码。在下一个发布的预览版中会有许多很好的改进,这些反馈来源于我们积极的团队分子,所以看看周围的同行们做的一些真实应用的真实代码是很好的资源。

所以,亲爱的读者们,在这里为你们奉上 “每周源代码”的第17篇,之后也会不断继续呈现。这里是我这周在读的一些源代码。

-------------------------------------------------------------

CodeCampServer

这是一个免费的开源Code Camp管理应用,如果你想运行Code Camp,那就从这里开始吧。这里写的是基于12月发布的ASP.NETMVC版本,使用StructureMapIESI CollectionsMVC Contriblog4netRhinoMocksNUnitNHibernate。Aaron Lerch和其他工作人员为Indy Code Camp研发这个应用,如果你在印第安纳珀里斯附近,你可以直接去查看。

好消息

阅读代码有一点很有趣,那就是看同行们命名类,比如 “BetterMousetrap”这样,好像在宣称比别的特定的名称更高级。这个项目就有“BetterMvcHandler”类:

    1: public class BetterMvcHandler : MvcHandler
    2: {
    3:     protected override void ProcessRequest(IHttpContext httpContext)
    4:     {
    5:         try
    6:         {
    7:             base.ProcessRequest(httpContext);
    8:         }
    9:         catch (Exception ex)
   10:         {
   11:             throw new Exception("Route used:" + RequestContext.RouteData.Route.Url, ex);
   12:         }
   13:     }
   14: }

他们所做的一切就是捕捉所有的异常,通过路径来显示当前URL,从而将初始异常降级。对我来说听起来有点玄乎,但值得MVC团队注意的是,我们需要对调试时路径的变化有更多的洞察力。

这里他们用的是RhinoMock所以他们可以不激活ASP.NET/IIS就直接测试他们的控件。他们会在下一个版本改变一些东西,但这个想法是完全正确的。我弄了个足足20分钟的视频来展示怎么做到这个:

    1: namespace CodeCampServer.UnitTests
    2: {
    3:     public static class Extensions
    4:     {
    5:         public static IHttpContext DynamicIHttpContext(this MockRepository mocks, string url)
    6:         {
    7:             IHttpContext context = mocks.DynamicMock();
    8:             IHttpRequest request = mocks.DynamicMock();
    9:             IHttpResponse response = mocks.DynamicMock();
   10:  
   11:             SetupResult.For(context.Request).Return(request);
   12:             SetupResult.For(context.Response).Return(response);
   13:             SetupResult.For(request.AppRelativeCurrentExecutionFilePath).Return(url);
   14:             SetupResult.For(request.PathInfo).Return(string.Empty);            
   15:  
   16:             return context;
   17:         }
   18:     }
   19: }

Demeter法则

创建大量域对象时,就比如在这个应用中,很多考虑的是Demeter法则是否有效。总的来说,这个法则是设计指导,告诉你你的对象需要避免导入其他对象。

比如,在这个应用中,有一个Attendee对象。这是一个Contact对象,含有像FirstName,LastName,Email等等的变量,如果你看到以下内容:

    1: [Test]
    2: public void ShouldGetProperName()
    3: {
    4:     Attendee attendee = new Attendee();
    5:     attendee.Contact.FirstName = "Homey";
    6:     attendee.Contact.LastName = "Simpsoy";
    7:     Assert.That(attendee.GetName(), Is.EqualTo("Homey Simpsoy"));
    8: }

你可能就想问自己如果为了获取FirstName和LastName而达到“两步”是不是一个很好的想法,因为你现在不仅仅是在用caller调用Attendee,还调用了Contact,更不要说你还假设Contact不是空的(null)。如果你很严谨地遵循这个法则,(我没说你必须这么遵循)缺点就是,在顶端对象上你会以一大堆wrapper 函数比如GetFirstName等来结束。这是需要考虑的东西。也许这对所有对象都互相交织的域模型背景下不太有效,但如果你在写a.b().c().d.这些东西,你真的应该钻进去瞧瞧。

辅助作用

在阅读你致力的框架的代码时,还要注意一点,那就是名为“Helper”的函数或者类。这也许是你的框架不能满足开发者需要的另一线索,尤其是用你的框架需要通过很多辅助来达到目的的情况下。

    1: public static class LinkHelpers
    2: {
    3:     public static bool IsActive(this ViewPage thePage, string controller, string action)
    4:     {
    5:         return thePage.ViewContext.RouteData.Values["controller"].ToString() == controller &&
    6:                thePage.ViewContext.RouteData.Values["action"].ToString() == action;            
    7:     }
    8:     
    9:     public static bool IsActive(this ViewMasterPage thePage, string controller, string action)
   10:     {
   11:         return thePage.ViewContext.RouteData.Values["controller"].ToString() == controller &&
   12:                thePage.ViewContext.RouteData.Values["action"].ToString() == action;            
   13:     }
   14: ..

在这个项目中有很多扩展至函数,开发者们以混合插件或扩展函数的形式在ViewPage和ViewMasterPage上接入新函数。有趣的是,他们从没停止使用辅助!我通常在事情开始变坏或者已经进入重构阶段后才创建辅助。通常我们应该对项目先有一个整体的视角,而不要在面对未来预期痛苦时创建一系列辅助项或者相关加载项。

重申下,我不是针对这个项目来说的,只是借它作为一个点来讨论下。读读代码,发发牢骚实属简单。就像Linus喜欢说的,“说说没什么实质,给我看代码才是真的。”

--------------------------------------------------------------

ASP.NET MVC中的Kigg- Digg Clone现场实录

有篇捧场文章写了Kazi Manzur RashidSonu是怎么写这个Digg Clone的。他们在他们的应用中做了很多有趣的事情。

JSON和Ajax

他们使用的是新加入.NET 3.5的DataContractJsonSerializerr,存在于System.Runtime.Serialization.Json,能让你序列化Javascript Object Notation (JSON)中的对象,或把对象序列到JSON中。他们创建了一个json.aspx文件,以获取当前ViewData并将其序列化至JSON:

    1: Response.Clear();
    2: Response.ContentType = "application/json";
    3: Response.Write(ViewData.ToJson());

很简单而且有效。无论什么时候controller想做JSON,他们只要调用ReviewView(“Json”,result)。我个人会做一个JsonViewEngine,让控件在需要的时候设置其ViewEngine,但假设那可能呈现RenderView抽象函数的第一个参数。

他们很好地使用了LINQ,阅读清晰的LINQ查询总是会让我们感到愉快。

    1: public TagItem[] GetTags(int top)
    2: {
    3:    return Tags.Select (
    4:                   t => new TagItem {
    5:                       ID = t.ID,
    6:                       Name = t.Name,
    7:                       Count = t.StoryTags.Count()
    8:                   }
    9:                )
   10:                .OrderByDescending(t => t.Count)
   11:                .ThenBy(t => t.Name)
   12:                .Take(top)
   13:                .ToArray();
   14: }

复杂的View和ViewData

主页很复杂,包括各种地方的大量数据,列表库,标签云和分类。另外页面上还有很多种表格(想想你以前有很多表格的网页),以搜索框为例。他们创建了一个抽象BaseViewData基类,为他们的特定试图添加额外的数据。

比如,Story Detail 页面被加到标准ViewData中:

    1: public class StoryDetailData : BaseViewData
    2: {
    3:     public StoryDetailItem Story
    4:     {
    5:         get;
    6:         set;
    7:     }
    8: }

然后StoryController就有了这个聪明的函数,只要type T是一个BaseViewData,有一个公共无参数构造函数(在最后的new()),就可以获取ViewData。

    1: private T GetViewData<T>() where T : BaseViewData, new()
    2: {
    3:     T viewData = new T();
    4:  
    5:     viewData.IsAuthenticated = IsUserAuthenticated;
    6:     viewData.UserName = CurrentUserName;
    7:     viewData.Categories = DataContext.GetCategories();
    8:     viewData.Tags = DataContext.GetTags(_topTags);
    9:  
   10:     return viewData;
   11: }

它生成由基类共享的函数,并返回派生类型。如果不是有些想法,我绝不会自己想到这些的。

这些项目各有特点,不过每个都是写ASP.NET MVC应用的很好的灵感来源。


阅读相关的文章和链接