posts - 14,  comments - 20,  trackbacks - 0
  2009年4月2日

  经常遇到ASPX页面重复提交的问题,以前觉得是浏览器和用户的责任,理由是版本高点的浏览器如IE8会提示"是否重复提交?...",如果用户不顾警告重复提交,那是他们的事情。如果浏览器不提示,则是浏览器的责任。
最近有时间,就想解决这个问题。网上一搜一大把,把基本的情况搞清楚了,自己也尝试了几种方法。最终采用了这里介绍的方法 。感谢这位同行。
写了一个简单的控件,方便调用:

1  protected void Page_Load(object sender, EventArgs e)
2   {
3   DuplicatePostTest_ButtonTest.Register(Button1,Button2);
4   }

DuplicatePostTest_ButtonTest是拖到设计器上的自定义控件,Register方法定义成: public void Register(params Button[] buttons),可以一次添加多个需要检测重复提交的Button。
在Button的单击事件方法里:
 1  protected void Button2_Click(object sender, EventArgs e)
 2     {
 3         if (DuplicatePostTest_ButtonTest.IsDuplicatePost)
 4         {
 5             Response.Write("重复提交。");
 6         }
 7         else
 8         {
 9             //做动作
10             Response.Write("正常提交。" + DateTime.Now.ToLongTimeString());
11 
12         }
13 
14         //重置:
15         DuplicatePostTest_ButtonTest.Reset();
16     }

DuplicatePostTest_ButtonTest.IsDuplicatePost用于测试是否重复提交。最后需要加上DuplicatePostTest_ButtonTest.Reset()重置状态。
下面是实现代码:
 
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;

public partial class XX_UserControl_DuplicatePostTest : System.Web.UI.UserControl
{

    List
<Button> buttonList = new List<Button>(10);
    
public void Register(params Button[] buttons)
    {
        
string ranID = GetRanID();
        
foreach (Button bu in buttons)
        {
            buttonList.Add(bu);
            
if (bu.PostBackUrl == "")
            {
                SetPostBackUrl(bu, ranID);
            }
        }
        
if (!IsPostBack)
        {
            Session[SessionKey] 
= ranID;
        }
    }
    
private void SetPostBackUrl(Button button, string ranID)
    {
        button.PostBackUrl 
= string.Format("{0}?{1}={2}"this.Page.AppRelativeVirtualPath, ButtonPostBackKey,ranID);
    }
    
public bool IsDuplicatePost
    {
        
get
        {
            
return !Request[ButtonPostBackKey].Equals(Session[SessionKey]);
        }
    }
    
public string ButtonPostBackKey
    {
        
get
        {
            
return "DuplicatePostKey";
        }
    }

    
string key;
    
/// <summary>
    
/// Session和ViewState里存放随机值RanIDkey
    
/// </summary>
    private string SessionKey
    {
        
get
        {
            
if (key == null)
                key 
= string.Format("DuplicatePostTest_{0}"this.Page.AppRelativeTemplateSourceDirectory);
            
return key;
        }
    }
    
/// <summary>
    
/// 生成随机的字符串
    
/// </summary>
    
/// <returns></returns>
    private string GetRanID()
    {
        
return DateTime.Now.Ticks.ToString();
    }
    
/// <summary>
    
/// 重置状态
    
/// </summary>
    public void Reset()
    {
        
string ranID = GetRanID();
        
foreach (Button bu in buttonList)
        {
            SetPostBackUrl(bu, ranID);
        }
        Session[SessionKey] 
= ranID;
    }
}

 

posted @ 2009-04-02 11:08 apple123 阅读(452) 评论(0) 编辑
  2009年3月30日

本文简单比较了动软三层结构和“Nhibernate三层结构”。  
1)动软三层架构

刚开发ASP.Net应用时,使用动软自动生成的三层架构,用起来方便。就是改动起来麻烦。比如要加一个查询数据的方法,先在DAL层添加接口方法,再到SQLServerDAL层添加实现代码,再到BLL层添加一个包装方法。向某个表里添加一个字段,就得重新生成一遍,要不然就自己一层一层改。

 

