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 勝出的。


2010年7月8日 星期四

C# Extension Methods

Extension Methods 顧名思義就是擴展方法,據說這是在 C# 3.0 出現的東西,
在以前沒有這東西的時代裡,我們如果想要檢查一個陣列(array)是否有值,
或者是否為 null 的時候,可能是寫個 method 去檢查,或是直接來。類似這樣:
// method 檢查
bool IsNullOrEmpty(string[] instance)
{
    return (instance == null) || (instance.Length == 0);
}
// 直接來
string[] array;
bool result = (array == null) || (array.Length == 0);
換成 Extension Methods 該怎麼寫呢?就像下面這樣:
namespace Frank.Extensions
{
    public static class ArrayExt
    {
        public static bool IsNullOrEmpty<T>(this T[] instance)
        {
            return (instance == null) || (instance.Length == 0);
        }
    }
}
關鍵字是 "this" 意思有點像是把自己當參數傳進來的那種感覺,
上面同時也用了泛型,因此只要 using Frank.Extensions;

各型別的陣列都可以用 IsNullOrEmpty() 這方法囉!
這樣在使用上就變得更簡單了,同時也更容易閱讀了吧。
比較一下用 Extension Methods 和完全不用的樣子看看:
string[] array;
// 使用 Extension Methods
if (array.IsNullOrEmpty())
{
    // do some thing
}
// 直接來
if ((array == null) || (array.Length == 0))
{
    // do some thing
}
是不是更容易閱讀了呢?


2010年7月7日 星期三

SyntaxHighlighter - 讓你的網頁顯示漂亮的程式碼

SyntaxHighlighter 是用來讓在網頁上的程式碼一樣可以有像 IDE 的顯示效果!
目前最新版本為 3.0.83,
官方網頁為:http://alexgorbatchev.com/SyntaxHighlighter/
使用上面相當的簡單,
1. 在你要使用這個 Javascript 的網頁上引用2支 js:
主要核心:scripts/shCore.js
程式語言:scripts/shBrushCSharp.js
(這是我要顯示 C# 程式所必須引用的,如果要顯示其他語言請自行替換掉這個部分)

2. 其次是引用兩支 CSS:
styles/shCore.css
styles/shThemeDefault.css
3. 然後呼叫 SyntaxHighlighter.all(); 這個 function
以上步驟完成長這樣:

注意:script 的部份,可以跟隨文章沒有問題,不過 css 的部份要放在 <head></head> 區段裡面,

如果你是用像痞客邦這類不是自己架設的系統的話,可以利用 css import css 的方式達成,

編輯原始樣式的 css 在其中加入:

@import "styles/shCore.css";
@import "styles/shThemeDefault.css";
接著在你要處理的程式區段用<pre class="brush: csharp;"></pre>包起來
像下面這樣,就可以得到你要的結果。
<pre class="brush: csharp;">
int sum = 0;
for(int i = 1; i &lt;= 100; i++)
{
    sum += i;
}
</pre>
結果就會像下面這樣囉 ^_^
int sum = 0;
for(int i = 1; i <= 100; i++)
{
    sum += i;
}