2012年9月26日 星期三

Using jQuery.ajax in a "Hello World" way

前言:

通常在寫 ASP.NET Web Form 的時候我們很習慣會把一些 html tag 以 Server control 來使用。

但是這些控制項若放在表單中,每次的POST動作都會把這些控制項的內容和狀態一起送回伺服器端,若是有需要的話倒還好,偏偏很多控制項的狀態是根本不需要維護的。像是 Button 啦~ Literal 啦~這些東西其實大多數的時候是死都不會改變的,不過因為天生架構的關係所以每次 POST 回去都會要把這些東西一起送回 Server,實在有點麻煩。所以若是這個時候可以用jQuery來取代 form 傳回 Server 並處理的話可以減輕不少流量的負擔。



實作:

一開始我們先開啟一個空的 Web From 專案~


然後新增一個Index.aspx頁面並設定為起始頁面。

在body中新增下列標籤:

<input type="text" id="textbox1" /><input type="button" id="button1" value="jQuery way" />
    <form id="form1" runat="server">
    <div>
        <asp:TextBox ID="formTextbox1" runat="server"></asp:TextBox>
        <asp:Button ID="formbutton1" runat="server" Text="form way" OnClick="formbutton1_Click" />
    </div>
    </form>


並且在body中新增下列script:

<script type="text/javascript">
    function jqueryAjax() {
        $.ajax({
            url: 'ajaxhandler.ashx',
            type: 'POST',
            dataType: 'text',
            cache: 'false',
            success: function (result) {
                $('#textbox1').val(result);
            }
        })
    };
    $(function () {
        $('#button1').bind('click', jqueryAjax)
    });
</script>


這段script的作用就在於,把id為button1的按鈕的click事件與jqueryAjax函式進行綁定。而jqueryAjax函式的工作就只是呼叫名為ajaxhendler.ashx的泛型處理常式來回應client端的呼叫。

而這次我們在泛型處理常式就直接用最原始的樣貌(就是新增泛型處理常式後直接用),不做任何修改。像是底下這樣:

public void ProcessRequest(HttpContext context)
{
    context.Response.ContentType = "text/plain";
    context.Response.Write("Hello World");
}


是的這次就是個標準的 hello world 級別的程式,不過其實這邊是可以進行資料庫的存取動作的。藉由把資料庫的存取放到這邊來我們可以讓UI的部份動的更順利(ps.若我富堅的毛病沒犯的話,這會在下回分享 = =)

以上這些就算是做好了使用jQuery.ajax方法來與 Server溝通的最簡單方式。底下就是使用jQuery的ajax方法來與Server互動的截圖,附上傳輸量:



另外,我們也要比較一下若是用web form來進行server的溝通的話傳輸量又會是如何:


這邊我們可以發現到,web form就算是簡單的"Hello Word"文字的傳送也需要1.27kb的傳送量,反觀若是用jQuery.ajax來溝通的話只需要344byte的傳輸量,若是傳輸的頁面更複雜的話,兩者的差距會越來越誇張!

所以囉~若是要與Servre溝通的話會建議儘量用ajax來傳輸,減輕網路流量的負擔,但這個範例是用web form,難免會有要用到控制項或是UpdatePanel這種恐怖的東西,若是轉換到ASP.NET MVC架構的話就可以避免和這種怪物正面對決的機會。

不過...若是在做專案的話是要和web form戰鬥還是要跟MVC快樂的舞蹈應該就不是我們工程師能決定的事了....

(ps.以後有機會會分享web form中使用jQuery.ajax的心得...一樣,若我富堅病沒犯的話...)

2012年9月3日 星期一

Single Login in ASP.NET Web Form

前言: 會有這樣的題目主要是因為業主的要求(廢話)。明確的說是這樣的,同一時間同一帳號只能在一個地方登入,之後登入的可以把前面登入的剔除。這個機制其實之前我並沒有想到要怎麼解,或是說沒想到不用資料庫的方式要怎麼解...(超遜的啦!)。不過認真想過後才發現這個機制其實不會太難,或是說我的解法其實很簡單,而且應該也可以用在MVC架構裡。

研究方法:
1)Session。 ASP.NET提供Session讓我們可對工作狀態進行管理,可以讓我們跨越多個要求儲存與瀏覽器工作階段關連的訊息,Session提供Key-value pairs的方式來存取這些資料。雖然我們很常拿Session來儲存使用者的資訊,像是使用者的帳號或是一些其他資訊,不過Session是每個使用者各自有的,這裡面的資訊並不會共享,因此利用Session來達成需求顯然是不行。我們需要的應用系統的全域變數,或是資料結構來對整個系統的使用者進行管理。這樣的需求我想全域應用程式類別(預設檔名為Global.asax)就是我這邊提出的解答!

