Quantcast
Channel: 黑暗執行緒
Viewing all 2429 articles
Browse latest View live

【茶包射手日記】Visual Studio編譯成功的專案在IIS發生組件版本不合

$
0
0

將原本運作正常的ASP.NET MVC專案,複製到新的Solution改版開發,出現Visual Studio編譯正常,在IIS Express執行冒出編譯錯誤的狀況:

Could not load file or assembly 'Newtonsoft.Json' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

Assembly Load Trace: The following information can be helpful to determine why the assembly 'Newtonsoft.Json' could not be loaded.

...省略...

LOG: Using application configuration file: X:\TFS\v4\Afa.WebApi\web.config
LOG: Using host configuration file: X:\Users\jeffrey\Documents\IISExpress\config\aspnet.config
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Redirect found in application configuration file: 4.5.0.0 redirected to 6.0.0.0.
LOG: Post-policy reference: Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed

檢查web.config,安裝Json.NET NuGet套件時已自動加入Redirect將所有版本指向6.0:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly>
    <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
  </dependentAssembly>
</assemblyBinding>

檢查Visual Studio中的專案參照,發現Newtonsoft.Json跑到Blend的安裝目錄去了,版本還變成4.6!

stackoverflow找到類似案例:原因出在ASP.NET MVC專案被直接複製到新位置,但csproj的Json.NET參照HintPath仍指向原Solution的packages目錄,該相對路徑在搬動後已失效。Visual Studio透過「自己的邏輯」找到替代品-Blend目錄的舊版Json.NET元件,故仍能編譯成功。但此一舊版元件不在IIS Express的搜尋範圍內,於是產生無法載入元件的錯誤。

解決方案,使用文字編輯器開啟csproj,將以下ItemGroup的HintPath指向正確路徑,問題排除!

<ItemGroup>
  <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
    <SpecificVersion>False</SpecificVersion>
    <HintPath>..\OldFolder\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll</HintPath>
  </Reference>

PS: 想了解VS找出DLL的邏輯,可調整以下設定,Visual Studio將會為你娓娓道來…


我的(偽)白帽駭客經驗

$
0
0

或許有些人不知道,一般人口中的「駭客」,還細分成幾類:

  • 白帽駭客(White Hat)
    有能力破壞電腦安全但不具惡意目的的駭客。白帽子一般有清楚的定義道德規範並常常試圖同企業合作改善被發現的安全弱點。
  • 黑帽駭客(Black Hat)
    可視為犯罪分子,他們的出發點是惡意的。在未經邀請下,他們就侵入受害者的電腦系統獲取自己的利益。也被稱為Cracker(破壞者),因為他們侵入電腦系統的行為就像銀行搶匪破壞保險箱一樣。
  • 灰帽駭客(Gray Hat)
    對於倫理和法律界線曖昧不清,游走於黑帽與白帽之間。
  • 激進駭客(Hacktivist)
    主要是為了表達抗議而入侵企業或政府組織的電腦網路和系統,例如有名的駭客組織Anonymous
  • 指令碼(工具)小屁孩(Script Kiddie)
    嚴格來說不算駭客。對技術沒什麼熱情也不太深入研究,多靠著在網路上找尋現成工具或範例從事入侵或破壞,並以「駭客」自居且沾沾自喜。

參考來源:維基百科趨勢科技Blog

早些年被SQL Injection嚇到,從此十年怕井繩,寫過幾篇討論SQL Injection、XSS…等資安議論的部落格與雜誌文章〔參考〕。也不知哪來的使命感,只要看到別人的程式碼出現SQL Injection,就要衝上去唸個兩句;看到有人大喇喇用MSN/Mail傳送密碼或連線字串明碼,也不免嘮叨碎唸一番〔註〕;對於團隊程式碼撰寫風格我一向看得很開,連Copy & Paste都能忍耐,唯一不能踩的紅線是SQL Injection,堅持「故意犯唯一死刑(阿魯巴到死)」的規定。

不過,十幾年過去了,SQL Injection仍是駭客的最愛,資安漏洞界的一哥,不曾在網路上絕跡。有時逛網站發現,肯定要雞婆地寫信問候對方老母提醒網站管理員,期盼儘早修正。但我得坦白說,一路下來受傷不少,很多信石沈大海;有些則是窗口客氣回覆,感謝之餘說會立刻告知技術人員,然後… 石沈大海;更氣人的是這種:一段時間後接到窗口回覆,工程師已研究過確認不會有風險,但仍衷心感謝我的雞婆提醒 orz。每每氣到拎杯很想用漏洞下兩行指令給他個痛快,讓禍首刻骨銘心永世不忘。但入侵或破壞都已踰越法律道德界線,而且做壞事不難,做壞事不被抓很難,不想為此惹來一身腥。人生苦短,混口飯吃已不容易,哪來的閒功夫為這種事傷神冒險。講了沒人要聽,又沒膽硬幹的下場是:白帽駭客一當再當,臉頰比人屁股燙,幼小心靈受了傷,好不窩囊~

直到前陣子,總算有一次不錯的白帽駭客經驗。從FB看到朋友提及某個PHP網站有SQL Injection漏洞,不但在QueryString加單引號就能引爆,錯誤畫面還直接噴SQL語法,一整個Hacker Friendly~ 問題頁面有附連絡Email,二話不說,當然要寫信提醒承辦人員。原本沒抱期望,以為又是一次對牛彈琴,寫完信還在FB上小小抱怨自己一路受挫的白帽經驗;隔了一天,意外接到回信,說明已轉告工程師。很好,至少是好的開始。

收到回信就壓根忘記這事兒(切記,當白帽要抱平常心,沒有期待才不會失望。可惡!我的失敗經驗多到人都變豁達了)後來,在信箱發現一封信:

再次檢查網站,SQL Inection漏洞已被補上!希望網路另一端的工程師已感受到我當年初識SQL Injection的震憾,從此永不再犯!地球減少(至少)一位寫出SQL Injection的開發者,開心!

(嚴格來說,白帽駭客的定義是靠技術發掘網站弱點,敲單引號就會爆的SQL Injection似乎不需要「駭」就能發掘,這一回其實只能算(偽)白帽吧!靠點本事才找出漏洞的經驗也不是沒有,但,唉… 甭提了)

註:前面提到用Email/即時通傳密碼可能會當豬隊友,那敏感資訊要怎麼傳才安全?最簡單的做法是拆成多個管道遞送,例如:Mail寄前半截Skype講下半截,或是寄成加密壓縮檔再電話告知解密密碼。我知道有人會嫌麻煩,但小心駛得萬年船,你知道的,Blah Blah Blah…(以下省略三千字)

KO範例31 - 也來寫野生官網好了

$
0
0

市長候選人柯P的競選團隊前幾天做了一件有趣的事(只有程式魔人覺得有趣),突發奇想地將官網內容透過Web API方式提供,歡迎開發人員自行開發野生官網。昨天,保哥瞬間變出AngularJS版,好不神奇! 依我的理解,這個需求還算簡單,應該也難不倒knockout.js,而更重要的是,這年頭大家都去玩NG了,如果我不寫,全台灣應該也沒有其他人會為Knockout寫範例了(KO堂口冷冷清清,頓時感到寂寞空虛覺得冷)。身為KO粉絲,我做了我該做的事 - 無關政治,但柯P官網API KO版範例來了。

既以練習為主,就不花時間在視覺設計上(事實是想做也做不來),單純只把官網API範例的jQuery DOM操作抽換成KO MVVM。看了API文件,資料來源不算複雜,分成文章、照片與影片三種,其中文章與照片有分類概念,操作時先點分類才下載該分類的項目,所以分類ViewModel要宣告項目的集合(ko.observableArray),點選分類時再呼叫API取回清單填入。最後,決定把文章、照片、影片邏輯全包進同一個ViewModel裡供三個網頁共用,全部寫成一個kp.js,不同網頁載入時只差在初始化時傳入不同參數載入所需分類資料。

學TypeSript後就不太愛徒手寫JavaScript,所以kp.js是TypeScript編譯產生的,但為避免失焦,這裡只看kp.js:

