[转载]重构代码的7个阶段

mikel阅读(793)

[转载]重构代码的7个阶段 | 酷壳 – CoolShell.cn.

你曾去想重构一个很老的模块,但是你只看了一眼你就恶心极了。文档,奇怪的函数和类的命名,等等,整个模块就像一个带着脚镣的衣衫褴褛的人,虽然能 走,但是其已经让人感到很不舒服。面对这种情况,真正的程序员会是不会认输的,他们会接受挑战认真分析,那怕重写也在所不惜。最终那个模块会被他们重构, 就像以前和大家介绍过的那些令人销魂的编程方式中的屠宰式编程一样。下面是重构代码的几个阶段,文章来自:The 7 stages of refactoring,下面的翻译只是意译。

第一阶段 – 绝望

在你开始去查看你想要重构的模块的,你会觉得好像很简单,这里需要改一个类,那里需要改两到三个函数,重写几个 函数,看上去没什么大不了的,一两天就搞定了。于是你着手开始重构,然后当你调整重构了一些代码,比如改了一些命名,修理了一些逻辑,渐渐地,你会发现这 个怪物原来体型这么大,你会看到与代码不符甚至含糊不清的注释,完全摸不着头脑的数据结构,还有一些看似不需要方法被调了几次,你还会发现无法搞清一个函 数调用链上的逻辑。你感到这个事可能一周都搞不定,你开始绝望了。

第二阶段 – 找最简单的做

你承认你要重构的这个模块就是一个可怕的怪物,不是一两下就可以搞定的,于是你开始着干一些简单的事,比如重新命名一下几个函数,移除一些代码的阻碍,产生几个常量来消除magic number,等等,你知道这样做至少不会让代码变得更糟糕。

第三阶段 – 再次绝望

但是接下来的事会让你再次撞墙。你会发现那些代码的瑕疵是些不痛不痒的事,改正这些事完全于事无补,你应该要做 的事就是重写所有的东西。但是你却没有时间这么干,而这些代码剪不乱理还乱,耦合得太多,让你再一次绝望。所以,你只能部分重写那些不会花太多时间的部 分,这样至少可以让这些老的代码能被更多的重用。虽然不完美,但是至少可以试试。

第四阶段 – 开始乐观

在你试着部分重构这个模块几天之后,随着重构了几个单元后,虽然你发现改善代码的进度太慢了,但此时,你已知道代码应该要被改成什么样,你在痛苦之 后也锁定了那些那修改的类。是的,虽然你的时间预算已经超支,虽然要干的事比较多,但你还是充满希望,觉得那是值得的。你胸中的那团火又被点燃了。

第五阶段  – 快速了结

在这个时候,你发现你已花了太多的时间,而情况越来越复杂,你感到你所面对的情况越来越让你越到不安,你明白你自己已经陷入了困境。你原本以为只需 要一次简单的重构,然而现在你要面对的是重写所有的东西。你开始意识到原因是因为你是一个完美主义者,你想让代码变得完美。于是你开始在怠慢你文档,并想 找到一个捷径来重写老的代码,你开始采用一些简单而粗暴,快速而有点肮脏的方法。虽然不是很完美,但你就是这样去做了。然后,你开始运行测试做UT,发现 UT报告上全是红色,几乎全都失败了,你恐慌了,于是快速地fix代码,然后让UT 能工作。此时,你拍拍自己胸口,说到,没问题 ,于是就把代码提交了。

第六阶段 – 修改大量的Bug

你的重写并不完美,虽然其过了测试,但是那些UT测试对于你的新的代码有点不太合适,虽然他们都没有报错,但是他们测试得范围太小了,没有覆盖到所 有的情况和边界。所以,在这以后,你还需要几周或是更长的时间不得不来修正越来越多的bug,这使得你的设计和代码在每一次quick-fix后就变得越 来越难看。此时,代码已经不像你所期望的那样完美了,但你依然觉得他还是比一开始要好一些。这个阶段可能历经几个月。

第七阶段  – 觉悟

经过了6个月,你重写的模块又出了一个比较严重的bug。这让你重构的那个模块变得更难堪。你发现出的这个问题是和当初的设计不一致,你还发现被你 重构掉的那段老的代码并不是当初看上去的那么坏,那段老的代码确实考虑到了一些你未曾考虑到的事情。这个时候,你团队里有人站出来说这个模块应该被重构或 是重写,而你却不动声色地一言不发,并希望那个站出来的人能在几个月后能觉悟起来。

——————

不知道这是不是你的经历,我经历过很多次这样的事。对于很多维护性质的项目,我犯过的错误让我成了一个实实在在的保守派,我几乎不敢动,那怕看到代 码很不合口味。当然,那些从来没有写过代码的敏捷咨询师一定会说用TDD或是UT可以让你的重构更有效也更容易,因为这样会让他们显得更我价值,但我想告 诉你,这种脱离实际的说法很不负责任,这就好比说—— 我在杀猪的时候遇到了一些麻烦,因为我对猪的生理结构不清楚,或是这本来就是一头畸形的猪,导致我杀的猪很难看,而伟大的敏捷咨询师却告诉我,要用一把更快更漂亮的刀。软件开发永远不是那么简单的事,杀猪也一样。

[转载]Repository模式

mikel阅读(1447)

[转载]Repository模式.

近来发现很多ASP.NET MVC的例子中都使用了Repository模式,比如Oxite,ScottGu最近发布的免费的ASP.NET MVC教程都使用了该模式。就简单看了下。

在《企业架构模式》中,译者将Repository翻译为资源库。给出如下说明:
通过用来访问领域对象的一个类似集合的接口,在领域与数据映射层之间进行协调。

在《领域驱动设计:软件核心复杂性应对之道》中,译者将Repository翻译为仓储,给出如下说明:
一种用来封装存储,读取和查找行为的机制,它模拟了一个对象集合。

使用该模式的最大好处就是将领域模型从客户代码和数据映射层之间解耦出来。

我们来看下在LinqToSQL中如何应用该模式。
1. 我们将对实体的公共操作部分,提取为IRepository接口,比如常见的增加,删除等方法。如下代码:

interface IRepository<T> where T : class
{
    IEnumerable<T> FindAll(Func<T, bool> exp);
    void Add(T entity);
    void Delete(T entity);
    void Save();
}

2.下面我们实现一个泛型的类来具体实现上面的接口的方法。

public class Repository<T> : IRepository<T> where T : class
{
    public DataContext context;
    public Repository(DataContext context)
    {
        this.context = context;
    }
    public IEnumerable<T> FindAll(Func<T, bool> exp)
    {
        return context.GetTable<T>().Where(exp);
    }
    public void Add(T entity)
    {
        context.GetTable<T>().InsertOnSubmit(entity);
    }
    public void Delete(T entity)
    {
        context.GetTable<T>().DeleteOnSubmit(entity);
    }
    public void Save()
    {
        context.SubmitChanges();
    }
}

3.上面我们实现是每个实体公共的操作,但是实际中每个实体都有符合自己业务的逻辑。我们单独定义另外一个接口,例如:

interface IBookRepository : IRepository<Book>
{
    IList<Book> GetAllByBookId(int id);
}

4.最后该实体的Repository类实现如下:

public class BookRepository : Repository<Book>, IBookRepository
{
    public BookRepository(DataContext dc)
        : base(dc)
    { }
    public IList<Book> GetAllByBookId(int id)
    {
        var listbook = from c in context.GetTable<Book>()
                       where c.BookId == id
                       select c;
        return listbook.ToList();
    }
}

上面只是为大家提供了一个最基本使用框架。

