[转载]ASP.NET MVC 重点教程:过滤器Filter

mikel阅读(1012)

[转载]ASP.NET MVC 重点教程一周年版 第六回 过滤器Filter – 重典的博客 – 博客园.

ASP.NETMvc中当你有以下及类似以下需求时你可以使用Filter功能

  1. 判断登录与否或用户权限
  2. 决策输出缓存
  3. 防盗链
  4. 防蜘蛛
  5. 本地化与国际化设置
  6. 实现动态Action

Filter是一种声明式编程方式,在ASP.NET MVC中它只能限制于Action(或它的Controller)。
Filter 要继承于ActionFilterAttribute抽象类,并可以覆写void OnActionExecuting(ActionExecutingContext)和
void OnActionExecuted(ActionExecutedContext)

以及void OnResultExecuting(ResultExecutingContext)和
void OnResultExecuted(ResultExecutedContext)

OnActionExecuting是Action执行前的操作,OnActionExecuted则是Action执行后的操作

而OnResultExecuting是解析ActionResult前执行,OnResultExecuted是解析ActionResult后 执行。

一、应用于Action的Filter

下面我给大家一个示例,来看看它的的执行顺序
首先我们先建立 一个Filter,名字叫做TestFilter

  public class TestFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            filterContext.HttpContext.Session["temp"] += "TestFilter OnActionExecuting<br/>";
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            filterContext.HttpContext.Session["temp"] += "TestFilter OnActionExecuted<br/>";
        }
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            filterContext.HttpContext.Session["temp"] += "TestFilter OnResultExecuting<br/>";
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            filterContext.HttpContext.Session["temp"] += "TestFilter OnResultExecuted<br/>";
        }
    }

然后建立一个Action:

        [TestFilter]//将此Filter应用于Action
        public ActionResult filteraction()
        {
            return View();
        }

在它的View中写入:

<%Session["temp"] += "View Execute<br/>"; %>

最后在其它页面得到Session[“temp”]的输出结果:

TestFilter OnActionExecuting
TestFilter OnActionExecuted
TestFilter OnResultExecuting
View Execute
TestFilter OnResultExecuted

由此可得到它们的执行顺序也是如上

二、Controller的Filter

将Filter应用在Controller上有2种方式

1.直接将Filter应用在Controller上,如:

    [TestFilter]
    public class EiceController : Controller
    {
     }

2.重写Controller内的
OnActionExecuting/OnActionExecuted/OnResultExecuting /OnResultExecuted的四个方法。

下面我们说几个系统的Filter

三、AcceptVerbs

规定页面的访问形式,如

        [AcceptVerbs(HttpVerbs.Post)]
         public ActionResult Example(){
             return View();
         }

页面只能以Post形式访问,即表单提交。

四、ActionName

规定Action的名称。

应用场景:如果不想用方法名做为Action名,或Action名为关键字的话,如

       [ActionName("class")]
         public ActionResult Example(){
             return View();
         }

五、NonAction

当前方法仅是普通方法不解析为Action

六、OutputCache

为Action添加缓存

        [OutputCache(Duration = 60, VaryByParam = "*")]
        public ActionResult Example()
        {
            return View();
        }

七、ValidateInput

该Action可以接受Html等危险代码(ASP.NET MVC在aspx中设置<%@ Page 的属性无法完成等同任务。)

        [ValidateInput(false)]
        public ActionResult Example()
        {
            return View();
        }

八、ValidateAntiForgeryTokenAttribute

用于验证服务器篡改。

        [ValidateAntiForgeryToken]
        public ActionResult Example()
        {
            return View();
        }

[转载]ASP.NET MVC :Action参数绑定的数据源优先顺序

mikel阅读(1026)

[转载]ASP.NET MVC :Action参数绑定的数据源优先顺序 – 阿不 – 博客园.

ASP.NET MVC框架从还未正式发布时就已经成为了我工作的方向和重点,而它也在不断的完善和发展,前两天,ASP.NET MVC2 RTM已经发布了,MVC2主要是在提高我们基于ASP.NET MVC框架之上的WEB程序开发效率上下功夫,新增了很多的常用功能的集成,API的优化以及IDE的改善支持等方面。随着开发效率的不断提高和资源的不 断丰富,以及MVC框架本身所具备的天生的优点,我相信未来ASP.NET平台的WEB开发将会逐步走向MVC方向。至少在ASP.NET Mobile Web的开发中,Webform已经被MVC所取代,在未来的ASP.NET中,包含Mobile WebForm 控件的System.Web.Mobile.dll程序将会被标上过期标志,以及发布新的基于ASP.NETMVC的Mobile Web开发指导。同时,ASP.NET MVC2仍然开放了它的所有源码,源码下载(最下方的mvc2-ms-pl.zip文件)。

以上是关于ASP.NET MVC的一些感想,并不是我本文的重点。本文要叙述的是关于ASP.NET MVC Action参数值来源的优先顺序问题。我们知道在ASP.NET MVC的Controller Action函数中,我们可以添加一些参数,不管这些参数的类型是简单类型,还是复杂类型的对象,MVC都会自动从请求的上下文中寻找全适匹配的值自动赋 值到参数中。我们还知道,一次的HTTP请求中,传值的方式可以有很多种,可以用QueryString,Header,Post Form等等。在MVC中,还可能会有经过Route解析过的Route Values。我们Action的参数值来源是从哪些传值方式中取值的呢?他们的优先级又是如何呢?这是很早以前一位同事提出的问题。花了几分钟阅读了一 下MVC关于赋值绑定的代码,大家可以从ValueProviderDictionary里的一段代码中找到答案。这段代码是获取要做为 ModelBinder绑定的数据源,ModelBinder在绑定参数据值所需的数据源就是从这里得到的:

01 private void PopulateDictionary()
02 {
03 CultureInfo currentCulture = CultureInfo.CurrentCulture;
04 CultureInfo invariantCulture = CultureInfo.InvariantCulture;
05 NameValueCollection form = this.ControllerContext.HttpContext.Request.Form;
06 if (form != null)
07 {
08 foreach (string str in form.AllKeys)
09 {
10 string[] rawValue = form.GetValues(str);
11 string attemptedValue = form[str];
12 ValueProviderResult result = new ValueProviderResult(rawValue, attemptedValue, currentCulture);
13 this.AddToDictionaryIfNotPresent(str, result);
14 }
15 }
16 RouteValueDictionary values = this.ControllerContext.RouteData.Values;
17 if (values != null)
18 {
19 foreach (KeyValuePair<string, object> pair in values)
20 {
21 string key = pair.Key;
22 object obj2 = pair.Value;
23 string str4 = Convert.ToString(obj2, invariantCulture);
24 ValueProviderResult result2 = new ValueProviderResult(obj2, str4, invariantCulture);
25 this.AddToDictionaryIfNotPresent(key, result2);
26 }
27 }
28 NameValueCollection queryString = this.ControllerContext.HttpContext.Request.QueryString;
29 if (queryString != null)
30 {
31 foreach (string str5 in queryString.AllKeys)
32 {
33 string[] strArray4 = queryString.GetValues(str5);
34 string str6 = queryString[str5];
35 ValueProviderResult result3 = new ValueProviderResult(strArray4, str6, invariantCulture);
36 this.AddToDictionaryIfNotPresent(str5, result3);
37 }
38 }
39 }

从代码中我们可以获知,Action 参数的数据首先会从当前请求的form表单中取值,之后是route解析过的route values,最后是从QueryString中取得所前面没有重复出现过的值。重复出现的以最先取的值为优先。在某些情况下,我们可能会因为这个细节而 花费大量的时间在调试为什么我们Action中的参数值不正确。

