如何“用aspx来做模板”

作者:翅膀的初衷 来源:本站原创 发布时间:2013-11-18 查看数:62847

用aspx来做模板,并不是很新鲜的话题!早在很多年前就有网友提出过疑问!但是并没有很多人来探讨其类似实现(注:在实际项目中有相关实现)!其实对于动态网页来说,就是一个文本的处理过程:当用户向服务器请求一个链接,IIS(此处不讨论其它WEB服务器)根据求请地址(后缀)将其交给特定的ISAPI处理,ISAPI接收后,经过一系列判断处理,然后获得实际的模板路径(WebForm中就是aspx页面),然后将开发人员在页面中定义的服务器标记(或者说是标签,WebForm中还包括控件等)替换(解释)成指定的数据后,生成一段包含HTML标记的纯文本返回给用户,所以,理论上只要可以处理文本的编程语言,就可以用来开发Web项目.比如JavasScript(Node.js)!

也就是说,WebForm本身就是有一个内置的模板解析引擎的! 在具体讲怎么用aspx来做模板前,我们先问问自己,为什么我们要用模板引擎,为什么我们不用常规的WebForm开发?

在十多年前时候,WebForm的出现确实是一件十分讨喜的事情,它颠覆了传统的Web开发模式,它让Web开发变得更简单,然而在十多年后的今天,它当年的大部分优点都已经变成了缺点,比如,它太臃肿,它产生很多我们不需要的HTML与脚本等等!

其实我们常规的WebForm开发本身就是在"用aspx来做模板",而你点击这个标题进来看这篇文章,很真实的目的是在于,如何再次封装WebForm的模板解析,当然,你也可能只是好奇!

那么我们希望我们的新的"aspx模板引擎"要具有哪些功能呢?我个人认为应该是大致如下的:

1.我不希望页面产生那些杂乱的VIEWSTATE代码
2.我希望能很好的将前端页面与后台代码分离
3.我希望能很方便的切换模板

那么我们再问问自己,为什么我们还要尝试"用aspx来做模板"呢,原因不外乎以下:

1.我的项目因为外部原因(多半是公司的要求)必须在WebForm(甚至还可能是.NET2.0)的环境下
2.我不希望增加第三方模板的学习成本

如果还有其它原因与理由不在上面,没关系,原因与理由不重要,重要的是你已经进来了!

本人反编译了System.Web与System.Web.UI 二个文件,跟踪了一下WebForm对Page处理过程,发现了一个有意思的现象,在asp.net mvc(以下统称为mvc)中,我们是先执行Action拿到Result后再去找模板(视图文件)的,而asp.net是先拿到模板(即aspx页面)再去找去后台对象的,因此aspx顶部的page申明才如此重要(好吧,其实这是一个常识),而且WebForm本身封装得比较死,有部分属性与方法都是internal的,要对它的aspx引擎进行再次封装存在一定难度,而我们并不希望做过多复杂的操作来实现以上的功能以增加我们的开发成本.


那么。我们要如何避免以上问题并达到我们的需求呢?首先看下本人搭的一个框架:

之前公司给我的要求是:

1.简单,不能使用新技术,不能太复杂,那怕是一个刚毕业的新人也能快速入手!
2.开发环境是VS2005(.net 2.0)

于是,就有了这个东西的出现!我搭建了5个项目,分别作用与功能如下:

1.Ajax:负责处理Ajax,所有的Ajax请求,不需要再写实际ashx或者aspx页面,只需要在ajax项目下定好相关方法,然后直接以http://地址/ajax/类名/方法名.aspx 调用,也不需要写Request.Form之类的代码,只定义好形参就行!由框架中的AjaxHandle自动处理!
2.CodeBehind:所有模板的后台代码实现
3.Core:系统核心
4.UI:主要是一些控件及 Page的一些重写类
5.Web:表现层

再看一下Web层的主要文件与文件夹:

1.Config/Site.config 系统通用配置,比如当前网站模板
2.Template 我们用来放模板,不同的模板放一个文件夹,Default 为默认模板

Core.PageHttpHandlerFactory 这是一个比较核心的东西,我们看一下代码:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.UI;

