分享幾個asp.net開發中的小技巧


下面這幾個,是在實際開發或閱讀中發現的一些問題,有些甚至是有很多年開發人員寫出的代碼,也是很多人經常犯的錯誤。各位可以看看,你有沒有躺着中槍。


第一個,對整型變量進行非null判斷。

// a 是int型 (不是int?)
if(a != null){
    //操作
}

 

個人點評:無意義判斷,值類型永遠不可能為null。

第二個,用static來保持頁面回發

static int id;
protected void Page_Load(object sender, EventArgs e)
{
    if (Request.QueryString["ID"] != null && Request.QueryString["ID"].ToString() != "")
    {
        id = Convert.ToInt32(Request.QueryString["ID"].ToString());
    }
}

 

個人點評:這個不解釋,不知道怎么說。但最近還真就遇到了,而且也不是什么小項目,WebForm無服務器控件開發。

第三個,用編程方式綁定數據控件時,數據源為DataSet時判斷null而不判讀DataSet內的Tables數。

DataSet ds = bll.GetList(); 
if (ds != null)
{
    Repeater1.DataSource = ds;
    Repeater1.DataBind();
}

 

個人點評:當bll.GetList()返回的DataSet非null但里面沒有包含數據表時,執行DataBind()方法時會報HttpException異常(IListSource 不包含任何數據源)。正確寫法應該是

DataSet ds = bll.GetList();
if (ds != null && ds.Tables.Count > 0)
{
    Repeater1.DataSource = ds;
    Repeater1.DataBind(); 
}
//
DataSet ds = bll.GetList()??new DataSet();
if (ds.Tables.Count > 0)
{
    Repeater1.DataSource = ds;
    Repeater1.DataBind(); 
}

 

第四個,用編程方式綁定數據控件時,數據源為DataTable或List<T>時判斷null。

DataTable dt = bll.GetList();
if (dt!=null)
{
    Repeater1.DataSource = dt;
    Repeater1.DataBind();
}

 

個人點評:無意義判斷,下面的寫法沒有任何問題,即使dt=null

DataTable dt = bll.GetList();
Repeater1.DataSource = dt;
Repeater1.DataBind();

 

第五個

Model m = new Model(); 
m = bll.GetModel(id);
m.name;

 

個人點評:以為只要聲明時不為null,后面就不需要做非空非null判斷了。萬一第二步BLL層返回的model就為null呢?

第六個,在Repeater1_ItemDataBound中寫這樣的代碼

Label lblPMID = (Label)e.Item.FindControl("lblPMID");
if (lblPMID.Text != "")
{
    //操作
}

 

個人點評:低效,無意義判斷,很可能出現NullReferenceException(未將對象引用設置到對象的實例)異常。
正確寫法:

Label lblPMID = e.Item.FindControl("lblPMID") as Label;
if (lblPMID!=null && lblPMID.Text != "") //視里面使用情況決定是否判斷lblPMID.Text為“”或空白
{
    //操作
}

 

第七個

string txtName = Request["txtName"] == null ? "" : Request["txtName"].ToString();
string strWhere += "and ID=" + userId + ""; //userId是int
if (txtName != "")
{
    strWhere += " and NAME='" + txtName + "'"; 
}
strWhere += " order by id desc";
//項目本身都是采用參數化查詢的,這里是一些暴露給Web層的高級查詢條件。

 

個人點評:1、值類型和字符串拼接會隱式裝箱,2、SQL注入危險。正確做法是userId.ToString()並且過濾txtName中特殊字符,限制字符串長度。
注意,拼接SQL時過濾字符串並不能完全防止SQL注入,但很多時候在高級查詢時拼接SQL是最簡單也是最方便的,這時候過濾不應該只過濾一些指定的特殊字符,
比如只過濾單引號,等號,大於/小於/等於,空格,括號之類的危險字符。應該對除中文字符、英文字母、和數字外的所有字符全部過濾掉(視情況而定)。
並且嚴格限制字符串長度,一般查詢時輸入的關鍵字不會太長,如果用戶輸入的有空格,就拆分成多個條件。這樣能盡可能的減小SQL注入的機會。


