2010年11月15日 星期一

2010年9月15日 星期三

[筆記]不用第三個變數,交換兩個數值的方法

今天翻到一段程式碼,想不起來這是在做些什麼?
後來終於找到這段程式是在做交換兩個數值,
而且,不宣告第三個變數!
其實好像也不是什麼大不了的東西,
不過還是筆記一下,人老了越來越會忘東忘西...

不宣告第三個變數暫存的方式
int a = 10;
int b = 5;

a ^= b;
b ^= a;
a ^= b;

結果就會從 a = 10, b = 5,
變成 a = 5, b = 10

宣告第三個變數暫存的方式
int a = 10;
int b = 5;
int temp;

temp = a;
a = b;
b = temp;

一樣的結果,不一樣的寫法,
簡單來說前者就是拿時間換空間,
後者就是拿空間換時間。


2010年8月2日 星期一

連結的伺服器(Linked Server)筆記

之前佈署測試環境,發生了一連串關於"連結的伺服器(Linked Server)"的問題,
第一個問題:
訊息 7399,層級 16,狀態 1,行 1
連結伺服器 "db_host" 的 OLE DB 提供者 "SQLNCLI10" 報告了錯誤。提供者並未給予任何關於錯誤的資訊。
訊息 7312,層級 16,狀態 1,行 1
不能使用連結伺服器 "db_host" 的 OLE DB 提供者 "SQLNCLI10" 的結構描述或目錄。提供了四部分的名稱,但提供者並未公開必要的介面,以使用目錄或結構描述。

這錯誤訊息的關鍵字不難看出是"提供者"。
點開"提供者"後,滑鼠右鍵點"SQLNCLI10"選屬性即可開啟"提供者選項"


因此,我們看了一下"提供者選項",發現裡面所有選項都被選取了...
這時候我們拜一下 Google 大神,找到取消"限層級零"與"不允許特定存取"可以解決我們的問題。


對於這些選項說明可以看一下:連結的伺服器屬性(提供者選項頁面)

感覺還會有問題...
果然,隔天又發現有 error,經過檢查發現,又是 Linked Server 的問題。
這次出現什麼碗糕哩!?
連結伺服器 "db_host" 的 OLE DB 提供者 "SQLNCLI10" 傳回訊息 "Multiple-step OLE DB operation generated errors. Check each OLE DB status value, if available. No work was done."。
訊息 7306,層級 16,狀態 2,行 1
無法從連結伺服器 "db_host" 的 OLE DB 提供者 "SQLNCLI10" 開啟資料表 ""db_name"."dbo"."table_name""。提供者不支援在這個資料來源上的索引掃描。

這訊息的關鍵字"提供者"、"索引"
看了就覺得這和上面的問題有點點關係,因為"索引為存取路徑"也是被勾選的嘛...
將它取消以後,系統便正常了...

目前尚有一個問題未解決,那就是預存程序更新 Linked Server 裡的 Database,
如果沒有加 Transaction,一切正常,加了以後就出現了:
連結伺服器 "db_host" 的 OLE DB 提供者 "SQLNCLI10" 傳回訊息 "異動已經以暗示或者明確的方式認可或中止了"。
訊息 7391,層級 16,狀態 2,行 5
無法執行作業,因為連結伺服器 "db_host" 的 OLE DB 提供者 "SQLNCLI10" 無法開始分散式交易。
這問題比較頭痛一點,因為照著 Google 找到的一些方法,仍然是沒能解決...
解決以後再來更新這篇文章吧...

最後一個問題終於解決,
基本上就是將兩台機器的 MSDTC 開啟,並且做一些設定,
首先進入"元件服務"


接著找到"我的電腦",然後右鍵點擊選取"內容"


選取 MSDTC 頁籤,並且點選"安全性設定"


將裡面的設定設定成下面畫面


在 Windows Server 2003 SP2 不需要特地去改機碼,
如果你是 SP1 的版本,或是用 Windows XP 測試的話,
請檢查下面這機碼是否存在,其值是否為1
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSDTC]
"TurnOffRpcSecurity"=dword:00000001

點此下載檔案

然後,最重要的一個步驟就是,將這兩台機器都重新開機

事實上,在 Microsoft 技術支援 裡面就有說明解法,
只是那時候心急,忽略了最後一個步驟:重新開機...
導致我的問題拖好久...

最後,如果只是加上 Transaction 的話,他應該還是會丟個錯誤出來 XD
連結伺服器 "db_host" 的 OLE DB 提供者 "SQLNCLI10" 傳回訊息 "異動中已經存在有徵募"。
訊息 7395,層級 16,狀態 2,行 2
無法為連結伺服器 "db_host" 的 OLE DB 提供者 "SQLNCLI10" 啟動巢狀交易。因為 XACT_ABORT 選項已設定為 OFF,所以必須要有巢狀交易。

所以我們必須要在 Transaction 之前,將 XACT_ABORT 設定為 ON,就像下面這樣
SET XACT_ABORT ON;
BEGIN DISTRIBUTED TRANSACTION
INSERT INTO [db_host].[db_name].[dbo].[table_name]
           ([id]
           ,[stringChar])
     VALUES
           (123
           ,'123456789abcdef')
COMMIT TRANSACTION

大功告成。



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;
}