本篇整理了一些我以前寫ASP.NET遇到的問題
希望能幫助從Java或其他語言跳槽到ASP.NET的人
有新的內容應該會在此篇繼續更新吧

 

===========[ 快速開發 ]===========

※善用using
using完的物件會自動Close與Dispose,所以建議需要Dispose的物件都可以使用using
像是SqlConnection, SqlCommand, SqlDataReader...

using (SqlConnection conn = new SqlConnection("xxx"))
{
    conn.Open();
}   // 結束時不需要Close跟Dispose

 

※資料庫交易(Transaction)不用那麼麻煩
.NET 2.0後新增了TransactionScope物件

using (TransactionScope scope = new TransactionScope())
{
    // TODO: SQL連線
    scope.Complete();   // 交易完成
}

如果中途因意外而跳出,無須Rollback
只要TransactionScope沒有執行Complete(),結束後就會自動Rollback
使用前必須要加入參考 System.Transactions 才能使用

 

※使用string.Join 省去串接功夫
假設有個陣列,你想把陣列裡的資料串成一行且用自訂符號隔開,你會怎麼做呢?

// 一般寫法
string[] values = { "1", "2", "3" };
StringBuilder sb = new StringBuilder();
foreach (string s in values)
{
    if (sb.length() > 0)
    {
        sb.append(',');
    }
    sb.append(s);
}
sb.ToString();   // 1,2,3
// string.Join
string.Join(",", values);   // 1,2,3
// Linq寫法
values.Aggregate((i, j) => i + "," + j);   // 1,2,3

 

※用out作為變數回傳值
宣告out的變數,在方法結束前一定會賦值,所以可以做為回傳值使用

private void Test()
{
    string a, b;   // 宣告時無須給初始值
    GetValue(out a, out b);   // 結果a="a", b="b"
}

private void GetValue(out string a, out string b)
{
    a = "a";
    b = "b";
}

 

※用@來設定多行的SQL語法
雖然我個人比較推薦把SQL語法都寫在查詢產生器中(減少程式碼複雜度,還能驗證)
不過某些時候還是必須要把SQL寫在code中
從Java轉過來的人可能會這樣寫

string sql = "SELECT field1, field2" +
             "FROM table1" +
             "WHERE field3 = @Param1";

其實在ASP.NET不需要這麼麻煩每一行要串接,在字串前加上@就能做到跨行

string sql = @"SELECT field1, field2
               FROM table1
               WHERE field3 = @Param1";

 

※用TryParse轉換可能會出錯的字串
幾乎所有的基本型態都有TryParse方法
TryParse的特點是如果字串轉換失敗,不會拋出Exception
如果出錯後,變數會變成初始值0,如果希望同時給予不同值,可以這樣寫

int i;
if (!int.TryParse("", out i))
{
    i = 100;
}

 

※如何要求使用者要在TextBox輸入特定格式的資料才能送出?
可以使用Validator驗證,如果格式不符,就無法Postback
(除非設定控制項屬性CausesValidation = false,使其不檢查)
另外要Validator預設會佔住畫面,如果要讓其動態顯示
需設定Validator屬性Display = Dynamic

 

※要清掉控制項的內容得寫好長一串,有沒有簡單的清空方法呢?

// 一般寫法
this.TextBox1.Text = "";
this.TextBox2.Text = "";
this.Label1.Text = "";

// 個人寫法: 寫個方法去呼叫
private void ClearControls(params Control[] controls)
{
    foreach (Control control in controls)
    {
        if (control is TextBox)
        {
            (control as TextBox).Text = "";
        }
        else if (control is Label)
        {
            (control as Label).Text = "";
        }
        else if (control is HiddenField)
        {
            (control as HiddenField).Value = "";
        }
     }
}
// 使用方法
ClearControls(TextBox1, TextBox2, Label1);

 

※用??代替麻煩的條件判斷

string str;
if (WebConfigurationManager.AppSettings["xx"] != null)
{
    str = WebConfigurationManager.AppSettings["xx"];
}
else
{
    str = "defaultValue";
}

string str = WebConfigurationManager.AppSettings["xx"] ?? "defaultValue";   // 與上面同義

string str = val1 ?? val2 ?? val3 ?? val4;   // 依序判斷val1, val2, val3, val4是否有值,有值即設定值

 

※有沒有辦法縮減SqlCommand.Parameters.AddWithValue的程式碼?

