[C#]ASP.NET MVC 1.0浅析

mikel阅读(778)

最近的项目没有升级到ASP.NET MVC 1.0,也没有评论的资格,没详细看过MVC1.0的源码,据说改动挺大,看来需要升级的工作又艰巨了不少,以下转载自:

http://www.cnblogs.com/andy1027/archive/2009/04/01/1427369.html

为什么要用ASP.NET MVC 1.0?当我刚知道1.0发布的时候,经常这样问。

最近正在考虑是否在我们的企业级应用中使用ASP.NET MVC 1.0框架,因此会一直找使用它的理由,希望大家在关注技术的同时,结合企业应用谈谈自己的看法。

1、MVC的组成
Models
:访问数据库,装载数据、处理业务逻辑。在项目中体现为数据实体类加业务代理类。
Views:显示数据,用户界面。在项目中体现为aspx页面,偶尔可以加上code-behind。
Controller:按路由规则将请求的数据传送给指定页面,用于显示;也可以把用户输入的数据传递给逻辑处理类。它可以包含简单的验证逻辑
。不应包含数据访问逻辑。
2、为何使用MVC
提出MVC的目的无非是提高开发效率、提高可测试性。
官方的ASP.NET MVC 1.0指南中指出(以下简称指南),基于MVC的Web应用程序有如下优点:
[1]对复杂的程序管理更方便
It makes it easier to manage complexity by dividing an application into the model, the view, and the ontroller.

[2]在开发上有更高的可控性
It does not use view state or server-based forms. This makes the MVC framework ideal for developers who want full control
over the behavior of an application.
[3]Routing使软件设计有更多灵活性
It uses a Front Controller pattern that processes Web application requests through a single controller. This enables you to
design an application that supports a rich routing infrastructure.
[4]更加适合测试驱动开发
It provides better support for test-driven development (TDD).
[5]团队开发项目中有更高的可控性
It works well for Web applications that are supported by large teams of developers and Web designers who need a high degree
of control over the application behavior.
同时MVC框架还有以下特点:
[1]将应用程序分成各个组成部份,更有利于测试。MVC框架是基于接口的,这样可以利用MOCK方式来替换你的实际类;做单元测试的时候,也
可以不运行Contrllers,这样的测试就更快更灵活。
[2]MVC框架是可扩展的,你可以自己设计并替换视频引擎、URL导向规则、Action的参数序列等等。同时MVC框架也支持依赖注入和控制反转,
你可以从外部注入实例,而不用让类自己创建实例,你还可以通过配置文件的方式创建实例,这样使得测试更方便。
[3]强大的URL映射组件使得你的应用程序的URL更易理解,同时具备搜索能力。你的URL不必包括文件路径,这样的设计很适合自定义查询引擎
和REST架构。
[4]MVC框架仍然支持ASP.NET中的页面、用户控件、母版页作为视图的模板;同时你也还可以使用嵌套母版页、行内表达式(in-line
expressions<%= %>)、服务器端控件、模板、数据绑定、本地化等等属于ASP.NET已有的东西。
[5]同时ASP.NET中的FORM验证、Windows验证、URL授权、Membership、角色、输出、数据缓存、Session、Profile 状态管理、配置、Provider
框架等特性在MVC框架中仍然是可用的。

小结:ASP.NET MVC 1.0框架是基于ASP.NET的,所以他包括了ASP.NET中的几乎所有特性。同时他为设计人员提供了一套测试的方案(当然这是所有语言平台MVC模式的共性)。在安装了框架的VS2008中还增加了不少功能,可以方便地添加Views、Models、Controllers。
3、与三层结构的ASP.NET应用程序比较
与普通ASP.NET比较而言,最大的区别还是在于前台开发,后台包括的数据库访问、逻辑处理与以往的方式没有明显区别,在MVC框架中,这些
统称为Model。而三层结构中,这些可以称为数据访问层与逻辑处理层。
[1]页面开发
用这种模式开发的站点,光看页面的代码的确比以往少一些,但它更多地使用了页面脚本(<% … %>)用于显示数据。在指南中并未提到不推
荐使用服务器端控件,但是它提供了大量的HTML HELPER,而且还允许你自己添加Helper,比如DataGridHelper,所以在MVC框架中使用这些Helper会更方便些,不过这对于熟练工来说应该影响不大,因为实际开发中我们更多使用的是Ctrl+C/Ctrl+V,复制几个标签和复制几个Helper方法所花的时间差不多。可能对于新手来说,如果对标签不熟悉的话,用这些Helper的速度会快些,但是这样会影响新手掌握标签,真是矛盾呐
[2]数据提交
普通的ASP.Net开发,在提交数据的时候可能还需要通过设置数据绑定,或者在code-behind里写封装代码;而在MVC中,框架自动帮助你将页面
上填写的数据封装到事先指定的Model中,数据提交操作在MVC框架挺方便。而且在普通ASP.NET页面中,经常会出现某个属性无法绑回去的情况,这点在MVC中应该可以得到解决。指南中提到了Routing的使用使得MVC框架下的应用程序在操作自定义查询时变得更方便,实际上在查询方面跟普通方式并没有多大区别,都是对封装好的类进行解析。至于“URL更容易理解”,现在应用程序都是从界面上点击来实现操作,很少有人会关注URL本身吧,所以这个优点不算优点。
[3]单元测试
从测试上讲,MVC框架确实做得不错,若用MOCK方式测试可以更方便,一个好的WEB应用程序设计就应该将页面呈现与逻辑分开,这点普通
ASP.NET应用程序也是可以做到的,关键在于设计。
[4]其它
MVC框架在验证、母版页这些地方有几个新特性,但与普通ASP.NET的方式大同小异,因此不仔细说了。

文中分析不对的地方,请指正。

[JQuery]JQuery ajax批量上传图片

mikel阅读(949)

在网上搜索了一下,发现以JQuery+ajax方式实现单张图片上传的代码是有的,但实现批量上传图片的程序却没搜索到,于是根据搜索到的代码,写了一个可以批量上传的。

         先看效果图

点击增加按钮,会增加一个选择框,如下图:

 

选择要上传的图片,效果图如下:

 

上传成功如下图:

 

 

 

下面来看代码:

前台html主要代码:

<button id="SubUpload" class="ManagerButton" onClick="TSubmitUploadImageFile();return false;">确定上传</button>&nbsp;&nbsp;

<button id="CancelUpload" class="ManagerButton" onClick="JavaScript:history.go(-1);">取消</button>&nbsp;&nbsp;

<button id="AddUpload" class="ManagerButton" onClick="TAddFileUpload();return false;">增加</button>

<tr><td class="tdClass">

         图片1

         </td><td class="tdClass">

         <input name="" size="60" id="uploadImg1" type="file" />

         <span id="uploadImgState1"></span>

         </td></tr>

 

 

 

 

 

 

 

 

 

 

因为用了JQuery,所以你完全可以把click事件放在js文件中

增加按钮js代码:

 

var TfileUploadNum=1; //记录图片选择框个数

var Tnum=1; //ajax上传图片时索引

         function TAddFileUpload()

         {

                   var idnum = TfileUploadNum+1;

                   var str="<tr><td class='tdClass'>图片"+idnum+"</td>";

                   str += "<td class='tdClass'><input name='' size='60' id='uploadImg"+idnum+"' type='file' /><span id='uploadImgState"+idnum+"'>";

                   str += "</span></td></tr>";

                   $("#imgTable").append(str);

                   TfileUploadNum += 1;

         }

 

 

 

 

 

 

 

 

 

 

确定上传按钮js代码:

 

function TSubmitUploadImageFile()

         {

                  M("SubUpload").disabled=true;

                   M("CancelUpload").disabled=true;

                   M("AddUpload").disabled=true;

                   setTimeout("TajaxFileUpload()",1000);//此为关键代码

}

 

 

 

 

 

 

 

 

关于setTimeout("TajaxFileUpload()",1000);这句代码:因为所谓的批量上传,其实还是一个一个的上传,给用户的只是一个假象。只所以要延时执行TajaxFileUpload(),是因为在把图片上传到服务器上时,我在后台给图片重新命名了,命名的规则是,如下代码:

 

 

Random rd = new Random();

StringBuilder serial = new StringBuilder();

serial.Append(DateTime.Now.ToString("yyyyMMddHHmmssff"));

serial.Append(rd.Next(0, 999999).ToString());

return serial.ToString();

 

 

 

 

 

即使我命名精确到毫秒,另外再加上随机数,可是还是有上传的第二张图片把上传的第一张图片覆盖的情况出现。所以此处我设置了延时1秒后在上传下一张图片。刚开始做这个东西的时候,用的是for循环,来把所有的图片一个一个的循环地用ajax上传,可是for循环速度太快了,可能第一张图片还没来得及ajax,第二张就被for过来了,还是有第二张覆盖第一张的情况出现。

下面来看TajaxFileUpload()函数,代码如下:

 

function TajaxFileUpload()

         {

                   if(Tnum<TfileUploadNum+1)

                   {

                            //准备提交处理

                            $("#uploadImgState"+Tnum).html("<img src=../images/loading.gif />");

                            //开始提交

                            $.ajax

                            ({

                                     type: "POST",

                                     url:"http://localhost/ajaxText2/Handler1.ashx",

                                     data:{upfile:$("#uploadImg"+Tnum).val(),category:$("#pcategory").val()},

                                     success:function (data, status)

                                     {

                                               //alert(data);

                                               var stringArray = data.split("|");

                                              

                                               if(stringArray[0]=="1")

                                               {

                                                        //stringArray[0]    成功状态(1为成功,0为失败)

                                                        //stringArray[1]    上传成功的文件名

                                                        //stringArray[2]    消息提示

                                                        $("#uploadImgState"+Tnum).html("<img src=../images/note_ok.gif />");//+stringArray[1]+"|"+stringArray[2]);

                                               }           

                                               else

                                               {

                                                        //上传出错

                                                        $("#uploadImgState"+Tnum).html("<img src=../images/note_error.gif />"+stringArray[2]);//+stringArray[2]+"");

                                               }

                                               Tnum++;

                                             setTimeout("TSubmitUploadImageFile()",0);

                                      }

                             });                     

                   }

         }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 上面的代码没什么可说的,很容易看懂。下面来看Handler1.ashx(一般处理程序)如何来处理post过来的图片的(此代码来自网上,具体地址忘记了),下面只给出关键代码,全部代码在附件里。

1

  

string _fileNamePath = "";

            try

            {

                _fileNamePath = context.Request.Form["upfile"];

                //开始上传

                string _savedFileResult = UpLoadFile(_fileNamePath);

                context.Response.Write(_savedFileResult);

            }

            catch

            {

                context.Response.Write("0|error|上传提交出错");

            }

 

 

 

 

 

 

 

 

 

 

 

 

2

 

//生成将要保存的随机文件名

string fileName = GetFileName() + fileNameExt;

//物理完整路径                   

string toFileFullPath = HttpContext.Current.Server.MapPath(toFilePath);

//检查是否有该路径 没有就创建

if (!Directory.Exists(toFileFullPath))

{

     Directory.CreateDirectory(toFileFullPath);

}

///创建WebClient实例      

WebClient myWebClient = new WebClient();

//设定windows网络安全认证   方法1

myWebClient.Credentials = CredentialCache.DefaultCredentials;

//要上传的文件      

FileStream fs = new FileStream(fileNamePath, FileMode.Open, FileAccess.Read);

//FileStream fs = OpenFile();      

BinaryReader r = new BinaryReader(fs);

//使用UploadFile方法可以用下面的格式      

//myWebClient.UploadFile(toFile, "PUT",fileNamePath);      

byte[] postArray = r.ReadBytes((int)fs.Length);

Stream postStream = myWebClient.OpenWrite(toFile, "PUT");

if (postStream.CanWrite)

{

postStream.Write(postArray, 0, postArray.Length);

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3、检查是否合法的上传文件

 

private bool CheckFileExt(string _fileExt)

{

     string[] allowExt = new string[] { ".gif", ".jpg", ".jpeg" };

     for (int i = 0; i < allowExt.Length; i++)

     {

         if (allowExt[i] == _fileExt) { return true; }

     }

    return false;

}

4、生成要保存的随即文件名

 

 

 

 

 

 

 

 

  

public static string GetFileName()

{

            Random rd = new Random();

            StringBuilder serial = new StringBuilder();

            serial.Append(DateTime.Now.ToString("yyyyMMddHHmmssff"));

            serial.Append(rd.Next(0, 999999).ToString());

            return serial.ToString();

}

 

 

 

 

 

 

 

Ok,基本上这个批量上传图片的JQuery+ajax方式实现的程序完成了。如果你要上传word文档,pdf文件,只要稍作修改,就可以实现了。

[JQuery]jQuery Calculation Plug-in

mikel阅读(763)

JQuery Calculation Plug-in (v0.4.04)

The Calculation plug-in is designed to give easy-to-use JQuery functions for commonly used mathematical functions.

This plug-in will work on all types of HTML elements—which means you can use it to calculate values in <td> elements or in <input> elements. You can even mix and match between element types.

Numbers are parsed from the element using parseNumber() method—which uses a regular expression (/-?\d+(,\d{3})*(\.\d{1,})?/g) to parse out the numeric value. You can change the regular expression that's used to determine what's consider a number by changing the default regular expression.

Download

Download the plug-in:
jquery.calculation.js
jquery.calculation.min.js

[DBMS]漫谈数据库索引

mikel阅读(908)

一、引言

对数据库索引的关注从未淡出我的们的讨论,那么数据库索引是什么样的?聚集索引与非聚集索引有什么不同?希望本文对各位同仁有一定的帮助。有不少存疑的地方,诚心希望各位不吝赐教指正,共同进步。[最近首页之争沸沸扬扬,也不知道这个放在这合适么,苦劳?功劳?……]

 

二、B-Tree

我们常见的数据库系统,其索引使用的数据结构多是B-Tree或者B+Tree。例如,MsSQL使用的是B+TreeOracleSysbase使用的是B-Tree。所以在最开始,简单地介绍一下B-Tree

B-Tree不同于Binary Tree(二叉树,最多有两个子树),一棵M阶的B-Tree满足以下条件:
1)每个结点至多有M个孩子;
2)除根结点和叶结点外,其它每个结点至少有M/2个孩子;
3)根结点至少有两个孩子(除非该树仅包含一个结点);
4)所有叶结点在同一层,叶结点不包含任何关键字信息;
5)有K个关键字的非叶结点恰好包含K+1个孩子;