作者:生鱼片
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
精彩评论:
进一步讨论一下,感觉使用泛型 Repository接口并不太合适,因为Repository接口是提供给Domain层的操作契约,不同的entity对于Domain来说可能有不 同的操作约束,比如User可能不应该被删除,bookOrder可能不应该被修改,也就是说domain层根本就不应该能调用 _repository<User>.Delete(user),_repository<BookOrder>.Update(bookOrder) 这样的操作.
因此Repository接口还是应该单独针对每个Eneity类来定义,比如User
interface IUserRepository
{
IEnumerable<User> FindAllUser();
void Add(User user);
//void Delete(User user); //User不应该能被删除,因此接口中没有此方法
string GetPassword(int userId);//User特有的操作
}
而泛型的Repository<T>类仍然用来减少重复代码,只是不能被UserRepository类直接继承,因为这样 Delete方法将侵入User类,所以改为在UserRepository中组合一个Repository<T>,将开放给domain可 见且又能使用泛型重用的功能委托给这个Repository<T>,
public class UserRepository : IUserRepository
{
private DataContext context;
private Repository<User> internalGenericRepository;

public BookRepository(DataContext dc)
{
this.context = dc;
this.internalGenericRepository = new Repository<User>(dc);
}

public void Add(User user);
{
this.internalGenericRepository.Add(user);
}

public string GetPassword(int userId)
{
string password = context.User.where(u=>u.id==userId).select(u=>u.password);
}

}

也就是说泛型接口->泛型类->具体接口继承泛型接口->具体类继承具体接口和泛型类的方式变为
具体接口->具体类继承具体接口+组合泛型类

再补充两句,IUserRepository和通常三层架构的IUserDal从形式和功能上看差不多,个人感觉区别两者在意图上有所不同.
Repository是DDD中的概念,强调Repository是受Domain驱动的,Repository中定义的功能要体现Domain的意图和约束,而Dal更纯粹的就是提供数据访问的功能,并不严格受限于Business层.
就拿上面写的User的例子来说,使用Repository,隐含着一种意图倾向,就是Domain需要什么我才提供什么,不该提供的功能就不要提供,一切都是以Domain的需求为核心;
而使用Dal,其意图倾向在于我Dal层能使用的数据库访问操作提供给Business层,你Business要用哪个自己选.换一个Business也可以用我这个Dal,一切是以我Dal能提供什么操作为核心.

[新闻]谷歌125亿美元收购摩托罗拉移动

mikel阅读(989)

大早晨打开电脑就弹出窗口“谷歌125亿美元收购摩托罗拉移动”,好消息啊!

新闻内容:

北京时间8月15日消息,据国外媒体报道,谷歌和摩托罗拉移动今天宣布,两家公司已经达成一致协议,谷歌将以每股40美元现金的价格收购摩托罗拉移动。这 笔交易的总价值达到约125亿美元,按照上周五的收盘价计算,该收购价比摩托罗拉移动的股价溢价63%。这笔交易已经分别获得谷歌和摩托罗拉移动董事会的 一致通过。

不知道google又要有什么大动作,下面是谷歌CEO拉里-佩奇(Larry Page)说是为了巩固Android的地位?!看来Google从微软和苹果的专利联合攻击中发现“搞硬件才能拳头硬”

以下为佩奇声明全文:

自2007年11月推出以来,Android不仅极大地丰富了用户的选择空间,而且还改善了用户的整个移动使用体验。目前,全球累计激活的Android设备超过1.5亿部,每天激活的设备数量高达55万部,这些来自全球39家硬件设备厂商的Android设备登陆了123个国家的231家运营商的网络。鉴于Android所取得的巨大成功,我们一直在寻求新的方式来进一步巩固Android的市场地位。这就是我今天如此高兴地宣布收购摩托罗拉移动的原因所在。

在通信技术、移动产品和知识产权开发三大领域,摩托罗拉有着80多年的创新历史,正是这些创新推动着我们今天的移动计算领域不断向前发展。摩托罗拉的里程碑主要包括:约30年前推出全球首款便携式手机,当时全球最小、最轻的手机StarTAC。 2007年,摩托罗拉率先参与组建了“开放手机联盟”(Open Handset Alliance),共同推广Android平台。我个人一直非常欣赏摩托罗拉手机手机,从当时的StarTAC到现在的DROID系列。

2008年,摩托罗拉就把全部的赌注都压在了Android系统身上,将Android作为其全部智能手机的唯一操作系统。这是一个明智的选择,我们为摩托罗拉今天所取得的成功感到无比兴奋。我们相信摩托罗拉的移动业务将继续呈现上行趋势,并获得爆炸式增长。

摩托罗拉同时还是家庭设备和视频解决方案市场的领跑者。由于整个市场向互联网领域转移,我们非常乐于与摩托罗拉和整个行业合作来为我们的伙伴提供支持,推动市场创新。

摩托罗拉全身心地投入到Android平台的发展是我们收购摩托罗拉的原因之一。除此之外,两家公司还有很多共同之处。合并后,我们将为用户提供更出色的用户体验,从而进一步巩固整个Android生态系统,这对用户、合作伙伴和开发人员来说都是十分有利的。而且收购摩托罗拉还会进一步增强谷歌的专利实力,以更好地保护Android系统免受微软、苹果和其他公司的侵权诉讼纠纷。

该交易不仅会进一步巩固Android的市场地位,而且还将提升移动操作系统市场的竞争激烈程度,并为消费者提供持续的创新产品、丰富的选择空间以及完美的使用体验。我坚信,这些体验将会为我们的股东创造出更多的价值。

我非常欢迎摩托罗拉员工成为谷歌大家庭中的一员。

[转载]用TCP/IP实现自己简单的应用程序协议:其余部分

mikel阅读(887)

[转载]用TCP/IP实现自己简单的应用程序协议:其余部分 – 浪雪 – 博客园.

接着上次用TCP/IP实现自己简单的应用程序协议:成帧器部分,现在把接下来的部分也说完

代码下载:

http://download.csdn.net/source/3519903