string str = null;
SqlCommand cmd = new SqlCommand(sql){
    if (str == null)
    {
        cmd.Parameters.AddWithValue("@Param1", DBNull.Value);
    }
    else
    {
        cmd.Parameters.AddWithValue("@Param1", str);
    }
}
// 如果你寫這樣,會跳出錯誤訊息
cmd.Parameters.AddWithValue("@Param1", str ?? DBNull.Value);
// 因為string跟object不是同一個型別
// 正確方法是
cmd.Parameters.AddWithValue("@Param1", (object)str ?? DBNull.Value);

 

※不會用到的控制項不要命名
有些人會很熱情的把一些顯示上的Label都取個新的ID
但這些Label其實並不會在程式上被指定到
硬要取名不但會增加選取程式碼自動完成的困難度,還可能會撞名
建議純顯示的Label就直接用預設值

 

※使用Google Loader載入jQuery跟jQuery UI
Google Loader是Google提供的一組API,可以讓你動態載入指定版本的jQuery JS
可以指定特定版本,也能指定最新版本。由於檔案不是放在主機上,更能減少頻寬(參考
使用方法如下(寫在aspx中)

<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
    google.load("jquery", "1.7");
    google.load("jqueryui", "1.8");
</script>

 

===========[ 功能教學 ]===========

※GridView在沒有資料的時候會變成空白,連標題列(Header)都不會顯示,要如何讓它顯示?
1. .NET 4.0新增了ShowHeaderWhenEmpty屬性
2. 自己寫物件,繼承GridView(參考
3. 在無資料時自己丟個空資料給GridView,但是不建議,因為這樣就無法用GridView1.Rows.Count判斷是否為空(參考
4. 在EmptyDataTemplate自行畫上Header,但更不建議,因為沒法動態修改(參考

 

※GridView要如何做到按下一行自動選取該行(select row)
內建竟然沒這功能,有點誇張
在GridView的RowDataBound輸入以下code

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        e.Row.Attributes["onclick"] = ClientScript.GetPostBackClientHyperlink(this.GridView1, "Select$" + e.Row.RowIndex);
        e.Row.Style.Add(HtmlTextWriterStyle.Cursor, "pointer");   // 滑鼠移上去時,變成點選樣式
    }
}

但輸入完後會出現以下訊息

gridview_row

因為這個方法的PostBack資料不是原始的控制項發出來的,會有安全性上的問題
解決法
1. 照著說明,設定EnableEventValidation = false
不過這樣會有被跨站攻擊的風險,不建議(參考
2. 覆寫該頁Render,註冊其事件驗證,推薦

protected override void Render(HtmlTextWriter writer)
{
    for (int i = 0; i < this.GridView1.Rows.Count; i++)
    {
        Page.ClientScript.RegisterForEventValidation(this.GridView1.UniqueID, "Select$" + i.ToString());
    }
    base.Render(writer);
}

 

※在刪除按鈕點下去前先跳出一個確認視窗,選擇確定才PostBack
Button的OnClientClick輸入
return confirm("Are you sure?");

 

※使用ASP.NET的按鈕控制項,使其按下去能呼叫JavaScript,但是不要PostBack
Button的OnClientClick是可以輸入多句JS語法的
只要最後加上return false;就能避免PostBack
javascript:xxx(); return false;



※如何顯示MessageBox
ASP.NET沒這東西,你只能動態註冊JS呼叫alert

ScriptManager.RegisterStartupScript(this, this.GetType(), "alert", "alert('" + Message + "');", true);

 

※如何叫出ConfirmBox,按鈕是寫Yes/No/Cancel而不是一般的OK/Cancel?
不但ASP.NET沒這東西,連JS都沒有
你可以用jQuery+jQuery UI的Modal confirmation,自己創立一個Dialog

 

※JS的Confirm視窗可不可以做到按下是或否跳到不同PostBack的效果
可以,但要用JS去呼叫PostBack方法

function check()
{
    var ret = confirm('Are you sure?');
    if (ret) {
        __doPostBack('question', 'true');
    } else {
        __doPostBack('question', 'false');
    }
}
protected void Page_Load(object sender, EventArgs e)
{
    string event_target = Request.Form.Get("__EVENTTARGET");
    string event_argu = Request.Form.Get("__EVENTARGUMENT");
    if (event_target == "question")
    {
        if (event_argu == "true")
        {
            // TODO: true
        }
        else if (event_argu == "false")
        {
            // TODO: false
        }
    }
}

 

※要怎麼做到按下按鈕後跳出新視窗(open new window),在新視窗輸入內容(Ex.帳號密碼)後回傳(return value)的效果
純ASP.NET無法做到,只能使用JS,請搜尋關鍵字showModalDialog
以下方法不是最好的方法...如果有更好的作法請跟我說

假設要從A頁面呼叫B頁面,輸入完資料再回傳A頁面
在A.aspx寫入此JS (要直接寫或用動態註冊皆可)

var returnValue = window.showModalDialog(url, "", "dialogWidth:300px;dialogHeight:300px;");   // 還有很多參數可用
if (returnValue != undefined) {
    __doPostBack(target, returnValue);   // target自訂
}

在B.aspx寫入此JS

function selectBack(valueString) {
    window.returnValue = valueString;
    window.close();
}

 
在B.aspx的控制項上呼叫此方法
javascript:selectBack('" + encodeKeyString + "')
最後在A.aspx.cs接收此PostBack參數

 

※網頁原始檔部份有內建的排版(format)工具嗎?
網頁全選後按右鍵,選擇"格式化選取範圍"

 

※要怎麼建立全域變數?
不可以用以下寫法
private static int i;
因為其他人打開網頁時所有人看到的變數是相同的
比方說有個網頁有個按鈕,點一下會執行i++,畫面上會顯示i的內容
第一個人進來時看到i=0,點了五次,i=5
第二個人進來時會看到i=5而非i=0
比較好的方法是使用HiddenField(但只能儲存string)
或用ViewState["key"]的方式去儲存/取得內容(可以儲存所有格式)
如果嫌太麻煩可以這樣使用

private int value_name
{
    get { return (int)ViewState["key"]; }
    set { ViewState.Add("key", value); }
}

但是在Page_Load的時候就必須給他初始值,不然在沒有初始值的時候取值會出錯

 

※如何避免SQL衝突 (兩人修改同一筆資料)
假設有個資料表是這樣(id是Primary Key)
id name
1  小明
2  小華
如果資料庫要修改欄位內容,一般SQL會寫
Update name = @Name Where id = @id
但如果同時有兩個人要修改小明的name,就會變成"後進先寫入"
也就是後面寫入的資料會蓋過前面寫入的資料
解決法是把SQL改寫成這樣
Update name = @Name Where id = @id and name = @origin_name
這樣當第一個人把"小明"修改成"小明1"之後
第二個人就無法找到name=小明的資料,所以不會覆蓋到第一個人的修改結果

 

※DropDownList要怎麼根據Text或Value改變他的選取值

DropDownList1.SelectedIndex = DropDownList1.Items.IndexOf(DropDownList1.Items.FindByText("text"));
DropDownList1.SelectedValue = "XXX";   // 如果只要根據Value選擇,直接這樣寫就好

 

要怎麼使用Ajax技術?
1. 使用Ajax Control Toolkit,這是一個用純JavaScript寫成的Ajax技術,都是控制項形式
2. 使用jQuery / jQuery UI
3. 使用Juice UI,這是一個用jQuery寫成的Ajax技術,也是控制項形式,但功能上比jQuery UI少
參考

 

※如何把控制項自動產生的編號歸零?
每拉一個控制項,會在後面自動增加號碼,例如Label1, Label2
其實那個號碼是去找最大控制項的號碼+1
比方說目前網頁有控制項Label1, Label99
下一次拉出的控制項ID就會自動命名為Label100
要怎麼讓他歸零?只要把前面號碼的都刪掉就好了
上面例子中,只要把Label99刪掉,下次自動命名就是Label2

 

※產生本機資源後後悔了,要怎麼移除掉該頁控制項的meta:resourcekey=xxx
尋找目標 meta\:resourcekey=[^:b>]*"
取代成 (不輸入)
並使用規則運算式即可

replace_meta_resource

 

※DateTime要怎麼重新設定時間,且不使用new
沒有辦法,請直接用new給予新值

DateTime dt = new DateTime(2012, 12, 12);
dt = new DateTime(2011, 11, 11);

DateTime是struct,所以直接取代掉就好了
struct是value type,所以並不會浪費記憶體

 

※如何讓DateTime指定為該月最後一天?
1. DateTime.DaysInMonth(year, month);
2. new DateTime(year, month, 1).AddMonths(1).AddDays(-1);

 

※如何跳出一個月曆讓使用者選取日期?
1. 用Ajax Control Toolkit的CalendarExtender
2. 用jQuery UI的Datepicker
3. 用專門顯示月曆的套件,如JSCal2(還能選時間)

 

※要怎麼用SqlDataReader讀取SqlDataSource的Select?

// 將SqlDataSource的Select轉成SqlDataReader物件的方法
// 特別注意: 必須先將SqlDataSource的DataSourceMode屬性設定為"DataReader",否則轉型會失敗
using (SqlDataReader reader = SqlDataSource1.Select(DataSourceSelectArguments.Empty) as SqlDataReader)
{
    while (reader != null && reader.Read())
    {
        reader["xxx"].ToString();
    }
}

// 將SqlDataSource的Select轉成DataTable物件的方法
// 特別注意: SqlDataSource的DataSourceMode屬性必須為"DataSet" (這是預設值)
DataTable dt = (SqlDataSource1.Select(DataSourceSelectArguments.Empty) as DataView).ToTable();

// 順便一提,SqlDataReader所占用的資源比起DataTable小很多
// 若沒有要對資料表作處理,可盡量使用SqlDataReader

 

※SQL要怎麼把很多筆資料(multi row)變成單行字串(single string)?
比方說今天有資料如下
ID Name
1 Name1
1 Name2
2 Name1
2 Name3
2 Name5
希望轉成以下的欄位
ID Name
1 Name1,Name2
2 Name1,Name3,Name5

如果是MSSQL有個偷吃步的方法,就是使用FOR XML PATH('')語法
將取得的欄位轉變成一個用空白tag包覆的XML內容

SELECT A.ID,
       (
          SELECT B.Name + ','
          FROM [TableB] AS B
          WHERE B.ID = A.ID
          FOR XML PATH('')
       ) AS Name
FROM [TableA] As A

 

===========[ 錯誤修正 ]===========

※DropDownList當項目有相同的Value的時候,點選的結果不正確
這個問題是PostBack的原罪,相同Value時回傳給Server的值是相同的(參考
1. 自己寫DropDownList控制項解決
2. 在綁定後自己修改item value,使其不重複

 

※使用自訂元件時出現「無法在屬性xxx 設定xxx」
這是Visual Studio 2008的固有Bug,解決法:請下載更新檔
更新檔)(參考

 

※使用動態註冊JS之後,某些TextBox輸入的文字會消失
不要用RegisterClientScriptBlock跟RegisterClientScriptInclude
使用RegisterStartupScript,他會把JS放到網頁最下面,確保前面的JS會被執行到(參考

 

※GridView某欄位在設定Visible=false後,PostBack之後無法取得該欄位的值
因為這個欄位在一開始產生網頁的時候就不會寫進去,所以自然無法取得不存在的資料
解決法
1. HeaderStyle跟ItemStyle設定CSS,CSS內容為display:none;
這樣就會Bind資料,且在畫面上看不到該欄位
2. 在RowDataBound的時候指定該欄位Visible = false;
但有壞處就是如果你的GridView沒有資料且沒資料時可以顯示標題欄
因為沒資料時不會呼叫RowDataBound,所以該欄標題會顯示
因此建議還是使用第一種方法

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.Header || e.Row.RowType == DataControlRowType.DataRow)
    {
        e.Row.Cells[0].Visible = false;
    }
}

 

※設定GridView的SelectIndex後,不會觸發SelectedIndexChanged事件?
是的,每次寫了更換SelectIndex後你要自行去呼叫SelectedIndexChanged
.NET 4.0很好心的加了SelectRow方法,可以設定Index後自動呼叫SelectedIndexChanged事件

 

※IE 9的CSS Selector跟其他瀏覽器不同,導致圓角矩形顯示上有問題
這情況我建議強迫IE 9使用IE 8的網頁排版方式,這樣圓角矩形的問題就不用麻煩了
方法有兩種,一種是直接在aspx中<title>的下方加上
<meta http-equiv="X-UA-Compatible" content="IE=8" />
或是在aspx.cs動態加上這段

protected void Page_Init(object sender, EventArgs e)
{
    Context.Response.AddHeader("X-UA-Compatible", "IE=8");
}

 

===========[ 觀念解析 ]===========

※建議用WebConfigurationManager取代ConfigurationManager
參考
一般書上都用ConfigurationManager取得Web.config的設定值
其實WebConfigurationManager才是正統的作法
雖然兩者目前是通用的,不過用正統方法還是比較保險一點

WebConfigurationManager.ConnectionStrings["xx"].ConnectionString;   // 標準寫法
WebConfigurationManager.AppSettings["xx"];   // 標準寫法

 

※字串直接串接比較好還是用string.Format串接比較好?
用string.Format串接比較快,因為其內部是使用StringBuilder實作

 

※用string.Empty好還是""好?
.NET 2.0之前,""會產生一個新物件,.NET 2.0後寫哪個都一樣
個人建議使用"",因為程式碼越短越好
參考1)(參考2

 

※string跟String有什麼不同?
完全一樣,string是String的別名
但如果是要宣告變數,個人建議用string比較統一(參考

 

※decimal跟double的差別?
Decimal 不是浮點數資料型別。Decimal 結構會保存二進位整數值,加上正負號位元和整數縮放比例,指定值的哪一部分是小數部分。
簡單的說,double因為是用byte去儲存小數,所以每次都會有誤差,而decimal是用十進位去存小數,所以不會有誤差
如果要儲存金額等這種不允許誤差的數值,就要用decimal(參考

 

※int, double是class嗎?
不是,其實是struct,可能是因為處理速度上比較快的關係

 

※所以string也是struct嗎?
不是,string是class,可以當object來用,不像struct傳遞一次就要copy一次
另外就是string pool的問題,用struct應該會有困難

 

※那為什麼List可以直接放基本型別?

List<int> list = new List<int>();   // 像這樣

因為C#會自動幫你做轉換(參考

 

※as跟強制轉型有什麼差別?
如果不能轉型的話,as會回傳null,強制轉型會丟出Exception

 

※Label跟Literal有什麼差別?
Label在編譯成網頁時會用span包起來,Literal則會直接轉成網頁文字
另外Label可以設定CSS style,Literal則否
如果在純顯示文字的情況,建議直接用Literal,這樣能用CSS直接調整整個頁面的字型

 

※int.Parse跟Convert.ToInt32有什麼差別?
1. int.Parse只能傳入string,Convert.ToInt32可以讀取各種型態
2. 當傳入參數為null時,int.Parse會拋出exception,Convert.ToInt32會回傳0
參考

 

※if跟switch哪個效率較好?
判斷int的情況,switch完勝,就算在第一個條件就符合的情況下,還是switch較快
這是因為switch會建立binary decision tree,所有的case都耗時O(1)
所以適合用switch的情況就盡量不要用if
當然if能用複合條件判斷,在使用上比較靈活
以下為測試結果
if(1) = 00:00:00.0320018
if(2) = 00:00:00.0430024
if(3) = 00:00:00.0470027
if(4) = 00:00:00.0600034
if(5) = 00:00:00.0630036
if(6) = 00:00:00.0730041
if(7) = 00:00:00.0820047
if(8) = 00:00:00.0880050
switch(1) = 00:00:00.0230013
switch(2) = 00:00:00.0230013
switch(3) = 00:00:00.0230013
switch(4) = 00:00:00.0250014
switch(5) = 00:00:00.0240014
switch(6) = 00:00:00.0250014
switch(7) = 00:00:00.0240014
switch(8) = 00:00:00.0240014

不過判斷string的情況,若前面條件就符合的話,if會比較快
以下為測試結果
if(1) = 00:00:00.1010058
if(2) = 00:00:00.1510086
if(3) = 00:00:00.2160124
if(4) = 00:00:00.2850163
if(5) = 00:00:00.3520201
if(6) = 00:00:00.4160238
if(7) = 00:00:00.4860278
if(8) = 00:00:00.5600321
switch(1) = 00:00:00.3790217
switch(2) = 00:00:00.3810218
switch(3) = 00:00:00.4210241
switch(4) = 00:00:00.3800217
switch(5) = 00:00:00.3820219
switch(6) = 00:00:00.3900223
switch(7) = 00:00:00.3850220
switch(8) = 00:00:00.3800217

 

===========[ 工具連結 ]===========

※修正舊版IE不能顯示Png透明度的問題
IEPngFix

 

※JS線上排版工具
Online JavaScript beautifier

 

※CSS漸層產生器
Ultimate CSS Gradient Generator
跟一般產生器不同的是,他的語法能支援到IE 6~9
會一併產生filter: progid:DXImageTransform.Microsoft.gradient

 

※CSS3按鈕產生器
CSS3 Button Generator
除了基本產生按鈕的功能外,還能使用別人分享調整好的按鈕樣式

 

※加強Visual Studio的外掛程式
Visual Assist X
能讓Visual Studio多了很多實用的功能,像是程式碼更多顏色
程式碼自動完成列出的內容更加切題
選擇方法後會自動加上括弧()
多了一些快速鍵(如按下Shift+8可以把一段程式碼用/* */包起來)
重構也更加快速方便
而且功能都是可以自行選擇是否要啟用
支援版本很多,從早期的VS6到最新的VS2012都可以使用
可惜的是這是共享軟體,只有30天試用

arrow
arrow

    蕭雲 發表在 痞客邦 留言(11) 人氣()