最后,給大家分享幾個小經驗,雖說有些只是語法糖,但卻可以幫助我們更高效編寫或閱讀代碼。

一、引用類型的null值很麻煩,因為類型為null時使用點運算符 (.)會報異常,所以經常要做非null判斷,可以用?? null 合並運算符減少代碼量。例如:

//寫法一
int ID;
if (Request.Form["ID"] != null && Request.Form["ID"].ToString() != "")
{
ID = Convert.ToInt32(Request.Form["ID"].ToString());
}

//寫法二
int id;
if (int.TryParse(Request.Form["ID"]??"",out id))
{

}

//方法一
string userName2=string.Empty;
if (Session["userName"]!=null)
{
    userName2 = Session["userName"].ToString();
}

//方法二
string userName1 = Session["userName"] == null ? "" : Session["userName"].ToString();

//方法三
string userName = (Session["userName"] ?? "").ToString();

 

二、Web項目中的所有Session或cookie最好統一放到一個類中管理。最重要的目的是把Session中索引名獨立出來管理,也就是除了本類外的所有頁面都不要輸入Session名。
可能用語言表達不夠直白,直接上代碼。
看到很多人是這樣,包括網上流行的一些很常見的輔助類庫。

 1     /// <summary>
 2     /// Session 操作類
 3     /// 1、GetSession(string name)根據session名獲取session對象
 4     /// 2、SetSession(string name, object val)設置session
 5     /// </summary>
 6     public class SessionHelper
 7     {
 8         /// <summary>
 9         /// 根據session名獲取session對象
10         /// </summary>
11         /// <param name="name"></param>
12         /// <returns></returns>
13         public static object GetSession(string name)
14         {
15             return HttpContext.Current.Session[name];
16         }
17         /// <summary>
18         /// 設置session
19         /// </summary>
20         /// <param name="name">session 名</param>
21         /// <param name="val">session 值</param>
22         public static void SetSession(string name, object val)
23         {
24             HttpContext.Current.Session.Remove(name);
25             HttpContext.Current.Session.Add(name, val);
26         }
27         /// <summary>
28         /// 檢測session是否存在
29         /// </summary>
30         /// <param name="name"></param>
31         /// <returns></returns>
32         public static bool CheckSession(string name)
33         {
34             try
35             {
36                 if (GetSession(name) == null)
37                 {
38                     return false;
39                 }
40                 else
41                 {
42                     return true;
43                 }
44             }
45             catch
46             {
47                 return false;
48             }
49         }
50     }
View Code

