在过去Win32编程时代,我们看到的程序界面都是由静态编程语言,从一个按钮的尺寸到布局,一行行地绘制出来。比如现在我们创建一个.Net WinForm窗体,打开其对应的designer.cs文件,就会看到长篇累牍窗体绘制代码。

打从VB和Delphi出现后,尽管通过界面设计器自动生成代码成为主流,然而随之互联网时代的发展,我们需要丰富多样的软件界面,更灵活地应对需求变动。比如一个软件往往有多种界面,包括C/S和B/S,C/S中包括Windows/Linux/IPhone,B/S包括IE/Firefox/Chrome。无论是先设计业务,还是先构勒界面,最好能独立、清晰、明确的进行。更不想影响业务逻辑。将业务逻辑和用户界面分离,实现低耦合,变得势在必行。

对于三层架构的概念,相信就是初学者也耳熟能详了。对于数据访问层(DAL)和业务逻辑层(Business Logic Layer)之间的分离,比较容易理解,也不难实现。但对于业务逻辑层和界面层(UI)的分离,就不是那么泾渭分明,至少我在很长时间里都比较模糊。

这一方面由于理念认识的距离,一方面由于以前的框架没给予有力支持。

我主要从事的是.Net Web开发,闲暇里也做一些C/S程序练练手。在初学编程时,我更倾向C/S开发,因WinForm中用的是统一的强类型语言,而B/S或Asp.Net开发中,众所周知,还需要掌握HTML/CSS/JavaScript。但随之这些技能障碍被逐渐克服,以及Asp.Net开发经验的积累,我的认识发生颠覆性变化,感觉开发Asp.Net要比WinForm更清晰,更容易把握,至少不会为改一个字体或效果重新发布整个程序,除非你把这些植写到配置文件中。

可如果你运用了像Asp.Net那样的视图分离技术,相当于,一切UI皆可配置,而且还可以内容与样式分离,只修改样式时可以避免修改页面导致的自动编译,只要更新CSS即可。

视图分离另一个好处是直观,无须费神地分析一大段代码,只要瞄一眼视图模板的结构,一切就了然于心。无论是设计、开发或维护,都可以大大提高效率。正如下面语句,

