2014年4月29日 星期二

ASP.NET Web Form 對應優先設計 (Mapping-First Design) 第一彈 - 提線木偶

如果你沒聽過什麼 對應優先設計 (Mapping-First Design) 的話

先別急著 估狗 偽基 因為...

這名詞 是我掰的 :P





It was long long ago...

那個時代 還沒有 ASP.NET MVC (或者有只是無人問津)

拉控項 Click 事件 寫 CODE 天經地義

一個 Click 事件 幾千行 CODE 家常便飯





過了一陣子 開始流行 3-tier n-tier

主要把 程式分成 表現層 邏輯層 資料存取層 封裝起來 降低耦合程度 增加可維護性

但是老實說 控制項 跟這些架構什麼的 相性 很差

很多時候 要額外寫一堆轉換的代碼

像是...

參數接控制項屬性

string name = TextBox1.Text;

控制項屬性接參數

TextBox1.Text = name;

當然 "絕不會" 只有一點點參數 跟 固定種類的控制項

當 TextBox CheckBox RadioButton DropDownList 參在一起做成撒尿牛丸

資料庫欄位 跟你玩 85度C ( c = column )

這時候...

真嗣表示:這時候,只要笑就可以了





但是這煩惱其實沒有困擾多久 因為後來 ASP.NET MVC 出現了

Model-First~

Model Binding~

ORM~

各種風騷技術

一開始自然覺得很硬 各種約束 奔放不起來

但是玩久了之後 發現就那麼回事而已

招式套路 基本會用上的就那幾招

控制項 神馬的 不用鳥他 潮爽der~

View 自由發揮就好了





因緣際會之下 又回來玩了一下 Web Form

之前的問題又回來困擾著我

但是意外發現 .NET 4.5 Web Form 竟然也吃 Model Binding 跟 Routing

傳說中的 瘟疫同化???





但是 4.0 或 之前的 專案 仍然無解 (誰也不敢貿然升等)

於是就想 那就自幹吧 寫個小元件來搞定他

試過很多奇奇怪怪的方式 最後都覺得不好

感覺上就是 寫了一些 偽 Model Binding 的類別

要搞 Model 何不直接用 MVC?

要搞 Model Binding 何不直接用 .NET 寫好的?

搞這些花招 又不能直接輸出到頁面上 就算真的可以直接輸出到頁面上的話 控制項又要幹嘛?

取值 轉物件 運算 塞值回去 繞一圈 這樣真的比較有意義?

想了許久...還是沒有滿意的做法





直到某天我看到小溪裡的小魚逆流而上

我頓時領悟到 根本不需要 Model 呀

寫多了 MVC 習慣用 MVC 來思考 自然怎樣都像在抄 MVC

回歸本質來想想 問題跟需求到底是什麼

首先 邏輯層 資料存取層 這兩層 沒什麼大困擾

不管是 Web Form 還是 MVC 都只是一些 物件導向的把戲

頂多差在 資料存取

Web Form 通常用 ADO.NET 搞

MVC 通常用 ORM 搞

問題還是在 表現層

MVC 靠 Model Binding 進 controller 的時候 就有現成的 Model

然後 資料存取也能沿用這些 Model 一氣呵成

但是 ADO.NET 在操作的時候 並沒有一定要使用 具體型別(Model)的要求

SqlParameter 只要給他 名稱 跟 值 就能用了 (保險點再給個 SqlDbType)

把 input 轉成 POCO 再給各層使用 在 web form 裡面的意義並不大 尤其只用 ADO.NET 來資料存取

(Plain-old CLR Object) 永續儲存無知物件 <= 中譯超有梗

只要想辦法處裡 "對應" 就好了

設法紀錄 控制項 跟 資料庫欄位的對應關係 之後就可以依靠對應關係來交流





不靠 Model 屬性名稱來做對應的時候...

Dictionary<TKey, TValue> 是你的好基友

Value 用 object 就好了 但是重點是 Key 該用什麼?

string 怕打錯 int 怕太抽象...(這些都是魔術化參數)

別忘了我們的 好碰友 列舉!! (列舉又能變 string 又能變 int)

先標記好欄位名稱當 KEY