阿不 http://hjf1223.cnblogs.com

[转载]C#添加修改删除文件文件夹大全

mikel阅读(881)

[转载]C#添加修改删除文件文件夹大全 – 蓝麒麟 – 博客园.

C#添加修改删除文件文件夹大全

StreamWriter sw = File.AppendText(Server.MapPath(“.”)+”\\myText.txt”);
sw.WriteLine(“追逐理想”);
sw.WriteLine(“kzlll”);
sw.WriteLine(“.NET 笔记”);
sw.Flush();
sw.Close();
C#拷贝文件
string OrignFile,NewFile;
OrignFile = Server.MapPath(“.”)+”\\myText.txt”;
NewFile = Server.MapPath(“.”)+”\\myTextCopy.txt”;
File.Copy(OrignFile,NewFile,true);

C#删除文件
string delFile = Server.MapPath(“.”)+”\\myTextCopy.txt”;
File.Delete(delFile);

C#移动文件
string OrignFile,NewFile;
OrignFile = Server.MapPath(“.”)+”\\myText.txt”;
NewFile = Server.MapPath(“.”)+”\\myTextCopy.txt”;
File.Move(OrignFile,NewFile);

C#创建目录
// 创建目录c:\sixAge
DirectoryInfo d=Directory.CreateDirectory(“c:\\sixAge”);
// d1指向c:\sixAge\sixAge1
DirectoryInfo d1=d.CreateSubdirectory(“sixAge1”);
// d2指向c:\sixAge\sixAge1\sixAge1_1
DirectoryInfo d2=d1.CreateSubdirectory(“sixAge1_1”);
// 将当前目录设为c:\sixAge
Directory.SetCurrentDirectory(“c:\\sixAge”);
// 创建目录c:\sixAge\sixAge2
Directory.CreateDirectory(“sixAge2”);
// 创建目录c:\sixAge\sixAge2\sixAge2_1
Directory.CreateDirectory(“sixAge2\\sixAge2_1”);

递归删除文件夹及文件
<%@ Page Language=C#%>
<%@ Import namespace=”System.IO”%>
<Script runat=server>
public void DeleteFolder(string dir)
{
if (Directory.Exists(dir)) //如果存在这个文件夹删除之
{
foreach(string d in Directory.GetFileSystemEntries(dir))
{
if(File.Exists(d))
File.Delete(d); //直接删除其中的文件
else
DeleteFolder(d); //递归删除子文件夹
}
Directory.Delete(dir); //删除已空文件夹
Response.Write(dir+” 文件夹删除成功”);
}
else
Response.Write(dir+” 该文件夹不存在”); //如果文件夹不存在则提示
}

protected void Page_Load (Object sender ,EventArgs e)
{
string Dir=”D:\\gbook\\11″;
DeleteFolder(Dir); //调用函数删除文件夹
}
// ======================================================
// 实现一个静态方法将指定文件夹下面的所有内容copy到目标文件夹下面
// 如果目标文件夹为只读属性就会报错。
// April 18April2005 In STU
// ======================================================
public static void CopyDir(string srcPath,string aimPath)
{
try
{
// 检查目标目录是否以目录分割字符结束如果不是则添加之
if(aimPath[aimPath.Length-1] != Path.DirectorySeparatorChar)
aimPath += Path.DirectorySeparatorChar;
// 判断目标目录是否存在如果不存在则新建之
if(!Directory.Exists(aimPath)) Directory.CreateDirectory(aimPath);
// 得到源目录的文件列表,该里面是包含文件以及目录路径的一个数组
// 如果你指向copy目标文件下面的文件而不包含目录请使用下面的方法
// string[] fileList = Directory.GetFiles(srcPath);
string[] fileList = Directory.GetFileSystemEntries(srcPath);
// 遍历所有的文件和目录
foreach(string file in fileList)
{
// 先当作目录处理如果存在这个目录就递归Copy该目录下面的文件
if(Directory.Exists(file))
CopyDir(file,aimPath+Path.GetFileName(file));
// 否则直接Copy文件
else
File.Copy(file,aimPath+Path.GetFileName(file),true);
}
}
catch (Exception e)
{
MessageBox.Show (e.ToString());
}
}

// ======================================================
// 实现一个静态方法将指定文件夹下面的所有内容Detele
// 测试的时候要小心操作,删除之后无法恢复。
// April 18April2005 In STU
// ======================================================
public static void DeleteDir(string aimPath)
{
try
{
// 检查目标目录是否以目录分割字符结束如果不是则添加之
if(aimPath[aimPath.Length-1] != Path.DirectorySeparatorChar)
aimPath += Path.DirectorySeparatorChar;
// 得到源目录的文件列表,该里面是包含文件以及目录路径的一个数组
// 如果你指向Delete目标文件下面的文件而不包含目录请使用下面的方法
// string[] fileList = Directory.GetFiles(aimPath);
string[] fileList = Directory.GetFileSystemEntries(aimPath);
// 遍历所有的文件和目录
foreach(string file in fileList)
{
// 先当作目录处理如果存在这个目录就递归Delete该目录下面的文件
if(Directory.Exists(file))
{
DeleteDir(aimPath+Path.GetFileName(file));
}
// 否则直接Delete文件
else
{
File.Delete (aimPath+Path.GetFileName(file));
}
}
//删除文件夹
System.IO .Directory .Delete (aimPath,true);
}
catch (Exception e)
{
MessageBox.Show (e.ToString());
}
}

需要引用命名空间:
using System.IO;

/// <summary>
/// 拷贝文件夹(包括子文件夹)到指定文件夹下,源文件夹和目标文件夹均需绝对路径. 格式: CopyFolder(源文件夹,

目标文件夹);
/// </summary>
/// <param name=”strFromPath”></param>
/// <param name=”strToPath”></param>

//————————————————–
// 作者:明天去要饭 QQ:305725744
//—————————————————

public static void CopyFolder(string strFromPath,string strToPath)
{
//如果源文件夹不存在,则创建
if (!Directory.Exists(strFromPath))
{
Directory.CreateDirectory(strFromPath);
}

//取得要拷贝的文件夹名
string strFolderName = strFromPath.Substring(strFromPath.LastIndexOf(“\\”) + 1,strFromPath.Length

– strFromPath.LastIndexOf(“\\”) – 1);

//如果目标文件夹中没有源文件夹则在目标文件夹中创建源文件夹
if (!Directory.Exists(strToPath + “\\” + strFolderName))
{
Directory.CreateDirectory(strToPath + “\\” + strFolderName);
}
//创建数组保存源文件夹下的文件名
string[] strFiles = Directory.GetFiles(strFromPath);

//循环拷贝文件
for(int i = 0;i < strFiles.Length;i++)
{
//取得拷贝的文件名,只取文件名,地址截掉。
string strFileName = strFiles[i].Substring(strFiles[i].LastIndexOf(“\\”) + 1,strFiles[i].Length –

strFiles[i].LastIndexOf(“\\”) – 1);
// 开始拷贝文件,true表示覆盖同名文件
File.Copy(strFiles[i],strToPath + “\\” + strFolderName + “\\” + strFileName,true);
}

//创建DirectoryInfo实例
DirectoryInfo dirInfo = new DirectoryInfo(strFromPath);
//取得源文件夹下的所有子文件夹名称
DirectoryInfo[] ZiPath = dirInfo.GetDirectories();
for (int j = 0;j < ZiPath.Length;j++)
{
//获取所有子文件夹名
string strZiPath = strFromPath + “\\” + ZiPath[j].ToString();
//把得到的子文件夹当成新的源文件夹,从头开始新一轮的拷贝
CopyFolder(strZiPath,strToPath + “\\” + strFolderName);
}
}