另外,对于一个结点,其内部的关键字是从小到大排序的。以下是B-TreeM=4)的样例:

  

对于每个结点,主要包含一个关键字数组Key[],一个指针数组(指向儿子)Son[]。在B-Tree内,查找的流程是:使用顺序查找(数组长度较短时)或折半查找方法查找Key[]数组,若找到关键字K,则返回该结点的地址及KKey[]中的位置;否则,可确定K在某个Key[i]Key[i+1]之间,则从Son[i]所指的子结点继续查找,直到在某结点中查找成功;或直至找到叶结点且叶结点中的查找仍不成功时,查找过程失败。

接着,我们使用以下图片演示如何生成B-TreeM=4,依次插入1~6):
从图可见,当我们插入关键字4时,由于原结点已经满了,故进行分裂,基本按一半的原则进行分裂,然后取出中间的关键字2,升级(这里是成为根结点)。其它的依类推,就是这样一个大概的过程。

  

 

三、数据库索引

1.什么是索引

在数据库中,索引的含义与日常意义上的“索引”一词并无多大区别(想想小时候查字典),它是用于提高数据库表数据访问速度的数据库对象。
A)索引可以避免全表扫描。多数查询可以仅扫描少量索引页及数据页,而不是遍历所有数据页。
B对于非聚集索引,有些查询甚至可以不访问数据页。
C聚集索引可以避免数据插入操作集中于表的最后一个数据页。
D一些情况下,索引还可用于避免排序操作。