然後老梗 拉控制項 先全用 TextBox 試水溫

再來 設計一個 ValueMap 物件 用來記錄 Key 跟 控制項 的對應關係

還有最主要的 GetlValue SetValue 方法

這邊稍微解釋一下

Func<T> OnGetValue 跟 Action<T> OnSetValue 是用來處理自定義的 取/設 值 的方式

否則預設的情況下 取/設 值 針對慣用的屬性

TextBox.Text

DropDownList.SelectedValue

RadioButton.Checked

CheckBox.Checked





有了 ValueMap 之外 還需要一個專們來操作 ValueMap 集合的 物件

這裡的 Buster 是指終結者 :p

然後就是來使用它

SetUpValueMaps 可以使用 partial class 的方式 包在另一頁 會乾淨一些 當然 見仁見智

最後就剩 Click 事件裡的東東啦

通常各家都有祖傳私釀的 SqlHelper 來玩 ADO.NET 或者用 enterprise library 也 OK

因為 參數吃 DataRow 跟 SqlParameter 要怎麼變出來 都無所謂

現在只剩兩行...

實際功能 DEMO 則不值一提 就是 查詢 / 更新 顯示在 TextBox 上面

但是樸素的外表又豈能看出 Click 事件 裡面竟然不需要碰到 噁心的控制項 這種美麗內在呢?





咦?如果我 TextBox 亂打 不合型別會怎樣?

呵呵呵 當然是會壞掉呀 因為現在還沒 實作欄位驗證

敬待 下回分曉 ˊ_>ˋ





標題梗:

2014年4月7日 星期一

table cell vertical align middle - 哪些年我們一起被坑的靴子

More Powerful ,more pits





This is a page, that is a Table

Can I make it anymore obvious?





But your customer say: I want those fucking words in the fucking middle!





You have no idea how to fix this crap with bootstrap





We need to find out where is this bootstrap's pits

Because you never know what selector that bootstrap assigned

There is in the bootstrap.css 1421 line





add a custom class





so those fucking words are in the fucking middle now

2014年3月13日 星期四

JOKE


  1. 當個 龍馬精神 的 神經碼農 - 2014/06/27
  2. 所謂的會議紀錄 指的是 下一次開會就會 timeout 的文件 - 2014/04/29
  3. 曾子曰:吾日三省吾身
    客戶曰:吾日三改需求 - 2014/03/13
  4. 質量守恆 但是 職量不守恆 - 2014/01/29

2014年2月14日 星期五

AspNet Identity 玩弄手札 第二彈 Social Login Facebook 登入

One ASP.NET 表示:天下大勢,分久必合,合久必分

AspNet Identity 表示:我也不過是被 One ASP.NET 玩弄於股掌之間的一個犧牲者

老疆表示:程式設計師用一些想到的點子來做一些無聊事情,這樣瞭不瞭

第一次聽到 AspNet Identity 是 老疆 的影片

http://www.youtube.com/watch?v=G2JC4KWTqXM&list=PLSvKFCIcFiosRfhmjcSCozircig3dRRrR&index=1

(11:15開始)

老疆的演講都很有梗

而且梗都是模仿不來的

別人說都不好笑 但是聽他講卻又有種獨特的幽默

我想這是一種 Aura









這次來玩 Social Login

為啥要 Social Login

也沒啥為什麼 因為流行

客戶說 OO 的網站可以用 XX 登入 我們的網站也要以

但是 social website 滿天下

每個都自成一套 API

而且 document 絕不會有中文

所以程式設計師總是永無止境的在熟悉 API 的用法

等到好像有點懂的時候 可能 API 又改版了 ._.




Micro$oft 發現了這個現象

所以就把它包裝起來 放到 AspNet Identity 裡面

好讓它更顯強大




首先是出場率最高的 Facebook 登入

在以往要做到這項功能

可能要先征服這頁

https://developers.facebook.com/docs/facebook-login/login-flow-for-web/



有個名詞兒叫 OAuth

OAuth(開放授權)是一個開放標準,允許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源(如照片,視頻,聯繫人列表),而無需將用戶名和密碼提供給第三方應用。

http://zh.wikipedia.org/wiki/OAuth