一.读取文本文件
1/// <summary>
2/// 读取文本文件
3/// </summary>
4private void ReadFromTxtFile()
5{
6 if(filePath.PostedFile.FileName != “”)
7 {
8 txtFilePath =filePath.PostedFile.FileName;
9 fileExtName = txtFilePath.Substring(txtFilePath.LastIndexOf(“.”)+1,3);
10
11 if(fileExtName !=”txt” && fileExtName != “TXT”)
12 {
13 Response.Write(“请选择文本文件”);
14 }
15 else
16 {
17 StreamReader fileStream = new StreamReader(txtFilePath,Encoding.Default);
18 txtContent.Text = fileStream.ReadToEnd();
19 fileStream.Close();
20 }
21 }
22 }
二.获取文件列表
1/// <summary>
2/// 获取文件列表
3/// </summary>
4private void GetFileList()
5{
6 string strCurDir,FileName,FileExt;
7
8 ///文件大小
9 long FileSize;
10
11 ///最后修改时间;
12 DateTime FileModify;
13
14 ///初始化
15 if(!IsPostBack)
16 {
17 ///初始化时,默认为当前页面所在的目录
18 strCurDir = Server.MapPath(“.”);
19 lblCurDir.Text = strCurDir;
20 txtCurDir.Text = strCurDir;
21 }
22 else
23 {
24 strCurDir = txtCurDir.Text;
25 txtCurDir.Text = strCurDir;
26 lblCurDir.Text = strCurDir;
27 }
28 FileInfo fi;
29 DirectoryInfo dir;
30 TableCell td;
31 TableRow tr;
32 tr = new TableRow();
33
34 ///动态添加单元格内容
35 td = new TableCell();
36 td.Controls.Add(new LiteralControl(“文件名”));
37 tr.Cells.Add(td);
38 td = new TableCell();
39 td.Controls.Add(new LiteralControl(“文件类型”));
40 tr.Cells.Add(td);
41 td = new TableCell();
42 td.Controls.Add(new LiteralControl(“文件大小”));
43 tr.Cells.Add(td);
44 td = new TableCell();
45 td.Controls.Add(new LiteralControl(“最后修改时间”));
46 tr.Cells.Add(td);
47
48 tableDirInfo.Rows.Add(tr);
49
50 ///针对当前目录建立目录引用对象
51 DirectoryInfo dirInfo = new DirectoryInfo(txtCurDir.Text);
52
53 ///循环判断当前目录下的文件和目录
54 foreach(FileSystemInfo fsi in dirInfo.GetFileSystemInfos())
55 {
56 FileName = “”;
57 FileExt = “”;
58 FileSize = 0;
59
60 ///如果是文件
61 if(fsi is FileInfo)
62 {
63 fi = (FileInfo)fsi;
64
65 ///取得文件名
66 FileName = fi.Name;
67
68 ///取得文件的扩展名
69 FileExt = fi.Extension;
70
71 ///取得文件的大小
72 FileSize = fi.Length;
73
74 ///取得文件的最后修改时间
75 FileModify = fi.LastWriteTime;
76 }
77
78 ///否则是目录
79 else
80 {
81 dir = (DirectoryInfo)fsi;
82
83 ///取得目录名
84 FileName = dir.Name;
85
86 ///取得目录的最后修改时间
87 FileModify = dir.LastWriteTime;
88
89 ///设置文件的扩展名为”文件夹”
90 FileExt = “文件夹”;
91 }
92
93 ///动态添加表格内容
94 tr = new TableRow();
95 td = new TableCell();
96 td.Controls.Add(new LiteralControl(FileName));
97 tr.Cells.Add(td);
98 td = new TableCell();
99 td.Controls.Add(new LiteralControl(FileExt));
100 tr.Cells.Add(td);
101 td = new TableCell();
102 td.Controls.Add(new LiteralControl(FileSize.ToString()+”字节”));
103 tr.Cells.Add(td);
104 td = new TableCell();
105 td.Controls.Add(new LiteralControl(FileModify.ToString(“yyyy-mm-dd hh:mm:ss”)));
106 tr.Cells.Add(td);
107 tableDirInfo.Rows.Add(tr);
108 }
109}

三.读取日志文件

1/// <summary>
2/// 读取日志文件
3/// </summary>
4private void ReadLogFile()
5{
6 ///从指定的目录以打开或者创建的形式读取日志文件
7 FileStream fs = new FileStream(Server.MapPath(“upedFile”)+”\\logfile.txt”,

FileMode.OpenOrCreate, FileAccess.Read);
8
9 ///定义输出字符串
10 StringBuilder output = new StringBuilder();
11
12 ///初始化该字符串的长度为0
13 output.Length = 0;
14
15 ///为上面创建的文件流创建读取数据流
16 StreamReader read = new StreamReader(fs);
17
18 ///设置当前流的起始位置为文件流的起始点
19 read.BaseStream.Seek(0, SeekOrigin.Begin);
20
21 ///读取文件
22 while (read.Peek() > -1)
23 {
24 ///取文件的一行内容并换行
25 output.Append(read.ReadLine() + “\n”);
26 }
27
28 ///关闭释放读数据流
29 read.Close();
30
31 ///返回读到的日志文件内容
32 return output.ToString();
33}

[转载]使用NVelocity制作HTML作报表

mikel阅读(1091)

[转载]使用HTML作报表 – 瑞雪年 – 博客园.

NVelocity 可以算上castle一个比较重头的组件了,TemplateEngine Component 是NVelocity的一个默认的应用实现,使用它可以 轻松使用HTML作为模板,生成报表。

组件下载地址:http://www.castleproject.org/castle/download.html

当然,报表都少不了导出PDF,这一还要借助另外一个开源组件:iText, 它的.NET移植版iText.NET 还 有一个C#重制版iTextSharp ,iText.NET的作者在项目介绍中还推荐在新项目中使用iTextSharp了。

示例:

报表模版可以这样写:

代码

<html>
<head>
<link rel=”stylesheet” type=”text/css” href=”$CssName”>
</link>
</head>
<body>
<div>Header</div>
<br/>

<table border=”0″ cellpadding=”0″ cellspacing=”0″ style=”margin:0;padding:0″>

#foreach ( $dr in $Dt.Rows)
#set($name=$dr.get_Item(3).ToString().Trim().Replace(” “, “%20”))

<tr>
<td style=’padding:5px’
onmousedown=”window.location.href(‘$name’)”
onmouSEOver
=”style.backgroundColor=’#DFEEEE’;”
onmouSEOut
=”style.backgroundColor=’#FFFFFF’;”>
<span style=”font-size: 20px; color: #0000FF”>Compound Name: </span><span style=”font-size: 20px; color: #FF0000″>$dr.get_Item(3)</span>
<br/><b><span>Drug Alias: </span></b><span>$dr.get_Item(4)</span>
<br/><b><span>Category: </span></b><span>$dr.get_Item(2)</span>
</td></tr>

#end
</table>
<br/>

<div>Bottom
</div>
</body>
</html>

代码:

报表预览:

代码


protected void Button2_Click(object sender, EventArgs e)
{
DataTable dataTable
= GetDataTable();
string output = ProcessTemplate(dataTable);
Response.Write(output);
Response.Flush();
Response.Close();
}