当然,众所周知,虽然索引可以提高查询速度,但是它们也会导致数据库系统更新数据的性能下降,因为大部分数据更新需要同时更新索引。

 

2.索引的存储

一条索引记录中包含的基本信息包括:键值(即你定义索引时指定的所有字段的值)+逻辑指针(指向数据页或者另一索引页)。

  

当你为一张空表创建索引时,数据库系统将为你分配一个索引页,该索引页在你插入数据前一直是空的。此页此时既是根结点,也是叶结点。每当你往表中插入一行数据,数据库系统即向此根结点中插入一行索引记录。当根结点满时,数据库系统大抵按以下步骤进行分裂:
A)创建两个儿子结点
B)将原根结点中的数据近似地拆成两半,分别写入新的两个儿子结点
C)根结点中加上指向两个儿子结点的指针

通常状况下,由于索引记录仅包含索引字段值(以及4-9字节的指针),索引实体比真实的数据行要小许多,索引页相较数据页来说要密集许多。一个索引页可以存储数量更多的索引记录,这意味着在索引中查找时在I/O上占很大的优势,理解这一点有助于从本质上了解使用索引的优势。

 

3.索引的类型

A聚集索引,表数据按照索引的顺序来存储的。对于聚集索引,叶子结点即存储了真实的数据行,不再有另外单独的数据页。
B非聚集索引,表数据存储顺序与索引顺序无关。对于非聚集索引,叶结点包含索引字段值及指向数据页数据行的逻辑指针,该层紧邻数据页,其行数量与数据表行数据量一致。