我想知道,這樣做有什么實際意義么?而且HttpContext.Current還不做null檢查。
項目中還到處都是SessionHelper.SetSession("name"),SessionHelper.GetSession("name")這樣編譯器無法找到具體引用的代碼,當你有很多頁面用到這個會話后,
你再想更改會話名稱或刪除這個會話那將是一場災難,而且這樣的代碼多了還可能出現多個會話重名造成沖突,名稱寫錯造成會話丟失。
要克服以上問題,Web項目的Session會話你應該這樣寫

  1    /// <summary>
  2     /// 系統中Session會話管理
  3     ///<para>注意:該類使用Session,若在一般處理程序中調用該類方法必須實現 IRequiresSessionState 接口</para>
  4     /// </summary>
  5     public class SessionManager
  6     {
  7         private const string USER_LOGIN_MARK = "userModel";             //用戶登錄
  8         private const string USER_REGISTER_MARK = "registerModel";      //用戶注冊
  9         private const string CHECKSUM_MARK = "checksumModel";           //校驗碼
 10         private const string CAPTCHA_MARK = "captchaModel";             //驗證碼
 11 
 12         #region 用戶登錄會話
 13         /// <summary>
 14         /// 添加用戶登錄標識
 15         /// </summary>
 16         /// <param name="cardModel"></param>
 17         internal static void AddUserLoginMark(UserModel model)   //UserModel是用戶對象
 18         {
 19             if (model == null)
 20             {
 21                 throw new ArgumentNullException("參數不能為Null");
 22             }
 23 
 24             HttpContext context = HttpContext.Current;
 25             if (context != null)
 26             {
 27                 context.Session[USER_LOGIN_MARK] = model;
 28             }
 29         }
 30 
 31         /// <summary>
 32         /// 移除用戶登錄標識
 33         /// </summary>
 34         internal static void RemoveUserLoginMark()
 35         {
 36             HttpContext context = HttpContext.Current;
 37             if (context != null)
 38             {
 39                 context.Session.Remove(USER_LOGIN_MARK);
 40             }
 41         }
 42 
 43         /// <summary>
 44         /// 獲取當前登錄用戶對象
 45         /// </summary>
 46         /// <returns></returns>
 47         internal static UserModel GetUserLogin()
 48         {
 49             HttpContext context = HttpContext.Current;
 50             if (context != null)
 51             {
 52                 return context.Session[USER_LOGIN_MARK] as UserModel;
 53             }
 54 
 55             return null;
 56         }
 57         #endregion
 58 
 59         #region 用戶注冊會話
 60         /// <summary>
 61         /// 添加用戶注冊會話標識
 62         /// </summary>
 63         /// <param name="cardModel"></param>
 64         internal static void AddUserRegisterMark(RegisterModel model)
 65         {
 66             if (model == null)
 67             {
 68                 throw new ArgumentNullException("參數不能為Null");
 69             }
 70 
 71             HttpContext context = HttpContext.Current;
 72             if (context != null)
 73             {
 74                 context.Session[USER_REGISTER_MARK] = model;
 75             }
 76         }
 77 
 78         /// <summary>
 79         /// 移除用戶注冊會話標識
 80         /// </summary>
 81         internal static void RemoveUserRegisterMark()
 82         {
 83             HttpContext context = HttpContext.Current;
 84             if (context != null)
 85             {
 86                 context.Session.Remove(USER_REGISTER_MARK);
 87             }
 88         }
 89 
 90         /// <summary>
 91         /// 獲取當前注冊會話對象
 92         /// </summary>
 93         /// <returns></returns>
 94         internal static RegisterModel GetUserRegister()
 95         {
 96             HttpContext context = HttpContext.Current;
 97             if (context != null)
 98             {
 99                 return context.Session[USER_REGISTER_MARK] as RegisterModel;
100             }
101 
102             return null;
103         }
104         #endregion
105 
106         #region 校驗碼會話(手機和郵件)
107         /// <summary>
108         /// 添加一個校驗碼會話
109         /// </summary>
110         internal static void AddChecksumMark(ChecksumModel model)
111         {
112             if (model == null)
113             {
114                 throw new ArgumentNullException("參數不能為Null");
115             }
116 
117             HttpContext context = HttpContext.Current;
118             if (context != null)
119             {
120                 context.Session[CHECKSUM_MARK] = model;
121             }
122         }
123 
124         /// <summary>
125         /// 移除當前用戶的校驗碼會話
126         /// </summary>
127         internal static void RemoveChecksumMark()
128         {
129             HttpContext context = HttpContext.Current;
130             if (context != null)
131             {
132                 context.Session.Remove(CHECKSUM_MARK);
133             }
134         }
135 
136         /// <summary>
137         /// 獲取當前用戶的校驗碼會話
138         /// </summary>
139         internal static ChecksumModel GetChecksum()
140         {
141             HttpContext context = HttpContext.Current;
142             if (context != null)
143             {
144                 return context.Session[CHECKSUM_MARK] as ChecksumModel;
145             }
146 
147             return null;
148         }
149         #endregion
150 
151         #region 驗證碼會話
152         /// <summary>
153         /// 添加一個驗證碼會話
154         /// </summary>
155         internal static void AddCaptchaMark(string c)
156         {
157             if (c == null)
158             {
159                 throw new ArgumentNullException("參數不能為Null");
160             }
161 
162             HttpContext context = HttpContext.Current;
163             if (context != null)
164             {
165                 context.Session[CAPTCHA_MARK] = c;
166             }
167         }
168 
169         /// <summary>
170         /// 移除當前用戶的驗證碼會話
171         /// </summary>
172         internal static void RemoveCaptchaMark()
173         {
174             HttpContext context = HttpContext.Current;
175             if (context != null)
176             {
177                 context.Session.Remove(CAPTCHA_MARK);
178             }
179         }
180 
181         /// <summary>
182         /// 獲取當前用戶的驗證碼會話
183         /// </summary>
184         internal static string GetCaptcha()
185         {
186             HttpContext context = HttpContext.Current;
187             if (context != null)
188             {
189                 return context.Session[CAPTCHA_MARK] as string;
190             }
191 
192             return null;
193         }
194 
195         /// <summary>
196         /// 檢查驗證碼是否和服務器端一致(不區分大小寫)
197         /// </summary>
198         /// <param name="code">用戶輸入的驗證碼</param>
199         /// <returns></returns>
200         public static bool ValidateCaptcha(string code)
201         {
202             if (string.IsNullOrWhiteSpace(code))
203             {
204                 throw new ArgumentNullException("參數不能為Null");
205             }
206             string code2 = GetCaptcha() ?? "";
207             if (code.ToUpper() == code2.ToUpper())
208             {
209                 return true;
210             }
211             return false;
212         }
213         #endregion
214     }
View Code

