Web站点风格切换的实现
引言
Web站点的风格切换是很常见、也很受大家欢迎的功能,比如大家熟知的博客园就提供了几十款风格模板供大家选择。在Asp.Net中,我们可以通过模板页master page和主题theme来实现网站的风格切换,但是.Net提供的默认设置不够强大和灵活。本文将向大家介绍如何在.Net提供的方法上进行改进和扩展,以提供更加强大的网站风格切换功能。
效果预览:http://www.tracefact.net/Demo/StyleSetting/default.aspx
NOTE:本文将master page称为模板(有的书上叫母版),将theme称为主题。
网页的结构 和 模板、主题配置的局限
静态网页的结构
网站的风格的切换,说白了,实际上就是对页面进行分解和重组。所以在进行之前,我们先简单回顾看一下页面究竟有哪些组成部分可以供我们分解,分清楚哪些是可变的、哪些是不变的,进行后继的工作才会容易得多。现在我们先暂时脱离服务器端,看一下一个静态的.htm页面由哪几部分组成:
结构(有语义的XHTML):这部分由XHTML标记组成,应该注意,这里使用“有语义”三个字作为修饰。XHTML的职责是告诉“这里是什么”,而不是告诉“这里应该如何显示”。尽管浏览器对于几乎每个XHTML的标记都赋予了某种内置的样式控制,但是XHTML的本意只是规范文档的结构。比如h1表示为标题,p表示为段落。而不是为了这个字显示的更大一些才去使用h1。
表现和布局(CSS):CSS用来控制页面的显示及布局。在Web标准的概念普及以前,我想大多人都是表格套表格来布局的,现在基本都在使用CSS了,这里没有太多好说的。
行为(Javascript):静态网页也可以添加一些交互的行为,这些行为由Javascript来完成。我们时常会把onclick="alert('hello')"这样的代码嵌入到XHTML标记中,比如一个Input标记上;一些结构、行为分离的狂热人士则主张将行为与结构(XHTML)分离,他们不会将javascript代码写到
之间,而全部写在了head中,或者是body下面,使用 window.onload=function(){ // …} 这种形式。有个极力主张这种做法的人(Peter-Paul Koch)写了本书叫《ppk on javascript》。 好了,大概了解了这些,我们看下.Net中如何将这三者分离,以及它的一些限制:
.Net对页面结构的分离
我到现在都觉得 行为与结构 完全分离的概念太前卫了,另外它也不影响对网站的风格设置,所以我们这里不讨论它。
我们先看一下结构:现在我们将思维向服务器端靠拢一下,很快会发现上面的结构部分仍需要再细分一次,就是XHTML标记和标记中的内容(网页内容)分离。XHTML标记属于变化的部分,不同的风格可能会需要不同的XHTML结构,而对于各个风格,它所显示的内容显然是一样的。想要得到这样的效果,我们可以使用Master Page模板页。由Master Page模板页对应XHTML结构(变化部分),由Page页面对应于XHTML页面的内容(不变部分),即是一个Page可以设置为不同的Master Page以达到不同的样式(look and feel)。
NOTE:这里在说一下CSS,如果你的CSS水平足够强,XHTML的代码写得足够好,那么你无需搞得这么复杂,仅仅使用CSS就可以实现换肤了。www.csszengarden.com有这样的一个项目提供给全世界的人作为实践,它提供一套统一的XHTML代码,其他人则自己编写CSS来对这个XHTML代码进行样式化,结果是百花齐放,一模一样的XHTML实现了风格迥异的页面设计。
现在在看一下表现部分,表现层分为全局式的CSS以及基于控件的皮肤Skin,这些都可以交由主题Theme来完成。
.Net 设置上的局限
看到这里,你可能觉得不用往下看了,使用Master Page和Theme谁不会啊。现在我们就讨论下.Net 中Master Page 和 Theme 的局限:
- 大家知道,我们可以在 Web.config中的System.Web结点下的pages结点中添加Theme和masterPageFile属性来对网站进行设置。但是它提供了一个全局性的配置,就是对于整个站点的所有页面,都将使用这个Master Page。而有时候我们希望能够每个页面不相同,这里就无法实现了。虽然我们可以通过使用location结点来为各个页面进行单独设置,但是显然太麻烦了。
- 我们希望每个风格都有个名字,比如说“默认风格”、“春意盎然”。
- 我们希望用户可以选择风格,而不是像博客园这样由博主设置风格。
实现网站的风格切换
自定义风格配置
有了思路后我们就来一步步地实现它,我们希望可以对风格进行简单的设置,我们应该先明确需要设置的内容:我们都有哪些风格、当前使用的风格是什么、每个风格使用了什么主题、哪个页面对应哪个模板。了解了这些之后,我们可以写下这样的结点配置来:
<!-- MasterPage的 Path为 masterRoot + theme + master -->
<!-- 不为Default设置masterPage,<br> 使用Default风格时会使用创建页面时所选择的Master Page -->
<styletemplates default="默认风格" masterroot="~/MasterPage"><br><style name="默认风格" theme="Default"></style>
<br><style name="春意盎然" theme="Spring"><br> <masterPages><br> <page path="/website/default.aspx" master="Default.master" /><br> <!--<page path="/other.aspx" master="Default.master" />--><br> <page path="/default.aspx" master="Default.master" /><br> </masterPages><br> </style>
<br></styletemplates>
styleTemplates是风格设置的根节点,default是默认的风格名称;masterRoot是指模板页的根目录的地址;style结点是各个风格的名称,以及其所使用的主题的名称;style结点下的masterPages结点组包含此风格所使用的模板的信息;其中Page子结点对应每个独立的页面,path属性记录的是页面的路径,master属性是每个页面对应的模板的路径。
page结点的master属性没有给出模板的全路径,这么做一个是为了使目录结构更一目了然,还有就是不想master属性的内容变得很长,所以需要在程序中指定页面的模板的路径为 masterRoot + Theme + masterPage名称。这样在模板的根目录下,必须建立与主题同名的目录,然后将模板位于该目录下。
在创建页面时,我们需要要选择一个模板。注意到这一点非常重要,因为这个模板实际上是该页面的默认模板。当我们在Web.config中将相应风格的page结点留空时(没有对此页面设置模板),那么就会应用这个模板。所以在Web.Config中定义风格的模板,只是覆盖了这个我们在创建页面时选择的模板。由此,我们只用定义一套完整的Master Page模板页,供每个页面在创建时选择。而Web.config风格结点下设置的master page,实际上仅仅是动态地覆盖这些模板。
以上面的配置为例:当我们将 “春意盎然” 下的第二个 page 结点注释掉时,它会应用创建页面时所选择的模板。“默认风格”下面没有设置任何的 page 结点,所以对于该风格的每个页面,都会应用创建页面时所选择的模板。
如果你希望仅使用Css来换肤,你可以使用也可以不使用Master Page,那么在Web.Config中可以像下面这样设置:
<styletemplates default="默认风格" masterroot=""><br><style name="默认风格" theme="Default"></style>
<br><style name="春意盎然" theme="Spring"></style>
<br><style name="秋高气爽" theme="Autumn"></style>
<br></styletemplates>
对于上面使用Master Page时的设置,站点的目录结构如下所示:
可以看到模板页的根目录下包含了目录Default、Spring与主题名称相同(必须)。之后我们要编写结点处理程序,如何编写自定义结点处理程序,我在《.Net 自定义应用程序配置》一文中已经详细的讨论了,所以这里我们直接看实现,在AppCode目录中新建一个文件StyleTemplateConfigHandler.cs:
public class StyleTemplateConfigHandler : IConfigurationSectionHandler
{
public object Create(object parent, object configContext, XmlNode section) {
return new StyleTemplateConfiguration(section);
}
}
// 映射 styleTemplates 结点的实体类
public class StyleTemplateConfiguration {
private XmlNode node; // styleTemplates 结点
private string defaultTheme; // 默认的主题名称
private string defaultStyle; // 站点默认风格名称
private HttpContext context;
private Dictionary<string> styleDic; // Key: 风格名称;Value: 主题名称<br> public StyleTemplateConfiguration(XmlNode node) {<br> this.node = node;<br> context = HttpContext.Current;<br> styleDic = new Dictionary<string string>();<br> // 获取所有style结点的 name属性 和 theme属性<br> XmlNodeList styleList = node.SelectNodes("style");<br> foreach (XmlNode style in styleList) {<br> styleDic[style.Attributes["name"].Value] = style.Attributes["theme"].Value;<br> }<br> // 获取 站点默认风格 名称<br> defaultStyle = node.Attributes["default"].Value;<br> // 根据 风格名称 获取主题<br> defaultTheme = styleDic[defaultStyle];<br> }<br> // 获取所有风格名称<br> public ICollection<string> StyleNames {<br> get {<br> return styleDic.Keys;<br> }<br> }<br> // 根据风格名称获取主题名称<br> public string GetTheme(string styleName) {<br> return styleDic[styleName]; <br> }<br> // 设置、获取 站点默认风格<br> public string DefaultStyle{<br> get {<br> return defaultStyle;<br> }<br> set { // 更改Web.Config中的默认风格,一般为站长才可以使用<br> XmlDocument doc = new XmlDocument();<br> doc.Load(context.Server.MapPath(@"~/web.config"));<br> XmlNode root = doc.DocumentElement;<br> XmlNode styleTemp = root.SelectSingleNode("styleTemplates");<br> styleTemp.Attributes["default"].Value = value;<br> doc.Save(context.Server.MapPath(@"~/web.config"));<br> }<br> }<br> // 获取默认主题名称<br> public string DefaultTheme {<br> get { return defaultTheme; }<br> }<br> // 根据页面路径获取其对应的 masterPage 的路径<br> public string GetMasterPage(string userTheme){<br> // 获取当前页面路径<br> string pagePath = context.Request.Path;<br> // 用于定位page结点的 XPath<br> string xpath = "style[@theme='" + userTheme + "']" + "/masterPages/page[@path='" + pagePath.ToLower() + "']";<br> // 获取与path属性相匹配的page结点<br> XmlNode pageNode = node.SelectSingleNode(xpath); <br> string master;<br> if (pageNode != null) {<br> // 获取page结点的 master属性的值<br> master = pageNode.Attributes["master"].Value;<br> return prepareMasterPath(master, userTheme);<br> } else<br> return null;<br> }<br> // 获取 Master Page 的路径<br> // MasterPagePath = 跟路径 + Theme路径 + 模板路径<br> private string prepareMasterPath(string masterPath, string userTheme) {<br> string path;<br> if (node.Attributes["masterRoot"] != null)<br> path = node.Attributes["masterRoot"].Value + "/" + userTheme + "/" + masterPath;<br> else {<br> if (userTheme != null) {<br> path = "~/" + userTheme + "/" + masterPath;<br> } else {<br> path = "~/" + masterPath;<br> }<br> }<br> return path;<br> }<br>} <p>这个类提供了一些简单的对XmlNode的操作,对styleTemplates结点进行了映射,这里需要明确两个概念:默认风格 和 用户风格: </p>
<ul>
<li>默认风格,指的是站点管理员 或者 博主设置的风格,也就是Web.Config 中styleTemplates结点的Default属性。 </li>
<li>用户风格,用户设置的风格,页面的实际显示是根据用户风格而 不是默认风格。当用户没有设置风格时,才显示默认风格。 </li>
</ul>
<p>很显然,这个类处理的所有的均是默认风格,我们来看一下它的几个主要方法和属性: </p>
<p>// 获取所有风格名称<br>public ICollection<string> StyleNames { get {;} }<br>// 根据风格名称获取主题名称<br>public string GetTheme(string styleName) {}<br>// 设置、获取 站点默认风格<br>public string DefaultStyle{}<br>// 获取默认主题名称<br>public string DefaultTheme { }<br>// 根据页面路径获取其对应的 masterPage 的路径<br>public string GetMasterPage(string userTheme){} <h5>IUserStyleStrategy,获取、设置用户风格</h5> <p>在继续进行之前,我们来考虑这样一个问题:因为我们要根据用户选择的风格来动态地为页面加载主题和模板,那么用户信息(用户选择了什么风格)应该保存在哪里,从哪里获得呢?我们有很多的选择:可以使用Session、可以使用Cookie,还可以保存到数据库中。此时最好将这部分抽象出来,以便日后为不同的方法提供实现。我们定义一个接口 IUserStyleStrategy,它用来定义如何获取、设置用户风格,在AppCode中新建文件IUserStyleStragety.cs: </p>
<p>public interface IUserStyleStrategy{<br> void ResetUserStyle(string styleName); // 重新设置用户风格<br> string GetUserStyle(); // 获取用户风格<br>} </p>
<p>然后我在这里提供了一个基于Cookie的默认实现: </p>
<p>// 默认风格设置方法:使用Cookie记录<br>public class DefaultStyleStrategy : IUserStyleStrategy {<br> private string cookieName; // cookie名称<br> private HttpContext context;<br> public DefaultStyleStrategy(string cookieName){<br> this.cookieName = cookieName;<br> context = HttpContext.Current;<br> }<br> // 重新设置用户风格名称<br> public void ResetUserStyle(string styleName) {<br> HttpCookie cookie;<br> if(context.Request.Cookies[cookieName]!=null)<br> cookie = context.Request.Cookies[cookieName];<br> else<br> cookie = new HttpCookie(cookieName);<br> cookie.Value = context.Server.UrlEncode(styleName);<br> cookie.Expires = DateTime.Now.AddHours(2); // 设置Cookie过期时间<br> context.Response.Cookies.Add(cookie);<br> // 因为风格(master page和theme)的动态设置只能在 PreInit 事件中<br> // 而Button的Click事件在PreInit事件之后,所以需要Redirect才可以生效<br> context.Response.Redirect(context.Request.Url.PathAndQuery);<br> }<br> // 获取用户自己设置的风格名称<br> public string GetUserStyle() { <br> if (context.Request.Cookies[cookieName] != null) {<br> string value = context.Request.Cookies[cookieName].Value;<br> value = context.Server.UrlDecode(value); // 避免出现中文乱码<br> return value;<br> } else<br> return null;<br> }<br>} </p>
<p>如果日后你需要将信息保存在数据库中,那么你只要重新实现一下这个接口就可以了: </p>
<p>// 如果将用户风格设置存储到了数据库中,可以实现这个接口<br>public class SqlStyleStrategy : IUserStyleStrategy {<br> private int userId; // 用户Id<br> public SqlStyleStrategy(int userId){<br> this.userId = userId;<br> // ...<br> }<br> public void ResetUserStyle(string styleName) {<br> throw new NotImplementedException();<br> }<br> public string GetUserStyle() {<br> throw new NotImplementedException();<br> }<br>} </p>
<h5>PageBase 类:继承自Page的基类</h5> <p>因为所有的页面都要运行这样的一个逻辑:判断用户是否有设置风格,如果没有,使用Web.Config中设置的默认风格;如果设置了,使用用户风格。最后动态地给Page类的Theme属性和MasterPageFile属性赋值。 </p>
<p>那么我们可以定一个基类,在这个基类中去做这件事,然后让所有的页面继承这个基类;又因为页面是一定要继承自System.Web.UI.Page类的,所以这个基类也必须继承自System.Web.UI.Page。现在在AppCode中再建一个文件 PageBase.cs: </p>
<p>// 供所有基类继承<br>public class PageBase : Page<br>{<br> protected StyleTemplateConfiguration styleConfig;<br> protected string userStyle; // 用户设置的风格<br> protected string userTheme; // 用户设置的主题<br> protected IUserStyleStrategy userStrategy; // 使用何种算法来获得用户自定义的信息<br> protected override void OnPreInit(EventArgs e) {<br> base.OnPreInit(e);<br> // 这里会被缓存只在第一次时调用有用<br> this.styleConfig = (StyleTemplateConfiguration)ConfigurationManager.GetSection("styleTemplates");<br> // 当你实现了自己的 Strategy时只需要更改这里就可以了<br> // 更好的办法是将Stragey的类型保存在Web.config中,<br> // 然后使用反射来动态创建<br> userStrategy = new DefaultStyleStrategy("userStyle");<br> // 获取用户风格<br> userStyle = userStrategy.GetUserStyle();<br> // 如果用户没有设置风格,使用默认风格<br> if (String.IsNullOrEmpty(userStyle)) {<br> userStyle = styleConfig.DefaultStyle;<br> userTheme = styleConfig.DefaultTheme;<br> } else {<br> // 根据用户设置的风格 获取 主题名称<br> userTheme = styleConfig.GetTheme(userStyle);<br> }<br> // 根据当前页获取MasterPage的路径<br> string masterPagePath = styleConfig.GetMasterPage(userTheme);<br> // 设置当前页的MasterPage<br> if (masterPagePath != null)<br> this.MasterPageFile = masterPagePath;<br> this.Theme = userTheme; // 设置当前页的主题<br> }<br>} </p>
<p>这里需要注意: </p>
<ol>
<li>ConfigurationManager.GetSection()方法只会调用一次,然后会将结果进行缓存;只要Web.Config不做修改,以后不管是Request还是PostBack都不会再重新生成StyleTemplateConfig的类型实例,而会从缓存中读取。我在《<a href="http://www.tracefact.net/CLR-and-Framework/Custom-Application-Configuration.aspx">.Net 自定义应用程序配置</a>》中忘记说了。 </li>
<li>userStrategy = new DefaultStyleStrategy("userStyle");这里可以将IUserStyleStrategy的类型信息保存在Web.config中,然后使用反射动态创建。具体方法依然参见《<a href="http://www.tracefact.net/CLR-and-Framework/Custom-Application-Configuration.aspx">.Net 自定义应用程序配置</a>》。 </li>
<li>如果想动态地为页面设置主题和模板,代码必须写在PreInit事件中。参见《<a href="http://msdn2.microsoft.com/en-us/library/ms178472.aspx">Asp.Net Page Life Cycle Overview</a>》。 </li>
</ol>
<h5>效果预览</h5> <p>因为这只是一个范例程序,我主要是表达实现的思路,而不是代码的编写,所以省略了很多诸如结点属性是否为空之类的判断。下面的测试仅仅在Web.Config中的配置正确时。在站点下新建一个页面,比如Default.aspx,注意创建一个模板页,因为这里设置的是会被覆盖的,所以无所谓选择哪个模板。 </p>
<p>添加App_Theme下创建目录Default、Spring,新建一个目录MasterPage,在下面创建目录Default、Spring,然后添加一些文件(这就不用我说了吧)。在页面上添加一个DropDonwList,一个Button,当我们选择要显示的模板时,会进行相应的切换,编写后置代码: </p>
<p>protected void Page_Load(object sender, EventArgs e) {<br> if (!IsPostBack) {<br> ltrStyleName.Text = userStyle;<br> foreach (string styleName in styleConfig.StyleNames) {<br> ListItem item = new ListItem(styleName);<br> if (string.Compare(styleName, userStyle) == 0)<br> item.Selected = true;<br> ddlStyles.Items.Add(item);<br> }<br> }<br>}<br>// 更换风格<br>protected void Button1_Click(object sender, EventArgs e) {<br> string styleName = ddlStyles.SelectedValue;<br> userStrategy.ResetUserStyle(styleName); // 委托给userStragety去处理<br>} </p>
<p>之后你可以看到下面的画面: </p>
<p><img alt="" src="http://www.tracefact.net/graph/StyleSetting/02.gif"></p>
<p><img alt="" src="http://www.tracefact.net/graph/StyleSetting/03.gif"></p>
<p>可以通过下面这个连接来看实际的效果,注意到:在这里我让 Default.aspx 和 Other.aspx 使用了同一个模板,你也可以设置它们使用不同的模板。 </p>
<p><a href="http://www.tracefact.net/Demo/StyleSetting/default.aspx">http://www.tracefact.net/Demo/StyleSetting/default.aspx</a> </p>
<h4>总结</h4> <p>在这篇文章中,我简单地向大家介绍了实现网站风格切换的一个思路。 </p>
<p>我们首先复习了网页的结构,了解了.Net默认配置的不足。接着分三个步骤实现了网站的风格切换:处理配置结点的程序、获取用户风格的方法、以及通过基类继承来为各个页面设置风格。最后我们通过简单的两个页面进行了下测试和预览。 </p>
<p>感谢阅读,希望这篇文章能给你带来帮助!</p></string></p></string></string></string>
Web站点风格切换的实现 - Asp.Net 编程 - TraceFact.Net
分享到:
相关推荐
在 张子阳 "Asp.Net 分页显示控件http://www.tracefact.net/asp-net/Extensible-Paging-User-Control.aspx" 基础上改的 并参照 杨涛 "AspNetPager分页控件http://www.webdiyer.com " 感谢两位作者
在 张子阳 "Asp.Net 分页显示控件http://www.tracefact.net/asp-net/Extensible-Paging-User-Control.aspx" 基础上改的 并参照 杨涛 "AspNetPager分页控件http://www.webdiyer.com " 感谢两位作者
在 张子阳 "Asp.Net 分页显示控件http://www.tracefact.net/asp-net/Extensible-Paging-User-Control.aspx" 基础上改的 并参照 杨涛 "AspNetPager分页控件http://www.webdiyer.com " 感谢两位作者
* 出处:Www.TraceFact.Net JimmyZhang.Cnblogs.Com * 文章:一步步构建“半自动”数据分页模块 * 路径:http://www.tracefact.net/Software-********************************************/
大多数关于ASP.NET的教程或书籍往往侧重于教授开发者如何使用.NET框架提供的高级API来构建Web应用程序,即所谓的“如何去做”。然而,对于那些希望深入了解背后机制的学习者而言,仅仅知道如何操作还不够,还需要...
Asp.Net在处理请求时,利用了应用程序域的概念,以实现对Web.config文件或.aspx文件变动的及时响应。应用程序域允许在单一进程中运行多个相互隔离的应用实例,这意味着当配置文件或代码发生变化时,可以卸载旧的应用...