顯示具有 ASP.NET MVC 標籤的文章。 顯示所有文章
顯示具有 ASP.NET MVC 標籤的文章。 顯示所有文章

2013年2月23日 星期六

SignalR 初體驗

最近有個需求,就是研究類似 facebook, plurk 等即時更新 client 畫面的技術,
技術細節可以 google "long-polling" 就會有需多相關文章,
也剛好,去年七月左右 Microsoft 推出了 SignalR ,當時只是覺得這功能超炫的,
但是沒有馬上動手試看看,直到最近開始找相關的文章,
覺得一定要動手嘗試看看,而且大大都把範例程式都給貼上來了,
所以馬上來測試。

不過立刻就碰到問題了,當時版本和現在版本差一半吧(0.5?和1.0)
連 namespace 都不一樣了,不過我想應該小小的修改一下就可以了吧。
原始程式範例請至黑暗執行緒大大的網站觀賞,以下僅列出需要修改的地方。

SignalRClient 專案的部分以 NuGet 安裝 Microsoft.AspNet.SignalR.Client
指令為 Install-Package Microsoft.AspNet.SignalR.Client
console 專案安裝 Microsoft.AspNet.SignalR 前後差異


需要 using 的  namespace
程式的部分
//須將
IHubProxy commHub = connection.CreateProxy("CommHub");
//改為
IHubProxy commHub = connection.CreateHubProxy("commHub");


接著是 web 專案,建立一個 asp.net mvc 4 的空白專案

安裝 Microsoft.AspNet.SignalR
指令為 Install-Package Microsoft.AspNet.SignalR
還要安裝 WebActivator
在 App_Start 加入 class RegisterHubs 內容如下(用來產生 signalr/hubs 的):

接著在 Controller 和 Model 中的 code 要修改的就是 namespace 和 Client 取得的方法,
// 原本的
context.Clients.ShowMessage(msg);
// 修改後的
context.Clients.All.ShowMessage(msg);
// 原本的
context.Clients[cid].ShowMessage(msg);
// 修改後的
context.Clients.Client(cid).ShowMessage(msg);