IDAL层,可以由OracleDALDB2DAL来实现,目的是屏蔽数据操作的细节。BLL层是有必要的,封装业务处理逻辑。上图展示了数据访问层和业务逻辑层,表示层就是aspx页面了,调用BLL取得数据,然后展示给用户,很多时候会用GridView显示数据。

在开发时,发现BLL层的两个功能:处理业务逻辑和显示数据,可以分离开来。动软给数据库中的每个表都生成一个BLL层的类,而有的表并不对应具体的业务逻辑(比如公司信息表,个人信息表),有的表需要混合其他表来完成业务逻辑(比如处理网站登陆的表Admin,需要结合用户表,角色表等)。这是可以改进的。对那些只需要提供数据、并不要处理业务逻辑的类(如用户表等),统一交给一个的类来管理,给一个表名(最后用视图封装)和查询条件,那个类就返回相应的数据,这是容易实现的。而把业务逻辑的类抽象出来。请看下面的”Nhibernate三层结构

2Nhibernate三层结构

Nhibernate作为一种对象关系映射框架,以面向对象的方式来操作数据库,简单方便。以下是项目中使用的“Nhibernate三层结构”:

NHibernate支持,每个Model层的实体类可以封装数据操作方法(如Save,Update等),BLL层可以方便调用。事实上,BLL大多涉及到单个对象的操作(新增或更新、删除),这个NHibernate的强项,而且能以面向对象的方式很好地配合业务逻辑的实现。

DataViewer是从原来动软三层架构里的BLL层分离出来的,负责给前台页面提供数据。应该归到表示层。这样,层次就清晰多了。BLL只负责处理业务逻辑,不提供前台展示的数据。或者说BLL层负责修改后台的数据库,而DataViewer不会修改数据库,只负责读数据库,向前台输出组合查询、分页后的数据。

DataViewer不使用NHibernateHQL,原因是HQL可能会导致效率的低下,使用原来的SQL查询方式,不用学习新的东西,还能做一些查询优化。DataViewer最好是操作视图,不操作具体的表,方便修改。

下次贴出DataViewer的具体实现。

 

posted @ 2009-03-30 10:06 apple123 阅读(2653) 评论(10) 编辑
  2009年3月27日
(1)方法C#中,一个类中可以定义多个方法,但相同签名的方法只能有一个。方法的签名包括: 方法的名称,形式参数的类型、个数、修饰符。如果方法是泛型方法,其签名还包括类型参数的个数。修饰符是指ref和out。参见:参数
(2)事件
事件相当于一个字段,这个字段是一个委托类型。但在类体的外面,只能在事件上执行+=或-=操作。也可以将事件定义成这样:
1 public class Button
2 {
3 private EventHandler handler;
4 public event EventHandler Click 
5 {
6   add { handler += value; }
7   remove { handler -= value; }
8 }
9 }