namespace 你的命名空间.Core
{
    public class PageHttpHandlerFactory : System.Web.UI.PageHandlerFactory
    {
        #region IHttpHandlerFactory 成员
        public override IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path)
        {
            if (virtualPath.StartsWith("/Ajax/", StringComparison.OrdinalIgnoreCase)) //如果是AJAX请求,则返回自定义的一个AjaxHandel
            {
                string[] ajax;
                int i = virtualPath.LastIndexOf('.');
                if (i > 0)
                {
                    ajax = virtualPath.Substring(0,i).Trim('/').Split('/');
                }
                else
                {
                    ajax = virtualPath.Trim('/').Split('/');
                }
                AjaxHandle handle = new AjaxHandle();
                if (ajax.Length > 2)
                {
                    handle.Action = ajax[2];
                    handle.Controller = ajax[1];
                }
                else if (ajax.Length > 1)
                {
                    handle.Action = ajax[1];
                }
                handle.Jsonp = Config.EnableJsonp ? context.Request.QueryString[Config.Jsonp] : null;
                return handle;
            }
            else if (virtualPath.StartsWith("/Admin/", StringComparison.OrdinalIgnoreCase)) //管理目录,不做处理
            {

            }
            else  //普通请求,重设请求的模板路径
            {
                virtualPath = string.Concat("/Template/", Config.Theme, virtualPath);
                path = context.Server.MapPath(string.Concat("~", virtualPath));
            }

            return base.GetHandler(context, requestType, virtualPath, path);
        }
        #endregion

        #region 自定义实现
      
        #endregion
    }
}


上面有具体的注释,处理模板路径,其实也可以用Url重写的方法去做,不过不推荐,因为重写的话是取得到原路径与新路径的,很容易引发一些其它问题!AjaxHandle.cs的代码不再放出来了,必竟与主题无关!

然后在Web.config中加上配置:

      <httpHandlers>
        <add verb="*" path="*.aspx" type="你的命名空间.Core.PageHttpHandlerFactory,你的命名空间.Core"/>
      </httpHandlers>

再看一下模板的定义:
新建一个aspx页面,将所有相关.cs 删除,然后将页面顶部改成如下格式

  <%@ Page Language="C#" AutoEventWireup="true" Inherits="你的命名空间.CodeBehind.类名" %>

比如:

<%@ Page Language="C#" AutoEventWireup="true" Inherits="你的命名空间.CodeBehind.Index" %>
<!DOCTYPE html>
<html>
<head>
    <title><%= Name %></title>
    <Label:Include ID="icMeta" TemplateFile="Meta.aspx" runat="server"></Label:Include>
</head>
<body>
<%= Name %> 这是一个默认模板
</body>
</html>

后台实现,在CodeBehind中添加一个Index.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using 你的命名空间.UI;
using 你的命名空间.Core;

namespace 你的命名空间.CodeBehind
{
    public class Index : BasePage
    {
        public string Name="test";//注意,这里应该定义成属性,我偷懒
        protected void Page_Load(object sender, EventArgs e)
        {

            //如果使用控件,比如:Repeater,应该这么写
            Repeater rpt = FindControl("rptList") as Repeater;
            if (rpt != null)
            {
                DataTable dt = new DataTable();
                dt.Columns.Add("name", typeof(string));
                dt.Columns.Add("index", typeof(int));

                DataRow dr = dt.NewRow();
                dr["name"] = "哈哈哈哈";
                dr["index"] = 1;

                rpt.DataSource = dt;
                rpt.DataBind();
            }
        }
    }
}

然后通过http://localhost:端口/index.aspx 就可以访问我们的页面了!

为了方便,我们可以将一些列表控件中的模板部分也做成一个文件,方法调用,以Repeater为例:在UI.WebControl下面新建一个Repeater.cs,代码如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Web.UI.WebControls;

namespace 你的命名空间.UI.WebControl
{
    public class Repeater : System.Web.UI.WebControls.Repeater
    {
        private string _alternatingitemtemplatefile;
        private string _itemtemplatefile;
        private int _pagesize;
        private int _currentpageindex;
        private int _pagecount;
        private int _recordcount;
        private bool _allowpaging;

        public Repeater()
            : base()
        {
            _alternatingitemtemplatefile = null;
            _itemtemplatefile = null;
            _pagesize = 20;
            _currentpageindex = 1;
            _pagecount = 1;
            _recordcount = 0;
            _allowpaging = false;
        }

        [Description("获取或设置控件模板路径")]
        public string AlternatingItemTemplateFile
        {
            get { return _alternatingitemtemplatefile; }
            set
            {
                _alternatingitemtemplatefile = value;
            }
        }