var kp;
(function (kp) {
var API_SERVER = "http://api.kptaipei.tw/v1/";
var accessToken = "[Your API Key]";
//透過reviver提供ISO 8601字串轉Data的功能
//REF: http://msdn.microsoft.com/zh-tw/library/ie/cc836466(v=vs.94).aspx
var dateReviver = function (key, value) {
var a;
if (typeof value === 'string') {
//標準ISO 8601 或 API傳回的yyyy-MM-dd HH:mm:ss格式
            a = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z*$/.exec(value);
if (a) {
returnnew Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
            }
//格式二 1408525510000
if (key.indexOf("date_") === 0 && value.match(/\d{13}/)) {
returnnew Date(parseInt(value));
            }
        }
return value;
    };
/** API呼叫共用函式 */
function callApi(act, id) {
if (typeof id === "undefined") { id = ""; }
var dfd = jQuery.Deferred();
var param = { accessToken: accessToken };
        $.get(API_SERVER + act + "/" + id, param, function (resString) {
var res = JSON.parse(resString, dateReviver);
if (!res.isSuccess) {
                alert("API Error: " + res.errorCode + " " + res.errorMessage);
                dfd.reject();
            } else {
                dfd.resolve(res.data);
            }
        }, "text");
return dfd.promise();
    }
    kp.callApi = callApi;
 
/** ViewModel */
var ViewModel = (function () {
function ViewModel() {
this.categories = ko.observableArray([]);
this.selCategory = ko.observable(null);
this.article = ko.observable(null);
this.albums = ko.observableArray([]);
this.albumTitle = ko.observable(null);
this.selAlbum = ko.observable(null);
this.photos = ko.observableArray([]);
this.playlists = ko.observableArray([]);
this.selPlaylist = ko.observable(null);
this.videos = ko.observableArray([]);
this.video = ko.observable(null);
var self = this;
            ko.computed(function () {
var item = self.selCategory();
                item && callApi("category", item.id).done(function (data) {
                    item.articles(data);
if (!self.article())
                        self.article(data[0]);
                });
            });
            ko.computed(function () {
//預設選取第一筆文章
if (!self.selCategory() && self.categories().length)
                    self.selCategory(self.categories()[0]);
            });
            ko.computed(function () {
var item = self.selAlbum();
                item && callApi("albums", item.id).done(function (data) {
                    self.photos(data.photos);
                    self.albumTitle(data.set.title);
                });
            });
            ko.computed(function () {
//預設選取第一本相簿
if (!self.selAlbum() && self.albums().length)
                    self.selAlbum(self.albums()[0]);
            });
            ko.computed(function () {
var item = self.selPlaylist();
                item && callApi("videos", item.id).done(function (data) {
                    item.videos(data);
if (!self.video())
                        self.video(data[0]);
                });
            });
            ko.computed(function () {
//預設選取第一部影片
if (!self.selPlaylist() && self.playlists().length) {
var playlist = self.playlists()[0];
                    self.selPlaylist(playlist);
                }
            });
        }
return ViewModel;
    })();
    kp.ViewModel = ViewModel;
 
    kp.model = new ViewModel();
function init(type) {
var map = {
            category: "categories",
            albums: "albums",
            videos: "playlists"
        };
        callApi(type).done(function (data) {
if (type == "category") {
                $.each(data, function (i, item) {
                    item.articles = ko.observableArray([]);
                });
            } elseif (type == "videos") {
                $.each(data, function (i, item) {
                    item.videos = ko.observableArray([]);
                });
            }
            kp.model[map[type]](data);
        });
    }
    kp.init = init;
    ko.applyBindings(kp.model);
})(kp || (kp = {}));

程式碼不長。第一部分是JSON日期格式處理,現行API用的日期格式有點亂,有"post_date": "2014-08-19 11:00:10"、"publishedAt": "2014-08-16T11:25:34.000Z"、"date_upload": 1408525503000三種規格,所以我放棄讓jQuery解析JSON,改成自取回原始字串配合自訂dateReviver進行JSON.parse(),以確保日期都能被正確解析。呼叫API部分則包成一個callApi函數,呼叫時只需傳入act(cateory、albums或videos)及id,callApi會組裝URL,接回JSON字串配合自訂日期解析轉成JavaScript物件,再判別isSucess旗標,失敗時alert錯誤,成功時再以jQuery.Deferred方式傳回結果中的data物件。

最後是ViewModel,裡面保存的資料物件基本上都沿用API傳回的物件定義,只有因應文章、照片分類點開才下載清單的行為,為類別物件加上articles及vidos observableArray,並宣告了selCategory、selAlbum、selPlaylit等選取狀態屬性,以computed函式觸發API呼叫填入清單項目,另外還要加上article、video等observable對映內容顯示,ViewModel就做完了。

當邏輯被抽到ViewModel,HTML只剩下元素定義及data-bind設定,完全看不到操作DOM的JavaScript程式碼,很乾淨吧?最後呈現效果力求與原範例相同。

<divclass="container">
<divclass="col-md-4">
<h2>文章類別目錄</h2>
<ulclass="categories"data-bind="foreach: categories">
<liclass="category">
<spandata-bind="text: name, click: $root.selCategory"></span>
<ulclass="articles"data-bind="foreach: articles">
<liclass='article'data-bind="text: title, click: $root.selArticle">
</li>
</ul>
</li>
</ul>
</div>
<divclass="page col-xs-12 col-sm-6 col-md-8 col-xs-6">
<divdata-bind="with: article">
<divdata-bind="text: title"></div>
<divdata-bind="html: content"></div>
</div>
<divdata-bind="visible: !article()">
無資料
</div>
</div>
</div>
<scriptsrc="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-3.1.0.js">
</script>
<script src="kp.js"></script>
<script>
        kp.init("category");
</script>

展示完畢!Live Demo

PS:對kp.ts有興趣的朋友可在Plunker找到原始碼。

[KO系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post

【笨問題】ASP.NET Script打包之debug.js/min.js處理原則

$
0
0

程式上線到UAT環境,因knockout.mapping未載入出錯,但在測試環境是好的。經過一番測試比對,發現犯了一個低級錯誤:

            bundles.Add(new ScriptBundle("~/bundles/ko").Include(
"~/Scripts/knockout-3.0.0.js",
"~/Scripts/knockout.mapping-latest.debug.js"
            ));

當初想在偵測階段追蹤knockout.mapping原始檔,而它有兩個檔案:knockout.mapping-latest.js(壓縮版本)及knockout.mapping-latest.debug.js(原始碼版本),我自作聰明地挑了debug.js。而UAT環境啟取消了debug模式(<compilation debug="false" targetFramework="4.5" />),啟用JavaScript打包,而打包壓縮後的JavaScript中並不包含knockout.mapping。查了資料才發現,Bundle程序預設會忽略.debug.js、.min.js。更進一步,我在ScriptBundle寫knockout.mapping-latest.debug.js根本多此一舉,只要寫knockout.mapping-latest.js,在偵錯模式下若有同名*.debug.js,ScriptBundle將優先改用debug.js版本。所以,上述程式應改為:

            bundles.Add(new ScriptBundle("~/bundles/ko").Include(
"~/Scripts/knockout-3.0.0.js",
"~/Scripts/knockout.mapping-latest.js"
            ));

在偵錯模式會自動載入knockout-3.0.0.debug.js及knockout.mapping-latest.debug.js。

最後補充一點:當同名*.min.js存在時,ScriptBundle將改用.min.js取代.js,但仍會重新壓縮,做個實驗驗證這一點。

準備兩個檔案boo.js及boo.min.js,其內容分別為:

var boo = { version: "Source" };

以及

//假裝是預先壓縮過的版本(明明不是)
var boo = { version: "Minified", test: function() {
var longVarName = "Test";
function log(msg) {
if (window.console) console.log(msg);
    }
    log(longVarName);
}};

實測結果,傳回的內容來自boo.min.js,但註解被移除了,內部變數名稱也被換成短版,且原本已縮為一列,經Chrome F12的Pretty print功能才排列如下圖,證明打包機制雖然改用boo.min.js,但仍會執行壓縮。

【笨問題】TF14010合併衝突

$
0
0

為了把現有Web專案的JavaScript升級成TypeScript,用TFS另外切出Branch改寫。第一次使用TypeScript開發缺乏經驗,一開始三步一踩雷五步一摔坑,爆炸聲與慘叫不絕於耳,慘烈程度不在話下… 不過很快抓到訣竅,上手後就挺順利的。最近TypeScript版通過測試,準備Merge回主流。

Merge時遇到以下問題:

TF14010: 無法合併至 '$/***/entities.ts',因為這個路徑中已存在合併衝突。請執行解決方法,處理現有的衝突。(英文版:TF14010: Cannot merge to '$/***/entities.ts' because a merge conflict already exists for this path. Run resolve to deal with the existing conflict.)

原因應是專案裡有個先導測試用的entities.ts,切Branch後又被修改,因而形成合併衝突。但我過去都仰賴TFS自動彈出解決衝突UI,這回按下OK後卻什麼都沒發生,在entities.ts上按右鍵也找不到選項可以解決合併衝突,TFS初級生這下黔驢技窮了~

爬文找到解答:

File/Source Control/Advanced/Resume Conflict Resolution,可愛的衝突解決視窗就出現囉!

筆記-使用VS2013解開.NET程式CPU衝高謎團

$
0
0

前幾天,幫同事追查 .NET 程式 CPU 衝高問題,才發現 Visual Studio 2013 效能分析工具真是威力強大,特筆記備忘順便分享。原本想拿實務案例說明,但考量太多無關細節會失焦,所以我弄了一個簡單程式當靶機練習射擊:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
 
namespace CPUSharMon
{
publicclass CPUHeater
    {
publicenum JobTypes {
            Light, Medium, High, Crazy
        }
staticvoid RunSHA()
        {
            Guid g = Guid.NewGuid();
            SHA512 sha512 = new SHA512CryptoServiceProvider();
            sha512.ComputeHash(g.ToByteArray());
        }
static Tuple<string, string> GenDESKey()
        {
string rndStr = Guid.NewGuid().ToString();
returnnew Tuple<string, string>(
                rndStr.Substring(0, 8), rndStr.Substring(8, 8));
        }
staticbyte[] Encrypt(DESCryptoServiceProvider des, byte[] data)
        {
            var desencrypt = des.CreateEncryptor();
return desencrypt.TransformFinalBlock(data, 0, data.Length);
        }
staticvoid RunDES()
        {
            var keyIv = GenDESKey();
            DESCryptoServiceProvider des = new DESCryptoServiceProvider();
            des.Key = Encoding.ASCII.GetBytes(keyIv.Item1);
            des.IV = Encoding.ASCII.GetBytes(keyIv.Item2);
            var data = newbyte[4096];
            var enc = Encrypt(des, data);
        }
staticvoid RunCrazyLoop()
        {
for (var i = 0; i < 10000; i++)
            {
                Guid.NewGuid();
            }
        }
publicstaticvoid RunJob(JobTypes type, int times)
        {
int j = 0;
for (int i = 0; i < times; i++)
            {
switch (type)
                {
case JobTypes.Light:
                        Random rnd = new Random();
                        j = i + rnd.Next(i);
break;
case JobTypes.Medium:
                        RunSHA();
break;
case JobTypes.High:
                        RunDES();                                                
break;
case JobTypes.Crazy:
                        RunCrazyLoop();
break;
                }
            }
        }
publicstaticvoid RunComplexJob()
        {
            RunJob(JobTypes.High, 80000);
            RunJob(JobTypes.Light, 120000);
            RunJob(JobTypes.Crazy, 6000);
            RunJob(JobTypes.Medium, 30000);
        }
    }
}

我寫了一個 Console Application 程式,核心邏輯放在 CPUHeater 類別,RunJob() 方法提供四種粗重程度不一(但毫無營養)的工作:亂數加計算、SHA512 雜湊計算、DES 加密,以及狂跑迴圈生 GUID。(所以程式名稱叫 CPUSharMon ,「瞎忙」無誤)最後,另外宣告 RunComplexJob() 循序執行四種工作。

在八核  i7 執行,耗時約 20 秒,CPU 最高衝到 13%,等於把單核吃到 100%。接著我們就來用 VS2013 找出誰是把 CPU 衝高的兇手。

從選單列 ANALYZE/Performance and Diagnostics 開啟精靈:

選取分析對象:

有四個分析選項:CPU 取樣、Instrumentation(指令執行次數及耗時)、記憶體配置及執行緒等待狀況,這次的案例鎖定 CPU,故選第一個:

選取專案:(亦支援EXE程式及ASP.NET/JavaScript程式)

按下完成,VS2013 便啟動 CPUSharMon 程式並開始蒐集數據:

程式執行完會自動產出報告,但也可在觀察到特定 CPU 暴衝後手動停止。資料處理需要一點時間,接著檢測報告就出爐了:

先說明 Sample Profiling 偵測效能瓶頸原理:觀測程式每隔固定週期中斷 CPU,取回 Callstack 資料,藉此掌握程式當行執行的段並進行統計。由於取樣間隔固定,若每次取樣時常停在特定函式上,便可推斷大部分的 CPU 時間都耗在該函式,一般就是耗用 CPU 較多的運算來源。

我們的展示程式很單純,從彙總報告就能一眼看出效能瓶頸,Hot Path 列出了RunDES() 及 RunCrazyLoop() 出現在總取樣次數的比例最高,分別為 52.73% 及 41.09%,可想成整個執行期間有一半花在 RunDES,近一半花在 RunCrazyLoop。而下方則列出在取樣記錄出現次數最多的個別函式,由 RunCrzayLoop 中的 Guid.NewGuid 及 RunDES 中的加密程序分獲冠亞軍。

報告是互動式,點選函式名稱可以進一步剖析追查兇手,例如點選 HotPath 的第二項 RunComplexJob():

上方可看出 RunComplexJob 所有時間都耗在 RunJob 函式上(Call了四次,參數不同),而下方 Function Code View 會以數字及顏色標示各程序所佔比例,比例愈高愈紅,接著我們點選上方三個Called funtions 最右邊的 RunJob:

報表將再針對 RunJob 展開,上方的藍色區塊圖及下方程式碼都能看出耗用 CPU 較多的函式,再選點上方藍色區塊的 RunDES:

再點選藍色區塊的 Encrypt:

最後抓出 CreateEncryptor()、TransformFinalBlock() 分佔總取樣次數的 13.6% 及 36.4%,到這一步已觸及 .NET Framework 的範圍,不能也不需要再深入。(除非你要改寫 .NET Framework XD)

除了上述的 Function Detail 檢視,報表還提供其他檢視,其中 Call Tree 也蠻好用的。

在 Call Tree 表格中,程式函式的從屬關聯一目瞭然,可透過樹狀結構展開追兇,操作效率更高。而圖中紅字數字標出兩個好用功能:1)火焰圖示用來標示 Hot Path 2)齒輪符號可過濾雜訊,忽略 CPU 佔用不多的項目。另外還有自訂條件過濾等功能,函式太多時很好用!