在一张表上只能创建一个聚集索引,因为真实数据的物理顺序只可能是一种。如果一张表没有聚集索引,那么它被称为堆集Heap)。这样的表中的数据行没有特定的顺序,所有的新行将被添加的表的末尾位置。

 

4.聚集索引

在聚集索引中,叶结点也即数据结点,所有数据行的存储顺序与索引的存储顺序一致。

  

1)聚集索引与查询操作

如上图,我们在名字字段上建立聚集索引,当需要在根据此字段查找特定的记录时,数据库系统会根据特定的系统表查找的此索引的根,然后根据指针查找下一个,直到找到。例如我们要查询“Green”,由于它介于[Bennet,Karsen],据此我们找到了索引页1007,在该页中“Green”介于[Greane, Hunter],据此我们找到叶结点1133(也即数据结点),并最终在此页中找以了目标数据行。

此次查询的IO包括3个索引页的查询(其中最后一次实际上是在数据页中查询)。这里的查找可能是从磁盘读取(Physical Read)或是从缓存中读取(Logical Read),如果此表访问频率较高,那么索引树中较高层的索引很可能在缓存中被找到。所以真正的IO可能小于上面的情况。

 

2)聚集索引与插入操作

最简单的情况下,插入操作根据索引找到对应的数据页,然后通过挪动已有的记录为新数据腾出空间,最后插入数据。