private string ProcessTemplate(DataTable dataTable)
{
INVelocityEngine VelocityEngine
= NVelocityEngineFactory.CreateNVelocityFileEngine(Server.MapPath(~/Templates), true);
System.Collections.Hashtable context
= new System.Collections.Hashtable();
context.Add(
CssName, ResolveAbsoluteUrl(~/css/default.css));
context.Add(
Dt, dataTable);
string output = velocityEngine.Process(context, temp.htm);
return output;
}

private DataTable GetDataTable()
{
DataTable dataTable
= new DataTable();
dataTable.Columns.Add(
a);
dataTable.Columns.Add(
b);
dataTable.Columns.Add(
c);
dataTable.Columns.Add(
d);
dataTable.Columns.Add(
e);
for (int i = 0; i < 10; i++)
{
DataRow dataRow
= dataTable.NewRow();
for (int j = 0; j < dataTable.Columns.Count; j++)
{
dataRow[j]
= string.Format(value: {0}, {1}, i, j);
}
dataTable.Rows.Add(dataRow);
}
return dataTable;
}

导出PDF:

代码


protected void Button5_Click(object sender, EventArgs e)
{
System.Reflection.Assembly.Load(
Apache.Xml.Commons);
System.Reflection.Assembly.Load(
Apache.Crimson);
System.Reflection.Assembly.Load(
iTextAsian);

DataTable dataTable = GetDataTable();
string output = ProcessTemplate(dataTable);
string fileName = Server.MapPath(~/Temp/temp.htm);
System.IO.File.WriteAllText(fileName, output, System.Text.ASCIIEncoding.ASCII);

MemoryStream stream = new MemoryStream();
com.lowagie.text.Document document
= new com.lowagie.text.Document(PageSize.A4, 80, 50, 30, 65);
PdfWriter.getInstance(document, stream);
HtmlParser.parse(document, fileName);

document.close();
stream.Flush();
byte[] bytes = stream.ToArray();
if (bytes != null)
{
Page.Response.Clear();
Page.Response.ContentType
= application/pdf;
Page.Response.AppendHeader(
Content-Disposition, attachment;filename=temp.pdf);
Page.Response.BinaryWrite(bytes);
Page.Response.End();
}
}

[转载]强大的模板引擎开源软件NVelocity

mikel阅读(1167)

[转载]强大的模板引擎开源软件NVelocity-DotNet-CSDN.

来源:wuhuacong(伍华聪)的专栏 - 博客园
NVelocity是从java编写的Velocity移植的.net版本, 是java界超强的模版系统,.net版本的NVelocity目前版本为0.42。

NVelocity拥有完善的、丰富的模板语言 (VTL,Velocity Template Language)  ,几乎所有高级语言的特性都可以在模板引擎语言中找到。(如流程控制语句、数学运算、关系和逻辑操作符、函数定义、注释等等)

NVelocity 可以做什么?
能够快速生成所需的代码、SQL脚本、页面文件等基于文本内容的文件
生成速度快、模板语言完善、灵活性好
容易学习、 开源,免费使用
前身为Velocity(Java),使用者多,资料全

用途
在编写代码的时候,我们可以发现很多 内容都是不需要变化的,变化的只是一小部分内容,对不同的对象,这些内容不同。

如果我们需要生成一个变化的文档,是否需要在代码拷贝 这些不变化的内容么,或者把它剥离开放到其他的文件去么?还有我们是否能对这些内容有一些简单的控制和引用么?