接著在 view 中需要修改的部分是 hub 呼叫 server/client 方法有更動
// 原本的
commHub.RefreshStats = function(dict) {
// 修改後的,呼叫 client 的 RefreshStats
commHub.client.RefreshStats = function (dict) {
// 原本的
commHub.register("Console");
// 修改後的,呼叫 server 的 register
commHub.server.register('Console');


到此修改完畢,可以好好地玩玩看 web 遠端遙控 console 的 client 了。
最後就可以得到,下面這樣的成果。



2010年7月27日 星期二

停止瀏覽器快取網頁 for ASP.NET MVC

瀏覽器為了加速網頁讀取,通常會採用快取,
所以使用者登出後,按下"回上一頁"幾乎都可以看到登出前瀏覽過的資料。
要避免使用者登出後使用"回上一頁"功能,最簡單的方式就是讓頁面不被快取,
在網路上找了一些方法,都是在<head></head>裡面加上:
//將網頁設為立即過期。
<meta http-equiv="Expires" content="0" />
//舊寫法,為了增加相容性。
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Pragma-directive" content="no-cache" />
<meta http-equiv="Cache-Directive" content="no-cache" />

不過這樣做,不知道為什麼在 IIS 6 的環境上,執行 ASP.NET MVC 的 Web Application 還是可以讓使用者按下"回上一頁"...
後來發現也可以直接加在 http response headers 上,
因此,改寫了一下專案裡面 base Controller 在 Initialize 時,設定 Response 的 cache 設定,設定如下:
protected override void Initialize(RequestContext requestContext)
{
    base.Initialize(requestContext);

    requestContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
    requestContext.HttpContext.Response.Cache.SetExpires(DateTime.MinValue);
    requestContext.HttpContext.Response.Cache.SetNoStore();
}
比較一下兩者狀況,
僅在頁面加上<meta...的方式:

圖上半部是程式部份,下半部是 Chrome 的開發人員工具檢視 http headers 的狀態。
瀏覽器一樣可以在登出後回上一頁。

直接在 http headers 告訴瀏覽器,不要快取:

登出後回上一頁,都會重新 request 頁面,
只要頁面需要檢查是否登入,基本上都回不去了。
問題解決...


2010年7月21日 星期三

ASP.NET MVC 之 ActionFilterAttribute

我們常常會在 Action 或是 Controller 上面看到黏了一些東西,像下面這樣:
[HttpPost]
public ActionResult Test(FormCollection testFormCollection)
{
    string Name = testFormCollection["Name"];
    string Title = testFormCollection["Title"];
    return View();
}
那個 [HttpPost] 就是我們說的 Attribute,
這可以做些什麼呢?
其實可以做的事情可多了,像是在 Action 執行前做些前處理的事情,
或是 Action 執行後做,而且很多 Action 都會用到的話,
那用 Attribute 來處理,是相當不錯的選擇。

實作上相當簡單,只要你寫個類別繼承 ActionFilterAttribute 這個抽象類別即可。

ActionFilterAttribute 這抽象類別主要有四個方法可以被覆寫:
1.OnActionExecuting
來自 IActionFilter 介面,於 Action Method 執行前,被呼叫。
2.OnActionExecuted
來自 IActionFilter 介面,於 Action Method 執行後,被呼叫。
3.OnResultExecuting
來自 IResultFilter 介面,於 Result Method 執行前,被呼叫。
4.OnResultExecuted
來自 IResultFilter 介面,於 Result Method 執行後,被呼叫。

這邊我們嘗試做一個 Form 驗證的 Attribute,
使含有不合法的字串的 Request,回應 302 Found。
using System.Collections.Specialized;
using System.Web.Mvc;

namespace MvcApplication1.Mvc.Attributes
{
    public class ValidateForm : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);
            NameValueCollection waitValidate = filterContext.HttpContext.Request.Form;
            for (int i = 0; i < waitValidate.Count; i++)
            {
                if (IsIllegalString(waitValidate[i].ToString()))
                {
                    filterContext.HttpContext.Response.Status = "302 Found";
                    break;
                }
            }
        }

        private bool IsIllegalString(string waitCheck)
        {
            string[] pattern = 
            { 
                "--", ";", "/*", "*/", "@@", "char", "nchar", "varchar", 
                "nvarchar", "alter", "begin", "cast", "create", "cursor", "declare",
                "delete", "drop", "end", "exec", "execute", "fetch", "insert", "kill",
                "open", "select", "sys", "sysobjects", "syscolumns", "table", "update"
            };
            foreach (string item in pattern)
            {
                if (waitCheck.ToLower().Contains(item))
                {
                    return true;
                }
            }
            return false;
        }
    }
}
然後我們在要處理的 Action 上,加上這個 Attribute,像下面這樣:
[HttpPost, ValidateForm]
public ActionResult About()
{
    return View();
}
接著找個 View 建立一個 Form 將資料 Post 給 About 這個 Action,像下面這樣:


執行畫面:


送出後,用 Firebug 查看,發現 Http Status 被改了!
如果輸入字串中並沒有任何 pattern 裡面的字串的話則會收到 200 OK


這樣就完成任務啦,Attribute 有很多應用面,比方說使用者的操作紀錄,
或是權限驗證與檢查等等,都是可以發揮的地方。
此範例可以將 Http Status 設為 302 Found 那邊換成你想要處理的邏輯。


2010年7月13日 星期二

ASP.NET MVC 之 Custom ModelBinder

在 ASP.NET MVC 要取得 Form 傳遞回來的資料有幾種方式,

一、直接取用 FormCollection ,例:

[HttpPost]
public ActionResult Test(FormCollection testFormCollection)
{
    string Name = testFormCollection["Name"];
    string Title = testFormCollection["Title"];
    return View();
}
輸入畫面:

結果:


二、直接在 Function 上設定接收參數,例:

[HttpPost]
public ActionResult Test(string Name, string Title)
{
    return View();
}
輸入畫面:

結果:


三、使用 ModelBinder ,例:

[HttpPost]
public ActionResult Test(User testModelBinder)
{
    return View();
}
ViewModel: User
namespace Captcha.Models
{
    public class User
    {
        public string Name { get; set; }
        public string Title { get; set; }
    }
}
輸入畫面:

