新增一个Edit.aspx文件,这个页面同样接受一个title参数,这个页面上主要就是FckEditor,并且保存事件也由FckEditor提供(为了某些方便性,我修改了一些FckEditor控件的源代码,这个修改的细节就不详细说了,所以在这里请使用我提供的FckEditor控件),在这里,我们要使用这个Edit.aspx页面提供新增和修改两项功能,所以先给Article类增加一个函数SaveArticle用来处理这两种情况,不过,让我们先来写个单元测试吧。
using NUnit.Framework;
using
VisualWiki.Models;
namespace VisualWiki.UnitTests
{
[TestFixture]
public class TestArticle
{
[Test]
public void TestSaveArticle()
{
Article.SaveArticle( " title " , " whatever it is " );
var o1 = Article.FindByTitle( " title " );
Assert.IsNotNull(o1);
Assert.AreEqual( " whatever it is " , o1.Content);
Article.SaveArticle( " title " , " hello, I'm coming " );
var o2 = Article.FindByTitle( " title " );
Assert.IsNotNull(o2);
Assert.AreEqual( " hello, I'm coming " , o2.Content);
}
}
}
namespace VisualWiki.UnitTests
{
[TestFixture]
public class TestArticle
{
[Test]
public void TestSaveArticle()
{
Article.SaveArticle( " title " , " whatever it is " );
var o1 = Article.FindByTitle( " title " );
Assert.IsNotNull(o1);
Assert.AreEqual( " whatever it is " , o1.Content);
Article.SaveArticle( " title " , " hello, I'm coming " );
var o2 = Article.FindByTitle( " title " );
Assert.IsNotNull(o2);
Assert.AreEqual( " hello, I'm coming " , o2.Content);
}
}
}
写单元测试,可以帮助我们测试我们的实现,帮助阅读代码,协助重构等;而先写单元测试,可以在我们考虑其实现方式之前,先考虑其用法,所以可以帮助我们实现更好用的库。我这里仍然使用的是NUnit,而不是VS自带的单元测试库,因为我发现,(至少早期来说)VS的单元测试库对于数组的比较需要自己用foreach,而NUnit可以直接用AreEqual。所以我还是用NUnit,反正只需要带一个十几K的DLL就可以了……
恩,现在,我们有了单元测试,不过,我们还没有实现这个函数,现在我们就来实现它。如果你安装了Resharper,可以让它从单元测试的代码里,帮你建立这个函数的原型。我们实现的SaveArticle函数如下:
public
static
void
SaveArticle(
string
title,
string content)
{
var o = FindByTitle(title);
if (o == null )
{
New.Init(title, content).Save();
}
else
{
o.Content = content;
o.Save();
}
}
var o = FindByTitle(title);
if (o == null )
{
New.Init(title, content).Save();
}
else
{
o.Content = content;
o.Save();
}
}
然后,我们运行单元测试,失败了,异常信息是:Lephone.Data.DbEntry的类型初始值设定项引发异常。恩,确实,我们的单元测试还没有配置数据源呢。
在VisualWiki.UnitTests中加入一个xml文件VisualWiki.config.xml,这个配置文件的配置项,基本上和VisualWiki中的Web.config一样,只是我这一次希望,其生成的SQL显示在单元测试的输出面板,而单元测试的输出面板直接接收Console的输出,另外,我希望Sqlite的数据库生成在系统的Temp目录下,修改后的VisualWiki.config.xml如下:
<?
xml version="1.0" encoding="utf-8"
?>
<
configuration
>
< configSections >
< section name ="Lephone.Settings" type ="Lephone.Util.Setting.NameValueSectionHandler, Lephone.Util" />
</ configSections >
< Lephone.Settings >
< add key ="SqlLogRecorder" value ="Lephone.Util.Logging.ConsoleMessageRecorder, Lephone.Util" />
< add key ="NameMapper" value ="Lephone.Util.Text.InflectionNameMapper, Lephone.Util" />
<!-- database defination -->
< add key ="AutoCreateTable" value ="true" />
< add key ="DataBase" value ="@SQLite : @{TempDirectory}UnitTest.db" />
< add key ="DbProviderFactory" value ="System.Data.SQLite.SQLiteFactory, System.Data.SQLite, Version=1.0.60.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139" />
</ Lephone.Settings >
</ configuration >
< configSections >
< section name ="Lephone.Settings" type ="Lephone.Util.Setting.NameValueSectionHandler, Lephone.Util" />
</ configSections >
< Lephone.Settings >
< add key ="SqlLogRecorder" value ="Lephone.Util.Logging.ConsoleMessageRecorder, Lephone.Util" />
< add key ="NameMapper" value ="Lephone.Util.Text.InflectionNameMapper, Lephone.Util" />
<!-- database defination -->
< add key ="AutoCreateTable" value ="true" />
< add key ="DataBase" value ="@SQLite : @{TempDirectory}UnitTest.db" />
< add key ="DbProviderFactory" value ="System.Data.SQLite.SQLiteFactory, System.Data.SQLite, Version=1.0.60.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139" />
</ Lephone.Settings >
</ configuration >
然后将VisualWiki.config.xml的编译方式设置为内嵌的资源,另外,我们也希望单元测试完成时删除数据库文件,所以修改TestArticle加入处理函数:
private
static
readonly
string
TestFilePath
=
SystemHelper.TempDirectory
+
"
UnitTest.db
";
[TearDown]
public void TearDown()
{
File.Delete(TestFilePath);
}
[TearDown]
public void TearDown()
{
File.Delete(TestFilePath);
}
好,现在,运行单元测试,显示成功,点选单元测试项,我们还能在它的面板里看到在这个测试运行时,所生成的所有SQL语句。其实,使用Sqlite,还可以使用内存数据库来做单元测试,不过,内存数据库是连接型数据库,连接结束后,数据也就抛弃了,所以,需要我们让这个测试函数内的所有调用,都在一个连接里完成,在DbEntry里,可以用DbEntry.Context.UsingConnection来做,使用方法和UsingTransation类似。我以前以为内存数据库的速度更快,不过考虑后,我觉得,如果用于测试的数据库需要预先有N个表,每个表里还有一些测试数据的话,使用文件数据库,直接复制的方式,速度反而可能更快,DbEntry 500多个测试,有一半左右需要准备数据库,使用文件型数据库,所有单元测试运行完,只需要10秒左右。
现在,我们已经实现了SaveArticle函数,所以我们开始编写Edit.aspx页面,除了保存Wiki内容之外,我们还希望保存之后,直接返回Show.aspx页面,所以,修改好的Edit.aspx如下:
<%
@ Page Title
=
""
Language
=
"
C#
"
MasterPageFile
=
"
~/Main.master
"
%>
< script runat ="server" >
[ HttpParameter ] public string title;
protected void Page_Load( object sender, EventArgs e)
{
if ( ! IsPostBack)
{
var article = Article.FindByTitle(title);
if (article != null )
{
Editor.Value = article.Content;
}
}
}
protected void Editor_SaveClick( object sender, EventArgs e)
{
Article.SaveArticle(title, Editor.Value);
Response.Redirect( new UrlBuilder( " Show.aspx " ).Add( " title " , title).ToString());
}
</ script >
< asp:Content ID ="Content1" ContentPlaceHolderID ="head" Runat ="Server" >
</ asp:Content >
< asp:Content ID ="Content2" ContentPlaceHolderID ="ContentPlaceHolder1" Runat ="Server" >
< fck:FCKeditor ID ="Editor" runat ="server" CssClass ="editor" BasePath ="~/fckeditor/"
Height ="100%" onsaveclick ="Editor_SaveClick" >
</ fck:FCKeditor >
</ asp:Content >
< script runat ="server" >
[ HttpParameter ] public string title;
protected void Page_Load( object sender, EventArgs e)
{
if ( ! IsPostBack)
{
var article = Article.FindByTitle(title);
if (article != null )
{
Editor.Value = article.Content;
}
}
}
protected void Editor_SaveClick( object sender, EventArgs e)
{
Article.SaveArticle(title, Editor.Value);
Response.Redirect( new UrlBuilder( " Show.aspx " ).Add( " title " , title).ToString());
}
</ script >
< asp:Content ID ="Content1" ContentPlaceHolderID ="head" Runat ="Server" >
</ asp:Content >
< asp:Content ID ="Content2" ContentPlaceHolderID ="ContentPlaceHolder1" Runat ="Server" >
< fck:FCKeditor ID ="Editor" runat ="server" CssClass ="editor" BasePath ="~/fckeditor/"
Height ="100%" onsaveclick ="Editor_SaveClick" >
</ fck:FCKeditor >
</ asp:Content >
然后我们在VisualWiki的目录下建立fckeditor目录,把FckEditor的相关文件复制到这个目录下,现在,从Edit.aspx运行程序,显示一个异常:The Parameter title can't be empty。恩,在url后加上参数?title=test,然后回车,显示出FckEditor的编辑界面,只是高度有点儿小,随便输入一些内容,点击FckEditor上的保存按钮,页面返回Show.aspx,并且显示了刚才我们编辑的内容。把url中的Show修改为Edit,然后回车,我们又进入了编辑界面,这一次,编辑框里已经有了我们刚才编辑的内容,修改一下,再点击保存按钮,我们回到了Show页面,它也显示出了我们编辑后的内容。
现在,我们来编辑一下Main.master,用以提供一个Edit链接。母板页也需要title参数,所以先设置它从Lephone.Web.SmartMasterPageBase继承,以便使用HttpParameter,然后添加一个Edit的HyperLink控件,在Page_Load里对这个链接控件进行一些设置,修改后的Main.master如下:
<%
@ Master Language
=
"
C#
"
Inherits
=
"
Lephone.Web.SmartMasterPageBase
"
%>
<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< script runat ="server" >
[ HttpParameter ] public string title;
protected void Page_Load( object sender, EventArgs e)
{
var pageName = Request.Url.Segments[ 2 ].ToLower();
if (pageName == " edit.aspx " )
{
Edit.Enabled = false ;
}
else
{
Edit.Enabled = true ;
Edit.NavigateUrl = new UrlBuilder( " Edit.aspx " ).Add( " title " , title).ToString();
}
}
</ script >
< html xmlns ="http://www.w3.org/1999/xhtml" >
< head runat ="server" >
< title ></ title >
< asp:ContentPlaceHolder id ="head" runat ="server" >
</ asp:ContentPlaceHolder >
</ head >
< body >
< form id ="form1" runat ="server" >
< div >
< asp:HyperLink ID ="Edit" runat ="server" > Edit </ asp:HyperLink >
< br />< hr />
< asp:ContentPlaceHolder id ="ContentPlaceHolder1" runat ="server" >
</ asp:ContentPlaceHolder >
</ div >
</ form >
</ body >
</ html >
<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
< script runat ="server" >
[ HttpParameter ] public string title;
protected void Page_Load( object sender, EventArgs e)
{
var pageName = Request.Url.Segments[ 2 ].ToLower();
if (pageName == " edit.aspx " )
{
Edit.Enabled = false ;
}
else
{
Edit.Enabled = true ;
Edit.NavigateUrl = new UrlBuilder( " Edit.aspx " ).Add( " title " , title).ToString();
}
}
</ script >
< html xmlns ="http://www.w3.org/1999/xhtml" >
< head runat ="server" >
< title ></ title >
< asp:ContentPlaceHolder id ="head" runat ="server" >
</ asp:ContentPlaceHolder >
</ head >
< body >
< form id ="form1" runat ="server" >
< div >
< asp:HyperLink ID ="Edit" runat ="server" > Edit </ asp:HyperLink >
< br />< hr />
< asp:ContentPlaceHolder id ="ContentPlaceHolder1" runat ="server" >
</ asp:ContentPlaceHolder >
</ div >
</ form >
</ body >
</ html >
再次从Show.aspx运行程序,添加参数?title=test,页面显示文章test,点击顶部的Edit链接,进入编辑页面,编辑之后,点击保存,回到显示页。
至此,VisualWiki项目已经可以进行可视化的新建、编辑操作,我们还需要进行很多的工作以便让它完成所有需求。不过现在,把它提交到SVN库里先。目前的源代码可以在这里下载: VisualWiki2.7z
未完待续……