简单例子(主要规则:引用 以$开头用于取得什么东西,而指令以# 开始用于做什么事情)

#set($foo = false)
#if ($foo)
this is true
#elseif ($bar)
this is false
#elseif  (true)
this should be followed by two blank lines
#end

##  this is a single line comment

#*
this is a multi line comment
#if  (
*#

#set($user = "jason")
#set($login = false)
#set($count  = 5)

#if ($user == "jason")
the user $user is logged in!
#end

#if  ($count == 5)
the count is 5!
#end

#if ($login ==  false)
the user isn't logged in.
#end

#if ($count != 3)
\$count is not equal to 3
#end

变量说明
在VTL中,所有变量标识符的开 头要加上$字符,如$Name,也可以用一种更加明确的方法表示,例如${name}。

变量标识符被映射到稍后即将介绍的 VelocityContext对象。在模板引擎处理模板时,变量名称(如name)被替换成VelocityContext中提供的值

C#代码

Velocity.Init("nvelocity.properties");

VelocityContext  context = new VelocityContext();
context.Put("list", Names);

Template  template = null;
try
{
template =  Velocity.GetTemplate("test.cs.vm");
}
catch (ParseErrorException  pee)
{
System.Console.Out.WriteLine("Syntax error: " +  pee);
}
if  (template != null)
{
template.Merge(context,  System.Console.Out);
}

注释
单行注释
## This is a single  line comment

多行注释
#*
Thus begins a multi-line comment.  Online visitors won't
see this text because the Velocity Templating  Engine will
ignore it.
*#

属性或方法
$customer.Address
$purchase.Total

$page.SetTitle(  "My Home Page" )
$person.SetAttributes( ["Strange", "Weird",  "Excited"] )

指令
#set( $primate = "monkey" )
#set(  $monkey.Friend = "monica" )

#set( $criteria = ["name",  "address"] )
#foreach( $criterion in $criteria )

#set(  $result = $query.criteria($criterion) )
#if( $result )
Query was successful
#end
#end

If / ElseIf / Else
Foreach  循环

#if( $foo < 10 )
<strong>Go North</strong>
#elseif(  $foo == 10 )
<strong>Go East</strong>
#elseif( $bar == 6 )
<strong>Go South</strong>
#else
<strong>Go West</strong>
#end

<ul>
#foreach( $product in $allProducts )
<li>$product</li>
#end
</ul>

宏 (称为函数更合适)
#macro  脚本元素允许模板设计者在VTL 模板中定义重复的段。 Velocimacros  不管是在复杂还是简单的场合都非常有用。下面这个Velocimacro,仅用来节省击键和减少排版错误,介绍了一些NVelocity宏的概念。
可 以带参数,参数放在宏名称的后面,空格隔开

#macro( d )
<tr><td></td></tr>
#end

#d()

#macro(  callme $a )
$a $a $a
#end

#callme( $foo.bar() )

包含
#include 脚本元素允许模板设计人员包含(导入)本地文件, 这个文件将插入到#include  指令被定义的地方。文件的内容并不通过模板引擎来渲染。
#include( "one.txt" )

解析
#parse 脚本元素允许页面设计员导入包含VTL的本地文件。 Velocity将解析和渲染指定的模板。
#parse( "me.vm" )

在根目录NVelocity-***\test\templates下有各种模板语言语法的实例,在NVelocity-***\  examples目录下有如何在C#中使用模板引擎的简单例子。
在.NET中使用时候,需要应用两个程序集,NVelocity.dll 和  Commons.dll,这些文件在NVelocity-***\Build目录下。
可以加入nvelocity.properties对模板引 擎的一些参数进行配置。

[转载]在ASP.NET MVC中绑定数据(包括分页和排序) - 三思而后行 - 博客园

mikel阅读(922)

[转载][翻译]在ASP.NET MVC中绑定数据(包括分页和排序) – 三思而后行 – 博客园.

注:此文翻译的比较仓促,个别用语没有统一,也没有时间对译文做复 查。

原作者的源代码是基于mvc2写的,我本机环境是mvc1,所以手工 将源代码做了转换,提供下载的源代码里有两个目录,其中MVC2Grid目录下是原作者提供的代码,在我本地没有测试(基于MVC2),MVCGrid是 我转换过的,在本地编译通过,运行成功。

代码下载:/Files/sansi/Mvc2Grid.rar
摘要:在此帖中我将向您展示如何在ASP.NET MVC中建立带排序和分页功能的Data Grid,你可以对生成的 HTML有足够的控制权,更重要的是这所有的操作都通过可测试的方法。另外当用户想分享某一页的排序数据时可以使用书签,只要在浏览器的地址栏里拷贝粘贴 URL。我们避免使用JavaScript实现这个,这样经常禁用JavaScript的用户不会错过任何东西。

任何网站应用程序的首先的需求之一是在表格里展示数据。在web form时代这是个简单的任务,直接在.aspx页面中放置GridView控件,配置一下数据源就可以了。但是这种方法你丢掉了最重要的东西。对于生成 的HTML代码没有足够的控件权,而且没有书签功能因为分页和排序信息要被存储在viewstate,这也是在开发web form程序的另一个问题。在此帖中,我将展示如何建立拥有GridView所有功能(像排序和分页)的Data Grid Helper方法,同时你还有对生成的HTML足够的控制权,并且你所做的这所有的一切都是可以测试的方法,在想分享某一页数据的时候使用书签功能。

为了在MVC.NET中创建任何HTML 都可以使用的辅助方法,你需要写一个HtmlHelpler类的扩展方法。因为我不想导入任何命名空间和web.config文件,我使用static类 在System.Web.Mvc.Html命名空间里创建一个扩展方法。

首先我将展示一个有排序功能的Grid Data Helper,然后我将创建一个Pager Helper为这个Grid增加分页功能。我使用的Grid Data Helper代码是Stephen Walther在他的ASP.NET MVC framework Unleashed书中提到的,分页部分我将使用Steven Sanderson在他的Pro ASP.NET MVC framework书提到的代码,该代码为分页链接生成HTML标记。我将扩展这段代码创建动态分页,并且使用Dynamic Linq示例代码写简单的排序逻辑,避免使用太多的switch case代码块。

让我们先看一下代码,然后研究它是如何工作的:

Grid Data Helper

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.IO;

namespace System.Web.Mvc.Html
{
public static class DataGridHelper
{
public static string DataGrid<T>(this HtmlHelper helper)
{
return DataGrid<T>(helper, null, null);
}

public static string DataGrid<T>(this HtmlHelper helper, object data)
{
return DataGrid<T>(helper, data, null);
}

public static string DataGrid<T>(this HtmlHelper helper, object data, string[] columns)
{
//Get items
var items = (IEnumerable<T>)data;
if (items == null)
items
= (IEnumerable<T>)helper.ViewData.Model;

//Get column names
if (columns == null)
columns
= typeof(T).GetProperties().Select(p => p.Name).ToArray();

//Create HtmlTextWriter
var writer = new HtmlTextWriter(new StringWriter());

//Open table tag
writer.RenderBeginTag(HtmlTextWriterTag.Table);

//Render Table Header
writer.RenderBeginTag(HtmlTextWriterTag.Thead);
RenderHeader(helper, writer, columns);
writer.RenderEndTag();

// Render table body
writer.RenderBeginTag(HtmlTextWriterTag.Tbody);
foreach (var item in items)
RenderRow
<T>(helper, writer, columns, item);
writer.RenderEndTag();

//Close  table tag
writer.RenderEndTag();

//return the string
return writer.InnerWriter.ToString();
}

private static void RenderHeader(HtmlHelper helper, HtmlTextWriter writer, string[] columns)
{
writer.RenderBeginTag(HtmlTextWriterTag.Tr);
foreach (var columnName in columns)
{
writer.RenderBeginTag(HtmlTextWriterTag.Th);
var currentAction
= (string)helper.ViewContext.RouteData.Values[action];
var link
= helper.ActionLink(columnName, currentAction, new { sort = columnName });
writer.Write(link);
writer.RenderEndTag();
}
writer.RenderEndTag();
}

public static void RenderRow<T>(HtmlHelper helper, HtmlTextWriter write, string[] columns, T item)
{
write.RenderBeginTag(HtmlTextWriterTag.Tr);
foreach (var columnName in columns)
{
write.RenderBeginTag(HtmlTextWriterTag.Td);
var value
= typeof(T).GetProperty(columnName).GetValue(item, null) ?? String.Empty;
write.Write(helper.Encode(value.ToString()));
write.RenderEndTag();
}
write.RenderEndTag();
}

}
}

这个helpers的核心代码仅仅创建一个表格。正如你看到的这个helpers是泛型方法,你可以在所有的领域模型类中使用。你可以通过参数传递 模型数据和列名,或者你仅仅通过ViewModel传递让helper为你做这个事。该第一个有趣的部分是我们使用了反射查找我们模型的列名。

columns = typeof(T).GetProperties().Select(p => p.Name).ToArray();

在这行代码中,我们获取所有的传递给helper模型的所有属性名称,把它放到列数据组,然后我们使用这个数据创建我们Grid表头的HTML代 码,并且我们将使用它生成的必须的用来调用action方法的链接,将列名做为排序表达式传递给它。

然后我们开始创建标记,正如你所看到的我们使用HtmlTextWriter类型实现这个任务更加简洁,可读,更少错误验证,当然也更加可测。但你 也可以使用StringBuiler类或者其它合作你觉得舒服的方法。

你所看到更加有兴趣的代码是RenderHeader和RenderRow方法,让我们来看一下:
RenderHeader:在这个方法 中,我们打算让表头列做为链接来调用排序方法。为了创建这些链接我们需要通过下面这行代码猎取当前的动作。

var currentAction = (string)helper.ViewContext.RouteData.Values[“action”];

然后我们使用 Html.ActionLink辅助方法生成实际的链接标记:

var link = helper.ActionLink(columnName, currentAction, new { sort = columnName });

RenderRow:在这个方法中我使用反射获取方法的属性填充 每个单元格,并且我们使用列数组指定属性的名称:

var value = typeof(T).GetProperty(columnName).GetValue(item, null) ?? String.Empty;

同样注意我们使用Html.Encode方法传递合法的Html标记到视图避免XSS或者其它JS攻击:

write.Write(helper.Encode(value.ToString()));

这样我们包含排序的GridData辅助方法就完成了,但分页怎么办呢?
我打算创建更有利于复用的分布辅助方法,这样我在Grid Data之外也可以使用。而且基于SoC的原因,我对将分页逻辑从排序逻辑分离出来非常感兴趣。首先来检查一下源代码然后分析它是如何工作的:

代码

namespace System.Web.Mvc.Html
{
public static class PageLinkHelper
{
public static string PageLink(this HtmlHelper html, int currentPage, int totalPages, Func<int, string> pageUrl)
{

var diff = 1;
StringBuilder result
= new StringBuilder();
var TotalPages
= totalPages;

if (currentPage < 1)
currentPage
= 1;

if ((currentPage + diff) < totalPages)
totalPages
= currentPage + diff;
var startPage
= 1;
if ((currentPage diff) > startPage)
startPage
= currentPage diff;

if ((currentPage diff) > 1)
{
TagBuilder tag3
= new TagBuilder(a);
tag3.Attributes.Add(
href, pageUrl(1));
tag3.InnerHtml
= 1;
result.AppendLine(tag3.ToString());
}

if ((currentPage (diff + 1)) > 1)
{
TagBuilder tag2
= new TagBuilder(a);
tag2.Attributes.Add(
href, pageUrl(currentPage (diff + 1)));
tag2.InnerHtml
= ;
result.AppendLine(tag2.ToString());
}

for (int i = startPage; i <= totalPages; i++)
{
TagBuilder tag
= new TagBuilder(a);
tag.Attributes.Add(
href, pageUrl(i));
tag.InnerHtml
= i.ToString();
if (i == currentPage)
tag.AddCssClass(
pageSelected);
result.AppendLine(tag.ToString());
}

if ((currentPage + (diff + 1)) < TotalPages)
{
TagBuilder tag2
= new TagBuilder(a);
tag2.Attributes.Add(
href, pageUrl(currentPage + (diff + 1)));
tag2.InnerHtml
= ;
result.AppendLine(tag2.ToString());
}

if ((currentPage + diff) < TotalPages)
{
TagBuilder tag3
= new TagBuilder(a);
tag3.Attributes.Add(
href, pageUrl(TotalPages));
tag3.InnerHtml
= TotalPages.ToString();
result.AppendLine(tag3.ToString());
}
return result.ToString();
}
}
}

这个辅助方法获取当前页和总页数,还有一个方法创建链接。
你将很快看到我们直接在视图中传递这些参数。我只是想展示这个分页助手的核心代 码:

代码

for (int i = startPage; i <= totalPages; i++)
{
TagBuilder tag
= new TagBuilder(a);
tag.Attributes.Add(
href, pageUrl(i));
tag.InnerHtml
= i.ToString();
if (i == currentPage)
tag.AddCssClass(
pageSelected);
result.AppendLine(tag.ToString());
}

在这里我们创建每一页的标记,为当前页加上CSS类,剩余的代码只是在创建”…”链接,检查当前页与首页或末页的区别。我创建只是显示首页,末 页和当前页的逻辑,还有上一页和下一页的分页链接和获取更多分页链接的”…”。很明显,你可以改变它实现你的需求,我期待在能在评论中看到更多更有效 更好的实现这些逻辑的方法。

到此为止我们所做的所有事情只是生成在视图上使用的基本的HTML标记!我们需要在控制器中创建核心的逻辑。但是首先我想解释一些重要的观点。首 先,在这个示例中我将使用默认的路由配置,并且借助于Repository和视图模型模式。我使用的数据库是Northwind示例数据库,我使用EF和 ORM,并且使用简单的DI模式从repository中分离控制器,使得它仅仅在依赖控制的结构。

好,让我们来检查一下控制器的源代码后看看它是如何工作的:

代码

namespace Mvc2Application1.Controllers
{
public class ProductController : Controller
{
private IProductsRepository _repository;
private int _pageSize;
public ProductController():this(new ProductsRepositoryEF())
{

}

public ProductController(IProductsRepository repository)
{
_repository
= repository;
_pageSize
= 10;
}

public ActionResult List(string sort, int? page)
{
var currentPage
= page ?? 1;
ViewData[
SortItem] = sort;
sort
= sort ?? Name;
ViewData[
CurrentPage] = currentPage;
ViewData[
TotalPages] = (int)Math.Ceiling((float)_repository.Products.Count() / _pageSize);

var products = from p in _repository.Products
select
new ProductViewModel()
{
Name
= p.ProductName,
Price
= p.UnitPrice ?? 0,
Category
= p.Category.CategoryName
};
var sortedProducts
= products
.OrderBy(sort)
.Skip((currentPage
1) * _pageSize)
.Take(_pageSize);
return View(sortedProducts);
}
}
}

你看到,我们通过参数传递排序和分页信息给动作方法,通过ViewData字典传递SortItem,CurrentPage和 TotalPages给视图。我使用实图模型模式传递所需的列给视图,并且让复杂的对象相对简单的类。最有趣部分是取代传递lambda表达式,我传递了 属性名做为排序条件。如果你尝试这样会得到错误。为了能够通过LINQ使用这种动态查询,你需要添加包括在Visual Studio中的Dynamic.cs类到你的工程 ,并且为你的控制器类添加System.Linq.Dynamic 命名空间,更多有关使用DynamicLinq的请参考Scott Guthrie’s blog 文章。这样我们的控制器足够简单并能工作,让我们转到视图代码完 成我们的杰作吧。

代码

<%@ Page Title=“” Language=C# MasterPageFile=~/Views/Shared/Site.Master Inherits=System.Web.Mvc.ViewPage<IEnumerable<Mvc2Application1.Models.ViewModels.ProductViewModel>> %>
<%@ Import Namespace=Mvc2Application1.Models.ViewModels %>
<asp:Content ID=”Content1″ ContentPlaceHolderID=”TitleContent” runat=”server”>
List
</asp:Content>

<asp:Content ID=”Content2″ ContentPlaceHolderID=”MainContent” runat=”server”>

<h2>List of Product</h2>
<%=Html.DataGrid<ProductViewModel>() %>
<div>
<%=Html.PageLink((int)ViewData[CurrentPage],
(
int)ViewData[TotalPages],
p
=> Url.Action(List,
new { page = p,
sort
= (string)ViewData[SortItem] }))%>
</div>
</asp:Content>

首先,请注意我导入了必须的命名空间。你看到视图的代码非常简单,我们通过Html.DataGrid助力展示Data Grid,对于分页部分我们只是调用了刚刚创建的Html.PageLink助手。最有趣的部分是我们传递给这个助手的方法:

p => Url.Action(“List”,new { page = p,sort = (string)ViewData[“SortItem”]})

这种方法让每个分页链接调用同一个动作方法(List),传递分页和排序参数,这样用户能通过传递Url的方式共享内容,因为所有的分页和排序信息 都可以在地址览中看到。这就是我们刚刚创建的 Data Grid,现在我们可以完全控制生成的HTML,并且我们可以轻松的测试我们的助手类。正如Rob Connery说的,每一次在你的视图中有一个if就创建一个助手类!这种你的视图更容易测试,并且HTML更友好。
总结:我们刚刚创建了两个有用的辅助方法,我们在我们的所有展示带排序和分页功能的Data Grid的工程中使用。另外,我们使用了动态Linq写简单的排序逻辑,并且实现我Data Grid 辅助类我们利用了C#的一些高级特性如:泛型和反射。我们还使用了TagBuilder和 HtmlTextWriter类写了简洁和可测试的辅助类。我们分页逻辑就完全从我们DataGrid中分离了,这样我们在任何数据列表中使用了。

原文地址:http://mvcsharp.wordpress.com/2010/02/11/building-a-data-grid-in-asp-net-mvc/

[转载]jquery的$(document).ready()和onload的加载顺序

mikel阅读(1054)

[转载]jquery的$(document).ready()和onload的加载顺序 – Joyce Liu – 博客园.

最近在改一个嵌入在frame中的页面的时候,使用了JQuery做效果,而页面本身也绑定了onload事件。改完后,Firefox下测 试正常流畅,IE下就要等个十几秒JQuery的效果才出现,黄花菜都凉了。

起初以为是和本身onload加载的方法冲突。网上普遍的说法是$(document).ready()是在页面DOM解析完成后执行,而 onload事件是在所有资源都准备完成之后才执行,也就是说$(document).ready()是要在onload之前执行的,尤其当页面图片较大 较多的时候,这个时间差可能更大。可是我这页面分明是图片都显示出来十几秒了,还不见jQuery的效果出来。

删了onload加载的方法试试,结果还是一样,看来没有必要把原本的onload事件绑定也改用$(document).ready()来 写。那是什么原因使得Firefox正常而IE就能呢?接着调试,发现IE下原来绑定的onload方法竟然先于$(document).ready() 的内容执行,而Firefox则是先执行$(document).ready()的内容,再执行原来的onload方法。这个和网上的说法似乎不完全一致 啊,呵呵,有点意思,好像越来越接近真相了。

翻翻jQuery的源码看看$(document).ready()是如何实现的吧:

1 if ( jQuery.browser.msie && window == top ) (function(){ 2 if (jQuery.isReady) return; 3 try { 4 document.documentElement.doScroll("left"); 5 } catch( error ) { 6       setTimeout( arguments.callee, 0 ); 7        return; 8     } 9    // and execute any waiting functions 10    jQuery.ready(); 11 })(); 12 13 …… 14 15 jQuery.event.add( window, "load", jQuery.ready ); 16  

结果很明了了,IE只有在页面不是嵌入frame中的情况下才和Firefox等一样,先执行$(document).ready()的内容, 再执行原来的onload方法。对于嵌入frame中的页面,也只是绑定在load事件上执行,所以自然是在原来的onload绑定的方法执行之后才轮 到。而这个页面中正好在测试环境下有一个访问不到的资源,那十几秒的延迟正是它放大出的时间差。

作者:Joyce Liu
出处:http://www.cnblogs.com/joycel/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]NVelocity实现违反了LSP法则

mikel阅读(1153)

[转载]NVelocity实现违反了LSP法则,使我的一个低级错误排查了一个下午。 – 奔波于北京城铁十三号线 – 博客园.

最近我在做一个CMS系统,需要一个模板引擎,选择了NVelocity,NVelocity是JAVA的开源模板引擎Velocity移植到 了.NET平台,这个不过多介绍。因为Velocity的模板语法很简单我就选用了它作为我们CMS的模板引擎。NVelocity好像是Castle Project维 护的,我在Castle Project的网站找到了入门文章, 看上去是比较简单的,很容易上手。然后我就照着他的例子编码:

var velocityEngine = new VelocityEngine();

var props = new ExtendedProperties();
props.Add(RuntimeConstants.INPUT_ENCODING,
utf-8);
props.Add(RuntimeConstants.OUTPUT_ENCODING,
utf-8);
props.Add(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, targetViewFolder);
velocityEngine.Init(props);

props.Add 那三句在例子中是没有的,是我自作聪明加上去的,因为如果不传入配置的话会按照NVelocity的默认配置,但是这么写是错的,为什么是错的稍后告诉大 家。

这段代码编译过去了,然后运行到这里也没有出问题,但是我在velocityEngine.GetTemplate的时候却出了问题, 报异常说找不到资源,经过断点调试发现了诡异的地方

为什么我的配置没有生效,反而还是默认的设置?然后我又在网上找了一些例子, 由于“老眼昏花”,并没有看出到底错在哪里,最后没办法我觉得需要追踪一下NVelocity的源代码,我在Castle Project的网站上下载整套源码,其中有NVelocity的代码,但是项目缺少directive.properties和 nvelocity.properties连个文件,我先项目中去掉了这个文件,发现虽然可以顺利编译,但是在使用NVelocity的时候连默认配置都 没有了,我只好在已经编译好的NVelocity.dll中取得了这个两个文件(以前还不知道怎么取DLL中的资源文件,又研究了N久),最后编译成功, 单步调适,跟着Init(props)方法进去看个究竟,果然发 现了奇怪的地方:

p.Keys怎么可能一个元素都没有?其实在监视props变量里面是有东东的,但是Keys里什么都没有,然后我通过分析ExtendedProperties发现了原因:
1)ExtendedProperties
是继承Hashtable 的
2)ExtendedProperties自己维护了 Keys

public new IEnumerable Keys
{
get { return keysAsListed; }
}

所 以,Add方法是Hashtable本身实现的,在使用Add方法增加元素的时候,ExtendedProperties.Keys并没有增加,应该使用扩展出来的AddProperty方法,执行 AddProperty才会顺便去维护keysAsListed, 我修改初始化的代码后发现一切正常了。后来我回头去看网上的例子,都是使用AddProperty的,汗死!!!最后贴出正确的初始化代码

var velocityEngine = new VelocityEngine();

var props = new ExtendedProperties();
props.AddProperty(RuntimeConstants.INPUT_ENCODING,
utf-8);
props.AddProperty(RuntimeConstants.OUTPUT_ENCODING,
utf-8);
props.AddProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, targetViewFolder);
velocityEngine.Init(props);