        [Description("获取或设置控件模板路径")]
        public string ItemTemplateFile
        {
            get { return _itemtemplatefile; }
            set
            {
                _itemtemplatefile = value;
            }
        }

        [Description("获取或设置在单页上显示的项数")]
        public int PageSize
        {
            get { return _pagesize; }
            set
            {
                if (value < 1)
                {
                    throw new ArgumentOutOfRangeException("PageSize");
                }
                _pagesize = value;
            }
        }

        [Description("获取或设置当前页的索引")]
        public int CurrentPageIndex
        {
            get { return _currentpageindex; }
            set
            {
                if (value < 1)
                {
                    throw new ArgumentOutOfRangeException("CurrentPageIndex");
                }
                _currentpageindex = value;
            }
        }

        [Description("获取或设置一个值,指示控件是否启用分页")]
        public bool AllowPaging
        {
            get { return _allowpaging; }
            set { _allowpaging = value; }
        }

        [Description("获取显示数据源中的所有项所需要的总页数")]
        public int PageCount
        {
            get
            {
                if (_pagecount == 0)
                {
                    _pagecount = this.RecordCount / this.PageSize;
                    if (this.RecordCount % this.PageSize == 0)
                    {
                        _pagecount += 1;
                    }
                    if (_pagecount == 0)
                    {
                        _pagecount = 1;
                    }
                }
                return _pagecount;
            }
        }

        [Description("获取数据源中的实际总项数")]
        public int RecordCount
        {
            get { return _recordcount; }
            set
            {
                if (value < 0)
                {
                    throw new ArgumentOutOfRangeException("RecordCount");
                }
                _recordcount = value;
            }
        }

        protected override void CreateChildControls()
        {
            if (!string.IsNullOrEmpty(this.ItemTemplateFile) && this.ItemTemplate == null)
            {
                this.ItemTemplate = Page.LoadTemplate(this.ItemTemplateFile);
            }

            if (!string.IsNullOrEmpty(this.AlternatingItemTemplateFile) && this.AlternatingItemTemplate == null)
            {
                this.AlternatingItemTemplate = Page.LoadTemplate(this.AlternatingItemTemplateFile);
            }
        }


    }
}

然后在Web.config中加上:

      <pages>
        <controls>
          <add namespace="你的命名空间.UI.WebControl" assembly="你的命名空间.UI" tagPrefix="Label"/>
        </controls>
      </pages>

设置模板:

在Template/Default/Control/下新建一个WebUserControl.ascx,删除cs文件,内容如下:

<%@ Control Language="C#" AutoEventWireup="true" %>
<li>
<%# Eval("name") %>
</li>

然后在页面中可以直接使用

<Label:Repeater ID="rptList" runat="server" PageSize="20" CurrentPageIndex="1" ItemTemplateFile="Control/WebUserControl.ascx"></Label:Repeater>

不过这样的话,我们需要在CodeBehind的Index中Find控件并绑定,不方便我们自由调用!我们可以把不同的内容,比如分类模块化,如:在UI.WebControl下面新建一个Category.cs,代码如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Web.UI.WebControls;

namespace 你的命名空间.UI.WebControl
{
    public class Category : Repeater
    {
        private int _parentid;

        public Category()
            : base()
        {
            _parentid = 0;
        }

        [Description("获取或设置父类编号")]
        [DefaultValue(0)]
        public int ParentId
        {
            get { return _parentid; }
            set
            {
                _parentid = value;
            }
        }


        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (base.AllowPaging)
            {
                //分页处理略...
            }
            else
            {

                this.DataSource = new CategoryDAL().GetList(ParentId);
                this.DataBind();
            }
        }
    }
}

模板如下:

<Label:Category ItemTemplateFile="Control/WebUserControl.ascx"></Label:Category>

很方便了,按此方法开发,不会在页面中产生杂乱的视图状态代码,但是有一些需要回发的控件会失效!如果希望正常使用它们并且不介意有VIEWSTATE的话,可以用<form id="form1" runat="server">包起来(会产生VIEWSTATE代码);

另外欢迎大家加入NET技术交流群:5089240,如果对.net 模板引擎感兴趣的朋友,推荐使JNTemplate

原创内容,转载请注明出处

作者:翅膀的初衷 出处:http://www.jiniannet.com/Article/I201311184727528