[C#]C#中的数字格式化 格式日期格式化

mikel阅读(1135)

字符串格式化这部分内容是我们经常用到的,如“2008-03-26”日期格式、“28.20”数字格式。
  举一个例子,我们有时需要将订单号“12”显示为“00000012”这种样式(不足8位前面补0),就可以使用下面的方法:
  int originalCode = 12;
  Response.Write(string.Format("{0:00000000}", originalCode));
  或者
  int originalCode = 12;
  Response.Write(originalCode.ToString("00000000"))又如我们在使用日期做为某种关键字时,比如图 片的文件名,一般是到秒级,如 “20080326082708”Response.Write(DateTime.Now.ToString("yyyyMMddHHmmss")); // 输出:20080326082708
  这样如果并发操作比较多的话,就会产生文件重名的现象。我们可以将日期精确到1/10000000秒,这样的话重名的可能性就很小了。
  Response.Write(DateTime.Now.ToString("yyyyMMddHHmmssfffffff"));// 输出:200803260827087983268
  =====================================================================
  格式
  基本内容是:可以在 Console.WriteLine(以及 String.Format,它被 Console.WriteLine 调用)中的格式字符串内的括号中放入非索引数字的内容。格式规范的完整形式如下:
  {index [, width][:formatstring]}
  其中,index 是此格式程序引用的格式字符串之后的参数,从零开始计数;width(如果有的话)是要设置格式的字段的宽度(以空格计)。width 取正数表示结果右对齐,取负数则意味着数字在字段中左对齐。(请参阅下面的前两个示例。)
  formatstring 是可选项,其中包含有关设置类型格式的格式说明。如果对象实现 IFormattable,formatstring 就会传递给对象的 Format 方法(在 Beta 2 和后续版本中,该方法的签名变为 ToString(string, IFormatProvider),但功能不变)。如果对象不实现 IFormattable,就会调用 Object.ToString(),而忽略 formatstring。
  另请注意,在 Beta 1 中不区分当前语言的 ToString 在 Beta 2 和后续版本中“将”区分语言。例如,对于用“.”分隔千位,用“,”分隔小数的国家,1,234.56 将会格式化成 1.234,56。如果您需要结果无论在什么语言下都是一样的,就请使用 CultureInfo.InvariantCulture 作为语言。
  若要获取有关格式的完整信息,请查阅“.NET 框架开发人员指南”中的格式概述(英文)。
  数字格式
  请注意,数字的格式是区分语言的:分隔符以及分隔符之间的空格,还有货币符号,都是由语言决定的 — 默认情况下,是您计算机上的默认语言。默认语言与执行线程相关,可以通过 Thread.CurrentThread.CurrentCulture 了解和设置语言。有几种方法,可以不必仅为一种给定的格式操作就立即更改语言。
  内置类型的字母格式
  有一种格式命令以单个字母开头,表示下列设置:
  G—常规,E 或 F 中较短的
  F—浮点数,常规表示法
  E—用 E 表示法表示的浮点数(其中,E 代表 10 的次幂)
  N—带有分隔符的浮点数(在美国为逗号)
  C—货币,带有货币符号和分隔符(在美国为逗号)
  D—十进制数,仅用于整型
  X—十六进制数,仅用于整型
  字母可以后跟一个数字,根据字母的不同,该数字可以表示总位数、有效位数或小数点后面的位数。
  下面是字母格式的一些示例:
  
  double pi = Math.PI;
  double p0 = pi * 10000;
  int i = 123;
  Console.WriteLine("浮点格式,无分隔符(逗号)");
  Console.WriteLine("pi, Left {0, -25}", pi); // 3.1415926535897931
  Console.WriteLine("p0, Rt. {0, 25}", p0); // 3.1415926535897931
  Console.WriteLine("pi, E {0, 25:E}", pi); // 3.1416E+000
  Console.WriteLine("使用 E 和 F 格式,小数点后保留 n(此处为 4)位");
  Console.WriteLine("pi, E4 {0, 25:E4}", pi); // 3.1416E+000
  Console.WriteLine("pi, F4 {0, 25:F4}", pi); // 3.1416
  Console.WriteLine("使用 G 格式,保留 4 位有效数字——如果需要请使用 E 格式");
  Console.WriteLine("pi, G4 {0, 25:G4}", pi); // 3.142
  Console.WriteLine("p0, G4 {0, 25:G4}", p0); // 3.142E4
  Console.WriteLine("N 和 C 格式带有逗号(分隔符)," +
  "默认小数点后保留两位,四舍五入。");
  Console.WriteLine("p0, N {0, 25:N}", p0); // 31,415.93
  Console.WriteLine("p0, N4 {0, 25:N4}", p0); // 31,415.9265
  Console.WriteLine("p0, C {0,25:C}", pi); //  $3.14
  Console.WriteLine("D 和 X 格式仅用于整型," +
  "非整型将产生格式异常——X 指十六进制");
  Console.WriteLine("i, D {0, 25:D}", i ); // 123
  Console.WriteLine("i, D7 {0, 25:D7}", i ); // 0000123
  Console.WriteLine("i, X {0, 25:X}", i ); // 7B
  Console.WriteLine("i, X8 {0, 25:X8}", i ); // 0000007B

  图片格式
  与字母格式不同,formatstring 可以包含“图片格式”。下面是从代码中摘录的几个实例。(这类似于 Basic 中的“Print Using”语句。)图片格式功能甚至包括以不同方式设置负数、正数和零的格式的能力。还有几个图片格式功能,下面的示例中未包括在内。有关详细信息,请 参阅“.NET 框架开发人员指南”或文档中的主题图片格式数字串(英文)。
  在下例中您将注意到,好心的博士既使用了“#”字符,又使用了“0”字符。如果相应的数字是前导零或尾随零,“#”字符就会替换为空值。无论相应数字 的值如何,“0”字符都会被替换为零字符 — 因此,数字将会被零填补。句号(如果有的话)表示小数分隔符的位置。
  那么,为什么要同时使用这些字母,比如“###0.##”? 如果要设置格式的值恰好为零,“#” 图片字符就被替换为“无”(连零字符也不是)。您可能“总是”希望在小数点的左边至少有一个“0”,否则,如果值为零,字段就没有输出。换言之,仅包含 “#”字符,一个“0”也没有的格式常被认为是一个编程错误。
  逗号有两种用法:如果一个逗号或一组逗号紧跟在句号的左边(或者没有句号时在结尾),它们就会告诉格式化程序分隔 10 ** (3 * n) 所显示的数字,其中,n 是逗号的个数。换言之,数字按千位、百万位、十亿位等分隔。
  如果逗号的右侧至少有一个“0”或“#”占位符,它就会告诉格式化程序在各数位组之间放置适当的组分隔符字符(在美国为逗号。)(在美国,每三个数位算一组。)
  可以设置百分比的格式,方法是在图片中放入“%”。“%”将在指定的位置显示,在显示前数字将被乘以 100(这样,0.28 就变成了 28%)。
  如果希望将图片格式用于指数表示法,可以指定“e”或“E”后跟加号或减号,再后跟任意个零,比如“E+00”或“e-000”。如果使用“e”,则 显示小写“e” 。如果使用“E”,则显示大写“E” 。如果使用加号,则指数的符号总是出现。如果使用减号,则符号只有在指数为负数时才会显示。(Beta 1 版在处理“-”时有问题,该符号会导致负号总是出现。)
  根据要设置格式的数字的符号,还有一个条件格式。在格式字符串中仅包含两个或三个独立的格式,它们由分号分隔。如果有两个格式,则第一个将用于非负数,第二个用于负数。如果有三个格式,则第一个将用于正数,第二个用于负数,第三个用于零。
  可以在格式字符串中包含文字字符。如果所需的字符具有特殊意义,请在其前面使用反斜杠符号,使其“转义”。例如,如果希望在不乘以 100 的情况下显示百分比符号,就可以在数字前面使用反斜杠(在 C++ 和 C# 中必须使用两个反斜杠),比如“#0.##\%”。(如果正在使用 C#,就可以使用极酷的逐字字符串文字,比如@"#0.##%"。)或者,也可以将字符串放入单引号或双引号中,以避免将其字符解释为格式命令。在 Beta 2 及更高版本中,可以通过使用双括号,从而在格式字符串中包含文字括号。
  下面是有关图片格式的一些示例:
  long m34 = 34000000; // 34,000,000
  Console.WriteLine("几种图片格式");
  Console.WriteLine("如果没有数位,0 将打印 0;" +
  "诸如 i: 的文字总是打印");
  Console.WriteLine("t句点代表小数分隔符的位置");
  Console.WriteLine("i, i: 0000.0 {0, 10:i: 0000.0}", i); //
  i:0123.0
  Console.WriteLine("如果没有有效数字 # 将不显示," +
  "逗号意味着放入分隔符");
  Console.WriteLine("请确保在数字图片中至少使用一个 0。");
  Console.WriteLine("p0, ##,##0.# {0, 10:##,##0.#}",-p0); // -31,415.9
  Console.WriteLine("m34, 0,, {0, 10:0,, 百万}", m34); // 34 百万
  Console.WriteLine("p0, #0.#E+00 {0, 10:#0.#E+00}", p0); // 31.4E+03
  Console.WriteLine("% 乘以 100 并打印百分号");
  Console.WriteLine("pi, ###0.##% {0, 10:###0.##%}", pi); // 314.16%
  Console.WriteLine("因为 \ 而没有进行乘法运算" +
  "(注意:两个反斜线!)");
  Console.WriteLine("pi, ###0.##\\% {0, 10:###0.##\%}", pi); // 3.14%
  Console.WriteLine("与 C# 的逐字字符串相同");
  Console.WriteLine(@"pi, ###0.##\% {0, 10:###0.##%}", pi); // 3.14%
  Console.WriteLine("10, '#'#0 {0, 10:'#'#0}", 10); // #10
  Console.WriteLine("基于符号的条件格式");
  Console.WriteLine("如果是 0 或正数打印 #,如果是负数打印 (#)");
  Console.WriteLine("-5 0;(0) {0, 10:0;(0)}", -5); // (5)
  Console.WriteLine("如果是正数打印 #,如果是负数打印 -#,如果是 0 打印 zip");
  Console.WriteLine(" 0 0;-0;zip {0, 10:0;-0;zip}", 0); // zip
  如您所见,格式功能非常强大。
  格式的工作方式
  文档中的示例对所传递的对象类型的变量调用 Format 方法。对这些 Format 方法仅传递格式规范的 formatstring 部分,而不传递 index 和 width。(在 Beta 2 中,对 Format 的调用将改为对 ToString 的调用。)
  index 和 width 由 String.Format(它被 Console.Write 和 Console.WriteLine 调用)使用,以获得调用 Format 的正确对象以及将该调用的结果左或右对齐。(顺便说一下,如果要设置格式的对象不实现 IFormattable(并因此调用 Format 方法),String.Format 将调用对象的 ToString() 方法,而忽略 formatstring。)
  换言之,Console.WriteLine 调用 String.Format,传递向它传递的所有参数。String.Format 分析字符串,查找“{”字符。找到该字符后,它将分析子字符串直到第一个“}”为止,以确定 index 数、width 和 formatstring。然后,它按照 index 访问相应的参数,并调用其 Format 方法,传递“{}”段中的 formatstring 部分。(如果参数对象不实现 IFormattable,则被调用的是 ToString。)
  无论是实现还是不实现,都会返回一个字符串,并且 String.Format 在继续分析格式字符串之前会将其与结果字符串连接。之后,String.Format 将生成的带格式字符串返回给 Console.WriteLine,由 Console.WriteLine 进行显示。
  对于 Beta 2 及更高版本,对象的 Format 方法(它是 IFormattable 中的 Format 方法)被 ToString 所替代,ToString 获取一个格式字符串和一个 IFormatProvider(或 null)。但 String.Format 仍存在,因此这些调用将不改变。
  自定义格式
  您自己也可以编写格式化程序,用于自己的类型或作为内置类型的自定义格式化程序,如“.NET 框架开发人员指南”中的自定义 Format 方法所说明的那样。如果编写内置类型的自定义格式化程序,就不能从 Console.WriteLine 中使用它,但可以通过调用 String.Format 的重载而使用它,String.Format 的重载将采用 IServiceObjectProvider(在 beta 2 及更高版本中称为 IFormatProvider)作为参数。
  日期和时间格式
  您将记起,有一个叫做 DateTime 的类,用于保存日期和时间。像您所猜想的那样,有大量方法可供设置 DateTime 对象的格式:仅日期、仅时间、世界时或本地时、若干种日/月/年顺序,甚至可分类。日期和时间格式是区分语言的。
  还可以使用自定义格式字符串来设置 DateTime 对象的格式。这种字符串将包含由某些字母组成的区分大小写的子字符串,以表示日期和时间的各个不同部分,如星期几、几号、月份、年份、纪元、小时、分钟、 秒或时区。这些部分中有许多具有多种格式,例如,M 是没有前导零的数字月份,MM 是有前导零的数字月份,MMM 是三个字母的月份缩写,MMMM 是所在国家语言对应的完整月份名称的拼写。在“.NET 框架参考”中可以找到自定义和标准格式字符的完整列表。
  下面是有关日期和时间格式的一个示例:
  Console.WriteLine("标准格式");
  // 后面的“分析”中会有更多信息
  DateTime dt = DateTime.Parse("2001 年 1 月 1 日,12:01:00am");
  Console.WriteLine("d: {0:d}", dt); // 1/1/2001
  Console.WriteLine("D: {0:D}", dt); // 2001 年 1 月 1 日,星期一
  Console.WriteLine("f: {0:f}", dt); // 2001 年 1 月 1 日,星期一 12:01 AM
  Console.Write("F: {0:F}", dt); // 2001 年 1 月 1 日,星期一 12:01:00 AM
  Console.WriteLine();
  Console.WriteLine("g: {0:g}", dt); // 1/1/2001 12:01 AM
  Console.WriteLine("G: {0:G}", dt); // 1/1/2001 12:01:00 AM
  Console.WriteLine("M/m: {0:M}", dt); // 2001 年 1 月
  Console.WriteLine("R/r: {0:R}", dt); // 2001 年 1 月 1 日,星期一 08:01:00 GMT
  Console.WriteLine("s: {0:s}", dt); // 2001-01-01T00:01:00
  Console.WriteLine("t: {0:t}", dt); // 12:01 AM
  Console.WriteLine("T: {0:T}", dt); // 12:01:00 AM
  Console.WriteLine("u: {0:u}", dt); // 2001-01-01 08:01:00Z
  Console.Write("U: {0:U}", dt); // 2001 年 1 月 1 日,星期一 8:01:00 AM
  Console.WriteLine();
  Console.WriteLine("Y/y: {0:Y}", dt); // 2001 年 1 月
  Console.WriteLine("自定义格式");
  // 对作为格式使用的字符必须“转义”—此处为 t 和 z
  // 同时使用引号(在文字字符串中)和反斜杠
  Console.WriteLine(@"dddd, dd MMMM yyyy"" at ""HH:mm:ss in zone zzz:");
  Console.WriteLine(@"{0:dddd, dd MMMM yyyy"" at ""HH:mm:ss in zone zzz}",
  dt);
  // 2001 年 1 月 1 日,星期一 00:01:00 于时区 -08:00
http://www.microsoft.com/china/MSDN/library/archives/library/welcome/dsmsdn/drguinet03292001.asp
  程序:
  using System;
  using System.Collections.Generic;
  using System.Text;
  namespace ConsoleApplication1
  {
   class Program
   {
   static void Main(string[] args)
   {
   double pi = Math.PI;
   double p0 = pi * 10000;
   int i = 123;
   long m34 = 34000000; // 34,000,000
   Console.WriteLine("几种图片格式");
   Console.WriteLine("如果没有数位,0 将打印 0;" +
   "诸如 i: 的文字总是打印");
   Console.WriteLine("t句点代表小数分隔符的位置");
   Console.WriteLine("i, i: 0000.0 {0, 10:i: 0000.0}", i); // i:0123.0
   Console.WriteLine("如果没有有效数字 # 将不显示," +
   "逗号意味着放入分隔符");
   Console.WriteLine("请确保在数字图片中至少使用一个 0。");
   Console.WriteLine("p0, ##,##0.# {0, 10:##,##0.#}", -p0); // -31,415.9
   Console.WriteLine("m34, 0,, {0, 10:0,, 百万}", m34); // 34 百万
   Console.WriteLine("p0, #0.#E+00 {0, 10:#0.#E+00}", p0); // 31.4E+03
   Console.WriteLine("% 乘以 100 并打印百分号");
   Console.WriteLine("pi, ###0.##% {0, 10:###0.##%}", pi); // 314.16%
   Console.WriteLine("pi, ###0.##% {0, 10:#######0.##%}", pi); // 314.16%
   Console.WriteLine("因为 \ 而没有进行乘法运算" +
   "(注意:两个反斜线!)");
   Console.WriteLine("pi, ###0.##\\% {0, 10:###0.##\%}", pi); // 3.14%
   Console.WriteLine("与 C# 的逐字字符串相同");
   Console.WriteLine(@"pi, ###0.##\% {0, 10:###0.##%}", pi); // 3.14%
  Console.WriteLine("10, '#'#0 {0, 10:'#'#0}", 10); // #10
   Console.WriteLine("10, '#'#0 {0, 10:##0}", 10); // 10
   Console.WriteLine("基于符号的条件格式");
   Console.WriteLine("如果是 0 或正数打印 #,如果是负数打印 (#)");
   Console.WriteLine("-5 0;(0) {0, 10:0;(0)}", -5); // (5)
   Console.WriteLine("如果是正数打印 #,如果是负数打印 -#,如果是 0 打印 zip");
   Console.WriteLine(" 0 0;-0;zip {0, 10:0;-0;zip}", 0); // zip
   Console.ReadLine();
   }
  }
 }

[C#]Emit学习系列文章导航

mikel阅读(1071)

Emit学习系列文章导航

这两个星期来一直在学习Emit方面的相关内容,基础的理论已经基本学习完毕,剩下的就要靠实践的积累了,在学习的过程中,也把自己的心得、体会、碰到的问题都记录了下来,形成了一个Emit学习的系列文章,现在这个系列暂时告一段落,等到有了一定的实践积累,或者在实践中发现了什么新的问题,我会继续更新这一系列的文章,尤其是最后实践篇中的内容。现在将这些内容整理了一下,发到首页,希望能够对那些对EmitIL有兴趣的人提供那么一点帮助,大家如果有什么问题可以在文后留言回复,我会尽力解答。这里留下我的联系方式:MSNyinqql.cn@163.com QQ:413183023 ,由于工作原因只能在晚上上网,请大家谅解,最后附上这一系列文章的导航:

1.   前言

2.   基础篇

l  HelloWorld

l  基本概念介

l  OpCodes说明

l  为动态类添加属性、构造函数、方法

l  使用循环

3.   进阶篇

l  异常处理

l  定义事件

4.   答疑篇

l  Call和Callvirt的区别

l  值类型和引用类型的区别

5.   实践篇

[SQL]在SQL Server中使用种子表生成流水号注意顺序

mikel阅读(979)

前几天一个人问到了关于流水号重复的问题,我想了下,虽然说这个问题比较简单,但是具有广泛性,所以写了这篇博客来介绍下,希望对大家有所帮助。

在进行数据库应用开发时经常会遇到生成流水号的情况,比如说做了一个订单模块,要求订单号是唯一的,规则是:下订单时的年月日+6位的流水号这样的规则。

对于这种要生成流水号的系统,我们一般是在数据库中新建了一个种子表,每次生成新的订单时:

1.读取当天种子最大值。

2.根据种子最大值和当时的年月日生成唯一的订单号。

3.更新种子最大值,使最大值+1。

4.根据生成的订单号将订单数据插入到订单表中。

以上几步操作是在一个事务中完成,保证了流水号的连续。这个思路是正确的,使用起来好像也没有什么问题,但是在业务量比较大的情况下却经常报错:“订单号违反主键约束,不能将重复的订单号插入到订单表中。”这是怎么回事?让我们做一个简单的Demo来重现一下:

1.创建种子表和订单表,这里只是一个简单的Demo,所以就省去了很多字段,而且订单号假设就是一个流水号,不用再使用年月日+6位流水号了。

Create TABLE Seek 种子表
(
    SeekValue 
INT
)
GO
Insert INTO Seek VALUES(0)种子初始值为0
GO
Create TABLE Orders
(
    OrderID 
INT PRIMARY KEY订单号,主键
    Remark VARCHAR(5NOT NULL
)

 2.创建一个存储过程,该存储过程传入Remark参数,根据生成的流水号插入到订单表中:

Create PROC AddOrder Author:深蓝
@remark VARCHAR(5传入的参数
AS
DECLARE @seek int 
BEGIN TRAN  开启一个事务
Select @seek=SeekValue 读取种子表中的最大值作为流水号
FROM Seek
生成订单号这一步省略,因为这里假定的订单的编号就是流水号

Update Seek SET SeekValue=@seek+1 更新种子表,使最大值+1

Insert INTO t1 VALUES(@seek,@remark插入一条订单数据

COMMIT 提交事务

3.新建一个查询窗口,使用以下语句调用创建的存储过程,不断的插入新订单:

WHILE 1=1
EXEC AddOrder 'test1' 不断的插入订单

 

4.再新建一个查询窗口,使用通过的方式,不断的插入新订单,这样用于模拟高并发时候的情况:

WHILE 1=1
EXEC AddOrder 'test2'

 

5.运行了一段时间后,我们停止这两个死循环,我们可以看到消息窗口中存在大量的异常:

消息 2627,级别 14,状态 1,过程 AddOrder,第 11 行
违反了 PRIMARY KEY 约束 'PK__Orders__C3905BAF08EA5793'。不能在对象 'dbo.Orders' 中插入重复键。

语句已终止。

为什么会这样呢?这得从事务隔离级别和锁来解释:

一般我们写程序时都是使用的是默认的事务隔离级别——已提交读,在第一步查询Seek表时,系统会为该表放置共享锁,而锁的兼容性中共享锁和共享锁 是可以兼容的,所以一个事务在读取Seek表最大值时,其他事务也可以读取出相同的最大值,两个事务中读取到了相同的最大值,所以产生了相同的流水号,所 以产生了相同的订单号,所以才会出现违反主键约束的错误。

既然知道了这其中的原理了,那么解决办法也就有了,只需要先对种子表中的数+1,然后再进行读取即可,修改存储过程如下:

Alter PROC AddOrderAuthor:深蓝
@remark VARCHAR(5)
AS
DECLARE @seek int
BEGIN TRAN
Update Seek SET SeekValue=SeekValue+1  先修改数据

Select @seek=SeekValue1 已经加了1,所以这里-1下来
FROM Seek
Insert INTO Orders VALUES(@seek,@remark)
COMMIT

 

为什么这样写就可以呢?第一步执行更新操作,系统会请求更新锁然后再升级为排他锁,因为更新锁和更新锁以及排他锁都是不兼容的,所以一个事务对Seek表进行了更新后,其他的事务就不能对表进行更新操作,只有等到事务提交以后才能继续。

这里附上锁兼容性表:

现有授予模式
请求模式 IS S U IX SIX X
意向共享 (IS)
共享 (S)
更新 (U)
意向排他 (IX)
意向排他共享 (SIX)
排他 (X)
【出自博客园深蓝居,转载请注明作者出处】

[C#][翻译-ASP.NET MVC]Contact Manager开发之旅迭代3 - 验证表单

mikel阅读(1027)

[翻译-ASP.NET MVC]Contact Manager开发之旅迭代3 – 验证表单

本翻译系列为asp.net mvc官方实 例教程。在这个系列中,Stephen Walther将演示如何通过ASP.NET MVC framework结合单元测试、TDD、Ajax、软件设计原则及设计模式创建一个完整的Contact Manager应用。本系列共七个章节,也是七次迭代过程。本人将陆续对其进行翻译并发布出来,希望能对学习ASP.NET MVC 的各位有所帮助。由于本人也是个MVC菜鸟,且E文水平亦是平平,文中如有疏漏敬请见谅。
注:为保证可读性,文中Controller、View、Model、Route、Action等ASP.NET MVC核心单词均未翻译。

 

ContactManager开发之旅-索引页

ContactManager开发之旅 迭代1 – 创建应用程序

ContactManager开发之旅 迭代2 – 修改样式,美化应用

迭代3 – 验证表单

这是Contact Manager的第三次迭代,在这次迭代中我们将为Contact Manager添加基本的表单验证。如果用户填写的表单不完整,我们将阻止其表单的提交。另外我们还要验证电话号码和电子邮件地址的合法性。(图1)

 clip_image001

图1

本 次迭代中,我们将验证逻辑直接写在controller的action中,不过这并不是ASP.NET MVC应用所推荐的方式。更好的办法是将这些验证逻辑布置到另外的service层中。下一次迭代的时候我们将重构Contact Manager应用,使其更易维护。

为了让本文看起来直观些,我们将在本次迭代中手写所有的验证代码。当然我们也可以利用某些现成的 验证框架来实现自动生成这些验证代码。比如你可以使用Microsoft Enterprise Library Validation Application Block (VAB)来实现ASP.NET MVC的验证逻辑。欲知更多VAB的信息,请看下面的链接:

http://msdn.microsoft.com/en-us/library/dd203099.aspx

为Create View添加验证规则

现在就让我们开始为Create view添加验证规则吧。在前两次迭代中,我们已经通过VS生成了Create view,拖VS的福我们的Create view已经包含了页面中显示验证消息所需的所有逻辑。Create view如下:

 

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ContactManager.Models.Contact>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<h2>Create</h2>
<%= Html.ValidationSummary("Create was unsuccessful. Please correct the errors and try again.") %>
    <% using (Html.BeginForm()) {%>
        <fieldset>
<legend>Fields</legend>
<p>
<label for="FirstName">FirstName:</label>
<%= Html.TextBox("FirstName") %>
                <%= Html.ValidationMessage("FirstName", "*") %>
            </p>
<p>
<label for="LastName">LastName:</label>
<%= Html.TextBox("LastName") %>
                <%= Html.ValidationMessage("LastName", "*") %>
            </p>
<p>
<label for="Phone">Phone:</label>
<%= Html.TextBox("Phone") %>
                <%= Html.ValidationMessage("Phone", "*") %>
            </p>
<p>
<label for="Email">Email:</label>
<%= Html.TextBox("Email") %>
                <%= Html.ValidationMessage("Email", "*") %>
            </p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
    <div>
<%=Html.ActionLink("Back to List", "Index") %>
    </div>
</asp:Content>

注意紧靠HTML表单顶部的Html.ValidationSummary()这个helper方法。如果存在验证产生的错误信息,这个方法将使用无序列表呈现这些消息。

另外,每个表单对象后面调用的Html.ValidationMessage()方法会显示对应这个表单对象所产生的验证错误消息。比如上面所示代码中的情况下,如果存在验证错误,那么这里将会显示一个*号。

最后,如果存在验证错误,Html.TextBox()方法会自动为相关表单对象添加一个名为input-validation-error 的样式

当你新建一个ASP.NET MVC应用程序的时候,Content文件夹中默认生成的Site.css中便包含了这些验证相关的样式定义:

/* messages
-------------*/
div.information, div.error, div.success, ul.validation-summary-errors
{
margin:1em 0;
padding:1em;
}
div.information
{
color:#C60;
background-color:#FF9;
border:1px solid #F90;
}
div.error, ul.validation-summary-errors
{
color: #F00;
background-color:#C99;
border:1px solid #900;
}
div.success
{
color: #060;
background-color:#9C9;
border:1px solid #060;
}
.input-validation-error
{
border: 1px solid #ff0000;
background-color: #ffeeee;
}

 

field-validation-error是用来定义Html.ValidationMessage()呈现时的样式,input- validation-error用来定义textbox(input)的样式,而Vaidation-summary-errors则用来定义 Html.ValidationSummary()方法呈现的无序列表的样式。

你可以通过修改这些默认的样式规则从而美化和自定义这些验证错误消息的表现。

为Create Action添加验证规则

到目前为止,Create view不会显示任何的验证错误信息。因为我们尚未编写其生成消息的逻辑规则。这里你需要向ModelState添加错误消息。

指定的表单对象的值与对应属性发生验证错误时,UpdateModel()方法将自动添加错误消息到ModelState中。例如,如果你打算将 “apple”作为BirthDate属性的值但是该属性只接受DateTime类型,则UpdateModel()方法会添加一个错误至 ModelState中。

修改后的Create()方法代码如下,我们其添加了在新联系人插入数据库前验证联系人属性的相关代码部分:

//
// POST: /Home/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
{
//Validation logic
if (contactToCreate.FirstName.Trim().Length == 0)
ModelState.AddModelError("FirstName", "First name is required");
if (contactToCreate.LastName.Trim().Length == 0)
ModelState.AddModelError("LastName", "Last name is required");
if (contactToCreate.Phone.Trim().Length == 0 && !Regex.IsMatch(contactToCreate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
ModelState.AddModelError("Phone", "Invalid phone number");
if (contactToCreate.Email.Trim().Length == 0 && !Regex.IsMatch(contactToCreate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
ModelState.AddModelError("Email", "Invalid email address");
if (!ModelState.IsValid)
{
return View();
}
else
{
try
{
_entities.AddToContactSet(contactToCreate);
_entities.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
  • FirstName属性的长度必须大于0(并且不计算空格)
  • LastName属性长度必须大于0(并且不计算空格)。
  • 如果Phone属性有值(且长度大于0),则Phone属性必须匹配正则表达式。
  • 如果Email属性有值(且长度大于0),则Email属性必须匹配正则表达式。

如果违反了其中某条验证规则,则一条错误消息将通过AddModelError()方法添加至ModelState中,你只需指定需验证的属性以及 违反相应验证规则时显示的错误提示信息。这条信息将会显示在view中调用Html.ValidationSummary()和 Html.ValidationMessage()方法的地方。

验证规则执行后就可以使用ModelState的IsValid属性了。该属性根据属性是否遵循ModelState中的规则返回一个布尔值。如果未通过验证,Create表单中将显示错误信息。

我是从http://regexlib.com的“正则表达式仓库”中找到验证电话号码和电子邮件地址的正则表达式的,希望它能帮到你。

为Edit Action添加验证规则

Edit() action用来更新一个联系人信息。它需要执行与Create() action中基本相似的验证规则。我们在这里重构Contact controller使得Create()及Edit() action可以复用验证规则。代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using ContactManager.Models;
namespace ContactManager.Controllers
{
public class ContactController : Controller
{
private ContactManagerEntities _entities = new ContactManagerEntities();
protected void ValidateContact(Contact contactToValidate)
{
if (contactToValidate.FirstName.Trim().Length == 0)
ModelState.AddModelError("FirstName", "First name is required.");
if (contactToValidate.LastName.Trim().Length == 0)
ModelState.AddModelError("LastName", "Last name is required.");
if (contactToValidate.Phone.Length > 0 && !Regex.IsMatch(contactToValidate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
ModelState.AddModelError("Phone", "Invalid phone number.");
if (contactToValidate.Email.Length > 0 && !Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
ModelState.AddModelError("Email", "Invalid email address.");
}
//
// GET: /Home/
public ActionResult Index()
{
return View(_entities.ContactSet.ToList());
}
//
// GET: /Home/Details/5
public ActionResult Details(int id)
{
return View();
}
//
// GET: /Home/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Home/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
{
//Validation logic
ValidateContact(contactToCreate);
if (!ModelState.IsValid)
{
return View();
}
else
{
try
{
_entities.AddToContactSet(contactToCreate);
_entities.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
//
// GET: /Home/Edit/5
public ActionResult Edit(int id)
{
var contractToEdit = _entities.ContactSet.Where(c => c.Id == id).FirstOrDefault();
return View(contractToEdit);
}
//
// POST: /Home/Edit/5
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Contact contactToEdit)
{
ValidateContact(contactToEdit);
if (!ModelState.IsValid)
return View();
try
{
var originalContact = _entities.ContactSet.Where(c => c.Id == contactToEdit.Id).FirstOrDefault();
_entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit);
_entities.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
//
// GET: /Home/Delete/5
public ActionResult Delete(int id)
{
var contactToDelete = _entities.ContactSet.Where(c => c.Id == id).FirstOrDefault();
return View(contactToDelete);
}
//
// POST: /Home/Delete/5
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(Contact contactToDelete)
{
try
{
var originalContact = _entities.ContactSet.Where(c => c.Id == contactToDelete.Id).FirstOrDefault();
_entities.DeleteObject(originalContact);
_entities.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
}

总结

在本次迭代中,我们为Contact Manager应用添加了基本的表单验证。我们的验证逻辑会阻止用户在新增联系人、编辑联系人的情景下将未填写FirstName或LastName属性的表单进行提交。并且用户需要填写有效的电话号码和电子邮箱地址。

本文中我们使用了最简单的方式为Contact Manager应用添加验证逻辑,然而将验证规则与controller耦合将会对应用程序的维护产生深远的负面影响。我们的应用将越来越难维护和修改。

我们会在下一次迭代中重构我们的验证逻辑和数据库存储逻辑并将其与controller解耦。得益于适当设计模式的帮助,应用中耦合将更加松散且程序更易维护。

[C#]P2P网络通讯程序(c#)

mikel阅读(996)

转载:http://www.cnblogs.com/dudu/archive/2009/03/26/1422677.html

       在网上看了很多程序(QQ、Azureus、Ants、PPStream)都实现了p2p,以前觉得技术很高深。通过这段时间的学习才发现,单纯的实现 p2p在局域网通讯很容易,但是要实现外网穿透(NAT)感觉很困难。最近看了Azureus和emule源码,分别是JAVA和C++版,本人对这两门 语言都不熟悉,看起来很吃力。最后只好根据VC++实现的P2PDemo程序进行了改版,根据设计思路用C#写了一个Demo出来。通过测试,多个客户端 在局域网能脱离服务端实现端到端工作。外网的情况要通过路由器,看了Azureus要实现uPnp进行端口映射,在CodeProject上下载了一个 uPnp源码看,测试结果没有启用uPnp路由器。结果现在郁闷了,不知道下一步怎么测试,是不是用upnp实现了端口自动映射成功就能实现象QQ那样通 讯。

下面是程序说明:

1、公共类

公共类主要定义一些包结构

a、Packet.cs 


[Serializable()]
    
public abstract class Packet
    {
        
/// <summary>
        
/// 命令类型
        
/// </summary>
        
/// <returns></returns>
        public virtual int GetCommandType()
        {
            
return 1;
        }
        
/// <summary>
        
/// 用户名
        
/// </summary>
        public string UserName
        {
            
get;
            
set;
        }
        
public Packet()
        { }
        
public Packet(string username)
        {
            
this.UserName = username;
        }
    }

b、MassTextPacket.cs  –分片传输类


 [Serializable()]
    
public class MassTextPacket:TextPacket
    {
        
private int seqID;
        
/// <summary>
        
/// 包序列
        
/// </summary>
        public int SeqID
        {
            
get { return seqID; }
            
set { seqID = value; }
        }
        
private int seqCount;
        
/// <summary>
        
/// 包数量
        
/// </summary>
        public int SeqCount
        {
            
get { return seqCount; }
            
set { seqCount = value; }
        }
        
private int _CLSD;
        
public int CLSD
        {
            
get { return _CLSD; }
            
set { _CLSD = value; }
        }
    }

 

2、客户端

a、消息传送时进行p2p通讯


        private bool SendMessageTo(string toUserName, Packet packet)
        {
            PeerEntity toUser 
= userList.Single(c => c.UserName == toUserName);
            
if (toUser == null)
            {
                
return false;
            }
            ReceivedACK 
= false;
            
for (int i=0; i<MAXRETRY; i++)            
            {      
                
// 如果对方P2P地址不为0,就试图以它为目的地址发送数据,
                
// 如果发送失败,则认为此P2P地址无效
                if (toUser.P2PAddress != null && toUser.P2PAddress.Port != 0)
                {
                    
if (packet.GetType() == typeof(TextPacket))
                    {
                        TextPacket msgPacket 
= new TextPacket(toUserName, (packet as TextPacket).Message);
                        
byte[] buffer = UtilityHelper.Serialize(msgPacket);
                        
if (buffer.Length > MAXBUFFERSIZE)
                        {
                         
                            MassTextPacket mtp 
= new MassTextPacket();
                            mtp.SeqID 
= 0;
                            mtp.SeqCount 
= (int)System.Math.Ceiling(buffer.Length / (decimal)MAXBUFFERSIZE);
                            mtp.CLSD 
= mtp.GetHashCode();
                            
                            
long pos = 0;
                            
long count = buffer.Length < MAXBUFFERSIZE ? buffer.Length : MAXBUFFERSIZE;
                            
while (pos < buffer.Length && pos > 0)
                            {
                                
byte[] bytes = new byte[count];                          ;
                                
for (int k = 0; k < count; k++)
                                    bytes[k] 
= buffer[pos + k];
                                
//数据组包
                                mtp.SeqID = mtp.SeqID + 1;
                                mtp.Message 
= Convert.ToBase64String(bytes);
                                
//发送数据
                                byte[] buf = UtilityHelper.Serialize(mtp);
                                client.Send(buf, buf.Length, toUser.P2PAddress);
                                Thread.Sleep(
100);
                            }
                        }
                        
else
                            client.Send(buffer, buffer.Length, toUser.P2PAddress);
                    }
                    
else if (packet.GetType() == typeof(FileStreamPacket))
                    {
                        FileStreamPacket fsp 
= packet as FileStreamPacket;
                        System.IO.FileStream fs 
= new System.IO.FileStream(fsp.FileName, System.IO.FileMode.Open, System.IO.FileAccess.Read, FileShare.Read);
                       handle1.Reset();
                        fsp.SeqID 
= 0;
                        fsp.SeqCount 
= (int)System.Math.Ceiling(fs.Length / (decimal)MAXBUFFERSIZE);
                        fsp.CLSD 
= fsp.GetHashCode();
                        
long pos = 0;
                        
long count = fs.Length < MAXBUFFERSIZE ? fs.Length : MAXBUFFERSIZE;
                        
while (pos < fs.Length && count > 0)
                        {
                            
byte[] buffer = new byte[count];
                            fs.Seek(pos, SeekOrigin.Begin);
                            fs.Read(buffer, 
0, (int)count);
                            pos 
+= count;
                            count 
= pos + MAXBUFFERSIZE < fs.Length ? MAXBUFFERSIZE : fs.Length  pos;
                            
//数据组包
                            fsp.SeqID = fsp.SeqID + 1;
                            fsp.Message 
= Convert.ToBase64String(buffer);
                            
//发送数据
                            byte[] buf = UtilityHelper.Serialize(fsp);
                            client.Send(buf, buf.Length, toUser.P2PAddress);
                            Thread.Sleep(
300);
                        }
                        handle1.Set();
                    }
                    
// 等待接收线程将标记修改
                    for (int j = 0; j < 10; j++)
                    {
                        
if (this.ReceivedACK)
                        {
                            
this.ReceivedACK = false;
                            
return true;
                        }
                        
else
                        {
                            Thread.Sleep(
300);
                        }
                    }
                }
                
// 构建P2P打洞封包
                
// 然后通过服务器转发,请求对方向自己打洞
                P2PConnectionPacket transMsg = new P2PConnectionPacket(UserName, toUserName);
                
byte[] msgBuffer = UtilityHelper.Serialize(transMsg);
                client.Send(msgBuffer, msgBuffer.Length, hostPoint);
                
// 等待对方的P2PCONNECTACK消息
                for(int j = 0; j < 10++j)
                {
                    toUser 
= userList.Single(c => c.UserName == toUserName);
                    
if ( toUser.P2PAddress != null && toUser.P2PAddress.Port != 0)
                        
break;
                    Thread.Sleep(
300);
                }
                
            }
            
return false;
        }

b、消息接受线程


/// <summary>
        
/// 接受线程处理
        
/// </summary>
        private void RecvThreadProc()
        {
            
byte[] buffer;
            
while (true)
            {
                
                
                buffer 
= client.Receive(ref remotePoint);
                Packet packet 
= UtilityHelper.Deserialize(buffer) as Packet;      
                Type msgType 
= packet.GetType();
                
if (msgType == typeof(UserListAckPacket))
                {
                    
// 转换消息
                    UserListAckPacket usersMsg = (UserListAckPacket)packet;
                    
// 更新用户列表
                    userList.Clear();
                    
foreach (PeerEntity user in usersMsg.Users)
                    {
                        userList.Add(user);
                    }
                    bUserListComplete 
= true;
                }                
                
else if (msgType == typeof(UserLoginAckPacket))
                {
                    ProcUserLogAckMsg(packet);
                }         
                
else if (msgType == typeof(TextPacket))
                {
                    
// 转换消息
                    TextPacket txtPacket = (TextPacket)packet;
                    printf(
"Receive a message: {0}", txtPacket.Message);
                    
// 发送应答消息
                    P2PAckPacket ackMsg = new P2PAckPacket();
                    buffer 
= UtilityHelper.Serialize(ackMsg);
                    client.Send(buffer, buffer.Length, remotePoint);
                }
                
else if (msgType == typeof(MassTextPacket))
                {
                    
lock (this)
                    {
                        MassTextPacket fPacket 
= (MassTextPacket)packet;
                        
if (packets.ContainsKey(fPacket.CLSD))
                            packets[fPacket.CLSD].Add(fPacket);
                        
else
                            packets.Add(fPacket.CLSD, 
new List<MassTextPacket>() { fPacket });
                        printf(
"PacketID:{0}–SeqNo:{1}–progress:{2}%", fPacket.CLSD, fPacket.SeqID, (int)(System.Math.Round(packets[fPacket.CLSD].Count / (decimal)(fPacket as MassTextPacket).SeqCount, 2* 100));
                        
//组包
                        if ((fPacket as MassTextPacket).SeqCount == packets[fPacket.CLSD].Count)
                        {
                            List
<MassTextPacket> temp = packets[fPacket.CLSD].OrderBy(c => c.SeqID).ToList();
                            List
<byte> values = new List<byte>();
                            
foreach (MassTextPacket mt in temp)
                            {
                                
byte[] buf = Convert.FromBase64String(mt.Message);        
                                values.AddRange(buf);
                            }
                            MassTextPacket value 
= UtilityHelper.Deserialize(values.ToArray()) as MassTextPacket;
                            printf(
"Receive a message: {0}", value.Message);
                           
                            
// 发送应答消息
                            P2PAckPacket ackMsg = new P2PAckPacket();
                            buffer 
= UtilityHelper.Serialize(ackMsg);
                            client.Send(buffer, buffer.Length, remotePoint);
                        }
                    }
                }
                
else if (msgType == typeof(FileStreamPacket))
                {                 
                    
lock (this)                    
                    {
                        FileStreamPacket fPacket 
= (FileStreamPacket)packet;
                        
if (packets.ContainsKey(fPacket.CLSD))
                            packets[fPacket.CLSD].Add(fPacket);
                        
else
                            packets.Add(fPacket.CLSD, 
new List<MassTextPacket>() { fPacket });
                        printf(
"PacketID:{0}–SeqNo:{1}–progress:{2}%", fPacket.CLSD, fPacket.SeqID, (int)(System.Math.Round(packets[fPacket.CLSD].Count / (decimal)(fPacket as FileStreamPacket).SeqCount, 2* 100));
                        
//组包
                        if ((fPacket as FileStreamPacket).SeqCount == packets[fPacket.CLSD].Count)
                        {
                            List
<MassTextPacket> temp = packets[fPacket.CLSD].OrderBy(c => c.SeqID).ToList();
                            System.IO.FileStream fs 
= new System.IO.FileStream((fPacket as FileStreamPacket).FileName + ".tmp", System.IO.FileMode.Create, System.IO.FileAccess.ReadWrite);
                            
foreach (FileStreamPacket mt in temp)
                            {
                                
byte[] buf = Convert.FromBase64String(mt.Message);
                                fs.Write(buf, 
0, buf.Length);
                            }
                            fs.Flush();
                            fs.Close();
                           printf(
"Receive a file: {0}", (fPacket as FileStreamPacket).FileName);
                           
//清除数据包
                           packets[fPacket.CLSD].Clear();
                           
// 发送应答消息
                           P2PAckPacket ackMsg = new P2PAckPacket();
                           buffer 
= UtilityHelper.Serialize(ackMsg);
                           client.Send(buffer, buffer.Length, remotePoint);
                        }
                    }              
                   
                }
                
else if (msgType == typeof(P2PAckPacket))
                {
                    
this.ReceivedACK = true;
                }
                
else if (msgType == typeof(P2PPurchHolePacket))
                {
                    ProcP2PPurchHoleMsg(packet, remotePoint);
                }
                
else if (msgType == typeof(P2PPurchHoleAckPacket))
                {
                    PeerEntity touser 
= userList.SingleOrDefault(c => c.UserName == (packet as P2PPurchHoleAckPacket).ToUserName);
                    
//更改本地的P2P连接时使用的IP地址
                    touser.P2PAddress = touser.RemoteEndPoint;
                }
                Thread.Sleep(
100);
            }
        }

c.建立p2p会话


        private void ProcP2PPurchHoleMsg(Packet packet,IPEndPoint remoteEP)
        {
            
//打洞请求消息           
            P2PPurchHolePacket purchReqMsg = (P2PPurchHolePacket)packet;
            PeerEntity toUser 
= userList.Single(c => c.UserName == purchReqMsg.ToUserName);
            PeerEntity user 
= userList.Single(c => c.UserName == purchReqMsg.UserName);
            toUser.P2PAddress 
= toUser.RemoteEndPoint;
            printf(
"Set P2P Address for {0}->[{1}]", user.UserName, toUser.P2PAddress.ToString());   
         
            
//uPnp实现端口映射
            if(NAT.AddPortMapping(toUser.P2PAddress.Port, ProtocolType.Udp, "AddPortMapping"))
                printf(
"Port mapping successed!");
            
// 发送打洞消息到远程主机
            P2PPurchHoleAckPacket trashMsg = new P2PPurchHoleAckPacket(purchReqMsg.UserName, purchReqMsg.ToUserName);          
            
byte[] buffer = UtilityHelper.Serialize(trashMsg);
            client.Send(buffer, buffer.Length, user.RemoteEndPoint);
        }

3、服务端

a、消息处理线程 


  private void RecvThreadProc()
        {
            IPEndPoint remotePoint 
= null;
            
byte[] msgBuffer = null;
            
while (true)
            {             
                msgBuffer 
= server.Receive(ref remotePoint);
                
try
                {
                    
object msgObj = UtilityHelper.Deserialize(msgBuffer);
                    
switch ((msgObj as Packet).GetCommandType())
                    {
                        
case Command.MSG_USERLOGIN:         //用户登录                            
                            ProcUserLoginMsg(msgObj as UserLoginPacket, remotePoint);
                            
break;
                        
case Command.MSG_USERLOGOUT:        //退出登录
                            ProcUserLogoutMsg(msgObj as UserLogoutPacket, remotePoint);
                            
break;
                        
case Command.MSG_GETUSERLIST:       //所有用户列表                            
                            ProcGetUserListMsg(msgObj as UserListPacket, remotePoint);
                            
break;
                        
case Command.MSG_P2PCONNECT:        //点对点连接信息                           
                            ProcP2PConnectMsg(msgObj as P2PConnectionPacket, remotePoint);
                            
break;
                        
case Command.MSG_USERACTIVEQUERY:   // 用户对服务器轮询的应答                            
                            ProcUserActiveQueryMsg(msgObj as UserActiveQueryPacket, remotePoint);
                            
break;
                    }
                    Thread.Sleep(
100);
                }
                
catch { }
            }
        }

b、服务端请求客户端建立p2p连接


        private void ProcP2PConnectMsg(Packet packet,IPEndPoint remoteEP)
        {
            
// 转换接受的消息
            P2PConnectionPacket transMsg = (P2PConnectionPacket)packet;
            printf(
"{0}({1}) wants to p2p {2}", remoteEP.Address.ToString(), transMsg.UserName, transMsg.ToUserName);
            
// 获取目标用户
            PeerEntity toUser = userList.SingleOrDefault(c => c.UserName == transMsg.ToUserName);
            
            
// 转发Purch Hole请求消息
            P2PPurchHolePacket transMsg2 = new P2PPurchHolePacket(transMsg.UserName, toUser.UserName);
            
//转发消息
            byte[] buffer = UtilityHelper.Serialize(transMsg2);
            server.Send(buffer, buffer.Length, toUser.RemoteEndPoint);
           
        }

    

4、测试

a、服务端

b、客户端

 

困惑:

1、能不能实现外网通讯,要实现像QQ那样通讯要做哪些改进。

2、文件续传如何实现。

3、C#封装的网络操作类(像QQ.NET源码的Net实现)

4、远程协助的实现。

最后,希望大家共同讨论、共同进步!!!

P2PDemo.RAR可执行文件

[JavaScript]javascript 极速:隐藏/显示万行表格列只需 60毫秒

mikel阅读(849)

隐藏表格列,最常见的是如下方式:

td.style.display = "none";

 

这种方式的效率极低。例如,隐藏一个千行表格的某列,在我的笔记本(P4 M 1.4G,768M内存)上执行需要约 4000毫秒的时间,令人无法忍受。例如如下代码:

<body>
<input type=button onclick=hideCol(1) value='隐藏第 2 列'>
<input type=button onclick=showCol(1) value='显示第 2 列'>
<div id=tableBox></div>
<script>
//——————————————————–
//
 时间转为时间戳(毫秒)
function time2stamp(){var d=new Date();return Date.parse(d)+d.getMilliseconds();} 
//——————————————————–
//
 创建表格
function createTable(rowsLen)
{
    
var str = "<table border=1>" +
                
"<thead>" + 
                    
"<tr>" +
                        
"<th width=100>col1<\/th>" +
                        
"<th width=200>col2<\/th>" + 
                        
"<th width=50>col3<\/th>" + 
                    
"<\/tr>" +
                
"<\/thead>" +
                
"<tbody>";
    
var arr = [];
    
for (var i=0; i<rowsLen; i++)
    {
        arr[i] 
= "<tr><td>" + i + "1<\/td><td>" + i + "2</td><td>" + i + "3<\/td></tr>";
    }
    str 
+= arr.join(""+ "</tbody><\/table>"// 用 join() 方式快速构建字串,速度极快
    tableBox.innerHTML = str; // 生成 table
}
//——————————————————–
//
 隐藏/显示指定列
function hideCol(colIdx){hideOrShowCol(colIdx, 0);}
function showCol(colIdx){hideOrShowCol(colIdx, 1);}
// – – – – – – – – – – – – – – – – – – – – – – – – – – – –
function hideOrShowCol(colIdx, isShow)
{
    
var t1 = time2stamp(); // 
    var table = tableBox.children[0];
    
var rowsLen = table.rows.length;
    
var lastTr = table.rows[0];
    
for (var i=0; i<rowsLen; i++)
    {
        
var tr = table.rows[i];
        tr.children[colIdx].style.display 
= isShow ? "" : "none";
    }
    
    
var t2 = time2stamp();
    alert(
"耗时:" + (t2  t1) + " 毫秒");
}
//——————————————————–
createTable(1000); // 创建千行表格

</script>

 

遗憾的是,我们 google 出来的用 JavaScript 隐藏列的方式,都是采用这样的代码。
实际上,我们可以用设置第一行的 td 或 th 的宽度为 0 的方式,来快速隐藏列。
我们把 hideOrShowCol() 函数改为如下代码:

 

function hideOrShowCol(colIdx, isShow)
{
    
var t1 = time2stamp(); // 
    var table = tableBox.children[0];
    
var tr = table.rows[0];
    tr.children[colIdx].style.width 
= isShow ? 200 : 0;
    
    
var t2 = time2stamp();
    alert(
"耗时:" + (t2  t1) + " 毫秒");
}

 

不过,仅这样还达不到隐藏的效果,还需要设置 table 和 td 样式为如下:

<style>
table
{
    border-collapse
:collapse;
    table-layout
:fixed;
    overflow
:hidden;
}
td
{
    overflow
:hidden;
    white-space
: nowrap;
}
</style>

 

重新测试,我们发现,隐藏千行表格的某列,只需要不到 15毫秒的时间。而即使用 createTable(10000) 创建万行表格,再来测试,也只需要 60 毫秒的时间(都是以我的笔记本上的执行时间为参照。实际上,你们大多数人的电脑配置都比我的笔记本高很多,因此时间会更短),效率十分令人满意。

补充:

根据 无常 网友的提议,加上了对 colgroup 处理的代码。奇怪的是,虽然处理原理完全一样,但对 colgroup 进行处理的时间达到了 140毫秒,即延长了一倍。尚不清楚原因。

完整代码:


<style>
table
{
    border
collapse:collapse;
    table
layout:fixed;
    overflow:hidden;
}
td
{
    overflow:hidden;
    white
space: nowrap;
}
</style>
<body>
<input type=button onclick=createTable() value='创建表格:使用 thead'>
<input type=button onclick=createTable(1) value='创建表格:使用 colgroup'>
<br>
<input type=button onclick=hideCol(1) value='隐藏第 2 列'>
<input type=button onclick=showCol(1) value='显示第 2 列'>
&nbsp;&nbsp;
<input type=button onclick=hideCol_fast(1) value='快速隐藏第 2 列'>
<input type=button onclick=showCol_fast(1) value='快速显示第 2 列'>
<div id=tableBox></div>
<script>
var tableRowsLen = 10000// 创建万行表格

//——————————————————–
//
 时间转为时间戳(毫秒)
function time2stamp(){var d=new Date();return Date.parse(d)+d.getMilliseconds();} 
//——————————————————–
//
 创建表格
function createTable(isUseColGroup)
{
    
if (isUseColGroup) // 使用 colgroup 标签
    {
        
var str = "<table border=1>" +
                    
"<colgroup>" + 
                            
"<col width=100 />" +
                            
"<col width=200 />" + 
                            
"<col width=50 />" + 
                    
"<\/colgroup>" +
                    
"<tbody>";
    }
    
else
    {
        
// 使用 thead 标签
        var str = "<table border=1>" +
                    
"<thead>" + 
                        
"<tr>" +
                            
"<th width=100>col1<\/th>" +
                            
"<th width=200>col2<\/th>" + 
                            
"<th width=50>col3<\/th>" + 
                        
"<\/tr>" +
                    
"<\/thead>" +
                    
"<tbody>";
    }
    
var arr = [];
    
for (var i=0; i<tableRowsLen; i++)
    {
        arr[i] 
= "<tr><td>" + i + "1<\/td><td>" + i + "2</td><td>" + i + "3<\/td></tr>";
    }
    str 
+= arr.join(""+ "</tbody><\/table>"// 用 join() 方式快速构建字串,速度极快
    tableBox.innerHTML = str; // 生成 table
}
//——————————————————–
//
 隐藏/显示指定列
function hideCol(colIdx){hideOrShowCol(colIdx, 0);}
function showCol(colIdx){hideOrShowCol(colIdx, 1);}
// – – – – – – – – – – – – – – – – – – – – – – – – – – – –
function hideOrShowCol(colIdx, isShow)
{
    
var t1 = time2stamp(); // 
    var table = tableBox.children[0];
    
var rowsLen = table.rows.length;
    
var lastTr = table.rows[0];
    
if (rowsLen > 1001
    {
        
if (!confirm("将要对 1000 行以上的表格操作,这将非常耗时(甚至导致浏览器死掉)。\n您确定要继续吗?"))
            
return;
    }
    
for (var i=0; i<rowsLen; i++)
    {
        
var tr = table.rows[i];
        tr.children[colIdx].style.display 
= isShow ? "" : "none";
    }
    
    
var t2 = time2stamp();
    alert(
"耗时:" + (t2  t1) + " 毫秒");
}
//——————————————————–
//
 隐藏/显示指定列 – 快速
function hideCol_fast(colIdx){hideOrShowCol_fast(colIdx, 0);}
function showCol_fast(colIdx){hideOrShowCol_fast(colIdx, 1);}
// – – – – – – – – – – – – – – – – – – – – – – – – – – – –
function hideOrShowCol_fast(colIdx, isShow)
{
    
var t1 = time2stamp(); // 
    var table = tableBox.children[0];
    
var thead = table.children[0]; // 可能是 thead 或者 tbody,也可能是 colgroup
    if (thead.tagName.toLowerCase()=="colgroup"// 对 colgroup 特殊处理
    {
        
var td = thead.children[colIdx];
    }
    
else
    {
        
// 注意:如果表格没有 thead 和 tbody 标签,则 table.children[0] 是 tbody
        var tr = thead.children[0];
        
var td = tr.children[colIdx];
    }
    td.style.width 
= isShow ? 200 : 0;
    
    
var t2 = time2stamp();
    alert(
"耗时:" + (t2  t1) + " 毫秒");
}
//——————————————————–
createTable();
</script>

 

[Asp.Net]动态指定网页过期

mikel阅读(797)

*网页过期*

   Response.Expires   =   -1;
   Response.ExpiresAbsolute   =   DateTime.Now;
   Response.AddHeader("pragma",   "no-cache");
   Response.CacheControl   =   "no-cache";

在pageload事件里面加入

 

Response.Expires 指出当前网页经过多少时间后网页过期,单位“分钟”。IIS会根据给出的过期数值添加到HTTP头中。但由于服务器时间与客户端时间的不一致,例如时区的 原因,或者哪个出了问题,设置Response.Expires=0,将不会达到网页立即过期的效果,相反您可以通过设置 Response.ExpireAbsolute来达到立即过期的目的。更好的方式是设置Response.Expires为一个负数,例如:

<% Response.Expires=-1 %>

       也可使网页立即过期。

 

1.2.7.5.       ExpiresAbsolute

Response.ExpiresAbsolute通过设置具体的日期和时间来达到过期的效果。如果仅指定日期而没有指示时间,网页将在指定日期的午夜12:00过期;如果仅指定时间而不指定日期,网页将在脚本运行的当天那个时间过期。

<% Response.ExpiresAbsolute=#May 31,2004 11:11:11# %>

  上例将使网页在2004年5月31日11点11分11秒过期。

1.2.8.       方法

1.2.8.1.       AddHeader

AddHeader方法可以在发送的HTTP数据中添加相应的头信息。

下例将强迫客户端使用基本验证方式:

<% Response.Addheader "WWW-Authenticate", "BASIC"  %>

[Javascript]Javascript页面加载顺序

mikel阅读(763)

一、在HTML中嵌入Javasript的方法

  1. 直接在JavaScript代码放在标记对<script>和</script>之间
  2. 由<script />标记的src属性制定外部的js文件
  3. 放在事件处理程序中,比如:<p onclick="alert("我是由onclick事件执行的JavaScript")">点击我</p>
  4. 作为URL的主体,这个URL使用特殊的Javascript:协议,比如:<a href="javascript:alert("我是由javascript:协议执行的javascript")">点击我</a>
  5. 利用javascript本身的document.write()方法写入新的javascript代码
  6. 利用Ajax异步获取javascript代码,然后执行

第3种和第4种方法写入的Javascript需要触发才能执行,所以除非特别设置,否则页面加载时不会执行。

二、Javascript在页面的执行顺序

  1. 页 面上的Javascript代码是HTML文档的一部分,所以Javascript在页面装载时执行的顺序就是其引入标记<script />的出现顺序, <script />标记里面的或者通过src引入的外部JS,都是按照其语句出现的顺序执行,而且执行过程是文档装载的一部分。
  2. 每个脚本定义的全局变量和函数,都可以被后面执行的脚本所调用。
  3. 变量的调用,必须是前面已经声明,否则获取的变量值是undefined。
    <script type="text/javscrpt">//<![CDATA[alert(tmp);  //输出 undefinedvar tmp = 1;alert(tmp);  //输出 1//]]></script>
  4. 同一段脚本,函数定义可以出现在函数调用的后面,但是如果是分别在两段代码,且函数调用在第一段代码中,则会报函数未定义错误。
    <script type="text/javscrpt">//<![CDATA[aa();            //浏览器报错//]]></script><script type="text/javscrpt">//<![CDATA[aa();//输出 1 function aa(){alert(1);}//]]></script>
  5. document.write()会把输出写入到脚本文档所在的位置,浏览器解析完documemt.write()所在文档内容后,继续解析document.write()输出的内容,然后在继续解析HTML文档。
    <script type="text/javascript">//<![CDATA[    document.write("<script type=\"text/javascript\" src=\"test.js\" temp_src=\"test.js\"><\/script>");    document.write("<script type=\"text/javascript\">");    document.write("alert(2);")    document.write("alert(\"我是\" + tmpStr);");    document.write("<\/script>");    //]]></script>  <script type="text/javascript">//<![CDATA[    alert(3);    //]]></script>

    test.js的内容是:

    var tmpStr = 1;    alert(tmpStr);
    • 在Firefox和Opera中的弹出值的顺序是:1、2、我是1、3
    • 在IE中弹出值的顺序是:2、1、3,同时浏览器报错:tmpStr未定义

    原 因可能是IE在document.write时,并未等待加载SRC中的Javascript代码完毕后,才执行下一行,所以导致2先弹出,并且执行到 document.write(’document.write(\"我是\" + tmpStr)’)调用tmpStr时,tmpStr并未定义,从而报错。

    解决这个问题,可以利用HTML解析是解析完一个HTML标签,再执行下一个的原理,把代码拆分来实现:

    <script type="text/javascript">//<![CDATA[    document.write("<script type=\"text/javascript\" src=\"test.js\" temp_src=\"test.js\"><\/script>");    //]]></script>  <script type="text/javascript">//<![CDATA[    document.write("<script type=\"text/javascript\">");    document.write("alert(2);")    document.write("alert(\"我是\" + tmpStr);");    document.write("<\/script>");    //]]></script>  <script type="text/javascript">//<![CDATA[    alert(3);    //]]></script>

    这样IE下和其他浏览器输出值的顺序都是一直的了:1、2、我是1、3。

三、如何改变Javascript在页面的执行顺序

  1. 利用onload
    <script type="text/javascript">//<![CDATA[window.onload = f;function f(){alert(1);}alert(2);//]]></script>

    输出值顺序是 2、1。

    需要注意的是,如果存在多个winodws.onload的话,只有最有一个生效,解决这个办法是:

    window.onload = function(){f();f1();f2();.....}

    利用2级DOM事件类型

    if(document.addEventListener){window.addEventListener("load",f,false);window.addEventListener("load",f1,false);...}else{window.attachEvent("onload",f);window.attachEvent("onload",f1);...}
  2. IE中可以利用deferdefer作用是把代码加载下来,并不立即执行,等文档装载完毕之后再执行,有点类似onload,但是没有onload那样的局限性,可以重复使用,但是只在IE中有效,所以上面的例子可以修改成为
    <script type="text/javascript">//<![CDATA[document.write("<script type=\"text/javascript\" src=\"test.js\" temp_src=\"test.js\"><\/script>");document.write("<script type=\"text/javascript\" defer=\"defer\">");document.write("alert(2);")document.write("alert(\"我是\" + tmpStr);");document.write("<\/script>");//]]></script><script type="text/javascript">//<![CDATA[alert(3);//]]></script>

    这样IE就不报错了,输出值的顺序变成:1、3、2、我是1

  3. 利用Ajax。
    因为xmlhttpRequest能判断外部文档加载的状态,所以能够改变代码的加载顺序。

[Flex]AmFast Remoting for Python

mikel阅读(917)

AmFast is a new AMF0/AMF3 encoder/decoder for Python.
• AmFast's core encoder and decoder are written in C, so it's around 18x faster than PyAmf.
• The encoder and decoder accept user-defined Python objects that allow customization of the encoding/decoding process.
• Supports custom class mapping.
• Supports remoting with NetConnection and RemoteObject.
• Remoting headers can be exposed to callable targets to allow for quick implementation of authentication and other AMF features that rely on headers.
• Supports data persistence with SQLAlchemy, including remotely-loadable lazy-loaded attributes.
• Supports Actionscript code generation for mapped classes.
Project Page
It is really great to see how opening the AMF and RTMP protocols have really improved integration with server languages. To exchange objects makes development so much easier.
Cheers,
Ted 🙂

[C#]ASP.NET MVC 1.0系列教程

mikel阅读(1075)

  1. ASP.NET MVC 1.0 – 1. 准备工作

  2. ASP.NET MVC 1.0 – 2. 流程分析 (System.Web.Routing)

  3. ASP.NET MVC 1.0 – 3. 流程分析 (MvcHandler & Controller)

  4. ASP.NET MVC 1.0 – 4. 流程分析 (ControllerActionInvoker)

  5. ASP.NET MVC 1.0 – 5. 流程分析 (ControllerActionInvoker 续)

  6. ASP.NET MVC 1.0 – 6. 流程分析 (ViewResult)

  7. ASP.NET MVC 1.0 – 7. Route Namespace

  8. ASP.NET MVC 1.0 – 8. TempData

  9. ASP.NET MVC 1.0 – 9. ModelBinder

  10. ASP.NET MVC 1.0 – 10. Controller

  11. ASP.NET MVC 1.0 – 11. ViewData

  12. ASP.NET MVC 1.0 – 12. NVelocityViewEngine

  13. ASP.NET MVC 1.0 – 13. OutputCacheAttribute

  14. ASP.NET MVC 1.0 – 14. CompressAttribute

  15. ASP.NET MVC 1.0 – 15. StaticCacheAttribute

  16. ASP.NET MVC 1.0 – 16. NoClientCacheAttribute

  17. ASP.NET MVC 1.0 – 17. Anti Attack

  18. ASP.NET MVC 1.0 – 18. ControllerContext