var content = string.Format(@"Dear {0},

                        Thank you for {1}.

                        {2}", receiver, thing, sender);

 

 

要优于:

var content = "Dear " + receiver + ",\r\nThank you for " + thing + ".\r\n" + sender;

在过去,应该是在2.0以前,Asp.Net只有页面,还有很多人固执地以C/S开发的思想做B/S,在页面后端类中生成控件,甚至直接输出html文本。 而随着MVC、Razor等技术的推广,页面的概念逐渐被模板取代。WPF和Silverlight技术,就是Asp.Net的视图分离思想在C/S开发上的移植。时至今日,视图分离技术取得了压倒性的主流地位。

还可以再兴几个视图分离技术的例子。

Visual Studio集成的ADO.Net Entity设计器(请参考园子的AEF专题):

 image

Visual Studio集成的工作流设计器(请参考麒麟同学的WF专题):

image

RDLC格式报表(请参考MSDN演练:创建 ReportViewer 报表):

image

这些视图都是基于扩展的XML定义。Html自然还是最常见XML扩展。随着浏览器差异的缩小,当你掌握Html/CSS后,开发B/S系统体验要更胜于C/S。

视图分离已不只是一门技术,而是软件设计中的重要思想,它分离了界面与业务逻辑的耦合,用途非常广泛。

最近开发一些系统服务,服务会定时做一些数据存取和分析工作,然后要将运行结果用邮件发送出去。邮件内容,要反映服务执行过程的细节。开始时做完是先定一个StringBuilder,每执行完一步,就写入执行结果,这样对简单的业务流程没问题。但实际上由于有些执行流程很长,分支很多,生成邮件内容的代码充斥各个角落,写起来很烦,维护时的可读性也很差。有时邮件格式要求稍为复杂一点,比如包含表格,开发负担会更重。 而如果最终呈现的邮件内容在在一些内在逻辑(比如对数据分析后,如果满足一定条件就生成一个报表),那就非常繁琐,会弄得你抓狂。

常言道,穷则思变,我把目光瞄向了视图分离。下面,将介绍邮件生成将如何运用视图分离的技术。

第一种方式很简单,也很好用,就是做一个邮件文本模板,将动态的要运行时生成的内容,用特殊符号标记。其实和String.Format方法的原理一样,不过待替换的字段最好不用{0}、{1}这样编号,而是用名称比如{地址}、{电话}等,可读性更好。在具体应用中,只要将动态内容以键值对形式添加到Dictionary中,而无须关心顺序问题。

这种方式用得很多,我看到普遍的问题是,大家都忽视了对替换内容与模板之间匹配的验证。Dictionary中的各个键,应该与模板中被替换的字段一致,不能多不能少,否则应该抛出异常,这可以有效提高系统可维护性。

MailBuilder类是用于创建内容基于模板的邮件,下面是它的属性:

public class MailBuilder
{
    /// <summary>
    /// The fields to be replaced.
    /// </summary>
    protected Dictionary<string, string> fields = new Dictionary<string, string>();
    /// <summary>
    /// The resources embedded in the mail.
    /// </summary>
    protected List<LinkedResource> resources = new List<LinkedResource>();
    /// <summary>
    /// The attachments to be sent out with the mail.
    /// </summary>
    protected List<Attachment> attachments = new List<Attachment>();
    protected SmtpClient client = new SmtpClient();

    #region Properties

    /// <summary>
    /// SMTP server location.
    /// </summary>
    public string Host { get; set; }

    /// <summary>
    /// Smtp credentials.
    /// </summary>
    public ICredentialsByHost Credentials { get; set; }

    /// <summary>
    /// Smtp port.
    /// </summary>
    public int Port { get; set; }

    /// <summary>
    /// Content of mail template.
    /// </summary>
    public string Template { get; set; }

    string tempFile;
    /// <summary>
    /// File path of mail template.
    /// </summary>
    public string TemplateFile
    {
        get { return tempFile; }
        set
        {
            tempFile = value;
            this.Template = File.ReadAllText(tempFile);
        }
    }
    /// <summary>
    /// Subject of mail
    /// </summary>
    public string Subject { get; set; }

    /// <summary>
    /// FROM address
    /// </summary>
    public string From { get; set; }

    /// <summary>
    /// TO addresses
    /// </summary>
    public string[] To { get; set; }

    /// <summary>
    /// CC addresses
    /// </summary>
    public string[] CC { get; set; }

    /// <summary>
    /// BCC addresses
    /// </summary>
    public string[] Bcc { get; set; }

    /// <summary>
    /// Priority
    /// </summary>
    public MailPriority Priority { get; set; }

    /// <summary>
    /// Indicates result message for sending email.
    /// </summary>
    public string Result { get; private set; }

    /// <summary>
    /// Get or sets the reply to address of the mail message.
    /// </summary>
    public string ReplyTo { get; set; }

    /// <summary>
    /// Indicate whether need to return receipt. If needed, the ReplyTo value should be set first.
    /// </summary>
    public bool ReturnReceipt { get; set; }
    #endregion

    …… ……
}

这个类除有添加替换字符串AddField方法外,还支持添加DataTable和实体集合,将被转换为相应的Html表格内容。

/// <summary>
/// Add a key-value pair
/// </summary>
/// <param name="key">key's name</param>
/// <param name="value">value</param>
public void AddTable(string key, DataTable dt)

/// <summary>
/// Add entities to render them as a html table.
/// </summary>
/// <typeparam name="T">Entity type</typeparam>
/// <param name="key"></param>
/// <param name="entities">Entities to be rendered</param>
/// <param name="columns">The corresponding html table's columns.</param>
/// <param name="rowValues">Function to get values of a row from an entity.</param>
public void AddEntities<T>(string key, IEnumerable<T> entities, string[] columns, Func<T, object[]> rowValues)

经过一段时间的使用,感觉还是相当顺手的,因此觉得拿出晒晒,希望大家不吝赐教。

纯文本邮件模板虽然简单方便,但对于实现逻辑复杂一点的邮件,就有点力不从心。比如邮件要示,未尾的提示,要根据前面的内容决定是否显示,我们总不能为一点差别用两个不同模板吧。于是,这种情况下我们面临新的课题—动态模板技术。其实这也不难,因为我们早就有合适的方案,就是老而弥坚XML + XSLT技术。

这种方式,模板换成了XSLT文件。XSLT不只可以转换XML数据,还可以将动态内容以参数方式传入,.Net甚至支持XSLT中嵌入Javascript和C#。不过不推荐嵌入C#调用外部函数,那会破坏视图的本质,一般前两种方式足够了。我倾向于XML传值,有层次感,贴一下转换的XSLT的代码:

/// <summary>
/// Transform XSLT file to HTML.
/// </summary>
/// <param name="template">Template's content.</param>
/// <param name="dicField">The fields to be filled in corresponding place of the template</param>
public static string TransformXsl(string template, Dictionary<string, string> dicField)
{
    var elements = from field in dicField
                   select new XElement(field.Key, field.Value.Contains("<table") ? (object)XElement.Parse(field.Value) : (object)field.Value);

    var xe = new XElement("Root", elements);

    using (var writer = new StringWriter())
    using (var reader = XmlReader.Create(new StringReader(template)))
    {
        var xslTransform = new System.Xml.Xsl.XslCompiledTransform();
        xslTransform.Load(reader);
        xslTransform.Transform(xe.CreateReader(), null, writer);
        return writer.ToString();
    }
}

介绍XSLT转换的文章很多,大家可以参考一下,随便找两篇:如何使用JavaScript 结合XSLT转换XML文档, 博客园备份档案浏览的小工具

这种方式的主要缺点是要求有一定XSLT语言基础,临阵磨枪学了点XSLT,对付小型应用还凑合,将来要是大规模应用,还是得专业点才行。

我无耻地希望,门槛可以再低点……我们学的语言已经够多了。

posted on 2011-11-18 14:45  小城故事  阅读(2269)  评论(0编辑  收藏  举报