這樣寫,項目中使用時:
登錄成功就添加會話SessionManager.AddUserLoginMark(當前登錄用戶對象);
頁面登錄檢查時判斷 SessionManager.GetUserLogin();返回是否為null就行。
退出登錄時SessionManager.RemoveUserLoginMark();即可。
這樣就只管調用就行,不用再去管Session名是什么,刪除、更改也更方便,當然也不會出現Session重名現象了(如果這樣都能整成會話重名的話,那你真成人才了)。

當然,這樣寫也不是一點缺點都沒有,和前一種相比,這種方法可能就不能做到一次書寫,到處使用了,需要跟據當前項目具體靈活改動相應代碼,但好處是很明顯的。這樣的方法同樣適用於Cache和Cookie。

 

三、最好不要用Request[]代替Request.Form[]和Request.QueryString[]。

如果頁面有很多Request.Form[]、Request.QueryString[]、Session[]最好在頁面Page_Load中就把所有值取出來存放在變量中,並轉換成需要的類型。
滿篇的Request.Form[]、Request.QueryString[]、Session[]編譯器沒法檢查[]中的字符串,容易出錯,影響閱讀,還可能同一參數需要多次類型轉換(這一條針對WebFrom無服務器控件開發時特別有用)。

 

四、盡量使用 as 代替引用類型間轉換(見上面第六個)

這個大家都知道,但還是發現很多人在用強制轉換,包括一些優秀的開源項目。


五、RegisterClientScriptBlock、RegisterClientScriptInclude、RegisterStartupScript、RegisterOnSubmitStatement、RegisterClientScriptResource等方法要求前台頁面必須有form服務器控件(<form runat="server"></form>)。
也就意味着在WebForm無服務器控件開發時,這幾個沒什么用了(同樣的還有Page.IsPostBack要小心了)。


六、微軟不推薦直接在后台使用Response.Write()輸出JS,並且有的瀏覽器的確會造成頁面變形。