客户端的调用

    public class VoteClientTCP {

      public static int CANDIDATEID = 888;//随便写了一个      

      public static void Main(String[] args) {

        int port = 5555;
        IPEndPoint ipep = new IPEndPoint(GetLocalhostIPv4Addresses().First(), port);

        Socket sock = new Socket(AddressFamily.InterNetwork,
                    SocketType.Stream, ProtocolType.Tcp);
        try
        {
            sock.Connect(ipep);
        }
        catch (SocketException e)
        {
            Console.WriteLine("Can not connect {0} ,reason:{1},NativeErrorCode:{2},SocketErrorCode:{3}", ipep.Address, e.Message, e.NativeErrorCode, e.SocketErrorCode);
            Console.ReadKey();
            return;
        }

        IVoteMsgCoder coder = new VoteMsgTextCoder();

        IFramer framer = new LengthFramer(sock);

        VoteMsg msg = new VoteMsg(true, false, CANDIDATEID, 1);
        byte[] encodedMsg = coder.toWire(msg);

        // 发送查询
        Console.WriteLine("Sending Inquiry (" + encodedMsg.Length + " bytes): ");
        framer.frameMsg(encodedMsg);

        // 投第一票给候选人888
        msg.IsInquiry = false;
        encodedMsg = coder.toWire(msg);
        Console.WriteLine("Sending Vote (" + encodedMsg.Length + " bytes): ");
        framer.frameMsg(encodedMsg);

        // 投第二票给候选人888
        msg.IsInquiry = false;
        encodedMsg = coder.toWire(msg);
        Console.WriteLine("Sending Vote (" + encodedMsg.Length + " bytes): ");
        framer.frameMsg(encodedMsg);

        // 再次查询
        msg.IsInquiry = true;
        encodedMsg = coder.toWire(msg);
        Console.WriteLine("Sending Inquiry (" + encodedMsg.Length + " bytes): ");
        framer.frameMsg(encodedMsg);
        
        encodedMsg = framer.nextMsg();
        msg = coder.fromWire(encodedMsg);
        Console.WriteLine("Received Response (" + encodedMsg.Length
                   + " bytes): ");
        Console.WriteLine(msg);


        msg = coder.fromWire(framer.nextMsg());
        Console.WriteLine("Received Response (" + encodedMsg.Length
               + " bytes): ");
        Console.WriteLine(msg);

        msg = coder.fromWire(framer.nextMsg());
        Console.WriteLine("Received Response (" + encodedMsg.Length
               + " bytes): ");
        Console.WriteLine(msg);

        msg = coder.fromWire(framer.nextMsg());
        Console.WriteLine("Received Response (" + encodedMsg.Length
               + " bytes): ");
        Console.WriteLine(msg);

        sock.Shutdown(SocketShutdown.Both);
        sock.Close();
        Console.ReadLine();
      }

    //辅助方法
      public static IPAddress[] GetLocalhostIPv4Addresses()
      {
          IPHostEntry hostEntry = Dns.GetHostEntry(Dns.GetHostName());
          List<IPAddress> list = new List<IPAddress>();
          foreach (IPAddress address in hostEntry.AddressList)
          {
              if (address.AddressFamily == AddressFamily.InterNetwork)
              {
                  list.Add(address);
              }
          }
          return list.ToArray();
      }

    }

服务端接收并返回数据:

    public class VoteServerTCP {

      public static void Main(String[] args){

        int port = 5555;

        Socket servSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );;
        //编码对象
        IVoteMsgCoder coder = new VoteMsgTextCoder();
          //服务对象
        VoteService service = new VoteService();
        //创建IP终结点
        IPAddress localAddress = AddressHelper.GetLocalhostIPv4Addresses().First();        
        IPEndPoint ipep = new IPEndPoint(localAddress, port);

        servSock.Bind(ipep);
        servSock.Listen(1);

        while (true) {
          Socket clntSock = servSock.Accept();
          Console.WriteLine("Handling client at " + clntSock.AddressFamily);

          IFramer framer = new LengthFramer(clntSock);
          try {
            byte[] req;
            while ((req = framer.nextMsg()) != null) { 
              Console.WriteLine("Received message (" + req.Length + " bytes)");
              VoteMsg responseMsg = service.handleRequest(coder.fromWire(req));
                //发回反馈消息
              framer.frameMsg(coder.toWire(responseMsg));
            }
          } catch (SocketException soe) {
              //比如说客户端提发送多条消息,又在全部接收前关闭socket时就会引发此类异常。
            Console.WriteLine("Error handling client: " + soe.Message);
          } finally {
            Console.WriteLine("Closing connection");
            clntSock.Close();
          }
        }
      }
    }

对字符串编码的简单实现

    public class VoteMsgTextCoder : IVoteMsgCoder
    {
        // 下面是需要进行编码的字段
        public static readonly String MAGIC = "Voting";
        public static readonly String VOTESTR = "v";
        public static readonly String INQSTR = "i";
        public static readonly String RESPONSESTR = "R";

        public static readonly String DELIMSTR = " ";
        UTF8Encoding encoder = null;


        public static readonly int MAX_WIRE_LENGTH = 2000;


        public VoteMsgTextCoder()
        {
            encoder = new UTF8Encoding();
        }



        public byte[] toWire(VoteMsg msg)
        {
            String msgString = MAGIC + DELIMSTR + (msg.IsInquiry ? INQSTR : VOTESTR)
                + DELIMSTR + (msg.IsResponse ? RESPONSESTR + DELIMSTR : "")
                + msg.CandidateID.ToString() + DELIMSTR
                + msg.VoteCount.ToString();
            byte[] data = encoder.GetBytes(msgString);
            return data;
        }

        public VoteMsg fromWire(byte[] message)
        {

            bool isInquiry;
            bool isResponse;
            int candidateID;
            long voteCount;
            string token;

            string StringSentByClient = Encoding.UTF8.GetString(message);
            string[] fields = StringSentByClient.Split(new char[] { ' ' });

            try
            {
                token = fields[0];
                if (!token.Equals(MAGIC))
                {
                    throw new IOException("Bad magic string: " + token);
                }
                token = fields[1];
                if (token.Equals(VOTESTR))
                {
                    isInquiry = false;
                }
                else if (!token.Equals(INQSTR))
                {
                    throw new IOException("Bad vote/inq indicator: " + token);
                }
                else
                {
                    isInquiry = true;
                }

                token = fields[2];
                if (token.Equals(RESPONSESTR))
                {
                    isResponse = true;
                    token = fields[3];
                }
                else
                {
                    isResponse = false;
                }

                candidateID = int.Parse(token);
                if (isResponse)
                {
                    token = fields[4];
                    voteCount = long.Parse(token);
                }
                else
                {
                    voteCount = 0;
                }
            }
            catch (IOException ioe)
            {
                throw new IOException("Parse error...");
            }

            return new VoteMsg(isInquiry, isResponse, candidateID, voteCount);

        }
    }

运行结果:

[转载]用TCP/IP实现自己简单的应用程序协议:成帧器部分

mikel阅读(824)

[转载]用TCP/IP实现自己简单的应用程序协议:成帧器部分 – 浪雪 – 博客园.

在前面《字节和字符,对信息进行编码》,《Socket=>流,TCP连接,TCP可靠性概述》一系列的随笔中我们已经表述了相应的理论知识,现在可以动手实现一个自己的应用程序协议。

将 数据转换成在线路上传输的字节序列只完成了一半的工作,在接收端还必须将接受到的字节序列还原成原始信息。如果以流作为传输的形式,那么首先面临的问题就 是在接收端如何确定这是一条消息,换句话说就是如何定位一条消息的开始和结束。值得注意的是,这个工作应该是在应用程序协议这一层来完成而不是在TCP这 一层来完成,应用程序协议必须指定消息的接受者如何确定何时消息已完整接收。


TCP协议中没有消息边界的概念,这会让我们在解析信息的时候产生一些问题。
如果接收者试图从套接字中读取比消息本身更多的字节,将可能发生以下两种情况:
1.如果信道中没有其他消息,接收者将阻塞等待,同时无法处理接收到的消息;如果发送者也在等待接收端的响应消息,那么就会造成“死锁”

2.如果信道中还有其他消息,则接收者会将后一条的消息的一部分甚至全部读取到第一条消息中,这将会产生一些“协议错误”

因此,在时候流TCP套接字的时候,成帧就是一个非常重要的考虑因素。

对于成帧,主要有两个技术能使接收者能够准确地找到消息的结束位置:
1.消息的结束由一个特殊的标记指明,比如把一个特殊的字节序列0001等显式添加到一个消息的结束位置。这里的限制就在于传输的内容中不能包含和该特殊字节序列中一样的字符。就像HTML中<和>符号不能直接包含在输出中,这时需要转义。

2.显式的告知长度。
在变长字段或消息前面附加一个固定的字段,用来表示该字段或者消息中包含了多少个字节。

我们来写一个网络上常见的投票来作为例子:
这个例子包含了两种类型的请求,一种是“查询”的请求,也就是查询当前的候选人获得的选票情况。
第二种是“投票”请求,服务器保存此次投票信息,并返回投完票后该候选人获得的结果。

在实现一个协议的时候,定义一个专门的类来存放消息中所包含的信息是大有裨益的。类提供了给我们封装的能力,通过属性来公开类中的可变字段,也可以维护一些不变的字段。

我在这里采用的发送消息大小的方式来确定一条完整的消息。

项目结构和功能说明如下:

IFramer接口的定义:
namespace VoteForMyProtocol
{
    public interface IFramer
    {
        void frameMsg(byte[] message);
        byte[] nextMsg();
    }
}

基于长度成帧的实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;

namespace VoteForMyProtocol
{

    public class LengthFramer : IFramer {
        public static readonly int MAXMESSAGELENGTH = 65535;
        Socket s = null;

        public LengthFramer(Socket s)
        {
            this.s = s;
        }

        //把消息成帧并发送
        public void frameMsg(byte[] message){
            if (message.Length > MAXMESSAGELENGTH) {
              throw new IOException ("message too long");
            }

            int totalSent = 0;
            int dataLeft = message.Length;     //剩余的消息
            int thisTimeSent;
            //保存消息长度
            byte[] datasize = new byte[4];
            datasize = BitConverter.GetBytes(message.Length);
            //将消息长度发送出去
            thisTimeSent = s.Send(datasize);
            //发送消息剩余的部分
            while (totalSent < message.Length)
            {
                thisTimeSent = s.Send(message, totalSent, dataLeft, SocketFlags.None);
                totalSent += thisTimeSent;
                dataLeft -= thisTimeSent;
            }
        }

        //按帧来解析消息
        public byte[] nextMsg(){
            if (s == null)
                throw new ArgumentNullException("socket null");
            int total = 0;  //已接收的字节数
            int recv;
            //接收4个字节,得到“消息长度”
            byte[] datasize = new byte[4];

            //如果当前使用的是面向连接的 Socket,则 Receive 方法将读取所有可用的数据,直到达到 size 参数指定的字节数。
            //如果远程主机使用 Shutdown 方法关闭了 Socket 连接,并且所有可用数据均已收到,则 Receive 方法将立即完成并返回零字节。 
            recv = s.Receive(datasize, 0, 4, 0);
            if (recv < 4)
                return null;

            int size = BitConverter.ToInt32(datasize, 0);

            //按消息长度接收数据
            int dataleft = size;
            //容器装满了就证明收集到了一条完整的消息。
            byte[] data = new byte[size];
            //直到容器填满再返回
            while (total < size)
            {
                recv = s.Receive(data, total, dataleft, 0);
                total += recv;
                dataleft -= recv;
                if (dataleft == 0)
                {
                    break;
                }
            }
            return data;
        }

    }
}

[转载]Socket=>流,TCP连接,TCP可靠性概述

mikel阅读(851)

[转载]Socket=>流,TCP连接,TCP可靠性概述 – 浪雪 – 博客园.

如前文所说,不同类型的Socket与不同类型的底层协议族以及同一协议族中的不同协议相关联。而我想说的主要就是TCP/IP协议族中的内容。现在TCP /IP协议族中的主要socket类型为”流套接字(stream socket)”和”数据报套接字(datagram socket)”。如果类比到现实中 stream socket类似于打电话沟通,datagram socket类似于写信沟通。当然,其他协议族当然也有相应的stream socket和datagram socket。
我学习的时候喜欢把类似的事物的异同点相比较,因此我就先从相同点开始说起。
一.Client和Server

无论是打电话还是写信,总会有打电话的一 方和写信的一方,相对应的就会有接电话的一方和收信的一方。后者通过回信或者接听电话对请求发出者作出相应。互联网通信也与这个过程类似。客户端 (client)和服务器(server)这两个术语代表了这两种不同的角色:client是通信的发起者,而server则会被动的等待客户端发起 信,并对其做出响应。
这里要提出的一点是,区分client和server的关键点是谁是请求的发出者,谁是请求的响应者。因此,client和 server之间的角色是可以互换的。一旦原先的client变成了请求的响应者,那么client就变成了server,反之亦然。server并不是 高配置的装了oracle等数据的高级服务器,client也并不是就是指个人电脑,也不是浏览器。记住,client和server只是对请求的发出者 和请求的响应者的一种抽象概念。
为什么要区分这两者呢?因为一个程序是作为client和server决定了它在于其对等端(peer)建立通信时所使用的Socket API的形式。什么叫对等端?客户端的对等端就是服务器,反之亦然。更进一步来说,是因为client首先需要知道server的所在地,也就是要知道 server的IP和port,但是反之则不需要。因为如果有必要,server可以通过相应的API来获取其client的地址。这与打电话非常类似, 被呼叫者不需要知道拨电话者的号码就可以接听电话,如果有需要,办个来电显示就可以知道打电话方的电话号码。只要通信建立成功,server和 client就没有什么区别了。(什么,接听免费?打电话收费?好吧,我承认这个是区别之一)
二.TCP Socket(stream socket)
在TCP/IP协议族中,Stream socket对应是以TCP作为其端对端协议,提供了一个基于连接的可信赖的“字节流”服务。
以下就是我第一次读到类似的话时产生的疑问,一直记录在我的书上:
1.什么是流?
2.什么是基于连接?连接又是什么?
3.什么是可信赖?到底可信赖到什么程度?难道发送端这边断电了接收端仍然还能接到数据?


我相信也会有人产生和我类似的疑问
1. 流简单来说就是一个简单有序的字节序列。注意它是有序的。很容易理解输入流就是以有序的方式写入新的字节,而输出流则是读取字节。因为TCP Socke是基于流的,所以我们可以猜到Socket实例中都会维护着相应的InputStream对象和OutputStream对象。当我们通过 Socket的OutputStream输出数据后,这些字节最终能在连接的另一端通过InputStream读取出来。
明白TCP传送的是一个没有记录边界概念的字节流。这一点很重要,可以总结为TCP中没有用户可见的”分组”概念,它只是传送了一个字节流,我们无法准确地预测在一个特定的读操作中会返回多少字节。最最简单的例子来说,你通过OutputStream send 3次的数据可能OutputStream read 2次就读取完了,而这中间又可能有多种情况存在。这里涉及到TCP的可靠性这一性质,这一点将在接下来的文章里细说。

2.和字节序列在网络的环境中称作报文一样,这里的“连接”也是一个基于特定上下文所使用的词。要说连接先从”无连接”来说更容易明白。很显然,如果不同的主机间需要通信,那么肯定需要以某种方式连接起来,无论是有线的还是无线的方式。那么无连接通信指的到底是什么?
回答就是,基于连接和无连接指的是一种协议。也就是说,在这里的连接并不指的是物理介质,而是一种传输的方式,说明了如何在物理介质上来传输数据。无论是基于连接还是无连接,都有可能在同一条网线上传送着数据。
对TCP来说,连接完全是”想象”的。它是由端点(client 和 server)所记忆的状态组成的,并不存在”物理”连接。虽然我们前面用打电话来类比TCP,但打电话的时候是有物理连接的。这 里的连接是指,只要连接的双方认为 TCP 连接存在,并且可以互相发送 IP packet,那么 TCP 连接就一直存在。简单来说,就是一端认为另一端能够接受数据,就记录数据的发送状态并把数据发送出去,除非知道另一端不再能接收到数据了。也就是说,所谓 的连接,就是client认为我能把数据传送到server,server是存在的。而server认为我应该等待client把数据传送过来。而 我们认为连接存在是通过三次握手来实现的。这其中又会产生问题,问题在于连接是想象的,因此并不是实时的。也就是说并不是像你拔掉网线后在右下角的连接提 示就会有个红叉叉出现。当一端出问题的时候,另一端可能仍然会认为连接是存在的。