add和remove访问器类似属性里面的get和set。
(3)索引器
提供类似数组模式的成员访问。
 1 public class Stack
 2 {
 3 private Node GetNode(int index) {
 4 Node temp = first;
 5 while (true) {
 6 if (temp == null || index < 0)
 7 throw new Exception("Index out of range.");
 8 if (index == 0)
 9 return temp;
10 temp = temp.Next;
11 index--;
12 }
13 }
14 public object this[int index] {
15 get {
16 return GetNode(index).Value;
17 }
18 set {
19 GetNode(index).Value = value;
20 }
21 }
22 
23 }
24 class Test
25 {
26 static void Main() {
27 Stack s = new Stack();
28 s.Push(1);
29 s.Push(2);
30 s.Push(3);
31 s[0= 33// Changes the top item from 3 to 33
32 s[1= 22// Changes the middle item from 2 to 22
33 s[2= 11// Changes the bottom item from 1 to 11
34 }
35 }

(4)类
方法、属性、索引器都可以是virtual的,意味着在子类中可以得到重写。
(5)静态类
像这样:
1 public static class Months
2 {
3 static Months() { … }
4 private static readonly string[] monthName = { … }
5 public static string GetMonthName(int mm) { … }
6 private static readonly int[,] daysInMonth = { … }
7 public static int GetDaysInMonth(bool isLeapYear, int mm) { … }
8 public static bool IsLeapYear(int yy) { … }
9 }

静态类只能这样用,typeof(Months)或者Months.IsLeapYear(200),不能被继承、实例化或者作为泛型类型参数。
(6)结构
结构和类相似,可以实现接口,却不能继承类或被继承。
举例:
 1 class Point
 2 {
 3 public int x, y;
 4 public Point(int x, int y) {
 5 this.x = x;
 6 this.y = y;
 7 }
 8 }
 9 class Test
10 {
11 static void Main() {
12 Point[] points = new Point[100];
13 for (int i = 0; i < 100; i++)
14 points[i] = new Point(i, i*i);
15 }
16 }

问Main方法中共有几个对象?一个points数组,和100个Point,共101个。
 1 struct Point
 2 {
 3 public int x, y;
 4 public Point(int x, int y) {
 5 this.x = x;
 6 this.y = y;
 7 }
 8 }
 9 class Test
10 {
11 static void Main() {
12 Point[] points = new Point[100];
13 for (int i = 0; i < 100; i++)
14 points[i] = new Point(i, i*i);
15 }
16 }

如果是这样呢?只有一个对象,就是points数组。
(7)接口
接口可以包含的成员是:
方法,属性,索引器,事件。接口中的成员默认都是public的,不能有任何访问修饰符。
1 interface IExample
2 {
3 string this[int index] { getset; }
4 event EventHandler E;
5 void F(int value);
6 string P { getset; }
7 }
8 public delegate void EventHandler(object sender, EventArgs e);

实现时,必须实现为public:
 1 interface IControl
 2 {
 3 void Paint();
 4 }
 5 interface IDataBound
 6 {
 7 void Bind(Binder b);
 8 }
 9 public class EditBox: Control, IControl, IDataBound
10 {
11 public void Paint() {…}
12 public void Bind(Binder b) {…}
13 }

如果要实现为private的,那就"显示实现":
1 public class EditBox: IControl, IDataBound
2 {
3 void IControl.Paint() {…}
4 void IDataBound.Bind(Binder b) {…}
5 }

这样用:
1 class Test
2 {
3 static void Main() {
4 EditBox editbox = new EditBox();
5 editbox.Paint(); // error: no such method
6 IControl control = editbox;
7 control.Paint(); // calls EditBox’s Paint implementation
8 }
9 }

实现时默认为private,不能有任何修饰符。
(8)代理
代理相当于C语言中的函数指针。c#中的代理值面向对象的(和class一样是一种类型),同时是类型安全的。
一个代理可以封装一个或多个方法,这些方法被成为“调用实体”。执行这个代理时,这些方法都将被调用。
方法可以是实例方法,也可以是静态方法。包含方法的类可以是任意的(甚至可以是匿名方法,即没有类的存在!),只要方法的定义和代理的定义相一致。
定义和使用:
 1 delegate void SimpleDelegate();
 2 class Test
 3 {
 4 static void F() {
 5 System.Console.WriteLine("Test.F");
 6 }
 7 static void Main() {
 8 SimpleDelegate d = new SimpleDelegate(F);
 9 d();
10 }
11 }

像方法一样使用代理。代理的返回值是,最后一个方法的返回值:
 
 1 delegate string GetAString();
 2   [TestFixture]
 3   public class DelegateTest
 4   {
 5   public string get1()
 6   {
 7   return "get1";
 8   }
 9   public string get2()
10   {
11   return "get2";
12   }
13   [Test]
14   public void TestDelegate()
15   {
16   GetAString dele = new GetAString(get1);
17   dele += get2;
18   Assert.That(dele(), Is.EqualTo(get2()));
19   }
20   }

上述代码使用了NUnit。简单介绍:[TestFixture]是一个c#中的特性(Attribute),这样Nunit就认为DelegateTest类是包含测试方法的类。 同样,[Test]标记了方法TestDelegate,意味着这个方法是一个测试方法。 测试方法返回值是void,没有参数,必须是public的,保证Nunit能调用到。Assert.That(dele(), Is.EqualTo(get2()));断言dele()的结果是get2()。断言失败则测试不通过。 (9)枚举 例子:  1 enum Color  2 {  3 Red,  4 Blue,  5 Green  6 }  7 class Shape  8 {  9 public void Fill(Color color) { 10 switch(color) { 11 case Color.Red: 12 … 13 break; 14 case Color.Blue: 15 … 16 break; 17 case Color.Green: 18 … 19 break; 20 default: 21 break; 22 } 23 } 24 } 枚举相当于符号常量的集合,这些符号常量是整型的,默认实现是int:  1  enum Fruit1  2   {  3   Apple, Orange  4   }  5   enum Fruit2:byte  6   {  7   Apple,Orange  8   }  9     10   [TestFixture] 11   public class EnumTest 12   { 13   [Test] 14   public void TestEnum() 15   { 16   Fruit1 f = Fruit1.Apple; 17   Assert.That(Enum.GetUnderlyingType(typeof(Fruit1)),Is.EqualTo(typeof(int))); 18   Assert.That(Enum.GetUnderlyingType(typeof(Fruit2)), Is.EqualTo(typeof(byte))); 19   Assert.That(Enum.GetValues(typeof(Fruit1)), Is.EqualTo(new Fruit1[] { Fruit1.Apple,Fruit1.Orange }).AsCollection); 20  21   } 22   } 注意Fruit2用byte作为枚举常量的类型。
posted @ 2009-03-27 20:35 apple123 阅读(250) 评论(0) 编辑
  2009年3月26日

C#规范解读之心得 1

(全部翻译过来太累了,只选重点供大家参考)

1c#中的运算符

 运算符有优先级和结合性。优先级很明白,结合性的意思是:当两个优先级相同的运算符出现在一个操作数的两边时,是从左还是从右开始计算。

比如:所有二元运算符除赋值运算符以外,都是左结合的。如x+y+z是这样计算的:(x+y+z,即先计算x+y的值,这个值再加上z,作为表达式最终的值。

对于赋值运算符,x=y=z,则是这样计算的:x=(y=z),说明赋值运算符是右结合的。

C#中的唯一一个三元运算符,?:条件运算符也是右结合的,比如false ? 4 : false ? 1 : 2是这样计算的:false ? 4 : false ? 1 : 2),而不是(false ? 4 : false ? 1 : 2,这样不符合语法。

2)类及访问性

常量,字段,方法,属性,事件,索引器,运算符,实例构造函数,析构函数,静态构造函数,内部类型定义(共11个)。泛型类还可以包括一个或多个类型参数。

每一个成员都可以设置可访问性。成员的访问级别有5种:

publicprotectedinternalprotected internalprivate,从左到右访问性逐步降低

当然,要访问某个类的成员,首先要能访问到这个类。类的访问级别共2中,internalpublic,默认是internalinternal是指“一个程序集的内部”,(C#的程序集有两种,exedll)。

可访问性的意思,比如说,如果一个类A的访问级别是internal,即这样定义类A

class A{…}internal A{},把这个类编译成动态连接库:a.dll。再写一个类BB的类体中不能 出现 A这个类型(另外定义又当别论),即使编译时引用了a.dll(编译时指定/r:a.dll)。

但如果A定义成public A{…},在B的类体中就能出现A这个类型了(编译时指定/r:a.dll),B可以有类型是A的字段,B的方法中也可以声明类型为A的局部变量。但A中的成员的访问性又受到成员的访问修饰符的影响,假如B有类型为A的字段aa,A类中有方法void F()。如果这样定义:public void F(){}B的成员aa可以调用F方法,意为“可见”。如果这样定义: internal void F(){}void F(){}(private F(){}),则aa不能调用F方法,意为“不可见”。

 

 

 

posted @ 2009-03-26 22:46 apple123 阅读(103) 评论(0) 编辑

最近做ASP.Net方面的项目,GirdView用得很频繁。因此准备设计一个通用的数据获取和展示模型。
具体如下:
(1)需求:
a.最终的效果是一个自定义的Gridview或Repeater(暂且叫MyGridView),能分页、排序等。数据库只返回当前需要显示的数据。
b.不需要额外处理MyGridView的PageIndexChanged,Sorting等事件。
c.统一管理那些Select SQL 语句,方便维护。
d.提供组合查询接口。
(欢迎提需求,看看设计是否是敏捷的)
(2)设计

(不成熟,等有基本的代码了再贴上来)

posted @ 2009-03-26 10:14 apple123 阅读(143) 评论(0) 编辑
  2009年3月20日
摘要: 这个资源池用来管理程序的宝贵资源。主要的类是ResourcePool<T>。用户可以通过调用GetResource方法请求一个资源,用完之后通过ReturnResource归还给资源池。由资源池决定什么时候释放多余的资源。接口IResourceProvider<T>用来获得资源。类ResourceTag<T>用来标志资源是否在用。具体的就不多说了,请看代码。欢迎...阅读全文
posted @ 2009-03-20 18:38 apple123 阅读(2151) 评论(8) 编辑
  2009年3月19日
摘要: 变量表示了存储位置。每一个变量都属于某个类型,这个类型决定了变量能保存什么样的值。局部变量是指在函数型成员如方法、属性、索引器中声明的变量。声明一个局部变量,需要指定一个类型名,一个代表变量名字的说明符,以及一个可选的初始值。比如:[代码]可以在一个局部变量的声明中包含多个变量的名字。上面的a和b可以定义成[代码]一个变量必须先赋值,才能取出它的值。例子:[代码]会导致编译错误,因为这个程序使用了...阅读全文
posted @ 2009-03-19 22:18 apple123 阅读(232) 评论(0) 编辑
摘要: C#提供了一个"统一的类型系统"。所有的类型-包括值类型在内-都是从object类型继承而来。可以在任何变量上调用object类的方法,甚至包括像int这样的基础类型在内。例子:[代码]一个整型的常量调用了object类定义的ToString方法,输出是"3"。例子:[代码]很有意思。一个int值可以转化成一个object,然后转化回来。这个例子用到了装箱和拆箱。当一个值类型的变量转化为引用类型时...阅读全文
posted @ 2009-03-19 21:08 apple123 阅读(213) 评论(0) 编辑
摘要: 数组可以是一维或者多维的。"矩形"数组和"不整齐"数组C#都支持。一维数组是最常见的类型了。例子:[代码]创建了一个int型的一维数组,初始化数组的元素后,打印出每个元素的值。输出是:arr[0] = 0arr[1] = 1arr[2] = 4arr[3] = 9arr[4] = 16例子中的类型int[]是一个数组类型。数组类型用一个非数组类型后跟一个或多个秩符号(译注:即方括号"[]")表示。...阅读全文
posted @ 2009-03-19 17:20 apple123 阅读(888) 评论(0) 编辑
  2009年3月18日
摘要:  预定义类型同时含了预定义的转化行为。比如,int和long之间存在预定义的转化。C#区分这两种类型的转化:隐式转化和显示转化。隐式转化指的是安全的、不需要仔细地指出来的转化。比如int到long的转化就是隐式转化。这种转化总是成功执行,不会造成信息的丢失(译注:此处指的是精度损失)。下面的例子:[代码]隐式地将一个int类型的变量转化成long类型的变量(译注:intValue本身的类...阅读全文
posted @ 2009-03-18 17:17 apple123 阅读(77) 评论(0) 编辑