2)簡述關於Global.asax的運作。
當ASP.NET應用程式收到第一個要求的時候會先使用ApplicationManager建立應用程式定義域(應用程式定義域可以隔離應用程式間的全域變數,而且能夠允許每個應用程式個別進行卸載。)。在所有核心物件初始化後會藉由建立System.Web.HttpApplication類別的實體來執行應用程式。若應用程式中有Global.asax(繼承自HttpApplication)則會改用衍生自HttpApplication的Global.asax來建立執行應用程式的實體。而當處理要求的時候則會執行下述事件:(注意!這邊僅列達成需求出最主要的兩個)

///當要求ASP.NET應用程式中的第一個資源(如:網頁)時呼叫,
///這個方法在應用程式生命週期中只會被呼叫一次。
///我們讓全域變數在這邊進行宣告。
Application_Start(object sender, EventArgs e) { }

///這個方法在MSDN裡面的解釋有點怪,不過在應用程式生命週期中該事件發生也是只有一次,
///就是當IIS重啟或是應用程式正常關閉時就會發起該事件。
///我們讓全域變數所用到的資源在這裡釋放。
Application_End(object sender, EventArgs e) { }
開始撰寫時我們要先準備一下這個管理使用者的容器類別:
/// 系統中記錄目前線上全部使用者的識別號的容器。
public sealed class SystemUserPool
{
    private static ConcurrentDictionary<string, string> _UserPool =
        new ConcurrentDictionary<string, string>();

    /// 記錄系統中新登入的使用者。
    /// userNumber = 登入者的帳號。          
    /// userIp = 登入者的 IP 位址。
    /// 使用者登入成功與否。
    public Boolean AddUser(String userNumber, String userIp)
    {
        return _UserPool.TryAdd(userNumber, userIp);
    }
 
    /// 剔除系統中指定的使用者。
    /// userNumber = 使用者的帳號。
    /// 剔除成功與否。
    public Boolean DeleteUser(String userNumber)
    {
        string str = String.Empty;
        return _UserPool.TryRemove(userNumber, out str);
    }
 
    /// 列出目前系統中在線上的使用者。
    /// 使用者列表。
    public List<string> ListAllUser()
    {
        return _UserPool.Keys.ToList<string>();
    }
 
    /// 判斷使用者是否仍在線上。
    /// userNumber = 欲查明的使用者。
    /// 若在線上則為 true,反之則為 false。
    public Boolean IsOnLine(String userNumber)
    {
        return _UserPool.Keys.Contains(userNumber);
    }
 
    /// 判斷使用者是否從同一個地方登入。
    /// userNumber = 使用者帳號。
    /// userIp = 使用者目前 IP 位址。
    /// 若 userIp 與系統中使用者的 IP 位址一致表示從同一個地方登入。
    public Boolean IsTheSameIP(String userNumber, String userIp)
    {
        String orignalIP = String.Empty;
        if (_UserPool.TryGetValue(userNumber, out orignalIP))
        {
            if (orignalIP == userIp)
                return true;
            return false;
        }
        return false;//若沒能取得,表示使用者已經離線。
    }
 
    /// 更新使用者的 IP 位址。
    /// userNumber = 使用者帳號。
    /// userIP = 使用者新的 IP 位址。
    public void UpdateUserIP(String userNumber, String userIP)
    {
        if (!IsOnLine(userNumber))
            return;
        _UserPool[userNumber] = userIP;
    }
 
    /// 把系統使用者儲蓄池清空。
    public void ClearUserPool()
    {
        _UserPool.Clear();
    }
}


在Application_Start中就這樣寫:
void Application_Start(object sender, EventArgs e) 
{
    Application["UserPool"] = new SystemUserPool();
}
void Application_End(object sender, EventArgs e)
{
    var pool = Application["UserPool"] as SystemUserPool;
    if (pool != null)
        pool.ClearUserPool();
}

以上就是我這次所提出來的解決方案!不過其實最常加在這邊的應該是記錄系統狀態的Log啦~只是Log並不在這次範圍內,以後有機會再說囉~

參考文獻:

1) HttpSessionState

2) ASP.NET應用程式生命週期 IIS 5.0/IIS6.0 IIS7.0

3) ApplicationManager

4) HttpApplication

5) ASP.NET生命週期概觀 --> 強烈推薦寫 ASP.NET Web Form的人可以去看看「網頁生命週期的其他考量」裡面的事件圖