[转载]分组报文,协议和Socket的概念

mikel阅读(967)

[转载]分组报文,协议和Socket的概念 – 浪雪 – 博客园.

这篇随笔是我接着上篇《字节和字符,对信息进行编码》继续写的内容,看过上篇随笔能更好的理解这篇内容。我想从基础的开始说起,一直说到ASP.NET,WCF为止。然后再转战数据库和数据结构。

信息是指由程序创建和建设的“字节序列”。在网络环境中,这些字节序列被称作“分组报文”。一组报文包括了网络用来完成工作的控制信息,还包括了数据信息。


协议相当于互相通信的程序(进程间通信)间达成的一种约定,它规定了分组报文的交换方式和它们包含的含义。一组协议规定了以下信息:
1.结构。 比如报文中哪一部分表明了其目的地址。
2.解析。如何对报文中所包含的信息进行解析。


设计一组协议,通常是为了在一定约束条件下解决某一特定问题。比如,超文本传输协议http是为了解决在服务期间传递超文本对象的问题。

网络层:网络层完成将分组报文传输到它们的目的地址的工作。注意,目的地只精确到网络接口,并不精确到应用程序。IP属于网络层。

传 输层:TCP协议和IP协议都有一个共同的功能,即寻址。回顾一下,IP协议只是将分组报文分发到了不同主机(更准确的说是网络接口,因为一台主机可能装 了多个网卡,而网卡才是目的地。cpu,主板等其他组成一台主机的其他组成部分并不是目的地)。很明显,还需要其他更细粒度的寻址将报文发送到主机中指定 的应用程序,因为同一台主机上可能有多个应用程序在使用网络。TCP协议和UDP协议使用的地址叫做“端口号”,这就是用来区分同一台主机中不同应用程序 的。TCP和UDP协议有时候也称作端到端传输协议(end-to-end transport protocol),因为它们将数据”从一个应用程序传输到另一个应用程序”,而IP协议只是将数据从”一台主机传输到另一台主机”

TCP 协议能够检测和恢复IP层提供的主机到主机的信道中可能发生的报文丢失,重复以及其他错误。TCP协议提供了一个可信赖的字节流(reilable byte-stream)信道。(注意流的概念,下面在提到NIO的时候会和buffer进行比较).使用TCP协议在很多方面都与文件的输入输出 (I/O)相似。实际上,由一个程序写入的文件再由另一个程序读取就是一个TCP连接的适当模型。

UDP协议并不尝试对IP层产生的错误进行修复,它仅仅简单的扩展了IP协议“尽力而为best effort”的数据报服务,使得数据能在应用程序之间工作,而不是在主机之间工作。因此,使用了UDP协议的应用程序必须为处理报文丢失,顺序混乱等问题做好准备。
下面来谈下Socket。
Socket:只是一个抽象层。用来表示程序已经加入到网络中。
这句话的意思就是一旦一个程序中有了一个Socket实例对象,那么这个程序就被加入到了网络当中,可以和网络中的其他应用程序进行通信。

现在来关注Socket是抽象层这段话。既然Socket是抽象的,那么它肯定有很多不同具体的实现,比如说以TCP为基础的TCPSocekt和以UDP为基础的UDPSocket。

不恰当的比方来说,拥有一个Sokcet就像狱警对犯人说你有和外界沟通的权力。和外界沟通这个概念是抽象的,具体是坐在桌子前面谈,还是通过 电话机隔着玻璃窗谈话,(请想象美剧中的场景)还是允许写信,单从“你可以和外界沟通“这句话来说都是不可知的。我们唯一能知道的是,我们可以和外界沟通 了。
接下来的随笔中我会继续说明这几种实现方式的异同点。

[转载]字节和字符,对信息进行编码

mikel阅读(857)

[转载]字节和字符,对信息进行编码 – 浪雪 – 博客园.

TCP/IP 协议以字节的方式传输用户数据,并没有对其进行检查和修改。这个特点使得应用程序可以非常灵活地对其中传输的信息进行编码。TCP/IP的唯一约束是,信 息必须在块(chunk)中发送和接收,而块的长度必须是8位的倍数。而字节正好是8位的,因此我们可以认为在TCP/IP协议中传输的信息是字节序列。 鉴于此,我们可以进一步把传输的信息看做数字序列或数组,每个数字的取值范围是0~255(8位)


应用程序协议:明确定义了信息的发送者应该怎样排列和解释这些位序列(bit sequence),同时还要定义接受者应该怎样解析,这样才使得信息的接受者和发送者能够抽取每个字段的意义。

在Java和C#程序中,Int数据都由32位表示(都映射到Int32上),因此,我们可以用4个字节来传输任意的Int变量或者常量。这时要注意的是,对于需要超过一个字节来表示的数据类型,我们必须知道这些字节的发送顺序。显然有2种选择,从右往左或者从左往右。


字符串和文本:
因 为人们习惯于处理各种各样以字符串形式表示的信息,如书本中和电脑显示器上显示的信息。因此,只要我们指定如何对要传输的文本进行编码,我们就几乎能发送 其他“任何类型”的数据:先将其表示成文本形式,再对文本进行编码转换成相应类型。用文本表示的类型和二进制表示的类型相比优势之一就是人可以读懂,第二 就是可以跨平台。显然,我们可以将数字和boolean类型的数据表示成String类型,如 “123456”,“1.23e33”,”true”等。也可以表示 成”<int>123</int>”,”<boolean>true</boolean>”等。我们也 知道,通过调用getBytes()方法,可以将一个字符串转换成字节数组。当然,还有其他方法实现这个功能。

为了更好的理解这个过程,我们首先得将文本视为由符号(如感叹号”!”,问号”?”)和字符组成。实际上每个String实例内部都对应了一个char[]类型。一个char在java内部表示为一个整数。如字符 “a” 与整数97对应,”!”则对应了33.

在一组符号与一组整数之间的映射称为编码字符集(coded character set)。你应该听说过ASCII编码字符集(美国标准信息交换码)。ASCII码将英语字母,数字,标点符号以及一些特殊符号映射成0~127的整数。
可以看出,它忽略了许多英语以外的其他语言所使用的符号,因此用ASCII码来开发应用程序和协议显然并不适合我们的情况。

128个数字明显不够用,可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。因此,Unicode字符集出现了,就像它的名字都表示的,这是一种所有符号的编码。将世界上大部分的语言和符号映射到0~65535之间。Unicode包含了ASCII,也就是说,原来在ASCII中字符所对应的数字在Unicode中也是用同样的数字来表示,这就提供了一定的兼容性。需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

发送者和接收者在字符映射这件事上达成共识就完了吗?这还得看情况而定。对于每个整数值都比255小的一小组字符,则不需要其他信息,因为它可以用一个字节 来表示,不会涉及到多个字节的排序问题。对于可能使用超过一个字节的大整数的编码方式,就需要对这些整数如何表示成字节序列统一意见,这个意见也叫做“编码规范”(encoding scheme).当然,只要你喜欢,你可以定义自己的编码规范。但是就如同前面所说得,这需要发送者和接收者沟通并达成一致。这挺费事,而且世界上已经有 大量不同的标准,我们只要选择一种标准来共同遵守就可以了。