找到疑犯,按右鍵可以快速切到其他檢視及相關函式,繼續深入調查:

另外,分析工具還提供多次觀測結果比較,像減肥廣告一樣做出「使用前」vs「使用後」的對照表,修改程式調校效能時很有用。

又到呼口號時間:Visual Studio 好威啊!

延伸閱讀:Analyzing Application Performance by Using Profiling Tools

【茶包射手日記】WebATM回報Smart Card服務未啟動

$
0
0

開啟某銀行 WebATM 網站,裝好 ActiveX 元件,接妥讀卡機,插入金融卡,網頁卻彈出 Smart Card 服務未啟動訊息。網站貼心地將我導到 FAQ,教導如何開啟 Smart Card 服務… 我確認 Windows 8 上的Smart Card 服務處於開啟狀態,但銀行 WebATM 元件就是一口咬定服務沒開。明明要轉帳,一回神,已捲好袖子準備射茶包… orz

由網站 JavaSript 追到以下邏輯:

      bRdrReady = XCsp.ConnectReader();
if (bRdrReady != 0)
         { 
if (bRdrReady==-2146435043){
if(BrowserDetect.OS=="Linux"){
                    alert("請確認 pcscd 是否已啓動!");
                    window.location="/webatm/Q&A_016.htm";
                }else{
if(BrowserDetect.OS=="Mac"){
                        alert("請確認 pcscd 是否已啓動!");
                        window.location="/webatm/Q&A_017.htm";
                    }else{
                        alert("請確認Smart Card Service是否已啓動!");
                        window.location="/webatm/Q&A_012.htm#10";
                    }
                }
            }else{
                 alert("讀卡機連線失敗,Rtn=" + bRdrReady); 
            }
             hidePinRow(2);
return;
         }

這裡面有個關鍵字 –2146435043,換算成 16 進位是 8010001d(可用 string.Format("{0:x4}", -2146435043)轉換)。以 Card Service 跟 0x8010001d 關鍵字爬文,大多是服務未啟動案例,但有一篇符合我的情境。我透過遠端桌面連上NB讀取讀卡機,文中提到 RDP Client 6.0 Smart Card Redirection 功能有個 Bug 會導致 0x8010001d。

改為直接操作後,問題排除!

【茶包射手日記】Web Deployment Project自動部署出現型別重複錯誤

$
0
0

接獲報案,Web Site Project 配合 Web Deployment Project 編譯,使用 CruiseControl.NET自動部署,於本機編譯測試正常,但部署後出現物件重複出現在兩個 DLL的錯誤訊息:

Compiler Error Message: CS0433: The type 'UserContrl_HeadInfo' exists in both 'c:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\fx\ca083b06\95835c45\assembly\dl3\2b9d72d9\c2f28c5d_0e8cce01\FX_deploy.DLL' and 'c:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\fx\ca083b06\95835c45\assembly\dl3\8fdf26e7\d13146b8_82c6cf01\FX.UserContrl.DLL'

太久沒用 Web Site Project,花了點時間大腦才從 MVC Context Switch 切回Web Site,其實錯誤訊息已經很明確,時間卻大部分花在重新熟悉 Web Deployment Project,這就是中年茶包射手執勤的寫照吧? XD

深入調查,Web Deployment Project 專案被設成每個資料夾編譯為單一組件(Assembly), HeadInfo 位於 UserContrl 目錄下,出現在 FX.UserContrl.DLL 很合理;跟它打架的另一顆元件 FX_deploy.DLL 是整個網站編譯成單一組件的產物,理論上跟 FX.UserContrl.DLL 互斥,二者不該並存,也因此導致錯誤。

Web Deployment Project 每之編譯會清空輸出目錄,不致出現矛盾, CruiseControl.NET 自動部署看來是本案最後一塊拼圖。ccNet 是透過 robocopy 複製檔案到網站主機,不可能每次都將目的地的檔案全部刪除換上新檔案,而是採取「新増或複寫」做法。而本案的關鍵在於一開始 Web Deployment Project 被錯設為編譯單一組件,自動部署後網站 bin 目錄便有了 FX_deploy.DLL;接著設定改為每個資料夾一顆組件後再次部署,FX_deploy.DLL 未刪,FX.UserContrl.DLL 就位,碰!

知道原因,將 FX_deploy.DLL 刪除,問題排除,收工!


KO的逆襲-HTML自訂元素上場

$
0
0

先看展示:

這是一個很簡單的 MVVM 繫結示範,模擬網頁常見「給幾顆星」的評分機制。上方透過 click 事件修改observable,同時繫結到下拉選單及Score=文字顯示。同一 observable 繫結到兩個 UI 元素的情境對KO來說是小菜一碟,不足掛齒,但 HTML 裡有玄機:

<div>
<star-ratingparams="value: score"></star-rating>
</div>
<selectdata-bind="value: score">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
  Score=<spandata-bind="text: score"></span>

咦?<star-rating param="value: score"></star-rating>? 有妖術!莫非 KO 追上 NG,樣開始支援自訂 Directive?

先前評估 KO 與 NG,我給「自訂Directive」很高的評價。在網頁使用<my-element>標籤置入自訂元素,能大幅提升 HTML 簡潔性並有利 UI 邏輯分割與重覆利用,是很讓人動心的特色,也是讓我傾向 NG 的關鍵之一。上個月, Knockout.js 釋出 3.2.0 版,最大亮點便是加入元件(Component)概念,在我心中,此一改變讓版號直接跳上4.0都不為過!XD

