代码改变世界

为视图自定义辅助方法(下)

2009-04-29 22:35  Jeffrey Zhao  阅读(25372)  评论(30编辑  收藏  举报

上一篇文章中,我们把繁冗的客户端脚本变成了可以由Visual Studio提示并轻易输出的服务器端辅助方法。但是,目前的做法还有不少可以改进的地方。我们编写辅助方法的目的便是为了简化开发,因此我们还可以在这条路上走的更远,让开发人员可以在使用我们的API时觉得更流畅,更有快感。

简化入口

目前,我们的辅助方法的使用方法大约如下:

<span>Name: </span> <!-- 必填 -->
<input type="text" name="user.Name" />
<% this.JQuery().Validate().Required("user.Name", "please provide your name!!!"); %>

而内置的辅助方法是这样使用的:

Html.ActionLink("Home", "Index")

比较两者就可以看出差别:

  • 由于只有“扩展方法”没有“扩展属性”,因此JQuery后面的括号不可少。
  • 由于利用了“扩展方法”,因此前面this关键字无法省略。

从某些角度看来,这么做其实并不会造成太大差别。不过在老赵看来,我们在前端定义视图时,我们并非在使用C#进行开发,而是在使用一种DSL——当然,是C#实现的Internal DSL。例如,在上面的代码中访问Html时,老赵并没有过分认为这是一个“属性”。在设计DSL,尤其是Internal DSL的时候,我们往往都需要考虑到实际需求,尽可能得到更加优美的语法。这样看来,this关键字和“作用不大”的括号也像是一种累赘。因此我们做的第一件事情,便是简化辅助方法的入口。

造成“累赘”的关键性因素便是“扩展方法”这个特性,但是如果我们要去除扩展方法的使用,就必须把“入口”直接定义在ViewPage中,这就要求我们的视图继承我们定义的基类,而不是直接继承ViewPage。这一点看上去是额外的工作,但是在实际项目中由于自然需要,我们一般都会定义这样的类型。而即使您的项目还没有自定义的基类,也建议您从一开始便采取这种做法。因为随着项目的发展,您很可能在某一时刻就发现您很渴望有一个统一的地方可以处理一些相同的逻辑,如果您这时再去修改项目中所有的视图,这会是一件劳民伤财的事情。相反,如果您一开始便定义了共同的基类,那么进行这种功能补充就会变得轻而易举。而且,即使您没有为基类添加任何功能,也不会造成任何损失。

目前,我们的基类只需要定义一个JQuery属性便可:

namespace MvcApp.Views
{
    public class ViewPageBase : ViewPage
    {
        private JQueryHelper m_jquery;
        public JQueryHelper JQuery
        {
            get
            {
                if (this.m_jquery == null)
                {
                    this.m_jquery = new JQueryHelper(this.ViewContext, this);
                }

                return this.m_jquery;
            }
        }
    }
}

由于不同的Page对象已经为我们做好了分离,我们已经不需要把JQueryHelper对象放在Page.Items集合中了。而现在,我们便可以使用这样的方式来改写之前的代码:

<span>Name: </span> <!-- 必填 -->
<input type="text" name="user.Name" />
<% JQuery.Validate().Required("user.Name", "please provide your name!!!"); %>

您是否觉得更清楚了一些呢?

链式API

经过改进之后,我们对于“年龄”文本框的判断便可以写成这样了:

<span>Age: </span> <!-- 必填,15到28之间的数字 -->
<input type="text" name="user.Age" />
<% JQuery.Validate().Required("user.Age", null); %>
<% JQuery.Validate().Number("user.Age", null); %>
<% JQuery.Validate().Range("user.Age", 15, 28, null); %>

这里的“坏味道”其实也非常浓郁:为什么user.Age重复了三遍呢?而JQuery.Validate()前缀也反复出现,这也违反了DRY原则,这并不是我们的目的,我们使用编写一套易于使用,流畅的API。说到“流畅”,“链式API”便是其中的典型之一。例如我们在使用StringBuilder的时候,其实便可以使用链式API来拼接字符串:

StringBuilder builder = new StringBuilder();
builder
    .Append("Hello ")
    .AppendLine("World")
    .AppendFormat("{0} {1}", "Hello", "World");

编写这种类型的API其实非常容易,只要每个方法都“返回当前对象”便可(或者可以进行后续操作的新对象)。再考虑到我们需要“定义一个元素”然后“多次使用”以避免反复,我们便可以构造一个新的类型辅助验证信息的收集:

public class JQueryValidation
{
    ...

    public ValidationElement Element(string name)
    {
        return new ValidationElement(name, this);
    }

    public class ValidationElement
    {
        internal ValidationElement(string name, JQueryValidation validation)
        {
            this.Name = name;
            this.Validation = validation;
        }

        public string Name { get; private set; }

        public JQueryValidation Validation { get; private set; }

        ...
    }
}

我们为JQueryValidation定义了一个内部类ValidationElement,并提供一个Element方法用于返回一个ValidationElement对象。在ValidationElement对象内部将保留元素的name以及用于收集信息的JQueryValidation对象。这样我们可以在ValidationElement内部重新定义一些验证方法,并且将调用直接委托给JQueryValidation对象上的方法。当然,每个方法最终还是会把当前ValidationElement对象返回,以便形成链式调用方式:

public class ValidationElement
{
    ...

    public ValidationElement Required(string message)
    {
        this.Validation.Required(this.Name, message);
        return this;
    }

    public ValidationElement Email(string message)
    {
        this.Validation.Email(this.Name, message);
        return this;
    }

    public ValidationElement Number(string message)
    {
        this.Validation.Number(this.Name, message);
        return this;
    }

    public ValidationElement Range(int min, int max, string message)
    {
        this.Validation.Range(this.Name, min, max, message);
        return this;
    }
}

看看结果:

<span>Age: </span> <!-- 必填,15到28之间的数字 -->
<input type="text" name="user.Age" />
<% JQuery.Validate().Element("user.Age")
       .Required(null)
       .Number(null)
       .Range(15, 28, null); %>

感觉如何?

多跨一步

不知您是否想到,其实这两篇文章中编写的辅助方法并非ASP.NET MVC转述,因为ASP.NET MVC使用WebForm来作为默认的视图,因此这些方法都可以轻而易举地用在WebForm模型中。我们唯一需要的就是……要不您自己思考一下?:)

总结

每个优秀的项目都会有一套完备的辅助方法类库来简化开发,这套类库随着项目的进行会不断完善,不断丰富。在编写辅助方法时,有时候会需要您尽可能地想象,不拘一格,唯一的目的便是优美易用的API。