不 过这回老眼昏花也让我理解了LSP法则,面向对象实践中有一些法则,我往往是只知其然,不知其所以然,最后说说什么是LSP法则。:

LSP: The Liskov substitution principle
子类必须能够替换基类。Subtypes must be substitutable  for their base types.

ExtendedProperties继承Hashtable,但是并不能够代替基类,反而会导致行为不正常,让我陷入歧途, 一个低级的错误,一个下午的时间,不过总在下班前解决了。

[转载]一步一步打造WebIM(2)——消息缓存

mikel阅读(911)

[转载]一步一步打造WebIM(2)——消息缓存 – 卢春城专栏 – 博客园.

WebIM系列文章

一步一步打造WebIM(1)一文中,已经介绍了如何实现一个简单的WebIM,但是,这个WebIM有一个问 题,就是每一次添加消息监听器时,都必须访问一次数据库去查询是否有消息,显然,如果用户比较多时,必然对数据库的压力比较大。解决这个问题的一个方法就 是先将消息缓存在内存中,不立即写入数据库,等到缓存满了才写入数据库。本文将介绍如何实现 消息缓存。

基本思路

实现一个消息缓存管理类,以用户为单位缓存所有消息,每一个用户对应着一个 List<Message>,保存着该用户新收到的消息,消息缓存管理用一个Hashtable保存着所有用户对应的 List<Message>。