如果数据页已满,则需要拆分数据页(页拆分是一种耗费资源的操作,一般数据库系统中会有相应的机制要尽量减少页拆分的次数,通常是通过为每页预留空间来实现):
A在该使用的数据段(extent)上分配新的数据页,如果数据段已满,则需要分配新段。
B调整索引指针,这需要将相应的索引页读入内存并加锁。
C大约有一半的数据行被归入新的数据页中。
D
如果表还有非聚集索引,则需要更新这些索引指向新的数据页。

特殊情况:
A如果新插入的一条记录包含很大的数据,可能会分配两个新数据页,其中之一用来存储新记录,另一存储从原页中拆分出来的数据。
B通常数据库系统中会将重复的数据记录存储于相同的页中。
C类似于自增列为聚集索引的,数据库系统可能并不拆分数据页,页只是简单的新添数据页。

 

3)聚集索引与删除操作

删除行将导致其下方的数据行向上移动以填充删除记录造成的空白。

如果删除的行是该数据页中的最后一行,那么该数据页将被回收,相应的索引页中的记录将被删除。如果回收的数据页位于跟该表的其它数据页相同的段上,那么它可能在随后的时间内被利用。如果该数据页是该段的唯一一个数据页,则该段也被回收。

对于数据的删除操作,可能导致索引页中仅有一条记录,这时,该记录可能会被移至邻近的索引页中,原索引页将被回收,即所谓的“索引合并”。

 

5.非聚集索引