但發現很多人在用,包括一些很優秀的開源項目。我暫時用在前台加入<%= strMsg %>來接收后台傳過來的消息,不知道各位都有什么好的方法。


七、最后向大家分享一段自己寫的小代碼,為Repeater控件添加EmptyDataTemplate模板(EmptyDataTemplate在FooterTemplate之前)。

原理很簡單,默認添加的模板會出現在FooterTemplate之后,在RenderChildren()方法中給它們換下位置就行。
代碼如下:

 1 using System;
 2 using System.ComponentModel;
 3 using System.Web.UI;
 4 
 5 namespace MyRepeater
 6 {
 7     public class Repeater : System.Web.UI.WebControls.Repeater
 8     {
 9         [PersistenceMode(PersistenceMode.InnerProperty), Browsable(false), TemplateContainer(typeof(TemplateControl))]
10         public ITemplate EmptyDataTemplate { get; set; }
11 
12         protected override void OnDataBinding(EventArgs e)
13         {
14             base.OnDataBinding(e);
15 
16             this.Controls.Clear();
17             this.ClearChildViewState();
18 
19             this.CreateControlHierarchy(true);
20             this.ChildControlsCreated = true;
21 
22             if (EmptyDataTemplate != null)
23             {
24                 if (this.Items.Count == 0)
25                 {
26                     EmptyDataTemplate.InstantiateIn(this);
27                 }
28             }
29         }
30 
31         protected override void RenderChildren(HtmlTextWriter output)
32         {
33             if (HasControls())
34             {
35                 for (int i = 0; i < Controls.Count; i++)
36                 {
37                     if (this.FooterTemplate != null && this.Items.Count == 0 && EmptyDataTemplate != null)
38                     {
39                         if (i == Controls.Count - 2)
40                         {
41                             Controls[i + 1].RenderControl(output);
42                             continue;
43                         }
44                         if (i == Controls.Count - 1)
45                         {
46                             Controls[i - 1].RenderControl(output);
47                             continue;
48                         }
49                     }
50                     Controls[i].RenderControl(output);
51                 }
52             }
53         }
54 
55         protected override void Render(HtmlTextWriter output)
56         {
57             RenderChildren(output);
58         }
59     }
60 }
View Code

 

暫時能想到的只有這么多,都是自己的經驗之談,當然也屬於一家之言,如果有什么錯誤或不合理的,可以在下邊留言給我或狠狠的踩上幾腳。同時也希望大家能把自己的一些開發經驗或技巧分享出來,供大家學習學習。

 

2014-01-04補充

錯誤修正

第二段、第七個、為Repeater控件添加EmptyDataTemplate模板會導致Repeater的ItemDataBound事件處理執行兩次。

請注釋掉下面這幾行代碼

            //this.Controls.Clear();
            //this.ClearChildViewState();

            //this.CreateControlHierarchy(true);
            //this.ChildControlsCreated = true;

 本來我的代碼就參考了kdalan的這篇文章,他的文章中本來沒有這幾句的,但在看見msdn中的示例有這幾句,當時也沒多想就加上了,也不一在用,沒出什么毛病。但前兩天在調試的時候發現ItemDataBound事件會執行兩次。為了查找為什么ItemDataBound事件為什么會多次執行,費了好大一番功夫,最后才排確定是this.CreateControlHierarchy(true)這句導致的。因為這幾句是在MSDN上的示例中看到的,所有一直沒懷疑它的正確性,在排錯的過程中放到最后,耽誤不少時間。其實最后想想,也大概知道了其中的原因,.net中的事件可以多次訂,基類和子類都調用this.CreateControlHierarchy(true);會導致多次訂閱ItemDataBound事件。為了證實我的猜想,專門建立了項目,對自定義的Repeater再次繼承重寫,在調用時ItemDataBound事件會被執行三次。測試項目我放到google code上,有興趣的朋友可以下載看看,http://my-repeater.googlecode.com/svn

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM