2011年6月9日 星期四

C# 的隱含型別

在 C# 語法裡面寫

var v1=38.38 ;
var v2=74.74;
var v_sum=v1+v2;


這樣會不會很奇怪?自己剛遇到時,還一度以為是誰不小心把 javascript 放到 C# 裡了。我的老師常說:「行有不得,反求諸己」,的確,我有好一陣子沒看程式設計的書了。

在 C# 3.0 開始,引進了「隱含型別 (implicitly type )」,編譯器會依據指定給隱含型別變數的初始值自動決定變數的型別。換言之,如果不指定初始值,那隱含型別是否就無法決定正確的型別了?沒錯,使用隱含型別,一定要指定初始值。下面的寫法在編譯時,都會產生錯誤:

var v1;
var v2=null;

使用隱含型別,目前聽到的主要好處有三:


第一:可以少打幾個字。
System.Data.DataSet ds1 = new System.Data.DataSet();
var ds2 = new System.Data.DataSet();
看到了吧!這樣可以避免過度使用你的手指頭...,甚至連 namespace 都可以不用引用了。

第二:配合 LINQ 所產生的查詢結果。
// for linq test
string[] words = {"a111","a222","b111","b222","c111" };
var v_selected_words = 
    from w 
    in words 
    where w[0] == 'b' 
    select w;

foreach (string s in v_selected_words)
{
    Console.WriteLine(s);
}

Console.ReadKey();

如此一來,對習慣使用 LINQ 的人來說,的確可以讓程式整潔一致些。

第三:提供匿名型別(Anonymous Type)。
// for anonymous test
var v_anonymous= new { Name="paladin", Empno="3388"};
Console.WriteLine(v_anonymous.Name);  //show: paladin
Console.WriteLine(v_anonymous.Empno); //show: 3388

Console.ReadKey();

這裡省去了明確宣告類別或變數的動作,允許你直接使用。如果沒有匿名型別,原先的程式寫法,很可能就會如下所示,相較之下,的確省了不少程式。
namespace ConsolVar
{
    class Program
    {
        
        static void Main(string[] args)
        {

            MyClass oM = new MyClass();
            oM.Name = "paladin";
            oM.Empno = "3388";
            Console.WriteLine(oM.Name); //show: paladin
            Console.WriteLine(oM.Empno);//show: 3388           

            Console.ReadKey();
        }        
    }

    public class MyClass
    {
        private string _Name;
        private string _Empno;
        public string Name {
          get { return _Name; } 
          set { _Name = value; } }
        public string Empno { 
          get { return _Empno; } 
          set { _Empno = value; } }
    }
}

瞭解了隱含型別所帶來的好處,但還是要知道他的限制。目前的隱含型別,只能夠用在區域變數而已,而且也不能夠用在類別屬性的宣告裡。例如上面的 MyClass ,下面的寫法則是錯誤的:
public class MyClass
    {
        private string _Name;
        private string _Empno;
        public string Name {
          get { return _Name; } 
          set { _Name = value; } }
        public string Empno { 
          get { return _Empno; } 
          set { _Empno = value; } }
       
        var v = "123"; //錯誤用法
    }

參考:
01:var (C# Reference)
02:Implicitly Typed Local Variables (C# Programming Guide)
03:[C#] 使用 var 關鍵字宣告隱含型別
04:C# 3.0的var隱含型別宣告
05:Anonymous Types
06:C# 3.0 Tutorials: What is Anonymous type?
07:Beginning ASP.NET 3.5 in C# 2008 From Novice to Professional,Second Edition,Matthew MacDonald,Apress

2011年5月27日 星期五

Global.asax 的 Application 事件觸發順序


在 Global.asax 所定義的事件,依據 ASP.NET Application Events in Global.asax 分類,可以分為:每次頁面的請求都會觸發,與依據條件來觸發兩種。在上圖所表示的,則是每個被使用者要求的頁面都會觸發的事件順序。

舉例來說,如果在 Application_BeginRequest 事件寫一行 Response.Write("paladin"); 則這個網站的每個頁面開頭第一行都會秀出 paladin 這個字眼。

另外依據條件來觸發的事件,則是如下:
從 Visual Studio 2005 開始,預設已經在方案總管理看不到 Global.asax 這檔案了。但如果個人有這方面的需求,還是可以透過新增的方式把這檔案加入。當你打開這 Global.asax 檔案時,會發現他是屬於 Code Inline 的寫法,對於習慣 Code Behind 的人來說,會有一點點不同,詳細的比較可以參考保哥文章(關於 Code Behind 與 Code Inline 開發模式的使用時機與技巧)。假如你想在  Global.asax 撰寫 Application_BeginRequest 事件,則只要直接加入下面一段就可以:

void Application_BeginRequest(object sender, EventArgs e)
    {
        //撰寫自己的 BeginRequest 程式碼
        Response.Write("***************");
    }

Steven Cheng 曾在論壇中提過另外有關 Code Behind 的寫法。
1.在與 Global.asax 同一層的檔案結構,新增一個 globalcode.cs 檔案。並讓這個類別繼承 HttpApplication。

//globalcode.cs

public partial class globalcode : HttpApplication
{
  public globalcode()
  {

  }

  void Application_BeginRequest(object sender, EventArgs e)
  {

    HttpContext.Current.Response.Write("paladin is here..");
  }
}

2.將原先 Global.asax 裡的
<%@ Application Language="C#"  %>
修改成
<%@ Application Language="C#"  CodeFile="globalcode.cs" Inherits="globalcode" %>

如此,也可以讓 Global.asax 以 Code Behind 來撰寫了。

參考:
01:ASP.NET Application Events in Global.asax
02:如何:使用 Visual C# .NET 建立 ASP.NET HTTP 模組
03:Working with the ASP.NET Global.asax file
04:Application_BeginRequest does not seem to run
05:關於 Code Behind 與 Code Inline 開發模式的使用時機與技巧

縮小我的 javascript 大小

近日看好幾個 open source 的程式,他們不約而同的都習慣將 xxx_src.js 另外再寫一支 xxx.js 。理由何在,就是要縮小 javascript 的檔案大小,這樣一來,使用者在讀取網頁時,也會因為所需下載的 javascript 檔比較小,而加快網頁傳送速度。xxx.js 是經過壓縮後的檔案,對於 javascript 檔而言,所謂的「壓縮」,僅僅去除多餘的空白甚至是註解而已,用以減少檔案的大小。但是,經過壓縮過後的 javascript,相對也會造成「閱讀不易」的缺點,所以這幾個 open source 的作法,就是同時保留原始的程式 xxx_src.js 與 壓縮過的 xxx.js 。而程式實際上的運行,當然是使用 xxx.js 。只是日後有任何修改需求時,需要先去修改 xxx_src.js ,然後再將他進行壓縮後,把結果放到 xxx.js 。

在網路上,目前有看到兩個不錯的線上壓縮 javascript 的網站,經過測試後,效果都還不錯。分別是 javascriptcompressor 與 YUI Compressor。

javascriptcompressor:預設的處理方式,會將原始程式多餘的空白、註解都刪除。但如果你還想進一步縮小的話,他有一個 「Shrink variables」選項可以勾選,打勾後你會發現,他連程式裡面函數的參數名稱,都會用最簡單的方式來取代。譬如說,你有個函式: function iNeed(woman,lady,spicy_girl) ,壓縮後的結果,就會變成 function iNeed(a,b,c) ,由原本 37 個字元變成 21 個字元。


YUI Compressor:預設的處理方式,會將原始程式多餘的空白、註解都刪除,並將函數的參數名稱,用最簡單的方式來取代。但如果你不希望函數裡的變數被取代時,可以將 JavaScript Options 裡的 「Minify only, no symbol obfuscation.」這選項打勾,這樣就可以避免變數名稱被替換。

參考:
01:http://javascriptcompressor.com/
02:http://refresh-sf.com/yui/

2011年5月19日 星期四

加強 Rich Editor 的安全性

為了讓網站提供一個豐富的編輯介面,一般都會去選擇一個功能強大的 Rich Editor / Html Editor 來使用。在 ASP.Net 裡,為了能夠順利配合 Rich Editor 的使用,會將頁面的 ValidateRequest 屬性設成 false。

<%@ Page Language="C#" ValidateRequest="false" %>

當 ValidateRequest 屬性為 true 時,ASP.Net 會去偵測頁面上是否有所謂的惡意字元,如果有發現的話,就會丟出 HttpRequestValidationException。但當你有 Rich Editor 的需求時,就不得不關閉 ASP.Net 預設所提供的安全防護。


假如當你關掉 ValidateRequest 後,使用者在你的 Rich Editor 編輯器輸入以下內容:

<style>@import"javascript:location.href='http://yahoo.com.tw'";</style>

之後存到資料庫,日後只要使用者瀏覽這段內容時,他的網頁就會被導到 http://yahoo.com.tw,造成我們耳熟能詳的釣魚網站攻擊。當然,比較新的瀏覽器已經針對這些有問題的寫法進行防堵,Fire Fox、Chrome、IE8 目前並不會執行上面那段程式碼,但 IE6 卻會。也就是說,當使用者的瀏覽器是 IE6 時,只要他瀏覽到被注入惡意程式碼網站時,就會發生網頁轉址的現象。

為了避免網站被值入惡意程式,可以參考[參考01][參考02]的作法,先對使用者網頁所輸入的內容利用 HttpUtility.UrlEncode() 進行編碼,然後在針對你所允許的 HTML 與法進行開放的動作。尤其是[參考02],連參考資訊都整理的非常豐富,值得好好拜讀。



參考:
01:How To: Prevent Cross-Site Scripting in ASP.NET
02:警告:为了安全请不要随意将ASP.Net的validateRequest="false" 

2011年5月18日 星期三

<button>在 IE6 與 IE8 有不同的表現行為

在網頁上,要顯示一個按鈕,除了使用
<input type="button" value="btn" id="btn" name="btn">
之外,你還可以用
<button id="btn" name="btn"></button>
來達到目的。

但使用<button>這語法,最近卻讓我吃足了苦頭,發現在 IE6 與 IE8 竟然會有不同的執行結果。經過觀察後發現,在 IE6使用<button>,並不會觸發 PostBack,但在 IE8 卻會。

原來,<button> 其實有一個屬性:type,可以允許你挑選 submit、button、reset。選擇 submit 時,就會觸發 Asp.Net 的 PostBack,而 button 則是視為一般的按鈕事件,並不會觸發 PostBack。 當你不指定時,就會以預設行為模式來處理。而有趣的地方,就在於這預設行為模式怎麼判定。在 IE6,預設行為模式是 button,也就是不會觸發 PostBack,但是在 IE8,則改成 submit,變成會觸發 PostBack了,另外,非IE 的瀏覽器,則預設行為一律都是 submit。

以下是測試程式:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Button.aspx.cs" Inherits="Button" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>未命名頁面</title>
    <style>
    * { font-size:14px }
    p {width:400px}
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <button id="btn" name="btn" value="btnTest">Default</button><br />    
    <button id="btnSubmit" name="btnSubmit" value="btnSubmit" type="submit">Force Submit</button><br />    
    <button id="btnButton" name="btnButton" value="btnButton" type="button">Force Button</button><br />    
    </div>
 <script>
        document.write('<p><font color="blue">Browser Name:</font><br/>'+navigator.appName+navigator.appVersion+'</p>');
  document.write('<font color="blue">Default:</font> ' + document.getElementById('btn').type + '<br />');
  document.write('<font color="blue">Force Submit:</font> ' + document.getElementById('btnSubmit').type + '<br />');
  document.write('<font color="blue">Force Button:</font> ' + document.getElementById('btnButton').type + '<br />');
  

 </script>
    
    </form>
</body>
</html>


執行後於各瀏覽器的結果:
所以,為了能在不同瀏覽器上達到預期的效果,請別忘了在使用 <button> 時,定義好他的 type 屬性。

參考:
01:IE 8 Button Element Bug
02:HF9015: IE6 IE7 IE8(Q) 中 BUTTON 元素的 type 属性默认值不是 submit

第56號教室

在加州的貧民小學裡,有位擔任五年級的老師雷夫.艾奎斯( Rafe Esquith),靠著對於教育的熱誠,並沒有放棄貧瘠之地的學生,而這些學生當中,不乏來自黑道與毒品的家庭。在雷夫的想法裡,我的學生並非沒有能力,只是他們太缺乏自信,從住家的周遭環境一直到學校,這一切對他們都是不友善的。如果老師僅僅依據教科書照本宣科來教,最後再以考試來判定學生學習成果,對這些孩子來說,是一點幫助都沒有,反而會讓他們更加放棄自己。雷夫摒棄傳統制式地教學,把我們熟知的大富翁遊戲,活生生地搬到教室裡,讓學生可以體驗、並選擇所要擔任的角色。此外,也透過虛擬貨幣(教室幣)的流通,讓孩子從小可以從遊戲中培養正確的金錢觀念。從中穿插歷史、數學等原先會讓他們頭疼的議題,讓這些冰冷的知識融入生活中,似乎讓雷夫找到可以跟孩子溝通的方法了。

與其將雷夫的教學行為視為「職業」,倒不如說是「生活」,每年48週,每週6天,每天12小時,他幾乎快與學生生活在一起了。下課後,陪學生編排莎士比亞名劇、打球、玩音樂,假日還陪學生到各地博物館、音樂廳參訪,而這些絕大多數的費用,都是雷夫自己掏腰包的。也因此,在他的教室裡,彷彿為這些孩子打造了一個快樂天堂,他們稱之為「第56號教室」。

在雷夫多年的經營下,昔日他所指導的學生陸陸續續唸完知名大學、也開始在業界有著不錯的工作。這些學長、學姐,自然感念當年雷夫對他們的付出,於是在週末的雷夫教室裡,常常出現這些學業事業有成的學長姊回來幫忙,更有人為雷夫成立了基金會,開始接受外界捐款,使得雷夫的教育方式可以繼續延續下去。

如今,雷夫的知名度曾獲頒「美國最佳教師獎」、「大英帝國榮譽成員」、「善用生命獎」,甚至有許多學校將校長職位虛位以待,就等雷夫點頭入座。而雷夫的回答則是:「我一輩子只想當五年級的老師,如果我沒做到,我就是騙子」。

參考:第56號教室的奇蹟: 讓達賴喇嘛、美國總統、歐普拉都感動推薦的老師

2011年5月17日 星期二

讓 fMath 的設定檔改抓 web.config

舉例來說,數學符號產生工具 fMath,他將可以設定的參數都放在 configMathMLEditor.xml 裡,由上圖可知,fMath 在執行時,會去把 XML 檔案讀進來。這的確是個很方便的維護方法,可是在 Asp.Net 裡自己已經有一個 Web.Config 了,是否可以將 configMathMLEditor.xml  裡某個屬性「urlGenerateImage」改成去讀取 Web.Config 的值呢?

進一步去看 fMath 的使用方式如下:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=10,0,0,0" width="850" height="460" id="editML" name="editML" align="middle">
 <param name=wmode value="transparent">
 <param name="allowScriptAccess" value="sameDomain"/>
 <param name="allowFullScreen" value="true"/>
 <param name="loop" value="false"/>
 <param name="quality" value="high" />
 <param name="flashVars" value="configUrl=configMathMLEditor.xml"/>
 <param name="movie" value="mathml/MathMLEditor.swf?configUrl=configMathMLEditor.xml" />
 <embed src="mathml/MathMLEditor.swf?configUrl=configMathMLEditor.xml"
     wmode="transparent"
  flashVars="configUrl=configMathMLEditor.xml"
  loop="false"
  quality="high"
  width="850"
  height="460"
        id="editML"
  name="editML"
  align="middle"
        swliveconnect="true"
  allowScriptAccess="sameDomain"
  allowFullScreen="true"
  type="application/x-shockwave-flash"
  pluginspage="http://www.adobe.com/go/getflashplayer" />
</object>

裡面有提到使用 configMathMLEditor.xml 的地方一共有四個。而我的作法,則是另外寫一隻新的 config.ashx,讓它先去讀取 configMathMLEditor.xml 的原始資料,而這個 xml 裡,已經先將 urlGenerateImage 的值用 $Capture 來取代了。接著再使用取代變數的方式,將 $Capture 以 Web.Config 裡 appSettings 的值來替換。如此,上面提到有4個地方引用了 configMathMLEditor.xml ,就要改成 config.ashx 了,也就是如下:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=10,0,0,0" width="850" height="460" id="editML" name="editML" align="middle">
 <param name=wmode value="transparent">
 <param name="allowScriptAccess" value="sameDomain"/>
 <param name="allowFullScreen" value="true"/>
 <param name="loop" value="false"/>
 <param name="quality" value="high" />
 <param name="flashVars" value="configUrl=config.ashx"/>
 <param name="movie" value="mathml/MathMLEditor.swf?configUrl=config.ashx" />
 <embed src="mathml/MathMLEditor.swf?configUrl=config.ashx"
     wmode="transparent"
  flashVars="configUrl=config.ashx"
  loop="false"
  quality="high"
  width="850"
  height="460"
        id="editML"
  name="editML"
  align="middle"
        swliveconnect="true"
  allowScriptAccess="sameDomain"
  allowFullScreen="true"
  type="application/x-shockwave-flash"
  pluginspage="http://www.adobe.com/go/getflashplayer" />
</object>

最後的重點,就是要撰寫自己的 config.ashx ,簡單的測試程式如下:

<%@ WebHandler Language="C#" Class="config" %>

using System;
using System.Web;
using System.IO;

public class config : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) {
        
        context.Response.ContentType = "text/xml";
        
        Stream iStream = null;
        
        // Identify the file to download including its path.
        string filepath =Path.Combine(context.Server.MapPath("./"), "configMathMLEditor.xml");

        // Identify the file name.
        string filename = System.IO.Path.GetFileName(filepath);

     
        // Open the file.
        iStream = new System.IO.FileStream(filepath, System.IO.FileMode.Open,
                    System.IO.FileAccess.Read, System.IO.FileShare.Read);

        //Read Data from FileStream and save to string
        StreamReader sRead = new StreamReader(iStream);
        string strNewFile = sRead.ReadToEnd();
        sRead.Close();

        //將變數取代為 web.config  的值
        strNewFile = strNewFile.Replace("$Capture", System.Configuration.ConfigurationManager.AppSettings["CaptureUrl"]);

        context.Response.Write(strNewFile);
        context.Response.Flush();
        context.Response.End();
      
        
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }

}

經過一番打造之後,就可以讓 fMath 裡的設定值,改成去抓 web.Config 了。

參考:
01:response.WriteFile 無法下載大型檔案
02:http://www.fmath.info/
03:FileStream - editing an existing text file