说来说去,其实就是一个映射问题,拿String实例的getBytes()方法来举例,该方法返回一个[]byte也就是字节数组。通过调用该方法,就完成一个“由字符串到字节的映射过程”.
比如调用”Test!”.getBytes(),你将获得按照UTF-8字符集编码的数组。
84 101 115 116 33

如果按照”Test!”.getBytes(“IBM037”),返回的结果将会是
227 133 162 163 90
上面的例子说明,发送者和接收者必须在文本字符串的表示方式上达成共识。
上面的是java代码,C#中可以明显的看出字符集的定义,由相应的类来表示:

classProgram
{
staticvoidMain(string[] args)
{
UnicodeEncoding unicodeEncoding = newSystem.Text.UnicodeEncoding();
UTF8Encoding utf8Encoding = newUTF8Encoding();
byte[] arr = utf8Encoding.GetBytes("Test!");
foreach(byteb inarr)
{
Console.WriteLine(b);
}
}
}

[转载]想爱容易,相处难:当ASP.NET MVC爱上IoC

mikel阅读(1262)

[转载]想爱容易,相处难:当ASP.NET MVC爱上IoC – dudu – 博客园.

也许你会问ASP.NET MVC为什么会爱上IoC?

相爱的理由常常很简单,就像一首歌中所唱——“只为相遇那一个眼神”。

ASP.NET MVC爱上IoC只为IoC能实现MVC控制器的依赖注入。

下面是博客园招聘频道(job.cnblogs.com)所用的一个MVC控制器:

public class EnterpriseController
{
    protected IJobService _jobService;
    protected IEnterpriseService _enterpriseService;

    #region Constructors
    public EnterpriseController(IJobService jobService, 
        IEnterpriseService enterpriseService)
    {
        _jobService = jobService;
        _enterpriseService = enterpriseService;
    }
    #endregion
}

如上面的代码所示,有了IoC进行依赖注入,就不需要在构造函数中专门创建对应于_jobService与 _enterpriseService的实例。IoC容器会在运行时自动创建IJobService与IEnterpriseService的实例,并传 递给EnterpriseController的构造函数。

就因为这一点,MVC就爱上了IoC。爱就这么简单。

但是相爱容易,相处难。。。相处的过程中总会遇到各种各样的问题。。。所以幸福来自于你是否能努力解决这些问题。

代码世界也一样,当我们让MVC与IoC相处时,就遇到了问题。这里我们以IoC容器Unity为例,说明一下我们遇到的问题与解决方法。

要想实现Controller的依赖注入,就需要让IoC容器接管Controller的创建,而ASP.NET MVC 3中提供的IDependencyResolver接口就为实现这个提供了可能。所以,我们首先创建一个实现IDependencyResolver接口 的UnityDependencyResolver类,代码如下:

public class UnityDependencyResolver : IDependencyResolver
{
    IUnityContainer container;

    public UnityDependencyResolver(IUnityContainer container)
    {
        this.container = container;
    }

    public object GetService(Type serviceType)
    {
        return container.Resolve(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return container.ResolveAll(serviceType);
    }
}

UnityDependencyResolver的作用就是调用IoC容器(这里是Unity)解析相应类型的实例。创建了 UnityDependencyResolver,我们还需要告诉MVC用它进行解析。在Global.asax的 Application_Start()方法中添加如下代码:

protected void Application_Start()
{
    IUnityContainer container = new UnityContainer();
    DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}

我们运行一下程序试试,出现下面的错误提示:

The current type, System.Web.Mvc.IControllerFactory, is an interface and cannot be constructed. Are you missing a type mapping?

从上面的错误信息可以分析出,错误是发生在调用UnityDependencyResolver.GetService方法时。ASP.NET MVC在运行的时候需要得到IControllerFactory的实现实例,然后用它去创建相应的控制器实例。如果不用IoC容器,MVC默认会创建 DefaultControllerFactory的实例。现在用了IoC,MVC找不到IControllerFactory的实现实例(我们根本没有 注册嘛),所以出现上面的错误。

为了解决这个问题,我们注册一下DefaultControllerFactory:

container.RegisterType<IControllerFactory, DefaultControllerFactory>();

继续运行程序,又出现新的错误:

The current type, System.Web.Mvc.IControllerActivator, is an interface and cannot be constructed. Are you missing a type mapping?

找不到IControllerActivator的实现实例,看来,创建Controller还需要这个东东。查看MVC的源代码发现 IControllerActivator的默认实现是DefaultControllerActivator,但郁闷的是它竟然是private class,无法注册它。别无选择,只能自己实现IControllerActivator,名叫CustomControllerActivator, 代码如下:

public class CustomControllerActivator : IControllerActivator
{        
    IController IControllerActivator.Create(
        System.Web.Routing.RequestContext requestContext,
        Type controllerType)
    {
        return DependencyResolver.Current
            .GetService(controllerType) as IController;
    }      
}

继续运行,又出现新的错误:

The current type, System.Web.Mvc.IViewPageActivator, is an interface and cannot be constructed. Are you missing a type mapping?

天哪!难道MVC中的所有接口都要注册一下。。。

这时,脑子里突然闪出一个指示牌:

于是,脚踩刹车,打了一把方向盘,驶上了另一条道 —— 如果IoC容器中没有注册,不引发异常,而是返回null,让MVC用自己的方式去处理。

修改UnityDependencyResolver的GetService方法:

public object GetService(Type serviceType)
{
    if (!this.container.IsRegistered(serviceType))
    {
        return null;
    }
    return container.Resolve(serviceType);
}

并取消之前在IoC容器中对DefaultControllerFactory与CustomControllerActivator的注册。

继续运行,成功!虽然成功,但停车一看,原来兜了一个圈子,又回到了出发的地方。一切还是交由MVC处理,IoC容器形同虚设,Controller的依赖注入无法实现。如果这时访问想依赖注入的Controller(构造函数带有参数),会出现下面的错误提示:

No parameterless constructor defined for this object.

虽然回到原地,看上去没有前进一步,但实际上你已离目标更近一些(积累了经验,下次前进速度会更快)。就像你追一个女孩子,费尽心思,却被拒 绝,看似你的一切努力付之流水,实际上她的心门已经有点松动。。。这时,你要有一种锲而不舍的精神,把失落感扔到九霄云外,然后继续努力,坚信“精诚所 至,金石为开”。解决技术问题也是同样道理。

重头再来!阅读MVC的源代码,了解MVC的请求处理过程,看看MVC是在什么地方创建Controller的实例的,然后看有没有办法让IoC容器来接管。

MvcHandler.BeginProcessRequest->MvcHandler.ProcessRequestInit,呵呵,找到:

factory = ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext, controllerName);

上面的代码中,factory的类型是 IControllerFactory,ControllerBuilder.GetControllerFactory()的作用是获取 IControllerFactory的实现实例,而实际是通过调用IDependencyResolver接口得到的(我们之前实现的 UnityDependencyResolver接管了IDependencyResolver接口)。但我们没有在IoC容器中注册 IControllerFactory,实际是由MVC返回IControllerFactory的默认实现 DefaultControllerFactory。从上面的代码还可以看出,Controller实例的创建是通过调用 IControllerFactory.CreateController()方法,所以,我们要在 DefaultControllerFactory.CreateController()方法中寻找线索,对应代码如下:

public virtual IController CreateController(RequestContext requestContext, string controllerName) {
    Type controllerType = GetControllerType(requestContext, controllerName);
    IController controller = GetControllerInstance(requestContext, controllerType);
    return controller;
}

CreateController()又调用了GetControllerInstance()得到Controller的实例,进一步查看其代码:

protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType) {
    return ControllerActivator.Create(requestContext, controllerType);
}