結果:


個人偏愛第三種,不然也不會有今天這篇!
使用 ViewModel 的好處是強型別可以在編譯的時候發現錯誤,
直接用 Default ModelBinder 也是蠻 OK 的,只要型別正確,
name 屬性值有對應的欄位,八九不離十都可以正確的 bind,
但是有時候為了特殊需求:

比方資料庫某一欄位紀錄值是用逗號分隔紀錄另外一個 table 的 id,
頁面我們用 checkbox 來選取,這樣在 Default ModelBinder
會將同樣 name 的 checkbox bind 成一個陣列,
如此我們便要將陣列再兜成用逗號分隔的字串,才能放進資料庫。

因此有了客製化 ModelBinder 的需求,
直覺的方式就是將 FormCollection 的內容,
依照 Model 各屬性的型別,嘗試轉型,並且將值寫入,
FormCollection 中 checkbox 就是被存成已逗號分隔的字串,
所以就直接存入。
大致上的概念就是這樣。

那首先 Custom ModelBinder 必須實做 IModelBinder
範例如下:
public class UserModelBinder : IModelBinder
{
    public object BindModel(
        ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        User result = new User();
        var tempForm = controllerContext.HttpContext.Request.Form;
        result.Name = tempForm["Name"];
        result.Title = tempForm["Title"];
        result.Checkbox = tempForm["testCheckbox"];

        return result;
    }
}
修改一下剛剛的 ViewModel: User
namespace Captcha.Models
{
    public class User
    {
        public string Name { get; set; }
        public string Title { get; set; }
        public string Checkbox { get; set; }
    }
}
輸入畫面:

結果:


結果是不是符合我們的期待呢!

P.S: 強調一下,這範例僅僅是範例,完全沒有做例外處理,請多加注意。



2010年7月9日 星期五

ASP.NET MVC 之 TagBuilder

使用 ASP.NET MVC 開發專案,沒有以往常使用的控制項,
不過卻也讓頁面的設計更加彈性,但是如果每個標籤都要自己敲,
那其實還挺累人的。

所以我們可以利用上一篇提到的 Extension Methods
把我們自己客製化的 Tag 加到 Html Helper

那建立一個 Tag 只能用兜字串的方式嗎?
以前不知道 TagBuilder 這好東西的時候,還真常這麼做呢!
使用 TagBuilder 建立各種 Tag 至少要知道它可以做些什麼!
最基本,最常用的,就下面兩個方法,和一個屬性,
其他詳情請參考 MSDN/TagBuilder Members

1. 方法
AddCssClass(string value)
在 Tag 加入一個 Class 屬性,其值為 value。
MergeAttribute(string key, string value[, bool replaceExisting])
在 Tag 加入一個屬性,屬性名稱為 key,值為 value,
多載加入 replaceExisting 表示是否取代已經存在的屬性。

2. 屬性
InnerHTML
標籤的內容

看到這,不如就來實際試試看囉。程式碼如下:
using System.Web.Mvc;

namespace Frank.Extensions
{
    public static class Ext
    {
        public static string CLink(
            this HtmlHelper helper, string css, 
            string href, string title, string inner)
        {
            // Create tag builder "a"
            TagBuilder link = new TagBuilder("a");
            // Add class
            link.AddCssClass(css);
            // Add attributes
            link.MergeAttribute("href", href);
            link.MergeAttribute("title", title);
            // Add inner html
            link.InnerHtml = inner;
            // Output string
            return link.ToString();
        }
    }
}
在 View 的部份呢,只需要把 namespace import 進來,就可以使用了。
<%@ Import Namespace="Frank.Extensions" %>
<%= Html.CLink("css_class", "#", "test tag builder", "測試 TagBuilder")%>
這樣我們可以得到,下面的結果:
<a class="css_class" href="#" title="test tag builder">測試 TagBuilder</a>
雖然說,簡單的 Tag 似乎用兜字串的方式比較快,不過想想日後重複利用的可能性,
以及正確性來說還是 TagBuilder 勝出的。