具体实现代码如下:

public class MessageCacheManagement
{
    static MessageCacheManagement m_Instance = new MessageCacheManagement();

    static public MessageCacheManagement Instance
    {
        get { return m_Instance; }
    }

    private MessageCacheManagement()
    {
    }

    Int32 m_Count = 0;
    Hashtable m_Cache = new Hashtable();

    List<Message> GetUserMessageCache(String user)
    {
        if (!m_Cache.ContainsKey(user))
        {
            m_Cache.Add(user, new List<Message>());
        }

        return m_Cache[user] as List<Message>;
    }

    /// <summary>
    /// 清除缓存
    /// </summary>
    public void Clear()
    {
        lock (m_Cache)
        {
            List<Message> msgs = new List<Message>();
            foreach (DictionaryEntry ent in m_Cache)
            {
                (ent.Value as List<Message>).Clear();
            }
            m_Count = 0;
        }
    }

    /// <summary>
    /// 获取所有缓存的消息
    /// </summary>
    /// <returns></returns>
    public List<Message> GetAll()
    {
        lock (m_Cache)
        {
            List<Message> msgs = new List<Message>();
            foreach (DictionaryEntry ent in m_Cache)
            {
                foreach (Message msg in ent.Value as List<Message>)
                {
                    msgs.Add(msg);
                }
            }
            return msgs;
        }
    }

    /// <summary>
    /// 获取某一用户缓存的消息的最小时间 
    /// </summary>
    public Nullable<DateTime> GetMinCreatedTime(string user)
    {
        lock (m_Cache)
        {
            List<Message> userMsgs = GetUserMessageCache(user);
            return userMsgs.Count == 0 ? null : new Nullable<DateTime>(userMsgs[0].CreatedTime);
        }
    }


    /// <summary>
    /// 在缓存中插入一条消息
    /// </summary>
    /// <param name="user"></param>
    /// <param name="msg"></param>
    public void Insert(String user, Message msg)
    {
        List<Message> userMsgs = null;

        lock (m_Cache)
        {
            userMsgs = GetUserMessageCache(user);
        }

        lock (userMsgs)
        {
            userMsgs.Add(msg);
            m_Count++;
        }
    }