ControllerActivator的类型是IControllerActivator,之前也提到 过,IControllerActivator的默认实现是DefaultControllerActivator,由此可以看出,Controller 实例的创建是由DefaultControllerActivator完成的。我们要实现依赖注入,就要由IoC容器来接管。

那如何来接管呢?——重载DefaultControllerFactory的CreateController方法,将创建Controller实例的工作转交给IoC容器,代码如下:

public class UnityControllerFactory : DefaultControllerFactory
{
    IUnityContainer container;
    public UnityControllerFactory(IUnityContainer container)
    {
        this.container = container;
    }

    protected override IController GetControllerInstance(RequestContext reqContext,
        Type controllerType)
    {
        return container.Resolve(controllerType) as IController;
    }
}

然后在IoC容器中注册一下UnityControllerFactory:

container.RegisterType<IControllerFactory, UnityControllerFactory>();

然后,运行程序。。。功夫不负有心人,依赖注入成功,问题解决!从此,MVC与IoC过上了幸福的生活。

小结

要实现ASP.NET MVC控制器的依赖注入,我们需要:

1. 实现IDependencyResolver接口并通过DependencyResolver.SetResolver告知MVC,将部分类型实例解析工作交由IoC容器来处理;

2. 继承DefaultControllerFactory,重载GetControllerInstance方法,并通过IoC容器将之注册为IControllerFactory的实现。

完整示例代码下载

[转载]DotLiquid-Asp.net模板引擎

mikel阅读(1198)

[转载]DotLiquid-Asp.net模板引擎 – loogn – 博客园.

以前用过一段时间的PHP,感觉非常不错,其中最让我难忘的就是Smarty模板引擎,当时就微微地想ASP.NET里有没有像这样的模板引擎呢?不过由于之后的工作内容都用不到,或者说没有想到用模板,这想法也没导致我做一些事情,就不了了之了。

现在也是工作需要,用模板是一个不错的选择。之前没用过这种东西,盲搜一片没找到自己想要的,于是自己就试着写写,大思路用的是Smarty的,用html页面做为模板,生成aspx页面,把数据放在HttpContext.Items里,代码如下:

html模板:

<div>{$title}</div>
<select>
{foreach $l in $list}
<option value=”{$l.Age}”>{$l.Name}</option>
{/foreach}
</select>

生成的aspx页面:

<%@ Page Language=C# %>
<%
if (HttpContext.Current.Items[SMARTY_TEMPLATE_DIR]==null)
{
Response.Write(
no direct access allowed);
Response.End();
}
%>
<div><%=DotSmarty.Smarty.GetTemplateArg(title) %></div>
<% var list = DotSmarty.Smarty.GetTemplateArg(list) as System.Collections.Generic.IList<SmartyTest.User>; %>
<select>
<% foreach (var l in list){ %>
<option value=”<%=l.Age %>”><%=l.Name %></option>
<%}%>
</select>

调用如:

Smarty smarty = new Smarty();
List
<User> list = new List<User>();
list.Add(
new User() {  Age=1, Name=name111});
list.Add(
new User() { Age = 2, Name = name222 });
smarty.Assign(
title, 标题);
smarty.Assign(
list, list, TemplateArgType.List);
smarty.Display(
user/userInfo.htm);

看起来很像Smarty,可越写难度越大!唉,能力有限,将来有能力再说吧,现在只能放弃。

前几天幸运地听说了DotLiquid,网址是:http://dotliquidmarkup.org。上面曰:“DotLiquid is a templating system ported to the .net framework from Ruby’s Liquid Markup.It’s easy to learn, fast and safe“。我想我终于找到了ASP.NET中的smarty了,更有图说明:

这里先介绍一下她的几个主要的概念:

Filter:”Filters are simple methods(过滤器是一些简单的方法)”

如标准Filter中的upcase,{{ “looGn” | upcase }}  值为”LOOGN”。

Tag:”Tags are used for the logic in your template(标签用于实现模板中的逻辑)”。

如标准Tag中的assign,{% assign freestyle = false %} 定义值为false的freestyle变量。

Block:其实block也是tag,如if..else,for..in, 可以说Block是有endtag的Tag。

如:{% for i in (1..5) %}

{{ i }}

{% endfor %}

下面跟大家分享一下这几天对她的理解及代码实现:

下载过发布的压缩包DotLiquid v1.5.zip里会有v3.5和v4.0两个版,引用DotLiquid.dll的对应版本到自己项目即可。其实她真的是easy to learn!看一个handler的代码:

public void ProcessRequest(HttpContext context)
{
context.Response.ContentType
= text/plain;
Template template
= Template.Parse(模板内容:{{hw}});//用模板内容做为参数解析得到Template对象
string result = template.Render(Hash.FromAnonymousObject(new { hw = Hello World! }));//用模板所需的元素做为参数呈现处理后的结果
context.Response.Write(result);
}

一切的一切,其实就这两步!关键是!我们如何应用!”技术不是秘密,秘密是如何善用技术!” 不记得在哪看得一句话,不过无所谓啦!

第一步: 从文件读入模板内容。模板大多数不是程序中的一个字符串变量,可能存在文件、数据库里等,这里说的是html文件模板。TemplateHelper如下

public static class TemplateHelper
{
#region Template路径

static TemplateHelper()
{
//可用文件配置,例子中用了字典
_map = new Dictionary<string, string>(50);
_map.Add(
master, ~/template/master.htm);
_map.Add(
index_content_main, ~/template/index_content_main.htm);
_map.Add(
list_content_main, ~/template/list_content_main.htm);
_map.Add(
list_content_script, ~/template/list_content_script.htm);
_map.Add(
detail, ~/template/uc/detail.htm);
}

#endregion

private static Dictionary<string, string> _map;

public static bool ContainsKey(string key)
{
return _map.ContainsKey(key);
}
public static string GetTemplateURL(string key)
{
try
{
return _map[key];
}
catch (KeyNotFoundException e)
{
KeyNotFoundException ne
= new KeyNotFoundException(e.Message + key: + key);
throw ne;
}
catch (Exception e)
{
throw e;
}
}
/*这个方法限制了文件模板路径必要配置,如果需要可以添加直接以文件路径为参数的方法,
*不过感觉这里配置起来是个好的习惯
*/
public static Template GetFileTemplate(string templateKey, Encoding encoding)
{
Template template
= HttpContext.Current.Cache[templateKey] as Template;
if (template == null)
{
string path = HttpContext.Current.Server.MapPath(GetTemplateURL(templateKey));
template
= Template.Parse(File.ReadAllText(path, encoding));
CacheDependency dependency
= new CacheDependency(path);
HttpContext.Current.Cache.Add(templateKey, template, dependency, Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration, CacheItemPriority.Default,
null);//把模板缓存起来
}
return template;
}

public static Template GetFileTemplate(string templateKey)
{
return GetFileTemplate(templateKey, Encoding.UTF8);
}
}

第二步:include文件。DotLiquid的include是一个标准Tag,如{% include top %}。调用include需要给Template.FileSystem赋值,它是一个IFileSystem接口,在IFileSystem里只有一个方法:

public interface IFileSystem
{
string ReadTemplateFile(Context context, string templateName);
}

很显然,解析到include top时会把top做为templateName调用Template.FileSystem.ReadTemplateFile方法,以此来实现include。所以实现IFileSystem的类要以templateName参数返回对应的内容:

