程序设计会议记录
第__7__次小组会议 第__1__页
时间: 2004-_5___月__13__日上/午 星期__4__
出席: 戴亮 鲍文俊 黄杰 刘佳俊 赵敏 许冯
缺席:
主持人: 鲍文俊
会议内容:
web.config中连接字符串的加密隐藏
C#编码标准--编码习惯
去除Asp:DataGrid中无用ViewState的方法
如何在DataGrid里面使用动态图形表示数字
datagrid的正反双向排序
DataGrid删除确认及Item颜色交替
各成员意见:
戴亮:我们都知道web.config可以保存连接字符串,我们在程序中也都是这么做的,web.config是XML,所以它有清晰的结构,是我们很容易可以读懂它,但是这也出现一个问题,我们数据库完全暴露给浏览该文件的人,这是我们所不希望的。我们可以使用一个简单有效的加密算法来加密这段连接字符,使直接浏览该文件的人不能清楚地看到这些信息。
我们一般以下面的形式保存连接字符串:
<appSettings>
<add key="ConnectionString" value="server=localhost;database=test;pwd=sa;uid=sa;" />
</appSettings>
为了我们自己可以看得明白这些加密的字符我们需要一个额外的程序专门生成这些加密数据(这个额外的程序你可以写得很复杂但是为了说明问题,我们使用简单的base64编码数据,这其实不是加密数据,但是原理是一样的)。我们建立一个WinForm工程,专门用来生成明文到密文的(base64)转换,如果使用其它的加密算法可以替换这个加密过程。转换按钮里面的代码如下:
private void button1_Click(object sender, System.EventArgs e) {
byte[] data = System.Text.ASCIIEncoding.ASCII.GetBytes(this.textBox1.Text);
string str = Convert.ToBase64String(data);
this.textBox2.Text = str;
}
其中textBox2里面就是变码以后的字符。我们可以将我们web.config里面的连接字符("server=localhost;database=test;pwd=sa;uid=sa;")取出来放在这个程序里面执行生成一个新的字符串(c2VydmVyPWxvY2FsaG9zdDtkYXRhYmFzZT10ZXN0O3B3ZD1zYTt1aWQ9c2E7)。
之后我们用这个字符替换未编码的字符串。如下所示:
<appSettings>
<add key="ConnectionString" value="c2VydmVyPWxvY2FsaG9zdDtkYXRhYmFzZT10ZXN0O3B3ZD1zYTt1aWQ9c2E7" />
</appSettings>
但是我们的程序也不认识了,没关系我们自己知道加密算法所以我们的程序可以看懂,最后就是在程序中如何使用了,我们的程序需要理解这个字符串的意义,我们在数据访问层里面添加如下的工具方法
private string GetConnectionString(){
string strconn = System.Configuration.ConfigurationSettings.AppSettings["ConnectionString"];
byte[] data = Convert.FromBase64String(strconn);
string strRealConn = System.Text.ASCIIEncoding.ASCII.GetString(data);
return strRealConn;
}
这样就可以得到真实的连接字符串了。
一个简单的加密(伪加密,其实本文实现的是一个编码而非加密)的方法,也许可以骗过一些人的眼睛,但是对于了解内幕的人还是起步到什么保护的作用,所以可以扩展这个算法,使用对称或者非对称的加密算法替换该案例里面的编码算法,这样基本上就万无一失了。
鲍文俊:
1. 避免将多个类放在一个文件里面。
2. 一个文件应该只有一个命名空间,避免将多个命名空间放在同一个文件里面。
3. 一个文件最好不要超过500行的代码(不包括机器产生的代码)。
4. 一个方法的代码长度最好不要超过25行。
5. 避免方法中有超过5个参数的情况。使用结构来传递多个参数。
6. 每行代码不要超过80个字符。
7. 不要手工的修改机器产生的代码。
如果需要编辑机器产生的代码,编辑格式和风格要符合该编码标准。
8. 避免利用注释解释显而易见的代码。
代码应该可以自解释。好的代码由可读的变量和方法命名因此不需要注释。
11. 不要硬编码数字的值,总是使用构造函数设定其值。
12. 只有是自然结构才能直接使用const,比如一个星期的天数。
13. 避免在只读的变量上使用const。如果想实现只读,可以直接使用readonly。
public class MyClass
{
public readonly int Number;
public MyClass(int someValue)
{
Number = someValue;
}
public const int DaysInWeek = 7;
}
15. 代码的每一行都应该通过白盒方式的测试。
16. 只抛出已经显示处理的异常。
17. 在捕获(catch)语句的抛出异常子句中(throw),总是抛出原始异常维护原始错误的堆栈分配。
catch(Exception exception)
{
MessageBox.Show(exception.Message);
throw ; //和throw exception一样。
}
18. 避免方法的返回值是错误代码。
19. 尽量避免定义自定义异常类。
20. 当需要定义自定义的异常时:
a) 自定义异常要继承于ApplicationException。
b) 提供自定义的序列化功能。
21. 避免在单个程序集里使用多个Main方法。
22. 只对外公布必要的操作,其他的则为internal。
25. 使应用程序集尽量为最小化代码(EXE客户程序)。使用类库来替换包含的商务逻辑。
28. 即使if语句只有一句,也要将if语句的内容用大括号扩起来。
29. 避免使用trinary条件操作符。
30. 避免在条件语句中调用返回bool值的函数。可以使用局部变量并检查这些局部变量。
bool IsEverythingOK()
{…}
//避免
if (IsEverythingOK ())
{…}
//替换方案
bool ok = IsEverythingOK();
if (ok)
{…}
31. 总是使用基于0开始的数组。
32. 在循环中总是显式的初始化引用类型的数组。
public class MyClass {}
MyClass[] array = new MyClass[100];
for(int index = 0; index < array.Length; index++)
{
array[index] = new MyClass();
}
33. 不要提供public 和 protected的成员变量,使用属性代替他们。
34. 避免在继承中使用new而使用override替换。
35. 在不是sealed的类中总是将public 和 protected的方法标记成virtual的。
37. 避免显示的转换,使用as操作符进行兼容类型的转换。
Dog dog = new GermanShepherd();
GermanShepherd shepherd = dog as GermanShepherd;
if (shepherd != null )
{…}
51. 表现给最终用户的字符串不要使用硬编码而要使用资源文件替换之。
52. 不要硬编码可能更改的基于配置的字符串,比如连接字符串。
53. 当需要构建长的字符串的时候,使用StringBuilder不要使用string
54. 避免在结构里面提供方法。
a) 建议使用参数化构造函数
b) 可以重裁操作符
55. 总是要给静态变量提供静态构造函数。
56. 能使用早期绑定就不要使用后期绑定。
57. 使用应用程序的日志和跟踪。
58. 除非在不完全的switch语句中否则不要使用goto语句。
59. 在switch语句中总是要有default子句来显示信息(Assert)。
int number = SomeMethod();
switch(number)
{
case 1:
Trace.WriteLine("Case 1:");
break;
case 2:
Trace.WriteLine("Case 2:");
break;
default :
Debug.Assert(false);
break;
}
60. 除非在构造函数中调用其他构造函数否则不要使用this指针。
// 正确使用this的例子
public class MyClass
{
public MyClass(string message )
{}
public MyClass() : this("hello")
{}
}
61. 除非你想重写子类中存在名称冲突的成员或者调用基类的构造函数否则不要使用base来访问基类的成员。
// 正确使用base的例子
public class Dog
{
public Dog(string name)
{}
virtual public void Bark( int howLong)
{}
}
public class GermanShepherd : Dog
{
public GermanShe pherd(string name): base (name)
{}
override public void Bark(int howLong)
{
base .Bark(howLong);
}
}
66. 总是选择使用C#内置(一般的generics)的数据结构
黄杰:
怎样去掉ViewState中的无用数据,而保留有用的数据。我们可以很明显的看出来,DataGrid保存在ViewState中的数据分为两个部分,一部分是保存索引用的,就是DataKeys和DataItems这样的属性使用的数据,我们把它称之为索引数据。还有一部分是DataGrid中数据源的内容,我们称之为列表数据。
我们如果把实际上无用的列表数据从ViewState中去除,这样可以大大减小页面ViewState的数据大小,使用DataGrid时ViewState数据量太大的根本原因就是列表数据存放在ViewState中。
在微软的.Net Framework SDK文档中没有找到关闭列表数据ViewState的任何内容,就是说微软没有给出DataGrid运行时的任何调用顺序和内部的工作机制。获得DataGrid的内部工作流程,发现它在数据绑定初始化的时候,生成了一个叫DataGridTable的控件对象,这个对象是继承System.Web.UI.WebControls.Table控件的。而且这个对象是最先加入(使用Controls.Add()方法)DataGrid中的。而且ViewState中的DataGrid列表数据也是这个控件加入到DataGrid中的。实际上,ViewState中的DataGrid的单元格中的数据实际上是System.Web.UI.WebControls.Table控件的SaveViewState()方法给加进去的。这些数据很多情况下是不需要的。
原因找到了,解决问题就好办了,因为微软已经给出了控制DataGrid中子控件的方法。我们既然知道DataGridTable控件是DataGrid中最先生成的控件,那么我们通过DataGrid.Controls属性就可以直接获取DataGrid中子控件的引用,获得引用后就可以控制子控件了。解决方法就是在数据绑定的时候,设置DataGrid中DataGridTable控件的EnableViewState属性为False就可以了。
代码如下:
首先必须重写DataGrid.ItemDataBound或DataGrid.ItemCreated事件,我们用它们来控制在向客户端写html之前DataGrid的动作。这两个事件任选其一,都可以实现效果。我们使用ItemDataBound事件来写例子,DataGrid示例ID为myDataGrid。
[C#]
首先在页面初始化中的InitializeComponent()方法内加入事件的委托:
private void InitializeComponent()
{
this.myDataGrid.ItemDataBound +=
new DataGridItemEventHandler(this.myDataGrid_ItemDataBound);
}
然后在myDataGrid_ItemDataBound方法内加入控制代码:
private void myDataGrid_ItemDataBound(object sender, DataGridItemEventArgs e)
{
myDataGrid.Controls[0].EnableViewState = false;
}
好了,使用DataGrid时,把上面的代码加入,将减小使用DataGrid时ViewState的90%的数据量。而且,DataGrid中许多使用ViewState的功能丝毫不少
注意:
1.上面说的法子有一种情况下不能使用,就是使用DataGrid的内部分页功能时,重写DataGrid.PageIndexChanged事件时,调用DataGridPageChangedEventArgs时,必须把所有的ViewState打开,包括列表数据,关闭任何的ViewState,都将导致DataGridPageChangedEventArgs的索引丢失,无法分页。
不过这个缺点很好解决,很多人已经写了自定义的分页控件,这些控件是不需要DataGrid的任何数据的。可以提供非常完美的分页功能。
2. (重要)上面的办法确实有效,可以大大的减少aspx页面的ViewState的数据量,但是这个办法在使用上有一个注意的地方,否则会出现数据无法显示的毛病。
微软写DataGrid为什么要加入一个DataGridTable类,而且还要把所有的数据库数据存放在ViewState中。经过测试才发现,这是因为有了System.Web.UI.Page.IsPostBack属性的缘故。我们经常喜欢利用Page.IsPostBack属性检测页面是否是第一次运行,如下代码:
private void Page_Load(object sender, System.EventArgs e)
{
if(!Page.IsPostBack)
{
// 页面第一次运行,执行数据绑定
SqlConnection conn = new SqlConnection(“...”);
SqlCommand comm = new SqlCommand(“...”,conn);
...
...
myDataGrid.DataBind() // 数据绑定
}
}
当然,网页执行第一次时,DataGrid的内容正常显示,而使用了上面的去除ViewState方法后,页面如果回发处理,DataGrid的内容将会消失。明白DataGrid内DataGridTable把数据存放在ViewState内的用意。微软的设计是非常严谨的,他们的用意就是当使用Page.IsPostBack属性时,仅访问一次数据库就可以永久保持DataGrid的数据(在不离开此页面的情况下),数据存放的地点就是页面的ViewState中。这样页面回发后,DataGrid就可以从ViewState中重新生成DataGrid的显示内容,无需访问数据库。所以说微软以牺牲客户下载的速度(ViewState数据量)来保证服务器的资源,大家都知道频繁访问数据库对服务器的资源消耗很大。
所以,使用上面减少DataGrid的ViewState数据的法子是可行的,但是必须使所有的页面回发处理都必须进行数据绑定,否则DataGrid无法获得数据库内容,也无法获得ViewState中保存的数据,那么回发后DataGrid将无法显示任何内容。
总结,使用上面减少ViewState的办法可以大大加快客户端的下载显示速度,但是频繁的数据库访问将加大服务器的压力;使用ViewState可以减轻服务器的压力,但是又加大了客户端的下载时间,它们是互相矛盾的。所以开发者要根据实际情况选择是否使用DataGrid.Controls[0].EnableViewState=false;的法子,如何选择,大家请自己斟酌。
刘佳俊:
本文将要简单的介绍如何在DataGrid里面用图形表是一个数字,听起来好象要用到GUI的编程,其实不然,如果你读完全文你就会发现其实很简单,只是一个“小把戏”而已,但是请不要失望,其实在实际的应用中这个“小把戏”就是你项目中的一个亮点。
首先为了实现这个功能我们需要一个DataGrid,并且为这个DataGrid邦定数据,具体的邦定代码如下(由于我说明问题的重点不在如何邦定DataGrid所以我就是用了最原始的邦定方法,目的旨在说明主题)。
画一个DataGrid在HTML里面,代码就像下面的一样,我使用了一个测试的数据库和一个测试的表名字都叫test,同时这个test表里面有a,b,c3个字段:
<asp:DataGrid id="DataGrid1" runat="server" Width="100%" AutoGenerateColumns="False">
<Columns>
<asp:BoundColumn DataField="a" HeaderText="名称"></asp:BoundColumn>
<asp:TemplateColumn HeaderText="数字的图形表示">
<ItemTemplate>
<asp:Label id="Label1" runat="server"></asp:Label>
</ItemTemplate>
</asp:TemplateColumn>
<asp:BoundColumn DataField="c" HeaderText="数字表示"></asp:BoundColumn>
</Columns>
</asp:DataGrid>
正如你看到的,我们使用了一个模版列来显示数据,它只是一个Label而不是Image,其他两个是邦定字段,c就是我们要显示的数字了。
OK,接下来我们看看,cs的部分,帮定代码如下:
SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["ConnectionString"]);
SqlDataAdapter da = new SqlDataAdapter("select a,c from test",conn);
DataSet ds = new DataSet();
da.Fill(ds);
this.DataGrid1.DataSource = ds.Tables[0];
this.DataGrid1.DataBind();
非常简单。因为我不喜欢将数据邦定代码写在HTML里面所以我使用了ItemDataBound事件来完成这件事情。具体的代码如下:
private void DataGrid1_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e) {
if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem){
Label lbl = (Label)e.Item.Cells[1].FindControl("Label1");
lbl.Text = "<HR align='left' color='blue' size='10' width='"+e.Item.Cells[2].Text+"'>";
}
}
按照上面的步骤创建一个页面,通过他还可以实现其他很多有关数据的显示,比如:比例。不过这种比例的计算你最好在邦定之前处理好放到DataTable里面,这样邦定的代码将非常简单。运行的结果如下:
赵敏:
在asp.net中利用datagrid控件按列进行排序很是方便。可是我们只能单项排序!如果我们需要正反排序那么就需要加入一些代码控制一下,下面我来详细讲解一下这个过程。
首先我们需要将datagird控件的属性设置为 AllowSorting="True",且需要排序列需要制定排序表达式 eg: SortExpression="kmdm"。设置好这些,我们进入代码文件,来编写响应排序的事件.
首先在Page_Load时间中加入如下代码:
if (!IsPostBack)
{
if(this.kjkm_dg.Attributes["SortExpression"]==null) //这里kjkm_dg为datagrid ID
{
this.kjkm_dg.Attributes["SortExpression"]="kmdm"; //这里给datagrid增加一个排序属性,且默认排序表达式为kmdm;
kjkm_dg.Attributes["SortDirection"]="ASC"; //这里给datagrid增加一个排序方向属性,且默认为升序排列;
}
mikecatbind(); //绑定函数,下面介绍
}
protected void mikecatbind()
{
string sqlStr="select * from zc_kjkm";
DataView dv=new DataView();
string SortExpression=kjkm_dg.Attributes["SortExpression"];
string SortDirection=kjkm_dg.Attributes["SortDirection"];
dv=us.Bind(sqlStr).Tables[0].DefaultView; //来自web service的dataset,这里随便一个ds就可以;
dv.Sort=SortExpression+" "+SortDirection; //指定视图的排序方式;
kjkm_dg.DataSource=dv; //指定数据源
kjkm_dg.DataBind(); //数据绑定
}
进行完上面的设置后我们进入重要的环节,排序事件的编写:
private void kjkm_dg_SortCommand(object source, System.Web.UI.WebControls.DataGridSortCommandEventArgs e)
{
string SortExpression=e.SortExpression.ToString(); //获得当前排序表达式
string SortDirection="ASC"; //为排序方向变量赋初值
if(SortExpression==kjkm_dg.Attributes["SortExpression"]) //如果为当前排序列
{
SortDirection=(kjkm_dg.Attributes["SortDirection"].ToString()==SortDirection?"DESC":"ASC"); //获得下一次的排序状态
}
kjkm_dg.Attributes["SortExpression"]=SortExpression;
kjkm_dg.Attributes["SortDirection"]=SortDirection;
mikecatbind();
}
许冯:
有时候我们需要在删除DataGrid中Item相对应的数据时,需要弹出一个确认对话框来提示使用者,其实这个功能非常简单,下面的代码大家可以试试,还有一个小技巧也顺便测试一下,可以在DataGrid的Item 中产生颜色交替的效果。
private void dg_ItemDataBound(object sender, DataGridItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
//删除确认
LinkButton delBttn = (LinkButton) e.Item.Cells[1].Controls[0];
delBttn.Attributes.Add("onclick","javascript:return confirm('确定删除" + e.Item.Cells[4].Text + "?');");
//颜色交替
e.Item.Attributes.Add("onmouseover","this.style.backgroundColor='seashell'");
if(e.Item.ItemType == ListItemType.Item)
{
e.Item.Attributes.Add("onmouseout", "this.style.backgroundColor='#ffffff'");
}
if(e.Item.ItemType ==ListItemType.AlternatingItem)
{
e.Item.Attributes.Add("onmouseout", "this.style.backgroundColor='seashell'");
}
}
}