非聚集索引与聚集索引相比:
A叶子结点并非数据结点
B叶子结点为每一真正的数据行存储一个指针
C叶子结点中还存储了一个指针偏移量,根据页指针及指针偏移量可以定位到具体的数据行。
D
类似的,在除叶结点外的其它索引结点,存储的也是类似的内容,只不过它是指向下一级的索引页的。

聚集索引是一种稀疏索引,数据页上一级的索引页存储的是页指针,而不是行指针。而对于非聚集索引,则是密集索引,在数据页的上一级索引页它为每一个数据行存储一条索引记录。

对于根与中间级的索引记录,它的结构包括:
A索引字段值
BRowId(即对应数据页的页指针+指针偏移量)。在高层的索引页中包含RowId是为了当索引允许重复值时,当更改数据时精确定位数据行。
C下一级索引页的指针

对于叶子层的索引对象,它的结构包括:
A
索引字段值
BRowId

  

1)非聚集索引与查询操作

针对上图,如果我们同样查找“Green”,那么一次查询操作将包含以下IO3个索引页的读取+1个数据页的读取。同样,由于缓存的关系,真实的IO实际可能要小于上面列出的。

 

2)非聚集索引与插入操作

如果一张表包含一个非聚集索引但没有聚集索引,则新的数据将被插入到最末一个数据页中,然后非聚集索引将被更新。如果也包含聚集索引,该聚集索引将被用于查找新行将要处于什么位置,随后,聚集索引、以及非聚集索引将被更新。

 

3)非聚集索引与删除操作

如果在删除命令的Where子句中包含的列上,建有非聚集索引,那么该非聚集索引将被用于查找数据行的位置,数据删除之后,位于索引叶子上的对应记录也将被删除。如果该表上有其它非聚集索引,则它们叶子结点上的相应数据也要删除。

如果删除的数据是该数所页中的唯一一条,则该页也被回收,同时需要更新各个索引树上的指针。

由于没有自动的合并功能,如果应用程序中有频繁的随机删除操作,最后可能导致表包含多个数据页,但每个页中只有少量数据。

 

6.索引覆盖

索引覆盖是这样一种索引策略:当某一查询中包含的所需字段皆包含于一个索引中,此时索引将大大提高查询性能。

包含多个字段的索引,称为复合索引。索引最多可以包含31个字段,索引记录最大长度为600B。如果你在若干个字段上创建了一个复合的非聚集索引,且你的查询中所需Select字段及Where,Order By,Group By,Having子句中所涉及的字段都包含在索引中,则只搜索索引页即可满足查询,而不需要访问数据页。由于非聚集索引的叶结点包含所有数据行中的索引列值,使用这些结点即可返回真正的数据,这种情况称之为索引覆盖

在索引覆盖的情况下,包含两种索引扫描:
A)匹配索引扫描
B)非匹配索引扫描

 

1)匹配索引扫描

此类索引扫描可以让我们省去访问数据页的步骤,当查询仅返回一行数据时,性能提高是有限的,但在范围查询的情况下,性能提高将随结果集数量的增长而增长。

针对此类扫描,索引必须包含查询中涉及的的所有字段,另外,还需要满足:Where子句中包含索引中的引导列Leading Column),例如一个复合索引包含A,B,C,D四列,则A引导列。如果Where子句中所包含列是BCD或者BD等情况,则只能使用非匹配索引扫描。

 

2)非配置索引扫描

正如上述,如果Where子句中不包含索引的导引列,那么将使用非配置索引扫描。这最终导致扫描索引树上的所有叶子结点,当然,它的性能通常仍强于扫描所有的数据页。

 

[参考]
[1]http://manuals.sybase.com/onlinebooks/group-asarc/asg1200e/aseperf/@Generic__BookTextView/3358
[2]
http://publib.boulder.ibm.com/infocenter/idshelp/v10/index.jsp?topic=/com.ibm.adref.doc/adref235.htm

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

mikel阅读(1136)

字符串格式化这部分内容是我们经常用到的,如“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阅读(1072)

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阅读(982)

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

在进行数据库应用开发时经常会遇到生成流水号的情况,比如说做了一个订单模块,要求订单号是唯一的,规则是:下订单时的年月日+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阅读(1031)

[翻译-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阅读(997)

转载: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阅读(850)

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

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>