public class IncludeFileSystem : IFileSystem
{
private Encoding _encoding = Encoding.Default;

public IncludeFileSystem(){}

public IncludeFileSystem(Encoding encoding)
{
_encoding
= encoding;
}

public string ReadTemplateFile(Context context, string templateName)
{
bool isOptional = false; //是否可选
string templateKey = templateName;
if (templateName.EndsWith(_optional))
{
isOptional
= true;
templateKey
= templateKey.Replace(_optional, “”);
}
if (templateKey.StartsWith(content_))
{
object ns = context.Environments[0][ns];
if (ns == null)
{
ns
= Path.GetFileNameWithoutExtension(HttpContext.Current.Request.RawUrl);
}
templateKey
= ns + _ + templateKey;
}
object result = HttpContext.Current.Cache[templateKey];
if (result == null)
{
if (isOptional && !TemplateHelper.ContainsKey(templateKey))
{
return string.Empty;
}
string path = HttpContext.Current.Server.MapPath(TemplateHelper.GetTemplateURL(templateKey));
result
= File.ReadAllText(path, Encoding.UTF8);
CacheDependency dependency
= new CacheDependency(path);
HttpContext.Current.Cache.Add(templateKey, result, dependency, Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration, CacheItemPriority.Default,
null);
}
return result.ToString();
}
}

这里的ReadTemplateFile有 点复杂,其实就是根据templateName来读文件,缓存,至于开始的判断后面再说。在适当的地方设置一下Template.FileSystem 可。Template.FileSystem = new IncludeFileSystem();我写在了Application_Start事件处理程序里。

第三步:提供测试数据。下面为了简单,没从数据库读取:

public static class WebHelper
{
public static Family GetFamily()
{
Family family
= new Family()
{
Host
= 孙悟空,
Address
= 大佛山小石村,
Count
= 4,
Desc
= 快乐的一家人!
};
return family;
}

public static List<Member> GetMembers()
{
List
<Member> members = new List<Member>() {
new Member(){ ID=1, Name=孙悟空, Age=42, Sex=true, Relation=本人, Desc=下等战士,重情重义、绝不欺骗朋友、喜欢帮助人,就算对着敌人也会帮助他。 多次救了地球和全人类。 },
new Member(){ ID=2, Name=牛琪琪, Age=40, Sex=false, Relation=妻子, Desc=孙悟空和琪琪结婚了,孙悟空没老可琪琪老了!},
new Member(){ ID=3, Name=孙悟饭, Age=22, Sex=true , Relation=长子, Desc=拥有极高的潜力,每次遇到危险时都会发挥出强大力量来保护自己。},
new Member(){ ID=4, Name=孙悟天, Age=15, Sex=true , Relation=次子, Desc=天赋极高,可老想着泡妞!},
};
return members;
}

public static Member GetMemberInfo(int id)
{
return GetMembers().Single(m => m.ID == id);
}
}
public class Family:Drop
{
public string Host { get; set; }

public string Address { get; set; }

public int Count { get; set; }

public string Desc { get; set; }

}
public class Member : Drop
{
public int ID { get; set; }

public string Name { get; set; }

public int Age { get; set; }

public bool Sex { get; set; }

public string Relation { get; set; }

public string Desc { get; set; }
}

第四步:实现母版编程 。一直感觉ASP.NET中母版很不错,Asp.net页面的对象模型来实际母版和用户控件也是顺理成章的事。要用DotLiquid的include标签来实现母版应该怎么做呢?这里说说我的解决方法。先看母版模板文件master.htm

<!DOCTYPE html PUBLIC -//W3C//DTD XHTML 1.0 Transitional//EN http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd>
<html xmlns=http://www.w3.org/1999/xhtml>
<head>
<title>{{title}}</title>
<link href=/css/style.css rel=stylesheet type=text/css />

<script type=text/JavaScript src=/js/JQuery-1.6.1.min.js></script>

{% include content_script_optional %}
</head>
<body>
<div id=root>
<div class=top>
<h2>
Top
in master</h2>
</div>
{
% include content_main %}
<div class=bottom>
<h2>
Bottom
in master
</h2>
</div>
</div>
</body>
</html>

对,关键还是在templateName上,”content_main”和”content_script_optional” 中的”content”和”optional”是两个约定,还记得IncludeFileSystem.ReadTemplateFile方法吧,就是有点复杂的那个!content指明这里是包含”内容模板(名词来自内容窗体)”,optional指明这个include可以没有,模板里到这就可以了,再看ReadTemplateFile方法有个object ns;默认是请求文件名,因为a页面可以用master.htm,b页面也可以用master.htm,ns默认就是”a”或”b”,这样做只是为了找到a或b的模板。假如index_content_main.htm首页模板:

<div class=”center”>
<table class=”tb” style=”width:400px;”>
<caption>家庭:{{f.Host}}
<a href=”/list.ashx”>查看成员</a>
</caption>
<tr>
<th>户主:</th><td>{{f.Host}}</td>
</tr>
<tr>
<th>地址:</th><td>{{f.Address}}</td>
</tr>
<tr>
<th>家庭成员数:</th><td>{{f.Count}}</td>
</tr>
<tr>
<th>描述:</th><td>{{f.Desc}}</td>
</tr>
</table>
</div>

index.ashx为访问接口:

public void ProcessRequest(HttpContext context)
{
context.Response.ContentType
= text/plain;
Template template
= TemplateHelper.GetFileTemplate(master);
string html = template.Render(Hash.FromAnonymousObject(new { title = dotliquid demo index,f=WebHelper.GetFamily()}));
context.Response.Write(html);
}

嘿嘿,这里有点怪吧,代码里看不出来和index页面有半点关系,template是根据master.htm得到的。这主要归功于那两个约定和一个ns默认值,如果把index.ashx改名为abc.ashx,上面呈现的代码就要这样写了:

string html = template.Render(Hash.FromAnonymousObject(new {ns=index , title = dotliquid demo index,f=WebHelper.GetFamily()}));

这几步到些完结!完整例子会附下载!

扩展:还记得代码图右边的几个主要概念吧,下面是三个例子,我写的一个,他们的两个!

public class TemplateFilters
{
public static string Text(bool? input, string trueText, string falseText, string nullText)
{
if (input == null) return nullText;
return input == true ? trueText : falseText;
}
public static string Text(bool input, string trueText, string falseText)
{
return input ? trueText : falseText;
}
}

这个是自己的Filters类,里面的每个方法都可以是一个Filter,{% true | text: “男” , “女” %} 此值为”男” ,调用第二个重载,text为方法名称(注意大小写),true是第一个参数input,”男”是第二个参数trueText,”女”是第三个参数 falseText。Filter要注册才可用,Template.RegisterFilter(typeof(TemplateFilters)); 这个我也写在Application_Start。

public class Rd : Tag //随机数
{
int _max;
public override void Initialize(string tagName, string markup, List<string> tokens)
{
base.Initialize(tagName, markup, tokens);
_max
= Convert.ToInt32(markup);
}
public override void Render(Context context, StreamWriter result)
{
result.Write(
new Random().Next(_max).ToString());
}
}

public class Scale : Block//概率出现,用的单词应该不当!~
{
int _max;
public override void Initialize(string tagName, string markup, List<string> tokens)
{
base.Initialize(tagName, markup, tokens);
_max
= Convert.ToInt32(markup);
}
public override void Render(Context context, StreamWriter result)
{
if (new Random().Next(_max) == 0)
base.Render(context, result);
}
}

还是要先注册,Template.RegisterTag<Rd>(“rd”);Template.RegisterTag<Scale>(“scale”);

用法分别是  {% rd 10 %} 值为0到10任意一个数字, {% scale 10 %}这里有10%的概率出现{% endscale %}。

下载