Facebook 登入 就是一種 OAuth

那不重要 因為大多數的人其實都不 Care 名詞解釋這回事

馬上來玩玩 就對了

但是玩之前必須要先申請 Facebook 應用程式

取得應用程式 ID (appId) 跟 應用程式密鑰 (appSecret)



申請過程沒啥好講的

但是如果我們在古早就 自行挑戰過 Facebook 登入的話...

WTF 介面改了 長得不一樣了

要先到 設定 這頁 點選 +新增平台



然後選網站



網站 URL 填好之後 切記! 要按 儲存



最近發現一個坑 那就是 只要網站沒有設定網域 (例如 localhost 或者 IP 的形式)

Facebook 只會讓應用程式擁有者 的帳號登入

用其他的帳號 一律看到這個 不明所以的錯誤



如果用 手工 javascript 的 FB.login() 的話更慘 會直接空白卡死



2014/02/26 補充:

後來發現原來是沙盒在作亂

http://stackoverflow.com/questions/20706322/how-to-disable-sandbox-mode-for-app-in-new-facebook-developer

在這頁可以切換...




MotherF___er!! 不能按是哪招??


後來經高人指點 才知道原來要填 E-mail 之後才能按 (X 鬼知道唷) 見圖2




再來 回到 VS 2013 建立一個 MVC 專案

他預設很好心的就準備好一堆範例程式 讓我們熟悉 他新攪和進來的東東

然後打開 \App_Start\Startup.Auth.cs

我們會發現只要取消註解 填上 appId 跟 appSecret 就結束了



此時登入頁面 就會多了 Facebook 登入的按鈕



如果點了 就GG了 那是因為我們使用 網址使用 localhost 然後又用了 該死的 IE



否則 點了會跳這個 不管問什麼都一定會按確定 的頁面



這時會回來網站問你...唉唷欸欸耶耶名字可以自己取耶呵呵呵呵呵呵



如果覺得有下底線的 username 很潮 我們會毫無意外的 獲得紅字



因為預設只接受英數字組合 中文也不行

修改 AccountController 的建構子 設定 AllowOnlyAlphanumericUserNames = false 就能解決

但是此時就沒任何規則 要加驗證要另外搞

public AccountController(UserManager<ApplicationUser> userManager)
{
    UserManager = userManager;
 
    UserManager.UserValidator = new UserValidator<ApplicationUser>(UserManager)
    {
        AllowOnlyAlphanumericUserNames = false
    };
}

然後就能快樂的登入了 可喜可賀



什麼? 這麼快? 快得令人覺得有些空虛 -.-

因為大部分都被黑箱了呀

好奇怎麼實作的話 就 reflector 催落起吧

AspNet Identity 玩弄手札 第一彈 Code First 自訂 profile data

One ASP.NET 表示:天下大勢,分久必合,合久必分

AspNet Identity 表示:我也不過是被 One ASP.NET 玩弄於股掌之間的一個犧牲者

老疆表示:程式設計師用一些點子來做一些無聊事情,這樣瞭不瞭

第一次聽到 AspNet Identity 是 老疆 的影片

http://www.youtube.com/watch?v=G2JC4KWTqXM&list=PLSvKFCIcFiosRfhmjcSCozircig3dRRrR&index=1

(11:15開始)

老疆的演講都很有梗

而且梗都是模仿不來的

別人說都不好笑 但是聽他講卻又有種獨特的幽默

我想這是一種 Aura









首先來玩 Micro$oft 最愛表演的戲碼 Code First + 自訂 profile data 神奇搞定會員登入功能

打開 VS 2013 建立一個 MVC 專案

他預設很好心的就準備好一堆範例程式 讓我們熟悉 他新攪和進來的東東

其實馬上就能執行了 可以註冊 登入 都沒問題



因為是 Code First 所以第一次呼叫 DbContext 的時候 會自動建立好 DB 跟 TABLE



以往要擴充 profile data 十分麻煩 有多麻煩我也不太瞭

因為相傳 業界公司 沒有人在用 都是自幹 會員機制 而我也不過是其中一人

現在他表明了 You can add profile data for the user by adding more properties to your ApplicationUser class

