上篇文章我向大家介绍了BlogEngine.Net中开发扩展的重要一部分——Extension。在本文中我将向大家展示它的另外一种扩展特性,那就是Widget小工具,主要是Widget的开发标准和工作原理等。
什么是Widget与为什么要使用Widget
Widget实际上就是一种带有界面的小工具,主要应用在桌面领域,例如:Yahoo!Widget,还有Vista自带的边栏等。在BlogEngine.Net中它就是页面左侧或右侧的那种小的分区,例如Category list,Calendar,Tag cloud等,这种东西实际上在很多Blog系统中都存在,在BlogEngine.Net中我们就叫它Widget。
Widget可以将Blog系统中很多的功能提供给用户一个统一的界面访问接口。如果有一些功能需要加到页面上,我们首先考虑的就是可否作成一个Widget来实现,因为这种Widget具有统一的开发标准,可以很方便的达到我们的目的。
WidgetBase和WidgetEditBase
在BlogEngine.Net中,作为一个有界面的Widget我们只需要继承WidgetBase类就行了。WidgetBase直接继承了UserControl,重写了Render用来生成统一的Widget界面,至于具体Widget中显示什么,则由自定义Widget类本身来完成,WidgetBase中有一个抽象方法LoadWidget用来完成Widget对象的初始化工作,IsEditable用于说明Widget是否可以编辑,如果可以编辑在Render时就会输出编辑按钮,那么这个Widget也必须提供一个编辑界面(继承了WidgetEditBase)。此外还需要注意WidgetBase中GetSettings会根据WidgetID从DataStore(前文讲述过)中获得相应的配置信息(也就是内容信息)并存储在Cache中。Cache在这部分的DataStore的处理中运用很多,希望大家留意。
如果某个Widget可以编辑,那么它还需要一个实现WidgetEditBase的类,这个类主要是给用户提供一个对于Widget配置信息(具体内容)修改的界面,它提供了一个抽象的Save方法用于将修改的信息保存到DataStore中,还有一个Saved事件用于外部监听以便进行扩展。
所有已经实现的Widget必须放在widgets目录下,并以Widget的名称给相应的文件夹命名,如果只是浏览我们加入一个widget.ascx就行了,如果需要修改还需要加入一个edit.ascx,以TextBox(可以设置一些关于作者的描述信息)的实现为例:
在widgets\TextBox中有widget.ascx和edit.ascx两个文件(加上Codebehind一共四个),在widget.ascx.cs的LoadWidget(重写父类的方法)获得DataStore信息并加入到Widget内容中。
1/**//// <summary>
2/// This method works as a substitute for Page_Load. You should use this method for
3/// data binding etc. instead of Page_Load.
4/// </summary>
5public override void LoadWidget()
6{
7 StringDictionary settings = GetSettings();
8 if (settings.ContainsKey("content"))
9 {
10 LiteralControl text = new LiteralControl(settings["content"]);
11 this.Controls.Add(text);
12 }
13}
14
15/**//// <summary>
16/// Gets the name. It must be exactly the same as the folder that contains the widget.
17/// </summary>
18/// <value></value>
19public override string Name
20{
21 get { return "TextBox"; }
22}
23
24/**//// <summary>
25/// Gets wether or not the widget can be edited.
26/// <remarks>
27/// The only way a widget can be editable is by adding a edit.ascx file to the widget folder.
28/// </remarks>
29/// </summary>
30/// <value></value>
31public override bool IsEditable
32{
33 get { return true; }
34}
35
同样对于edit.ascx.cs中重写了父类的Save方法将内容又保存回DataStore中。
Widget的增,删,改,排序等是如何处理的
在admin目录下存在WidgetEditor.aspx用来对Widget的操作进行处理。它实际上是一个通用的模板,也是一个操作的路由,根据请求的参数进行相应的操作。
增加Widget:我们将自己的按照上文的标准开发的Widget放在widgets的目录以后就已经完成了Widget的安装。通过添加列表(这是WidgetZone生成的一个列表,实际上就是搜索widgets目录下的文件而获得的所有已安装的Widget列表)就可以转向WidgetEditor.aspx,之后将这个Widget添加进来,而WidgetEditor.aspx是通过AddWidget方法完成的。
删除Widget:这里的删除不是卸载,而只是从WidgetZone中移出Widget,同时删除DataStore信息。当我们点击Widget中的删除时会使用WidgetEditor.aspx来处理,而WidgetEditor.aspx又是通过RemoveWidget完成的。
修改Widget:当我们点击Widget的修改时会转向WidgetEditor.aspx,而WidgetEditor.aspx使用InitEditor读取了DataStore中的信息并加载了相应Widget中的edit.ascx,实际上对于Widget的修改一部分(总体的描述信息)是通过WidgetEditor.aspx直接修改完成的,而另一部分(Widget的配置信息)则是由WidgetEditor.aspx委托edit.ascx来完成的,这样做的好处显而易见,可以实现修改界面的个性化处理,这也是继承带来的好处。
1private void btnSave_Click(object sender, EventArgs e)
2{
3 WidgetEditBase widget = (WidgetEditBase)FindControl("widget");
4 if (widget != null)
5 widget.Save();
6
7 XmlDocument doc = GetXmlDocument();
8 XmlNode node = doc.SelectSingleNode("//widget[@id=\"" + Request.QueryString["id"] + "\"]");
9 bool isChanged = false;
10
11 if (node.Attributes["title"].InnerText != txtTitle.Text.Trim())
12 {
13 node.Attributes["title"].InnerText = txtTitle.Text.Trim();
14 isChanged = true;
15 }
16
17 if (node.Attributes["showTitle"].InnerText != cbShowTitle.Checked.ToString())
18 {
19 node.Attributes["showTitle"].InnerText = cbShowTitle.Checked.ToString();
20 isChanged = true;
21 }
22
23 if (isChanged)
24 SaveXmlDocument(doc);
25
26 WidgetEditBase.OnSaved();
27 Cache.Remove("widget_" + Request.QueryString["id"]);
28
29 string script = "top.location.reload(false);";
30 Page.ClientScript.RegisterStartupScript(this.GetType(), "closeWindow", script, true);
31}
排序Widget:当我们在拖动某个Widget时它会进行新的排序,这个排序是持久性的。最初见到BlogEngine.Net时这个功能就很吸引我。那么它是如何实现的呢?
1/**//// <summary>
2/// Moves the widgets as specified while dragging and dropping.
3/// </summary>
4/// <param name="move">The move string.</param>
5private void MoveWidgets(string move)
6{
7 XmlDocument doc = GetXmlDocument();
8 string[] ids = move.Split(';');
9
10 for (int i = 0; i < ids.Length; i++)
11 {
12 string id = ids[i];
13 XmlNode node = doc.SelectSingleNode("//widget[@id=\"" + id + "\"]");
14 XmlNode parent = node.ParentNode;
15
16 parent.RemoveChild(node);
17 parent.AppendChild(node);
18 }
19
20 SaveXmlDocument(doc);
21 WidgetEditBase.OnSaved();
22}
从这里可以看出move是一个使用";"分隔的WidgetID序列来表示顺序,这个序列是JavaScript生成的。服务器接收到这个序列以后使用MoveWidgets并对于DataStore中的Widget列表进行重新的排序。对于JavaScript的实现已经超出本文的范围,这里不做讨论。感兴趣的朋友可以研究一下admin/widget.js,Widget这部分的所有JavaScript都在此,写的也是不错的。
由此可见WidgetEditor.aspx是Widget管理的一个核心。
WidgetZone是一个Widget的容器
WidgetZone上文已经涉及到,它就是一个Widget的容器。WidgetZone继承自PlaceHolder,在OnLoad时会根据DataStore将已经添加的Widget加载到PlaceHolder中,在Render的时候还会去查找安装在widgets目录下的Widget列表。这个WidgetZone在BlogEngine.Net中并不是一个必须的类,你可以将Widget直接放在主界面的某个位置上就可以使用。但是,如果不使用它来管理而直接显示Widget会失去Widget的管理特性,在下一篇制作Theme的文章中我会对其进行详细的说明。
总结
Widget的实现无非是BlogEngine.Net中的经典。BlogEngine.Net对这些界面上的小工具进行了统一的抽象,尤其是继承的使用带来了很大的扩展空间。WidgetEditor.aspx的统一管理也是非常经典的,尤其是排序和修改部分更值得大家仔细研究。此外,Widget的XCopy安装也是很不错的,实际上BlogEngine.Net的一个重要特性就是在Web上实现了很多这种热插拔(Plug'n play),包括Extension,Widget等。
继承的正确使用给我们带来的好处实在太多
上一篇:BlogEngine.Net架构与源代码分析系列part9:开发扩展(上)——Extension与管理上的实现
下一篇:BlogEngine.Net架构与源代码分析系列part11:开发扩展(下)——自定义Theme