來看如何在KO自訂HTML元件,方法很簡單,透過 ko.components.register() 註冊元件名稱,並提供 ViewModel 及 Template 定義,搞定!(完整程式及線上展示

    ko.components.register('star-rating', {
      viewModel: function(params) {
var self = this;
//Data
        self.value = params.value;
        self.options = [1,2,3,4,5];
//Behaviors
        self.pick = function(n) { self.value(n); }.bind(self);
      },
      template: 
'<div data-bind="foreach: options" class="stars">' +
'<span data-bind="click: $parent.pick, 
         text: $data<=$parent.value()?\'★\':\'☆\'"></span>' +
'</div>'
    });

每個元件自成一個獨立 ViewModel,因此在同一網頁上重複加入多個元件也不致打架。ViewModel 在建構時能透過 params 參數取得外部傳入的固定值參數、observable、observableArray、computed,將其轉為元件 ViewModel 屬性(例如:self.value = params.value),在 Template中透過 data-bind="text: value",便能與外部傳入的 observable 建立繫結。Template 與 ViewModel 的整合方式跟一般的 KO 做法完全相同,已有 KO 經驗的開發者幾乎不需學習就能上手。

KO 官網已新増一個章節介紹元件,是最權威的文件來源,在此整理幾則重點:

  1. 除了直接寫成<my-element params="…">,KO 也支援<div data-bind="component: { name: 'my-element', params: … }">寫法。但說真的,有龍蝦,誰要吃小魚乾呢? XD
  2. <my-element>的寫法在 IE6/7/8 受限制,必須確保 ko.components.register("my-element", …) 已執行,網頁才能出現<my-element>,否則要加入document.createElement('my-element'); 避免 my-element 元素被無視。
  3. 不可以寫成<my-element params="…" />(Self-Closing),一定要拆成<my-element></my-element>,這是 HTML 規格限制。(這點跟<script></script>一樣,想到之前踩到的釘子,腳彷彿還在痛)
  4. KO 會自動將 params="boo: someObservable() + 1" 這種摻雜 observable 的計算型參數轉成 observableExpression,當someObservable 改變時連動 boo 以反應到 UI。
  5. 針對複雜元件,可以透過 createViewModel 函式動態決定 ViewModel。
  6. 除了以字串方式指定 Template,也可將 Template 放進 DOM 元素(<template>或<script type="text/template">)並寫成template: { element: 'my-component-template' },或是更進一步將元件的 ViewModel JavaScript 及 Template HTML 分別存成獨立 .js 與 .html 檔案,配合 AMD動態載入。

得知 KO 的新功能後,二話不說,馬上將專案一個重複出現 N 次的 HTML 片段改寫成自訂元素,看著修改後變清爽的 HTML,我決定再次評估 KO 與 NG 的選擇考量:

KO 比較輕巧,偏向 Library 而非 Framework,如果對架構已有自已的規劃,不想受限 NG 複雜且龐大的框架,KO 比較容易相處好上手,對一些輕量級前端應用較有彈性。(但別誤會,KO 絕對能拿來寫大型 SPA,結合 Grunt、Gulp、Jasmine、Karma 實現自動測試及CI。Steven Sanderson前陣子在NDC 2014的演講有個完整展示,前10分鐘有個 Window 8 動態磚風格的網頁操作,很酷!)

NG 是前端主流架構的情勢短期內不會改變(但長期而言,Who Knows?),採用 NG 將享受較多的資源,而其較嚴謹的架構及大量運用設計模式將有助於開發人員學習建立中大型系統必要的知識與技巧。(但相對的,對初學者而言門檻也較高)

若回歸到只想實現 JavaScript 端 MVVM 的場合,雖然 NG 不需宣告 observable 就能建立繫結的概念很吸引人,但探究其背後是靠反覆重算的 Direty Check 實現,以我個人觀點,倒是偏好用 obeservable 及computed 自己精準掌控相依關聯,從另個角度來看,宣告 observable 就像 Strong Type 一樣,付出代價也會享受到好處。在 KO 加入 HTML 自訂元素功能後,我想在純粹 MVVM 應用上,KO 還是一個很棒的選擇。

[KO系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post

【茶包射手日記】相同專案在另一台機器出現元件版本不合錯誤

$
0
0

先說明遇到的狀況:我修改了單元測試專案Check In TFS,同事取回編譯測試時,出現NLog版本不合錯誤!專案需要3.1版,但實際卻是2.1版。

經初步檢查,疑點重重:

  1. 同一.sln還有其他專案,部分專案仍採用NLog 2.1版,出問題的單元測試專案使用NLog 3.1,但app.config有bindingRedirect設定要求全部改用3.1:
    <dependentAssembly>
    <assemblyIdentityname="NLog"publicKeyToken="5120e14c03d0593c"
    culture="neutral"/>
    <bindingRedirectoldVersion="1.0.0.0-3.1.0.0"newVersion="3.1.0.0"/>
    </dependentAssembly>

    依我的理解,有此設定,即便其他參照專案使用NLog 2.1,在單元測試專案裡也一律要強制改用3.1。莫非我一直以來的認知有誤?
  2. 單元測試的bin\Debug\NLog.dll是2.1版,可以解釋版本不符的來源。但同一專案在我的機器上編譯、執行均無問題,在同事的機器上才出錯。
  3. NLog安裝自NuGet,檢查UnitTest.csproj,有以下設定:
    <ReferenceInclude="NLog">
    <HintPath>..\..\packages\NLog.3.1.0.0\lib\net45\NLog.dll</HintPath>
    </Reference>

    同時專案已啟動NuGet Package Restore,就算原本沒有也會自動下載安裝,更何況檢查packages目錄,NLog3.1.0.0\lib\net45\NLog.dll的確存在!

由以上觀察,怎麼都無法解釋單元測試專案為何使用2.1版,除非我的觀念有錯,這就可怕了!

攪和好一陣子,終於找出問題關鍵-同事跟我用的.sln不是同一個!!

先前因應專案位置調整,Boo.sln搬過家,我用的是新位置X:\TFS\Boo\Boo.sln,但舊檔未清,同事用到舊檔X:\TFS\Boo\Web\Boo.sln,而\TFS\Boo與\TFS\Boo\Web都有NuGet建立的packages資料夾。UnitTest.csproj於位\TFS\Boo\Web\UnitTest,當我參照NLog 3.1時,相對路徑..\..\packages指向\TFS\Boo\packages;同事取得新版時的確也還原下載了NLog 3.1,但pacakges跟著.sln,NLog 3.1被裝在\TFS\Boo\Web\packages下,而\TFS\Boo\packages只有先前安裝的2.1。我們在比對檢查時未察覺此一差異,直覺認定NLog 3.1存在,卻沒發現檢查的目錄並非UnitTest.csproj要參照的TFS\Boo\packages。由於csproj的NLog參數設定未指定版本,當<HintPath>提示路徑找不到,而其他專案包含NLog 2.1,VS編譯時使採用2.1版順利過關,但app.config有bindingRedirect設定,在執行階段才發揮作用,強制要求改用3.1版,碰!(跟上回VS可以編譯但在IIS爆炸如出一轍,未來遇到可編譯但執行階段才冒出的版本錯誤,應優先朝此方向偵辦)

詭異現象有了合理的解釋,幸好只是烏龍一場而非長期以來的觀念有誤,可喜可賀!

KO範例32-pureComputed

$
0
0

KO 3.2版險了美妙的HTML自訂元素,還有一項小革新 - pureComputed。

依據官方文件,pureComputed 的 pure 借用自 Pure Function,其主要設計理念在於:

  1. 計算 computed observable 時不應產生任何副作用。
  2. computed observable 結果不因估算次數或其他「隱藏」資訊而不同,只與其他 observable 值相關,對應到 Pure Function,其他 observable 的值相當於輸入參數。

這定義聽起來好學術,但用起來沒這麼複雜。簡單來說,pureComputed 有兩種狀態,Sleeping 與 Listening,在未被訂閱時(訂閱來源可能是其他 computed、pureComputed 或是 data-bind="" 宣告…等等),pureComputed 將停止重算,即便其所依賴的 obervable 改變,也不會重新計算結果。但是當它再被其他來源訂閱追蹤時,其表現就跟一般 computed 完全相同。

pureComputed 最大的好處在於能節省非必要的反覆計算,例如在大型 SPA(Single Page Applcation)中,在某些 UI 不顯示時,其相關 ViewModel 的  computed 並不需持續更新,此時便是使用 preComputed 的好時機。

用一個例子來展示:

在以上程式中,有一個每秒減少1的倒數值 observable(靠 setInterval 驅動),Countdown(n) 及 Computed(p) 分別用 data-bind="text: …" 繫結到 computed 及 pureComputed 寫成的函式取得文字。每次 computed 及 pureComputed 重算時,會在下方 textarea 寫 Log 方便觀察執行次數。最上方的按鈕可以切換 data-bind 繫結的元素出現與否(這裡用 if切換而非 visible,因為 visible 隱藏時元素仍存在DOM之中,會繼續訂閱 preComputed)。

由實驗結果可以發現,當元素被隱藏,computed 仍會持續重算,但 preComputed 因無人訂閱進入 Sleeping 狀態,不再因 setInterval 改變倒數值重算;當元素恢復顯示,訂閱生效,preComputed  進入 Listening 狀態,也恢復定期重算。

完整程式碼如下:Live Demo

<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<title>KO PureComputed</title>
<style>
</style>
</head>
<body>
<inputtype="button"value="Toggle Panel"data-bind="click: toggle"/>
<divdata-bind="if: showPanel">
<divdata-bind="text: countdownDispN"></div>
<divdata-bind="text: countdownDispP"></div>
</div>
<textareaid="logger"style="width: 480px; height: 200px;">
</textarea>
<scriptsrc="http://knockoutjs.com/downloads/knockout-3.2.0.js"></script>
<script>
var logger = document.getElementById("logger");
function log(msg) {
      logger.value += ", " + msg;
    }
function myViewModel() {
var self = this;
      self.countdown = ko.observable(256);
      self.showPanel = ko.observable(true);
      self.toggle = function() {
        self.showPanel(!self.showPanel()); 
      };
      self.countdownDispN = ko.computed(function() {
        log("computed");
return"Countdown(n)=" + self.countdown();
      }); 
      self.countdownDispP = ko.pureComputed(function() {
        log("pureComputed")
return"Countdown(p)=" + self.countdown();
      });
    }
var vm = new myViewModel();
    setInterval(function() {
      vm.countdown(vm.countdown() - 1);
    }, 1000);
    ko.applyBindings(vm);
</script>
</body>
</html>

最後補充一點:pureCompouted 在每次由 Sleeping 切換到 Listening 時也會重算,而 computed 只有在相依 observable 變化時才重算,若遇到頻繁切換 Sleeping / Listening 狀態的情境,使用 pureComputed 的效能反而不如 computed。

[KO系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post

關於Gmail五百萬筆密碼洩漏傳聞與資安提醒

$
0
0

在網路上看到 Gmail 密碼外洩消息,我「震驚」了…由於與個人資安切身相關,當然要深入了解,便找了資料來讀,順便整理分享。

本週二,有人在俄羅斯 Bitcom 論壇貼了一份 493 萬筆 Gmail 帳號密碼清單,被俄羅斯媒體 CNews 報導後,隨即在網路「瘋傳」(咳… 可以不要玩這些哏了嗎?)。論壇管理者事後移除清單裡的密碼,只留下帳號,而貼出清單的原PO則再跑出來聲稱其中 60% 的密碼是有效的。

初步分析帳號名稱,主要來自英國、西班牙及俄羅斯,且看起來是長時間蒐集所得,部分帳號已改過密碼或停用。依 Google 的看法,並沒有證據顯示 Gmail 系統出現漏洞導致密碼被竊,而目前大家也較認同洩漏源自「密碼共用」。猜測為使用者在其他網站註冊會員時使用 Gmail 作為登入ID,同時又將密碼設定與 Gmail 相同,一旦該網站被駭或作業疏失造成帳號密碼外洩,就等同 Gmail 帳號密碼流入他人之手。

網路媒體 Mashable 則有更明確的資訊。依據資安專家 Matteo Flora 檢測:清單有 60 位他認識的人,經連繫,其中 30 位指出清單上的密碼從未用在 Gmail 或是年代久遠。另外,陸續有很多位名列清單的使用者證實,清單所指的密碼從未用於Gmail。由此推論,清單來自其他網站會員資料庫的可能性頗高。

有人寫了網站服務可以檢查自己的帳號是否在洩漏清單中,但提供者身分不明,要當心輸入的 Email 被拿來發垃圾信。(另外,如果將來出現網站要你輸入 Gmail 帳號密碼檢查有沒有外洩,千萬別 Key 呀!)如果有疑慮,建議馬上改密碼,這是絕無副作用的自保之道。

個人結論如下:

  1. 此次所謂 500 萬筆 Gmail 密碼,蒐集自其他網站註冊資料的可能情極高,應非 Gmail 服務出現資安漏洞,不需驚慌,若有疑慮,就把密碼換掉吧!Z > B。
  2. 不要共用密碼!不要共用密碼!不要共用密碼!很重要,所以說三次。
    每個網站的安全防護強度不一,全部共用同一組密碼,代表任一網站被破,所有網站身分的安全性都亮紅燈。擔心密碼太多記不住?請愛用KeyPass
  3. 請善用「兩步驟驗證」,GmailHotmail都已支援。既然電子郵件已是網路身分的重要依據,裡面又擺滿個資,沒理由不讓它更安全一點。啟用兩步驟驗證後,要用陌生機器登入,就需要簡訊認證,如此歹徒就算拿到密碼,沒有你的手機也無法登入。
  4. 遇到有好心網站要你提供 Email、帳號及密碼說要幫你尋找朋友、檢查帳號安全,輸入前請張大眼睛,當心被騙。

【參考資料】

TechDays 2014筆記

$
0
0

【Kanban看板及Lean精實的技巧在大型軟體開發專案】

by  Ruddy & Franma

Ruddy 老師的課一定會有主題電影,這次是露西!

Kanban(看板) 源自豐田式管理,由 David J. Anderson (看板方法之父),將其轉化為 Kanban Method (看板方法)應用於敏捷開發。(推薦書藉:Anderson 的著作 - 看板方法:科技企業漸進變革成功之道,另一本 Henrik Kniberg 的書「 精益開發實戰:用看板管理大型專案」似乎更有名,但 Ruddy 老師以為該書部分論述易生誤導,建議先看 Anderson 的書。)

看板的重要組成,區分為 To Do –> Work In Process(WIP) –> Feedback –> Done 四個併列的垂直區塊,任務(Task)會處於四個狀態之一,透過拖拉的在狀態間移動,以求一目瞭然,突顯問題。其中,處於 In Process 的任務數量要愈少愈好(因為 Multitasking is Evil!),放進 Done 的項目要經過檢討後才能刪除。

多工是不好的!(文獻) 電腦靠多工能減少閒置提高效率,但人類在多工切換時,效率卻會嚴重下降,尤以 Programmer 為甚。

Kanban 只是很簡單的 FlowControl,重點在 Lean,不浪費,可以跟現有流程方法結合併用。

薑!薑!薑!薑!Ruddy 老師的自製看板系統內附上課講義登場!嗯… 很好,用HTML5 + CSS3 + MVC + AngularJS + SingalR(用於多看板即時同步狀態)寫的,並聲明以後不會再用 Windows App 寫上課程式。(心中OS:老師明明在寫 XAML,一眨眼就點完 AngularJS、MVC、SignalR 技能是要逼死誰?)

比較常用的開發方法:(括號內數字為流程產出項目的數量)

RUP(120+), XP(13), Scrum(9), Kanban(3), Do Whatever(0)
愈向右約束愈少、適應性更高,看板要求少,僅次於瞎搞(Do Whatever),容易實現,亦較易與現有方法併用。看板只規定兩件事:工作流程圖要可見、WIP要有上限(記住:多工是邪惡的)

Lean 的核心精神:不浪費!
Programmer最大的浪費,製造一堆 Bug,Debug 得很開心,卻全無生產力。

格言:敏捷不是學完一種敏捷方法就可以擁有的,是透過不斷追求所換來的成效。

格言:看板系統!= 看板方法,看板方法是一種不浪費,實現可持續的步調、克服變革的阻力。

敏捷化案例:

士氣低落,成員程度不均,協同合作困難,部分人仍陷於其他專案的泥淖,充滿某某人要離職耳語的末日專案團隊。

成功的管理作為:

*專注於質量
*減少進行中的工作
*頻繁交付
*根據交付速率來平衡需求
*消除變異性的根源,提升可預測性

為什麼不要有半成品(WIP)?同時間多工作好嗎?

利特爾法則(Little's law)

Lead Time(產出時間)= 存貨數量×生產節拍
Throughput Rate (生產效率)= WIP(存貨數量)/ Cycle Time,CT(周期時間)
要提高生產效率有兩種做法,提高WIP數量或減少CT。實例:麥當勞得來速。WIP 增加,Thoughput 上升,但 CT 也上升。用些許 Throughput 降低去交換 CT 明顯下降,值得!增加盈餘時間,讓員工有時間學習、幫助其他人,對團隊發展有益。

效能高的資源要放對地方,才能有效提高產率,故要每天觀察看板進行調校,以求減少浪費,達到最佳的 Throughput。

看板方法重點:

  1. Visualize workflow :電子跟實體各有好處
  2. Limit Work-In-Process:讓人員專心處理一件事,Cycle Time下降
  3. Measure and Manage Flow 管理流暢度:可以 CFD(累加流程圖)可以看Cycle Time

七原則:消除浪費、增強學習、盡量延遲決定、盡快交付、授權團體、嵌入質量、全域優化

Franma:個人經驗分享 - 混亂的專案,做不完的工作,雜亂的需求,層出不窮的Bug。

  • 花一週時間把所有工作記錄下來,禁止成員私下接受客戶指令
  • 對工作 Breakdown、界定關聯,重新評估排序
  • 當年用Excel管理,開發、認領工作、…、驗收 12個步驟,靠行政(妹)人工追進度
  • TFS及看板簡化了上述任務管理,轉檯很久再回到專案,也能藉視覺化的具體資訊快速上手
  • 不要貿然導流程,一定要有「妹」輔助(誤)

Trello:另一種管理任務的選擇,很適合管理私人工作。VS 可以與 Trello 密切整合,雙向同步。將工作任務混合進 Trello 與私人項目一併檢視。

個人覺得這場最受用的觀念 -個人看版(Personal Kanban,詳情可見 Ruddy 老師的專文

一個人跑 Scrum 有點蠢,但絕對可以藉由個人 Kanban 減少浪費,提升個人效率,管理好自己的生活與工作流程。逼自己檢視面臨的任務(不一定是工作,修吸塵器、騎車攻武嶺也算),衡量各項之間的優先順序!(個人覺得是時間管理中 重要、緊急 維度分析的具體實踐)

如何改善:開始結束時間(Cycle Time)、每項工作是否按步就班在可接受範圍內做完、超過WIP限制的狀況是否嚴重?要放寬WIP上限還是緊縮?

因為看得到所以知道:漏了什麼?每日排序、提醒(井然有序)、睡前自問(我忙嗎?目標是要逐漸不忙)、提醒自己

年看板、月看板(不漏東西)、星期看板(主要檢視對象)

課程相關資料下載:http://1drv.ms/1xyToMp

筆記完畢,謝謝收看,各位學員我們明年見!
(揮手下降… 但隨即被眾人拖上來) 

【後記】

應該有不少人發現,2008年開播的 TechEd/TechDays 文字播報隨堂筆記,今年怎麼沒了?

基於某個奇妙念頭,今年 TechDays 開始前就決定不寫隨堂筆記,雖然這是長達五年的傳統,但我們都知道:Deadline 是用來 Delay 的,傳統是拿來打破的,大家說是吧?(謎:並不是!)不過認真上課,抄紙本筆記還是要的。無奈造化弄人… 第二天在會場遇到 Ruddy 老師,冷不防被預告要欽點我幫大家抄筆記!噗,身為中年程序員,被心中的燈塔點名是種光榮,自當戳力以赴。而「Deadline 是用來 Delay 的,傳統是拿來打破的」之外應該還要再加一個「決定是用來推翻的」,也算實踐「敏捷」精神啦!於是,原本不存在的隨堂筆記出現啦~ XD

再回到:為什麼不寫了?奇妙的念頭是什麼?

說來有些可笑,扯不上什麼評估考量,就只是很單純的念頭,不想寫了。我不知道大家有沒有經歷過,但真的就這麼簡單,連自己都覺得莫名其妙 XD

人生,開始或結束某件事,不一定要有理由~ (事隔多年,忽然對阿甘正傳裡的這一段很有感覺,想跟阿甘說:嗯,我懂~)

【茶包射手日記】Chrome偵錯導致Window.Open失敗

$
0
0

使用 Chrome 偵錯網頁時,意外出現 window.open 失效的狀況。吊詭的是-偵錯是為了抓其他問題,與 window.open 無關,而該網頁運作已久,若 window.open 有問題,根本無法使用,不可能沒被發現。測試過程,window.open 並不是每次都出錯,出錯後重新啟動 Chrome 就會正常,一度讓我懷疑這是 Chrome 的 Bug。

歷經一番比對推敲,終於搞懂是怎麼一回事!

請看以下示範:

將中斷點設在 window.open(),按 F5 繼續執行可以過關。但如果在中斷點按 F10 逐步偵錯,就會遇到彈出式視窗被封鎖的狀況。(先前沒到留意封鎖彈出視窗的提示,才卡關卡這麼久)

瀏覽器只接受使用者點擊動作觸發的 window.open() 是常識!我的 window.open() 寫在 onclick 事件裡,合法性無庸置疑。但 Chrome 似乎認定透過 F10/F11 逐步偵錯呼叫的 window.open() 要歸類成「由程式自行觸發」,因而慘遭封鎖。

回想先前所謂的「時好時壞」,應是有時按 F5,有時按 F10 偵錯才導致不同結果;而重啟 Chrome 後,F12 偵錯工具未開啟,就不會遇到問題,形成重開後恢復正常的假象。至此,真相大白!

順便測了 IE 及 Firefox,驗證唯獨 Chrome 有此行為,日後使用 Chrome 偵錯 window.open() 時要當心。

2014光橋夜跑

$
0
0

第18馬,第三屆光橋夜跑。

第一屆慢跑變溯溪的經驗刻骨銘心!去年起,大會爭取到高架道路路權,忘記為了什麼理由失之交臂,今年毫不猶疑報了名。光橋,我又來了。

大會會場在光復橋下。

 

提早到會場,便四處亂逛,看到成排獎盃,幻想到自己有一天也站凸台… 我的計劃是:反正已經沒法再跑更快,就努力維持目前成績,等自己由男丙組、男乙組、男甲組一直升到男子長青組,應該就有機會拿下分組名次了,哇哈哈哈~

 

賽前的大鼓表演。

 

預計五點起跑,因交通問題延後15分鐘。雖然氣溫近30度,但多雲微風,也沒有要下雨的跡象,已屬值得感激的好天氣。

 

起跑後先沿堤防邊的道路跑約2公里折返再上高架道路,這一段伴隨著河濱淤泥的腥臭味,噁~

 

跑上高架道路了,有國道馬的 fu,屬於很好跑的賽道~ 無奈,近來狀況不佳,加上天熱,跑七分速都能讓心跳破錶,今天這場註定只能瞎混,確保無傷無痛不落馬就算成功,至於成績,就置之度外囉!

 

新北大橋到了,記得上回跑是跑旁邊的機車道,還要摸黑通爬樓梯,這回大大方方從橋面栱柱穿過,心情大好!

 

機會難得,跑友紛紛停下拍照留念,對於我們這群不在意成績的中後段班,這是跑馬拉松的重要樂趣之一。

 

通過新北大橋後,有一段可遠眺對岸的新光摩天大樓及台北101,這段夜景可謂光橋夜跑的精華之一。

 

一座我不認識的橋,兩側一整排 LED 燈會輪流切換不同顏色,相當華麗~ 時值晚上十點,清風徐來,通體舒暢,此時此刻漫步河畔,真是人生一大樂事!咦… 不對,我現在在跑馬拉松,已經快要六小時關門了!

 

很好,32公里暖身結束,比賽正式開始了!但是不意外地,拎抔已進入「力竭汗喘,殆欲斃然」的準升天狀態,只剩無靈魂的機械化循環:看錶->暗,要來不及了->跑起來->暗,好喘->停下來走路->看錶->暗,來不及了->跑起來…

 

通過最後的折返點沒多久,剩下不到2K,驚見對向車道兩台收容車緩緩駛來,後段班的同學們,塊陶呀!

 

籃球比賽的壓哨球好看,跑不出好成績,就來個壓哨馬吧!最後成績為5:57:34,關門馬再添一場。

 

回到會場人群已稀,但仍有冰仙草可喝,讚!忘了說,這場比賽沒什麼華麗補給,就是香蕉、檸檬、餅乾、麵包、西瓜,但水站密集,水與運動飲料準備充分,就算掛車尾,一路也沒缺過水,連降溫的大水桶自始至終也沒空過(有看到水車來回巡弋,應是機動補充策略奏效),感謝大會及辛苦的工作人員。

 

完賽禮:一瓶水、毛巾、麵包餐盒、完賽獎牌。

 

   

獎牌正反面。

最後介紹我新發現的心頭好:330ml 小水瓶!(旁邊的 TechDays 600ml 水瓶權充比例尺)
出汗量驚人的我,如何補水是夏天跑步的一大挑戰。前陣子來了一次六寺的跑步帶騎車偽鐵人LSD,前後喝下超過三公升的飲料,回家體重還是直掉三公斤,足以想像出汗量有多恐怖。過去最常發生的狀況是:流汗口渴,一到水站就迫不及待牛飲三五杯還止不住口乾,但一口氣灌下過量水分腸胃來不及吸收反成負擔(標準建議是每20分鐘補充100-200ml的水份,每小時不要超過800ml,參考來源),老是搞到一肚子水難過得要死。

改用小瓶子的好處是可以在水站裝水再分次慢慢喝,加上容量已知較好估算補水量,小瓶身又比 600ml 輕巧好握,決定將它列入「馬拉松好汗」的奧林匹克指定配備。

 

最後一段路通過新北大橋時仰頭拍下這張照片,光線不足,線條模糊,是張不合格的作品,但魔幻般的色彩令人難忘,每每看到都會憶起當下獨特的氣氛與感動,就用它劃上句點吧!


【茶包射手日記】硬碟燈長亮無法開機

$
0
0

週日想將光橋夜跑 GPS 資料上傳到家裡的桌上型電腦,發現原本應處於睡眠狀態的電源燈閃爍不見了,原以為跳電導致關機不以為意,直接按下電源開關,電源燈與硬碟燈長亮,螢幕毫無反應,硬碟燈未出現正常開機應有的熄滅閃爍。事情大條,電腦壞了~

拆開機殼,確認主機板的上的電力指示 LED 會發亮,按下電源開關,CPU 風扇會運轉,但螢幕無訊號。開始用排除法檢測,拔掉顯示卡,拆掉硬碟、光碟機,一路移除所有周邊,最後只留主機板、CPU、RAM及電源供應器(PSU),清除 CMOS,記憶體拔到只剩一條,甚至心一橫拆掉散熱風扇重裝 CPU。結果依舊,開機後電源燈及硬碟燈長亮,CPU 風扇會轉,無開機反應。

爬文查到硬碟燈長亮的案例多是主機板或 CPU 損壞,但要命是的 CPU 腳位規格每幾年就翻一次,壞掉的是 i7 2600,腳位為 1155,家裡空有兩台 E6400 為 Socket 775,規格不符無法替換比對。一度懷疑是 PSU 有問題,故意拔下為 CPU 供電的兩個 4PIN 12V 電源插頭(下圖中 CPU 散熱風扇上方的外接電線處),一樣呈現 HD 燈長亮 CPU 風扇會轉但不開機。用三用電錶量了 4PIN 接頭輸出,超過 12V,但還有一種可能:PSU 負載能力不足,撐不住 CPU 耗電。我不敢在開機狀況下去量主機板驗證這點,主機板表面一堆米粒大的 SMT 零件,手一抖就放煙火了。為了求證,狠心拆了另一台 PC 的 PSU 接線測試 - 開機後 HD 燈會熄,反應不同了。趕緊接上螢幕,出現 BIOS 開機畫面… 喵的,我感動到快哭了!主板跟 CPU 沒壞,又是他X的 PSU 掛了。

下班去光華帶了顆新電源回家,休克兩天的電腦,終於醒過來了!

心得:開機後電源燈與硬碟燈長亮,CPU 風扇轉動但螢幕無訊號,硬碟燈未出現正常開機應有的閃爍 -> 可能是 PSU 供電問題,請優先檢查。

後話:打電話問過客服,壞掉的 PSU 仍在保固內(好像是五年免費、兩年免費收送),附上 100 元回程運費寄去公司,約 7-14 天就會修好。決定趕快送修,讓家裡多一顆 PSU 備品,下回再遇電腦故障可優先檢測 PSU 問題,才不會像這回折騰大半天!

善用Selenium擴充,測試更加得心應手

$
0
0

講到網頁自動測試,一哥地位非 Selenium 莫屬(參考)。雖然 Selenium IDE 支援錄製網頁操作產生測試腳本,但錄製產生的指令常依賴元素的位置順序定位,配置稍有更動立刻破功。依實務經驗,開發者依據設計邏輯手工撰寫還是較可靠的作法,測試指令也較簡潔有效率。Selenium 本身提供好幾種定位器,包含:ID、Name、DOM、XPATH、CSS Selector…等。習慣 jQuery 再回頭使用這些彈性受限的定位器,每每遇到複雜情境,都會先想 jQuery 如何操作,再設法對應成 CSS Selector,但三不五時就會超出 CSS 能力範圍(例如::contains()、:has(),或是結合 closest()、parent() ),心中滿是恨鐵不成鋼的怨氣。

既然 jQuery 好用,我測試的網頁也多半已載入 jQuery,為什麼寫測試時必須將就這堆能力受限的內建定位器呢?最後,我找到一個解法,為 Selenium 寫擴充函式直接支援 jQuery!

我寫了一個user-extensions.js,為 Selenium 類別加入幾個新函式:

  • locateElementByJq: 所有適用 css=… 選擇器的場合,都可以寫成  jq=… 使用 jQuery 選擇器
  • getJqText:支援 verifyJqText/waitForJqText,傳入 jQuery 選擇器取回 .text()
  • getJqVal:支援 verifyJqVal/waitForJqVal,傳入 jQuery 選擇器取回 .val()
  • getJqHtml:支援 verifyJqHtml/waitForJqHtml,傳入 jQuery 選擇器取回 .val()
  • doExecute:Selenium 雖然有 runScript,但執行時 this 指向 Selenium 物件,如要存取 DOM 必須寫 window.document.* 有點囉嗉。改良版的 execute 支援直接輸入 JavaScript,以 window.eval() 執行,就跟在 F12 主控台下指令一樣方便。因此,execute 是為我寫測試最慣用的兵器。
  • getExecute:沿用 execute 概念,可執行複雜 JavaScript 指令自網頁取值,可配合 verifyForExecute、waitForExecute 使用,威力強大!(例如:取得元素內容進行 Regular Expression 比對、檢查元素個數介於 2 到 4 之間… 這類內建指令不易實現的運算比對)

user-extensions.js 的內容如下:

//http://docs.seleniumhq.org/docs/08_user_extensions.jsp
Selenium.prototype.doExecute = function(script) {
this.browserbot.getCurrentWindow().eval(script);
};
Selenium.prototype.getExecute = function(script) {
returnthis.browserbot.getCurrentWindow().eval(script);
};
Selenium.prototype.getJqMethod = function(selector, method) {
returnthis.getExecute('$("' + selector + '").' + method + '();');
};
Selenium.prototype.getJqText = function(selector) {
returnthis.getJqMethod(selector, "text");
};
Selenium.prototype.getJqHtml = function(selector) {
returnthis.getJqMethod(selector, "html");
};
Selenium.prototype.getJqVal = function(selector) {
returnthis.getJqMethod(selector, "val");
};
PageBot.prototype.locateElementByJq = function(selector, inDocument) {
// FF/Chrome/IE9+: defaultView, OldIE: parentWindow
return (inDocument.defaultView || inDocument.parentWindow)
            .eval("jQuery('" + selector.replace(/'/g, "\\'") + "')[0];");
};

要使用它,只需開啟 Selenium IDE 選項設定,加入 user-extensions.js 路徑。血清注射完成,美國隊長要現身了!

用一個簡單網頁示範:

<!DOCTYPEhtml>
 
<html>
<body>
<div>
<inputtype="text"value="Darkthread"/>
<spanclass='name'>Jeffrey</span>
</div>
<div>
<inputtype="button"value="Hide"/>
<span>Test</span>
</div>
<divclass="last">
<inputtype="text"value="Selenium"disabled/>
<spanstyle="display:none">Selenium IDE</span>
</div>
<scriptsrc="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js"></script>
<script>
            $(":button").click(function() {
                $(".name").hide(3000);
            });
</script>
</body>
</html>

如以下圖示,展示使用 jq=div:has(:text) 配合 verifyElementPresent 可以做到 CSS 不支援的 :has() 選擇器;verifyExecute 時應用 JavaScript .match() 做 Regular Expression 比對;另外還展示用 execute 執行 JavaScript 直接對 DOM 動手動腳。

能使用 jQuery 選擇器,又可直接用 JavaScript 操作網頁或進行運算,就能輕鬆在 Selenium 測試案例加入各種自訂邏輯,寫起測試案例再也不會被綁手綁腳囉!

再談TypeScript的this

$
0
0

前陣子談過 TypeScript 使用 this常遇到的問題,經網友提醒,我才發現 TypeScript 對 () => { … } (Arrow Function Expression,箭頭函式表示式,索性簡稱「箭函式」XD )加了魔法:自動宣告 var _this = this ,並將 () => { … } 裡的 this 自動換成 _this。

前陣子重整一個中型網站,將 JavaScript 翻成 TypeScript,改寫不少原本用JavaScript function 模擬的 ViewModel 類別,不免觸及存取自身屬性或方法的情境。原本 ViewModel 裡用 var self = this 的方式解決 this 混淆問題(self 慣例源於Knockout,請參照 Managing ‘this’ 一節),起初我依賴箭函式(Arrow Function Expression)處理 this 置換,省下自行宣告 var self = this 的麻煩,但踩過一些地雷之後,最後仍決定回歸 var self = this 做法。

使用「箭函式 + this」我遇到比較容易混淆的情境有以下幾種

1.箭函式的 this 置換只適用在類別的函式宣告

module Blah {
class Foo {
        name = "Foo";
        speak: () => void;
  }
  export class Boo {
      name = "Boo"
      test = () => {
var foo = new Foo();
          foo.speak = () => {
              alert("Hi, I am " + this.name);
          }
          foo.speak();
      }
  }
}
 
var b = new Blah.Boo();
b.test();

以上案例有兩個物件 Foo 及 Boo,在 Boo 類別內宣告 test 箭函式,在其中建立 Foo 並指定其 speak 方法,寫成 foo.speak = () => { alert("Hi, I am " + this.name); },由於這段程式被放在 Boo.test() 中,雖然我們透過箭函式指定 foo.speak,但因為不在 Foo 類別內部,故 this 指向 Boo 的 Instance(執行個體),而非 Foo 的 Instance,執行結果將為 "Hi, I am Boo"。如想顯示 Foo 的名稱,在這個案例用 foo.name 取代 this.name 即可。Live Demo

2.箭函式內的 this 置換屬強制性,無法避免

考慮以下範例:

class blah {
    target: JQuery;
    constructor(t: JQuery) {
this.target = t;
    }
    setColor = () => {
this.target.each(() => {
var o = $(this);
            o.css("color", o.text());
            o.text("Converted");
        });
    }
}
$("body").append("<div class='c'>red</div>");
var b = new blah($('.c'));
b.setColor();

我們在 setColor 箭函式中使用 jQuery.each(),並很時尚地也用箭函數寫 .each() 處理邏輯。很不幸的,以上程式無法正常運作,理由是在類別方法箭函式內的箭函式,this 也會被一併置換成類別物件的 Instance,如下圖所示。

箭函式中的 this 置換屬強制性,無法避免,但要克服很簡單,將箭函式改回傳統 function() { … } 即可:

    setColor = () => {
this.target.each(function() {
var o = $(this);
            o.css("color", o.text());
            o.text("Converted");
        });
    }

踩過幾次地雷,大概就能摸清楚 TypeScript 箭函式的 this 特性,幾乎不再誤用。但經一番琢磨,決定回歸自己宣告 var self = this,並在函式中使用 self 表示物件 Instance 的做法,不依賴 TypeScript 幫忙置換。理由是難免會遇到箭函式內穿插 $.each()、.click(function() { }) 等 this 代表其他物件、元素的場合,用 self 與 this 將二者明確區隔開,Z > B。

我的TypeScript新手村心得

$
0
0

前陣子將一個中型網站的 JavaScript 翻寫成 TypeScript,轉換完數千行程式。身為 TypeScript 魯雞(Rookie),少不了一段步步踩雷、天天摔坑的日子,接著就漸入佳境,轉換後體驗到按  F2 立即更名、-調介面便知哪些地方要改的便利,令人感動不已。這段時間累積了一些心得,整理如下,供其他也要挑戰 JavaScript 轉 TypeScript 的朋友參考:

  1. 重要觀念:左手 TypeScript 只是輔助!
    JavaScript 還是得學好,但不需要研究如何用 JavaScript 實踐繼承、介面、Namespace 等進階技巧,這部分 TypeScript 已提供一套容易理解與應用的做法。
    但 TypeScript 終究只是輔助,充其量幫你「化繁為簡」、「理出頭緒」。在 JavaScript 不能 Find Reference、Goto Definition,不能靠編輯器 Rename 某個屬性或方法,無法在編譯時期找出打錯字,當 JavaScript 長到上千行,這些缺點會被放大,讓你置身地獄,而 TypeScript 算是種救贖,有助於減輕痛苦(別誤會,並不是從地獄直上天堂,大概是從 18 層爬到第 8 層吧!許多 JavaScript 及前端面對的難題仍然得咬牙面對)。既然 TypeScript 只是化繁為簡、理出頭緒,也要有繁可化,當有能力寫出複雜的 JavaScript,才能享受 TypeScript 的好處。
    改用 TypeScript 不是打血清,無法讓 JavaScript 魯雞變美國隊長,充其量只是讓開發者有機會在 JavaScript 享受強型別語言的結構化與嚴謹,讓重構(Refactoring)不再是天方夜譚,讓複雜的JavaScript 程式毛線球變得沒那麼複雜,如此而已。但是,當你的 JavaScript 程式已經攪成很大一團,光這點改善就足以讓人落淚。(話說,改寫前的版本也能讓人落淚,只是哭的是要接手維護的那一位)
  2. 雷中之王 - this
    this 是我踩到最多的地雷!先前已陸續分享過:
    * TypeScript的this陷阱
    * 再談 TypeScript 的 this
  3. 善用 module
    TypeScript 提供了物件導向,但在實務上,我只有需要享受繼承的優點時使用 class,使用更多的是 module。基本上,建議將函式、參數儘量都包進 module,例如:
    module Blah {
        export var Boo: string = "Foo";
    var internalVar: string = "Jeffrey";
        export function Test() { 
            alert("Test!");
        }
    function internalFunc() {
            window.console && console.log("Test"); 
        }
    }

    它將被編譯成
    var Blah;
    (function (Blah) {
        Blah.Boo = "Foo";
    var internalVar = "Jeffrey";
    function Test() {
            alert("Test!");
        }
        Blah.Test = Test;
    function internalFunc() {
            window.console && console.log("Test");
        }
    })(Blah || (Blah = {}));

    這種(function() { … })();的寫法稱之為Immediately Invoked Function Expression (IIFE)。
    TypeScript 透過 IIFE 讓 internalVar 及 internalFunc 只在該 module 範圍內才能存取;而Boo 及 Test 前方加註 export,則視為 Boo 公開的屬性及方法,可透過 Blah.Boo 及 Blah.Test() 存取。如此,就不怕在多段程式出現同名變數或函式彼此覆蓋。
    另外,有注意到 (Blah || ( Blah = {} )) 的巧妙寫法嗎?這個設計讓你可以在多段 JavaScript 重複宣告同一個 module 的不同片段,最後仍融合在一起,就像 C# 的 partial class 一樣方便。

  4. 關於第三方程式庫定義檔
    在 TypeScript 的強型別中,所有介面、方法要經宣告定義方能使用。JavaScript 轉 TypeScript 時,一定會遇到第三方程式庫 API 介面未定義,無法編譯的情境。莫非要把第三方程式庫也翻寫成 TypeScript? 當然不是!有三種解法:
    * 透過 declare var someObject; 將物件宣告成任意型別不做檢查,但 TypeScrpt 的強型別優勢也會因此消失
    * 查詢 DefinitelyTyped是否有善心人士已經寫好定義檔,透過 NuGet 下載安裝
    * 自己為程式庫加上定義檔,順便上傳 DefinitelyTyped 做功德。
    寫定義檔不難,常用技巧就那幾個,值得花點時間學習。
    延伸閱讀:為jQuery Plugin撰寫TypeScript定義檔
  5. 強制指定型別
    在 TypeScript 中一定會遇到強型別與任意型別混用的狀況。例如我遇到的一個實例:
    kendo.toString 有個多載宣告是 function toString(value: number, format: string): string;
    在寫 KO Handler 時,我打算由 ko.utils.unwrapObservable(valueAccessor()) 取回數值、由 allBindingsAccessor().format 取回格式字串,要藉由 kendo.toString() 轉出格式化數字。但很不幸地,ko.utils.unwrapObservable 跟 allBindingsAccessor().format 都是任意型別(any),即便我們確定它們一定是數字跟字串,也過不了編譯器這關。面對這種狀況,可比照 C# (T) someVar 的概念,利用 <T> someVar 強迫宣告型別,該宣告並不會產生額外 JavaScript 程式碼,純粹是向編譯器拍胸脯擔保該變數的型別,這樣就不會被編譯器刁難囉!
    kendo.toString( 
                    <number>ko.utils.unwrapObservable(valueAccessor()),
                    <string>allBindingsAccessor().format)
    再來一個例子:

    即便 y 是 any 型別,由於三元運算子嚴格限制冒號前後的型別必須相同,故上述寫法在 JavaScript 絕對可行,在 TypeScript 卻無法編譯,這也要靠指定型別 打通關:
    var y = (x == 1) ? <any>{ a: x } : x; 搞定收工。
  6. HTML元素強型別轉換
    前一點提到的強型別問題也常發生在 HTML 元素操作上,例如: 

    jQuery 集合物件的陣列元素被定義成 HTMLElement,而 checked 是 <input> 才有的屬性,需要加個轉型,改成:
    (<HTMLInputElement>$(this).find(":checkbox")[0]).checked = true;
    才能成功編譯成功。
  7. 使用列舉
    在 C# 中列舉可以嚴格限制變數值範圍,杜絕打錯字的風險,也方便更名調整,好處多多,在 TypeScript 也建議多多利用。但應用時一定會面臨列舉、字串、數字間的轉換,可參考先前文章:TypeScript列舉型別
  8. 動態加入屬性及方法
    在 JavaScript 裡,我們可以直接用 window.boo = "foo"、localStorage.foo = "boo" 為現有物件動態加上自訂屬性、方法,但它們未出現在定義檔,將導致編譯失敗。為臨時性的動態成員更動定義檔不符效益,可改寫為 window["boo"]、localStorage["boo"] 解決問題。
  9. 一個曲折的型別調整案例
    以下的寫法還常見的,可以用來計時,其中 diff 傳回的結果即為時間長度(單位為ms):
    var st = new Date(); 
    var ed = new Date(); 
    var diff = ed – st; //耗時(ms)

    但以上程式無法通過 TypeScript 編譯,理由是 Date 型別不能相減。那麼 <number>ed – <number>st 呢?很抱歉,Date 不能轉型成 number:Cannot convert 'Date' to 'number': Type 'Number' is missing property 'toDateString' from type 'Date'. 
    最後我的解法是把 st 及 ed 宣告成任意型別:(any 無敵!但不符合強型別精神,請參考更新說明)
    var st: any = new Date();
    var ed: any = new Date();
    var diff = ed - st; 

    [2014-09-19更新]
    陸續接到好幾位朋友的回饋,此案例應改用 Date.getTime() 以符合 TypeScript 強型別精神,在此補上:
    var st: number = new Date().getTime();
    var ed: number = new Date().getTime();
    var diff = ed - st;

  10. 未賦與初值的類別屬性,第一次存取才會出現
    考慮以下程式:
    class boo {
        prop1: number;
        prop2: number;
    }
    var b = new boo();
    for (var p in b) {
        alert(b + "->" + b[p]);
    }

    執行時會看到什麼?"prop1 –> undefined" 跟 "prop2 –> undefined"?錯!什麼都沒有。
    一開始我沿襲 C# 的類別概念,總想成在類別宣告過的屬性,物件建構完時屬性就會存在,只是未被賦與初值。其實不然,在 TypeScript 類別宣告屬性但未給初值,僅是賦與該屬性的合法使用權,並不會產生任何對應的 JavaScript 程式。上述程式轉成的 JavaScript 如下:
    var boo = (function () {
    function boo() {
        }
    return boo;
    })();
    var b = new boo();
    for (var p in b) {
        alert(p + "->" + b[p]);
    }

    如果你想確保建構物件後屬性就存在,記得要給初值:
    class boo {
        prop1: number = 0;
        prop2: number = 0;
    }
    var b = new boo();
    for (var p in b) {
        alert(p + "->" + b[p]);
    }

    修改後產生 JavaScript 如下,就能如預期執行了:
    var boo = (function () {
    function boo() {
    this.prop1 = 0;
    this.prop2 = 0;
        }
    return boo;
    })();
    var b = new boo();
    for (var p in b) {
        alert(p + "->" + b[p]);
    }

以上是我的新手村心得,祝大家順利升級,早日出村冒險。

【茶包射手日記】System.Core 2.0.5無法載入錯誤

$
0
0

接獲報案,某 ASP.NET 4.0 Web Form 專案部署後發生錯誤:

FileLoadException: Could not load file or assembly
'System.Core, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, Retargetable=Yes'
or one of its dependencies. The given assembly name or codebase was invalid.
(Exception from HRESULT: 0x80131047)

訊息明確,很快查到 Scott Hanselman 的文章:Fixing System.Core 2.0.5 FileLoadException, Portable Libraries and Windows XP support。原因出在網站引用 .NET Portable Class Library(讓程式庫能被Windows Desktop、Windows Phone、Silverlight、Windows App、XBox 360 共用的一種技術)而 .NET 4.0 Framework 未更新到新版。問題根源確認後,事件便在通知系統管理人員安裝 KB2468871更新後平和落幕。(謎:意思是本來打算白刀子進紅刀子出來著?)

餘下一個問題,單純網站為什麼會跟 Portable Class Library 扯上邊,拿到詳細錯誤訊息,答案揭曉:網站裡用了 Telerik RadControl 元件,引爆點在 RadScriptManager 類別,應是元件廠商考慮程式庫共用,將部分共用類別轉為可攜形式,遇上未即時更新 .NET Framework 的平台才導致問題。

Viewing all 2429 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>