在這邊加屬性就搞定了

// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser
{
    public string Email { getset; }
 
    public int Point { getset; }
 
    public bool Gender { getset; }
 
    public DateTime Birthday { getset; }
}

原先的 RegisterViewModel 也順手加上 以利欄位驗證

public class RegisterViewModel
{
    [Required]
    [Display(Name = "使用者名稱")]
    public string UserName { getset; }
 
    [Required]
    [StringLength(100,
        ErrorMessage = "{0} 的長度至少必須為 {2} 個字元。",
        MinimumLength = 6
    )]
    [DataType(DataType.Password)]
    [Display(Name = "密碼")]
    public string Password { getset; }
 
    [DataType(DataType.Password)]
    [Display(Name = "確認密碼")]
    [Compare("Password"ErrorMessage = "密碼和確認密碼不相符。")]
    public string ConfirmPassword { getset; }
 
    [Required]
    [EmailAddress]
    [Display(Name = "E - mail")]
    public string Email { getset; }
 
    [Required]
    [Display(Name = "點數")]
    public int Point { getset; }
 
    [Required]
    [Display(Name = "性別")]
    public bool Gender { getset; }
 
    [DataType(DataType.Date)]
    [Display(Name = "生日")]
    public DateTime Birthday { getset; }
}

AccountController 的 Register 也要從 ViewModel 把值帶入 ApplicationUser

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser()
        {
            UserName = model.UserName,
            Email = model.Email,
            Point = model.Point,
            Gender = model.Gender,
            Birthday = model.Birthday
        };
 
        var result = await UserManager.CreateAsync(usermodel.Password);
        if (result.Succeeded)
        {
            await SignInAsync(userisPersistentfalse);
            return RedirectToAction("Index""Home");
        }
        else
        {
            AddErrors(result);
        }
    }
 
    // 如果執行到這裡,發生某項失敗,則重新顯示表單
    return View(model);
}

如果你覺得欄位很多很給掰 可以使用一些 Mapping 或者 Injecter 的套件

像是 Value Injecte



只要屬性名稱一致 就能無腦的搞定

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser().InjectFrom(modelas ApplicationUser;
 
        var result = await UserManager.CreateAsync(usermodel.Password);
        if (result.Succeeded)
        {
            await SignInAsync(userisPersistentfalse);
            return RedirectToAction("Index""Home");
        }
        else
        {
            AddErrors(result);
        }
    }
 
    // 如果執行到這裡,發生某項失敗,則重新顯示表單
    return View(model);
}

最後當然別忘了 View 可不會自動生出 HTML 也補上

<div class="form-group">
    @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
    <div class="col-md-10">
        @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
    </div>
</div>
<div class="form-group">
    @Html.LabelFor(m => m.Point, new { @class = "col-md-2 control-label" })
    <div class="col-md-10">
        @Html.TextBoxFor(m => m.Point, new { @class = "form-control" })
    </div>
</div>
<div class="form-group">
    @Html.LabelFor(m => m.Gender, new { @class = "col-md-2 control-label" })
    <div class="col-md-10">
        男
        @Html.RadioButtonFor(
            m => m.Gender, true, new { @id = "", @checked = "checked" })
        女
        @Html.RadioButtonFor(
            m => m.Gender, false, new { @id = "" })
    </div>
</div>
<div class="form-group">
    @Html.LabelFor(m => m.Birthday, new { @class = "col-md-2 control-label" })
    <div class="col-md-10">
        @Html.TextBoxFor(m => m.Birthday, new { @class = "form-control" })
    </div>
</div>



搞定 馬上開心執行 立馬報錯 -.-



因為先前執行過一次已經 產生過 DB 跟 TABLE

但是這次 Code First 發現 schema 被異動過 無法 ORM 就跳錯了

請透過 套件管理主控台 來更新他

我們可以在 檢視/其他視窗/套件管理主控台 找到這貨

啟用轉移:Enable-Migrations -ContextTypeName MvcSample.ApplicationDbContext

轉移版本:Add-Migration '版本名稱'

更新資料庫:Update-Database

出現 Seed 就表示成功啦



這下搞定了 資料乖乖進 DB 可喜可賀