    /// <summary>
    /// 查找缓存中接受者为user,发送时间大于from的消息
    /// </summary>
    public List<Message> Find(String user, DateTime from)
    {
        List<Message> userMsgs = null;

        lock (m_Cache)
        {
            userMsgs = GetUserMessageCache(user);
        }

        lock (userMsgs)
        {
            List<Message> msgs = new List<Message>();

            int i = 0;
            while (i < userMsgs.Count && userMsgs[i].CreatedTime <= from) i++;

            while (i < userMsgs.Count) { msgs.Add(userMsgs[i]); i++; }

            return msgs;
        }
    }

    /// <summary>
    /// 获取消息总量
    /// </summary>
    public Int32 Count
    {
        get { return m_Count; }
    }
}

添加消息监听器

增加消息缓存后,添加消息监听器的流程也要修改,具体思路是先获取消息接收者在缓存中发送时间最早的消息的发送时间,显然,如果监听器的From大于或等于这个最小发送时间时,无需访问数据库,可以直接访问缓存。具体代码修改 为:

/// <summary>
/// 添加消息监听器,如果查找到符合监听器条件的消息,返回false,此时不会添加监听器
/// 如果没有查找到符合监听器条件的消息,返回true,此时监听器将被添加到m_Listeners中
/// </summary>
public bool AddListener(String receiver, String sender, Nullable<DateTime> from, WebIM_AsyncResult asynResult)
{
    MessageListener listener = new MessageListener(receiver, sender, from, asynResult);
    lock (m_Lock)
    {
        if (!m_Listeners.ContainsKey(receiver))
        {
            m_Listeners.Add(receiver, new List<MessageListener>());
        }
        List<MessageListener> listeners = m_Listeners[receiver] as List<MessageListener>;

        //获取用户receiver缓存的消息的最小发送时间
        Nullable<DateTime> min = MessageCacheManagement.Instance.GetMinCreatedTime(receiver);

        List<Message> messages = new List<Message>();

        //当from >= 缓存在内存中的消息的最小时间时,不必查询数据库
        if (min == null || from == null || from.Value < min.Value)
        {
            //查询数据库
            messages.AddRange(Find(receiver, sender, from));
        }

        //在缓存中查询
        messages.AddRange(MessageCacheManagement.Instance.Find(receiver, from.Value));

        if (messages.Count == 0)
        {
            //插入监听器
            listeners.Add(listener);
        }
        else
        {
            //发送消息
            listener.Send(messages);
        }
        return messages.Count == 0;
    }
}

发送消息

增加消息缓存后,发送消息的流程也要修改,具体思路是:先将消息保存到缓存中,之后判断缓存的消息的 总数,如果超过设定的上限,就将消息写入数据库。具体代码修改为(您可以通过修改MAX_CACHE_COUNT修改缓存消息数的上限):

/// <summary>
/// 插入新的消息,插入消息后将查询m_Listeners中是否有符合条件的监听器,如存在,同时将消息发送出去
/// </summary>
public Message NewMessage(String receiver, String sender, DateTime createdTime, String content)
{
    lock (m_Lock)
    {
        Message message = new Message(sender, receiver, content, createdTime, ++m_MaxKey);

        List<Message> messages = new List<Message>();
        messages.Add(message);

        if (m_Listeners.ContainsKey(receiver))
        {
            List<MessageListener> listeners = m_Listeners[receiver] as List<MessageListener>;
            List<MessageListener> removeListeners = new List<MessageListener>();
            foreach (MessageListener listener in listeners)
            {
                if ((listener.Sender == "*" || String.Compare(listener.Sender, sender, true) == 0) &&
                    (listener.From == null || message.CreatedTime > listener.From))
                {
                    listener.Send(messages);
                    removeListeners.Add(listener);

                    System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(listener.Complete));
                }
            }

            foreach (MessageListener listener in removeListeners)
            {
                //移除监听器
                listeners.Remove(listener);
            }
        }

        MessageCacheManagement.Instance.Insert(receiver, message);

        if (MessageCacheManagement.Instance.Count >= MAX_CACHE_COUNT)
        {//超过缓存的最大值,将缓存中的消息全部写入数据库
            //启动事务
            SQLiteTransaction trans = m_Conn.BeginTransaction();

            try
            {
                List<Message> cacheMsgs = MessageCacheManagement.Instance.GetAll();

                foreach (Message msg in cacheMsgs)
                {
                    SQLiteCommand cmd = new SQLiteCommand(
                        "insert into Message (Receiver,Sender,Content,CreatedTime,Key) values (?,?,?,?,?)",
                        m_Conn
                    );
                    cmd.Parameters.Add("Receiver", DbType.String).Value = msg.Receiver;
                    cmd.Parameters.Add("Sender", DbType.String).Value = msg.Sender;
                    cmd.Parameters.Add("Content", DbType.String).Value = msg.Content;
                    cmd.Parameters.Add("CreatedTime", DbType.DateTime).Value = msg.CreatedTime;
                    cmd.Parameters.Add("Key", DbType.Int64).Value = msg.Key;

                    cmd.ExecuteNonQuery();
                }

                trans.Commit();
            }
            catch
            {
                trans.Rollback();
            }

            MessageCacheManagement.Instance.Clear();
        }

        return message;
    }
}

源代码下载此次源代码仅修改了MessageManagement.cs文件,如果您有任何问题,可通过WebIM和我联系

[转载]一步一步打造WebIM(3)——性能测试

mikel阅读(1133)

[转载]一步一步打造WebIM(3)——性能测试 – 卢春城专栏 – 博客园.

WebIM系列文章

一步一步打造WebIM(1)(2)中,已经讨论了如何开发一个WebIM,并且使用缓存来提高WebIM的性能,本文将编写一个程序模拟大 量用户登录来对WebIM进行性能测试。

1.200个用户同时 在线

测试一将模拟200个用户同时登录的聊天室,每个用户以1条消息 /秒的速度发送消息(由于网络和服务器处理延迟,可能多于1秒)

环 境参数

操作系统:Window Server 2003

内存:2G

CPU:AMD Athlon(tm) 64 X2 Dual 2.4GHz

服务器:IIS6

数据库:SQLite

消 息缓存数量:200

测试过程截图

1

2

测试结果

200

测试结果解析

测试程序启动了200个接收线程和200个发送线程(以1条消息/秒的速度发送消 息,连续发50条),根据测试结果显示,发送消息的平均延迟大概为1000ms(即从客户端发送消息到服务 器将消息写到缓存中大概要1000ms),接收消息的平均延迟为大概600ms(服务器将消 息写到缓存600ms后,用户才在浏览器中接收到该消息)。由于每一个用户都会接受到其它 199个用户的消息,因此,200个接受线程如果消息完全接收,应该有200*50*200 = 2000000条消息,显然,当发送消息 的200个线程结束时,接收消息的线程仍然没有结束。由于发送消息的平均延迟大概为1000ms,因此,相当于200个用户同时以1条消息/2秒的频率向聊天室发送消息。

2.500个用户同时在线

测试一将模拟500个用户同时登录的聊天室,每个用户以1条消息/秒的速度发送消息(由于网络和服务器处理延 迟,可能多于1秒)

环境参数

操作系统:Window Server 2003

内存:2G

CPU:AMD Athlon(tm) 64 X2 Dual 2.4GHz

服务器:IIS6

数据库:SQLite

消息缓存数量:200

测试过程 截图

3

测试结果

500

测试结果的意义和测试1类似,因此不做详细解析

从以上测试来看,同时在线人数达到500时,延迟还是比较大的

测试程序及WebIM 源代码下载