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

【茶包射手日記】送出鈕OnClick事件return false在IE7/8無效

$
0
0

某個網頁出現<input type="submit"> onclick事件return false;卻無法阻止Postback的情況,進一步測試發現問題只出現在IE7/8,表單在IE9則如預期會因onclick事件回傳false取消Postback。

使用刪去法,將網頁元素及程式一點一點移除並反覆測試,終於查出問題所在,答案有點離奇,竟是因為網頁使用了jQuery BlockUI的$(element).block()造成異常。深入jquery.blockUI.js原始碼,更進一步鎖定問題來源,網頁所用的jquery.blockUI 2.07版(經測試改用2.5.4新版時倒無此問題),於設定block層時會透過以下程式碼掛載document.onclick事件:

// bind anchors and inputs for mouse and key events
var events = 'mousedown mouseup keydown keypress click';
b ? $(document).bind(events, opts, handler) : $(document).unbind(events, handler);

而在handler函數最後一行為:

// allow events for content that is not being blocked
return $(e.target).parents().children().filter('div.blockUI').length == 0;

推敲其用意是在全網頁被.blockUI()遮蔽時,故意傳回false以取消對document的所有滑鼠及按鍵操作! 在這個案例中,使用了block(),handler事件會生效,但由於非blockUI(),故document.onclick時預期將return true; 透過IE Dev Tools偵錯程式碼,在按下送出鈕時,<input type="submit"> onclick事件return false,document.onclick卻return true,同一個click動作出現兩個不同的return值,故有可能是IE7/8/9對此一情境的行為不同所致。

整理成一個重現問題的極精簡範例:

<%@Page Language="C#"%>
<scriptrunat="server">
void Page_Load(object sender, EventArgs e) 
    {
if (IsPostBack) {
            Response.Write("PostBack!");
            Response.End();
        }
    }
</script>
<!DOCTYPEhtml>
<htmlxmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Test Return False</title>
</head>
<bodyonclick="return true;">
<formid="form1"method="post"runat="server">
<inputtype="submit"value="Submit"onclick="return false;"/>
</form>
</body>
</html>

這個網頁在使用IE7/8相容模式檢視時,按下Submit鈕,即便onclick="return false"仍會觸發PostBack。使用IE9/Chrome/Firefox則不會PostBack,算是驗證了此一行為差異。

一個簡單的解決方案是修改送出鈕onclick事件,除了return false再加上event.returnValue = false,如此即可在IE7/8/9/Chrome/Firefox均順利運行:

<input type="submit" value="Submit"  onclick="if (event) event.returnValue = false; return false;"/>


Hacking樂無窮-ReportViewer隱藏關卡: 匯出TIFF檔選項

$
0
0

ReportViewer預設的匯出格式只有PDF、Word跟Excel三種,如果還想再增加匯出TIFF檔的選項,該怎麼做?

由於ReportViewer未提供匯出檔案格式的設定選項,網路上可找到一些替代做法:

  1. 設定ShowExportControls=false,隱藏工具列上的匯出鈕,在網頁加入自訂匯出鈕,於Server-Side OnClick事件以Render() API自行產生匯出檔並傳回。(參考: 皮尼網這走- Cliend-Side Report 匯出 Excel, PDF)
  2. 透過Javascript事後修改頁面的匯出格式下拉選單(參考: 如何改變 ReportViewer 的預設匯出格式為指定格式:PDF(一點通系列 - MVP 撰寫))

兩種做法多少都得費點小工。事實上Reserved.ReportViewerWebControl.axd本就支援TIFF檔,只差在預設匯出格式下拉選單未提供連結而已,理論上只需仿照原有選項再多加一個TIFF項目即可,但經初步評估,ReportViewer 2005/2008的匯出格式選單是單純<select>,ReportViewer 2010/2012則改成較華麗的Microsoft AJAX Menu,要新增選項頗花手腳。駭客熱血再起,就追進ReportViewer原始碼,發現一個有趣的方法: LocalReport.ListRenderingExtensions 方法 (Microsoft.Reporting.WebForms),而ReportViewer就是依這個清單決定支援哪些匯出格式,用一小段程式來檢視RDLC LocalReport支援的匯出選項:

foreach (var opt in ReportViewer1.LocalReport.ListRenderingExtensions())
    {
        Response.Write(string.Format("<li>{0}-{1}-{2}</li>",
            opt.Name, opt.LocalizedName, opt.Visible));
    }
    Response.End();

結果如下:

  • Excel-Excel 2003-False
  • EXCELOPENXML-Excel-True
  • IMAGE-TIFF 檔案-False
  • PDF-PDF-True
  • WORD-Word 2003-False
  • WORDOPENXML-Word-True

有趣吧? 原來RDLC還有三種額外格式: Excel 2003、TIFF以及Word 2003,只是選項的Visible屬性被設為False而被隱藏。換句話說,只要把該屬性改成True,不費吹灰之力,選項就會在選單中出現囉~

唯一的小挑戰--Visible是唯讀屬性,無法直接修改,但這可難不倒程式魔人: 二話不說,我在檯面上覆蓋一張Reflection牌,結束這回合...

using System;
using System.Linq;
using System.Reflection;
using Microsoft.Reporting.WebForms;
 
publicpartialclass Report : System.Web.UI.Page
{
protectedvoid Page_Load(object sender, EventArgs e)
    {
if (!IsPostBack)
        {
using (var ctx = new MarathonEntities())
            {
                var data = ctx.RaceRecords.OrderBy(o => o.Rank).ToList();
                ReportViewer1.LocalReport.DataSources.Add(
new ReportDataSource("RaceDataSet", data));
                TurnOnImageExportOption(ReportViewer1.LocalReport);
            }
        }
    }
protectedvoid TurnOnImageExportOption(LocalReport report)
    {
        RenderingExtension imageOption = report.ListRenderingExtensions()
            .SingleOrDefault(o => o.Name == "IMAGE");
if (imageOption != null)
        {
//Set IsVisible property to true via Reflection
            FieldInfo fiVisible = imageOption.GetType().GetField("m_isVisible",
                BindingFlags.NonPublic | BindingFlags.Instance);
            fiVisible.SetValue(imageOption, true);
        }
    }
}

薑!薑!薑!薑~ 只用不到五行程式碼,TIFF檔選項就出現了!

PS: 類似做法也可以用來隱藏選項、調整選項順序,很酷吧! 不過這種Hacking解法涉及元件未公開的API,可能會在元件改版後失效,跟手機JB的概念差不多,不在官方支援之列,特此說明。

關於Reporting Service PDF中文亂碼的一點研究

$
0
0

最近研究到SSRS的匯出PDF功能,由於應用平台涵蓋SSRS 2000,對於PDF的中文亂碼問題多了一點心得,特整理備忘。

首先,PDF對於字型有兩處理方式: 第一種是只記載各段文字所使用的字型,Client端讀取軟體開啟時再使用所處作業系統相同名稱的字型繪製文字。因此,Client端與製作文件端的字型檔必須100%相同,才能產生 完全一致的呈現結果。當跨越不同平台時容易衍生問題,例如Linux、Mac與Windows的標楷體不盡相同,便可能發生閱讀時看到的排程與原始設計有所出入的情況。

於是PDF提供第二種做法: 把有文件中用到的中文字向量資料從字型檔擷取出來嵌入PDF中,如此即便Client沒有安裝相同字型也能正確顯示。(也可以將整個字型檔全部嵌入,如此該PDF檔未來甚至能直接在Client端編輯,但中文字型檔常高達數十MB,實務上較少有嵌入完整中文字型的做法)

SSRS從2005起才開始支援PDF字型內嵌(SSRS 2005SSRS 2008),SSRS 2000並不支援:

Fonts

The PDF rendering extension does not embed fonts. Fonts that are used in the report must be installed on the report server and on the client computers used to view the report.
來源: http://technet.microsoft.com/en-us/library/aa178934(SQL.80).aspx

換句話說,SSRS 2000匯出的PDF檔要正確呈現,Client必須具備跟Report Server完全相同的字型。那麼如果Server跟Client都是Windows,通通用最保險的"新細明體"總行了吧?

很不幸地,答案是"未必"!

從Vista/Windows 2008起,微軟內建的新細明體(PMingLiU)字型改採Unicode 3.1標準,造成Windows 2000/XP/Windows 2003上的"新細明體"與Vista/Windows 7+/Windows 2008的"新細明體"變成兩套規格不同的字型,在Windows 2000/Windows 2003使用指定新細明體製作PDF檔,在XP上開啟還算正常,一旦改用Windows 7,照樣亂碼滿天飛。
(補充: 依維基百科,XP/Windows 2003用的是新細明體3.21-4.55,Vista/Win2008起為新細明體6.02版,開始改採Unicode 3.1標準,微軟曾出了一個新細明體更新套件試圖讓XP/2003的新細明體也改為Unicode 3.1標準,但問題不斷罵聲不絕,黯然下架...)

最後來個實驗驗證。製作一個RDL檔,放入八個TextBox分別標註成不同字體(新細明體用"PMingLiU"及"新細明體"中英文FontFamily各測一次),Value的部分則為FontFamily值加上"中文測試"四個字:

<TextboxName="textbox8">
<Top>1.25in</Top>
<Width>1.8in</Width>
<Style>
<FontFamily>Arial Unicode MS</FontFamily>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
<ZIndex>7</ZIndex>
<CanGrow>true</CanGrow>
<Left>1.625in</Left>
<Height>0.25in</Height>
<Value>Arial Unicode MS 中文測試</Value>
</Textbox>

測試一 SSRS 2000 on Win2003,Client端為Win2003,PDF只有細明體及新細明體正確無誤

測試二 SSRS 2000 on Win2003,Client端為Win7,PDF中連新細明體都爆了

測試三 SSRS 2005 on Windows 2003,Client端為Win7,PDF顯示大致正常,只有Arial因屬純英文字型,不含中文字,故中文部分變成方塊,PDF不像Word/網頁,若該字在指定字型找不到對應時並不會改用預設字型,故Arial中大變方塊的行為還算在預期之內。

結論: SSRS 2000的PDF匯出功能在中文呈現上有嚴重缺陷,塊陶啊~

防止程式同時執行多份,比檢查Process清單更好的方法

$
0
0

在某些情境下,我們需要限制同一支程式同時間只能執行一份,很直覺的想法是檢查Process清單,由程式名稱在清單中出現一次以上來判斷是否已有同名程式在執行。這個做法直覺有效,在大部分情境也適用,甚至在CodeProject上也不乏類似"教學範例",很自然地,這也一度是我愛用過的解法(誰沒有過去呢?);但是,檢查Process清單並非是最簡潔嚴謹的做法。(前述CodeProject文章的評比只有一顆星,留言中不乏負評,這故事告訴我們,爬文時莫急莫慌,至少先看看鄉民怎麼說再決定要不要抄)

ProcessName比對法的缺點不少,更完美的做法是善用Mutex機制,保哥對此有一篇詳盡說明。剛好讀到K. Scott Allen另一篇古老文章,對於ProcessName法的缺點及Mutex應用還有些額外闡述,簡單整理後,也來狗尾續貂一番。

Scott分析了幾個ProcessName法難以應付的"極端案例":

  1. 當人品差到極點,可能出現兩個程式同時開始執行,同時看到對方,同時關閉的情境。
  2. Process.GetProcesses()取得的是全機器的Process清單,難以滿足本機登入及遠端登入(Terminal Service)限制每個登入階段(Login Session)只執行一份的需求。(註: 應有方法依Session再分組,但得花點工)
  3. 若不幸系統中有另一個同名同姓的其他Process,就杯具了。(註: 可以加上路徑比對避免)

因此,利用Mutex的強制排他性,可以輕易實現任何時候只有單一程序成功。而採用GUID或自訂唯一的Mutex名稱,則可防止同名同姓Process的干擾。

Scott文章另外提到了"using (Mutex)包住到程式結束為止"的技巧,目的在避免當後段程式未再使用Mutex物件被GC機制意外回收的極端案例。(註: Scott範例程式中的GC.Collect()是故意加上的,旨在觸發資源回收突顯問題,實務上除非記憶體耗用驚人,Mutex被意外回收的情境並不常出現,但改良版無疑更加完美)

以下是加了註解的簡單範例,展示如何實現程式防止多份同時執行: (移除@"Global\"可做到每個登入階段限定一份)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
 
namespace SingleInstance
{
class Program
    {
//每支程式以不同GUID當成Mutex名稱,可避免執行檔同名同姓的風險
staticstring appGuid = "{B19DAFCB-729C-43A6-8232-F3C31BB4E404}";
 
staticvoid Main(string[] args)
        {
//如果要做到跨Session唯一,名稱可加入"Global\"前綴字
//如此即使用多個帳號透過Terminal Service登入系統
//整台機器也只能執行一份
using (Mutex m = new Mutex(false, "Global\\" + appGuid))
            {
//檢查是否同名Mutex已存在(表示另一份程式正在執行)
if (!m.WaitOne(0, false))
                {
                    Console.WriteLine("Only one instance is allowed!");
return;
                }
 
#region程式處理邏輯
 
                Console.WriteLine("Press Enter to exit...");
                Console.ReadLine();
 
//如果是Windows Form,Application.Run()要包在using Mutex範圍內
//以確保WinForm執行期間Mutex一直存在
 
#endregion
            }
        }
    }
}

如何取消MSN與Skype的帳號合併連結?

$
0
0

MSN將於2013/3/15吹熄燈號(嚴格來說,大家口中的MSN其實是指Windows Live Messenger,但正名Live Messenger像把牛仔褲唸成牛"紫"褲一樣彆扭,講MSN才是正港台灣人啦~),未來功能會由Skype取代,雖然喜愛MSN遠超過Skype,只是天要下雨娘要嫁人,為了繼續和MSN裡上百位親友保持連繫,乖乖裝好Skype開始培養感情吧!

網路上有很多介紹MSN與Skype帳號合併的教學[範例範例],從Skype官網下載了6.0版(注意,不少人被搜索引擎導到PCHome版Skype下載頁去了,記得要從www.skype.com網站下載),但用Microsoft帳號登入後卻沒有出現教學文"步驟5加入SKYPE帳號資訊"可以選擇"我擁有Skype帳戶"的畫面,便直接進入Skype主畫面,而且MSN帳號已經綁上一個"live:blahblah"的全新Skype帳號,感覺是Skype自動用我的MSN帳號 blahblah @ hotmail.com 取出"blahblah"建立的新帳號。不確定是哪個環節出錯導致我沒機會選擇現有Skype帳號,我打算取消合併重選既有Skype帳號,網路爬文找到不來恩的文章提到無法取消合併,也有人說透過客服可幫忙取消合併,便在Skype的技術支援網頁寫了支援需求,馬上有封受理回信說24小時內會客服回覆處理。

約8小時後就收到客服回信(不錯的回應速度,辛苦了),給我一個FAQ網頁(查了一下,該Support網站目前尚無中文版),原來Skype網站的個人資料(Profile)介面已經有取消連結(Unlink)功能囉~ (注意: 用Microsoft或Facebook帳號登入Skype自動建立的"live:blahblah" Skype帳號在取消連結後資料會被清除,其設定的連絡人及儲值點數會消失)

取消連結的步驟如下:

登入Skype網站,選擇Microsoft帳號登入

登入後找到Profile(個人資料)

在個人資料區可看到Skype、Microsfot、Facebook帳戶的連結關係。有趣的是,雖然以Microsoft帳戶登入,Skype的觀點卻是以"live:blahblah"帳號為主,Microsoft帳戶blahblah @ hotmail.com被綁在該帳號上,Unlink(取消連結)Microsoft帳戶後,是"live:blahblah"的資料被清除。

取消連結後再次使用Microsoft帳戶登入,就重新獲選擇現有Skype帳號合併的機會囉~

黑暗執行緒Facebook分舵成立囉~

$
0
0

使用Facebook好一陣子了,看到好些朋友在FB上成立社團,藉由FB的管道聚集了志同道合的社群朋友,感覺挺不賴的。雖然自忖人老珠黃年老力衰,又因工作形態無法隨時駐站,恐怕很難把社群經營到有聲有色,但我想即便慘淡經營,增加一個讓站友相識交流的管道總是好的。

所以,"黑暗執行緒的ThreadPool"[https://www.facebook.com/groups/darkthread]成立囉!

簡單來說,Facebook分舵算是黑暗執行緒部落格的延伸,宗旨如下:

* 作為開發技術相關或議題研究過程瑣碎資訊的集散地
(謎之聲: 根本是懶得補齊成文章,想東一句西一句胡亂記記了事)
* 提供部落格之友彼此技術交流的管道
(謎之聲: 說穿了就是老人家容易覺得空虛寂寞覺得冷,想多認識些同好就對了)

歡迎有FB帳號的讀者朋友加入,有空來泡茶~

jQuery 1.9

$
0
0

jQuery已於2013/1/15釋出1.9版~

有趣的是,1.9版最值得關注的,倒不是又增加什麼新東西,而是它拿掉哪些舊東西!! 1.9版移除或變動了不少舊API,升級後可能導致現有程式無法相容,為此官方一併釋出升級指南(延伸閱讀: 我的升級筆記[稍後補上])以及升級Plugin(jQuery Migrate)協助開發者避雷保身。

之前預告過,jQuery 1.9將是最後一個支援IE 6/7/8的版本(簡稱"老IE", oldIE),從jQuery 2.0起(註: 2.0現在仍0處Beta階段),將只支援IE 9+及其他HTML5瀏覽器。(XP User要哭了! 不,是規格書要求支援IE6/7/8的網站開發者要嘆氣了!)

1.9跟2.0這兩個版本的關係比較特殊,不像以前的新舊版,基本上二者API相同,可替換使用,最重要的差別在於jQuery 1.9仍能在IE 6/7/8(老IE)上執行,但jQuery 2.0不支援老IE,犠牲了對老IE的相容性換取更小的程式庫體積、更快的執行速度。結論是,開發者可以在不修改程式直接切換jQuery 1.9及2.0(因為二者API相同),抉擇關鍵在於1.9雖能相容老IE但較痴肥緩慢,如果開發者人品好到不需理會老IE(例如: 只針對行動裝置瀏覽器的情境),則建議改用2.0享受其輕巧快速。

兩全其美的做法是用以下寫法見人說人話,見鬼說鬼話...

<!--[if lt IE 9]><script src="jquery-1.9.0.js"></script><![endif]--><!--[if gte IE 9]><!--><script src="jquery-2.0.0.js"></script><!--[endif]—>

由於jQuery 1.9正式移掉不少了過去標註為過時(deprecated)的API,如果你目前程式已經避用過時API,升級到1.9不會有什麼大問題,但大部分開發者沒這麼好運,多年累積下來的程式少不涉及舊API,此時jQuery Migrate可幫上大忙,蒐羅了1.6.4以來存在但1.9已不支援的老API。在使用jQuery 1.9/2.0時,一併載入jQuery Migrate,可以不用改程式繼續使用大部分被1.9移除或更動的老舊API,還能透過console.log記錄程式使用到哪些1.9不相容API,做為修改的參考。(註: 使用jQuery Migrate上線版(Production version, jquery-migrate-1.0.0.min.js)只提供相容而不產生警告訊息,要使用開發版未壓縮的jquery-migrate-1.0.0.js,才會在console留下如JQMIGRATE: jQuery.browser is deprecated的警告訊息,詳細的不相容訊息及解決方法可以參考官方文件)

以下是jQuery 1.9的新東西及改變: (api.jquery.com的文件需要幾週才會補齊)

  1. API整頓與革新
    移掉了很多老舊及瞹眛的API(詳見升級指南)
  2. 可以一次取回多個CSS屬性
    .css(CSS屬性名稱陣列)可傳回多個CSS屬性值的物件
    var cssDictionary = $("#boo").css(["width","height","color"]);
    cssDictionary將得到如下內容
    {width:"100px",height:"50px",color:"#ff00ff"}
  3. 強化CSS3選擇器跨瀏覽器支援
    讓:nth-last-child, :nth-of_type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :target, :root及:lang在老賊瀏覽器IE6嘛也通
  4. 新增.finish()
    立即結束所有仍在佇列中的動畫
  5. 支援Source Map
    讓壓縮版(Minified)版的jquery-1.9.0.min.js在偵錯時也能對應到正確的原始程式碼。Source Map原理可參考阮一峰先生的文章,但目前只有Chrome支援。
  6. 修好一籮筐的Bug~

祝大家升級愉快~

jQuery 1.9升級指南

$
0
0

前文所說,jQuery 1.9是一次"拿掉哪些東西"比"增加哪些東西"來得重要的升級,大刀闊斧地移除及改掉許多不一致或沒效率的API,而革命總免不了要流點血(還記得jQuery 1.6的.attr()/.prop()事變嗎?),雖然大部分的過時(Deprecated)API在過去1.7/1.8版釋出時就已強調過,但許多開發者可能跟我一樣,老師在講也沒在聽,過去怎麼寫就繼續寫下去。終於,1.9正式把這些過時API刪的刪改的改,該是面對現實的時候了! (當然,貼心的jQuery RD仍提供了繼續鴕鳥龜縮的選擇)

也因為1.9變動幅度較大,為減輕大家改版的痛苦,官方特別出了一份jQuery Core 1.9 Upgrade Guide,羅列升級1.9版的注意事項。為了避免升級1.9變成天堂路,這份指南不可不讀,閱讀之餘,順便摘要寫成筆記,備忘兼分享:

  1. 請善用jQuery Migrate外掛,可以暫時避開程式壞光光的困境(但這樣子搞,換到1.9的意義不大),並能自動列出程式不相容的地方。(記得要使用未壓縮的開發版jQuery Migrate才會顯示不相容警告。雖然加掛jQuery Migrate後老程式大部分都可繼續正常運作,建議還是趁早改掉過時API為宜,老話一句: 出來混遲早要還!)
  2. 移除.toggle(fn1, fn2)
    讓toggle()回歸"切換元素顯示與否"的單一用途。[可透過jQuery Migrate還原]
  3. 移除jQuery.browser()
    1.3就已宣告過時,1.9終於拿掉。[可透過jQuery Migrate還原]
    建議改用Modernizr
  4. 移除.live()
    1.7宣告過時,1.9正式移除。建議改用.on()。[可透過jQuery Migrate還原]
  5. 移除.die()
    1.7宣告過時,1.9正式移除。建議改用.off()。[可透過jQuery Migrate還原]
  6. 移除jQuery.sub()
    因為使用頻率低,移出核心程式庫,有需要請加掛jQuery Migrate。
  7. .add()
    1.9版起,加入後節點元素將會依其在DOM的出現順序排列,沒掛入document的元素擺在最後。
  8. 以.addBack()取代.addSelf()
    1.8版建議用.addBack()取代.addSelf()以求正名,另外.addBack()還多支援選擇器參數進行過濾。例如: $("section,aside").children("ul").addBack("aside")會得到section與aside下所有ul外加所有aside元素。.addSelf()在1.9仍可用,但jQuery Migrate會提出警告。
  9. 離線節點的.after(), .before(), .replaceWith()
    1.9版前,若針對未放進DOM的離線節點元素進行.after()、.before()、.replaceWith(),有時會傳回看似相同卻非原集合的結果,1.9已排除此一令人困擾行為。
  10. AJAX全域事件應永遠以document為對象
    過去如有$("#boo").ajaxStart(fn);的寫法,請改為$(document).ajaxStart(fn);
  11. .trigger()事件時的Checkbox/Radio checked狀態
    1.9版之前,當使用.trigger("click")或.click()觸發Checkbox及Radio時,在事件中會看到與實際checked屬性相反的狀態(詳情可參考舊文),1.9終於撥亂反正。
  12. focus事件觸發順序
    理論上,呼叫某個元素.focus()時,應先觸發前一焦點元素的blur事件再觸發新取得焦點元素的focus事件,但1.9版前順序相反,會先觸發新焦點元素focus事件才觸發原焦點元素的blur事件,1.9已修正此問題。
    注意: 直接呼叫DOM元素的.focus(),只有在元素原本未取得焦點且可成功取得焦點時才觸發focus事件;呼叫jQuery的.focus()則無論成功與否都會觸發focus事件。另外,IE6-10 focus事件會以非同步方式執行,讓jQuery.trigger("focus")難以掌握事件執行狀態,會造成focus事件重覆執行,建議改用DOM內建的focus()較單純,例如: $("#boo").get(0).focus()。
  13. jQuery(htmlString)與jQuery(selectorString)
    1.9版以前,jQuery以字串中是否包含HTML標籤來判定傳入的是HTML字串要建立元素? 還是選擇器字串要選取元素? 在某些情境可能將選擇器字串誤判為HTML字串。
    1.9版起改為一定要以"<"開頭才視為HTML字串,遇到無法符合此要求的HTML字串可使用$($.parseHTML(htmlString))克服。 加掛jQuery Migrate後將恢復原本行為。
  14. .data()取得名稱含"."的資料
    從1.9起,.data("abc.def")只會取回名為abc.def的資料,原本還可取得abc的密技已取消。此點就算掛了jQuery Migrate也回不去。
  15. 離線節點(Disconnected Nodes)的順序
    原則上jQuery集合中節點應依照其在DOM出現順序排列。而在1.9版前,若jQuery集合中混雜掛在DOM的節點及未放進DOM的離線節點,則可能出現不可預期的隨機排序。
    1.9起jQuery集合一律改為先依DOM的順序排列節點,最後再加上離線節點。原來的隨機排法太鳥,jQuery Migrate決定無視它,無從恢復。
  16. HTML字串中的Script
    在1.9版以前,$(htmlString)、.append()、.wrap()會執行HTML字串所包含的Script部分並將之移除以免重覆執行,但這違背某些稍後想再執行的情境。1.9版改為仍保留Script但標註為已執行過。
    只是,把JavaScript跟HTML混在一起的現法實在不怎麼入流,這點大家就當成沒看到吧!
  17. .attr()與.prop()
    曾在1.6版造成爭議的.attr()與.prop()分離,1.6.1版屈於現實又改回相容。1.9再次捲土重來,讓.attr()、.prop()明確區隔,減少應用上的混淆。念舊或不想改Code的人,請靠jQuery Migrate繼續當鴕鳥。
  18. 老IE與$("input").attr("type", newType)
    1.9版前為遷就IE6/7/8,不允許透過attr()方式修改<input> type,呼叫時會抛出錯誤;1.9起解禁,開發者可以在IE9+及其他瀏覽器以.attr()改變input type,但如在IE6/7/8(老IE)上出錯後果自負。使用jQuery Migrate後,$("input").attr("type", newType)不會丟出例外,但console會有警告訊息。
  19. hover虛擬事件
    1.9版起,事件名稱"hover"將不再是mouseenter mouseleave的替代縮寫,而是指開發者要自訂hover事件。如需修改可尋找與取代一下就搞定,實在不想改,用jQuery Migrate還原吧!
  20. jQuery物件的.selector屬性
    過去保留.selector是為了.live(),1.9拿掉.live(),.selector也一併移除,掛jQuery Migrate也回不去了。
  21. jQuery.attr()
    1.9版移除了jQuery.attr(elem, name, value, pass)密技,用jQuery Migrate可恢復。
  22. jQuery.ajax取JSON結果時的空字串解析
    1.9版前,$.ajax()取JSON/JSONP結果如遇伺服器傳回空字串時會將結果設為null,仍算JSON解析成功;1.9版起,空字串將被視為無效JSON觸發錯誤,可使用error事件捕捉。
  23. jQuery.proxy()
    1.9版前,$.proxy(null, fn)、$.proxy(undefined, fn)的this會指向window,而$.proxy(false, fn)的this則指向new Boolean(false);1.9起若context傳入null/undefined/false,函數的this會維持原先context,不被改變。
  24. .data("events")
    取回事件資料結構的密技.data("events")取消了! 用jQuery Migrate可恢復之
  25. Event事件移除部分屬性
    Event物件的attrChange、attrName、realtedNote、srcElement屬性自1.7版因無法跨瀏覽器已被宣告過時,1.9版正式移除。如要引用請透過event.orginalEvent存取或安裝jQuery Migrate恢復之。
  26. 其他消失的密技
    jQuery.data()、jQuery.removeData()、jQuery.attr()移除了某些未列在文件的參數呼叫方式。
    另外,文件沒寫的jQuery.deletedIds、jQuery.uuid、jQuery.attrFn、jQuery.clean()、jQuery.event.handle()、jQuery.offset.bodyOffset()也被移掉了,有在偷用的同學摸摸鼻子改程式吧!

【結論】

這次jQuery 1.9版升級加入的新東西不多,但API改變頗大,且某些屬常用寫法(如.live(), .attr(), .prop()),極可能造成現有程式不相容。雖然加掛jQuery Migrate可還原大部分的原有API行為,但如此便失去了1.9版API整頓與效能改善的意義,既然未來要升級更新版本遲早得面對,不妨趁早調整程式,至少新開發的程式就別依賴jQuery Migrate,好好擁抱1.9。


CODE-縮短版GUID字串

$
0
0

頗特殊的需求: 一個跨平台整合在傳遞以GUID為Primary Key資料時,對方的參數欄位只接受最長30個字元,即使使用16進位數字表示法(例如: 4854c292c333480890f916d1a062b8e3),GUID字串也長達32字元,超出限制。另外想一種不會重複的識別編號法則是種解法,但要做到GUID等級的唯一性得付出不少代價。因此,另一個思考方向是如何用較短的字串長度表示GUID,評估是較省力的做法。

要比16進位表示法更簡短,最簡便的做法是將其GUID先轉為byte[],再用Base64編碼轉為字串。例如: 4854c292c333480890f916d1a062b8e3 可以轉換成 ksJUSDPDCEiQ+RbRoGK44w==,只要24個字元。但有個問題,對方系統的資料庫定序(Collation)被設為不分大小寫,而Base64編碼需大小寫有別,唯一限制及查詢比對可能出問題。

原本想拿出自己在VB6時代發明的36進位表示去(使用A-Z及0-9共36個英數元表示二進位資料),但轉念一想,如果有標準可循,還是別自己造輪子好。查詢後,真的有所謂的Base32編碼,而且還是RFC標準(RFC 4648)! Base32只用A-Z及2-7共32個字元表示,編碼結果比Base64長約20%,但好處是:

  • 適用於不分大小寫的情境
  • 數字段避開0, 1, 8,可避免與字母O, I, B混淆
  • 使用於URL時完全不需要UrlEncoding

只是Base32的應用不若Base64普遍,在.NET基本類別庫無現成可用的函數編解碼。所幸,既是公開標準,就不難找到前輩先進寫好的範例。我在stackoverflow找到一個實作範例,但測試發現它在預估字串長度時計算有誤差,會有多餘的Padding字元"=",但這只需要小小調整就可改善: [已留言建議]

原本是 charCount = (int)Math.Ceiling(input.Length / 5d) * 8;

應改為 charCount = (int)Math.Ceiling(input.Length / 5d * 8);

以下是測試範例,建立10,000個GUID進行編碼及還原,逐筆驗證還原結果無誤並計算執行時間。(同時支援Base64、Base32兩種格式)

class Program
    {
staticvoid Main(string[] args)
        {
 
            List<Guid> guidPool = new List<Guid>();
for (int i = 0; i < 10000; i++)
            {
                guidPool.Add(Guid.NewGuid());
            }
 
string str = null;
            Guid restored = Guid.Empty;
            Stopwatch sw = new Stopwatch();
//Base64 version test
            sw.Start();
foreach (Guid uid in guidPool)
            {
                str = GetShortGuidString(uid);
                restored = ParseShortGuidString(str);
if (!uid.Equals(restored))
thrownew ApplicationException("Test Failed!");
            }
            sw.Stop();
            Console.WriteLine("Base64 Version in {0:N}ms,\n  {1:N} -> {2}",
                sw.ElapsedMilliseconds, restored, str);
 
//Base32 version test
            sw.Restart();
foreach (Guid uid in guidPool)
            {
                str = GetShortGuidString(uid, true);
                restored = ParseShortGuidString(str, true);
if (!uid.Equals(restored))
thrownew ApplicationException("Test Failed!");
            }
            sw.Stop();
            Console.WriteLine("Base32 Version in {0:N}ms,\n  {1:N} -> {2}",
                sw.ElapsedMilliseconds, restored, str);
 
            Console.Read();
        }
 
staticstring GetShortGuidString(Guid uid, bool useBase32 = false)
        {
if (useBase32)
return Base32Encoding.ToString(uid.ToByteArray());
else
return Convert.ToBase64String(uid.ToByteArray());
        }
 
static Guid ParseShortGuidString(string s, bool useBase32 = false)
        {
if (useBase32)
returnnew Guid(Base32Encoding.ToBytes(s));
else
returnnew Guid(Convert.FromBase64String(s));
        }
    }

測試結果如下:

Base64 Version in 13.00ms,
  4854c292c333480890f916d1a062b8e3 -> ksJUSDPDCEiQ+RbRoGK44w==
Base32 Version in 38.00ms,
  4854c292c333480890f916d1a062b8e3 –> SLBFISBTYMEEREHZC3I2AYVY4M

推測目前找到的Base32編碼邏輯,最佳化程度不及.NET內建的Convert.ToBase64String,故Base32執行速度較Base64慢3倍,但1萬筆只需0.04秒,在整合應用時成為瓶頸的可能性不高,測試驗證可行。

TransactionScope與COMMIT TRAN

$
0
0

手邊的專案遇到一個情境: TransactionScope中包含兩段SQL操作,因未共用連線,預期將啟動MSDTC分散式交易。而第二段SQL操作使用了T-SQL的BEGIN TRAN與COMMIT TRAN。若TrasactionScope未能呼叫TransactionScope.Complete(),第二段COMMIT TRAN的DB寫入動作是否會Rollback? 這個議題,引起討論。

依我的理解,BEGIN TRAN/COMMIT TRAN形成的子交易,仍會參與當下的環境交易(Ambient Transaction,包在using TransactionScope範圍的資料庫操作,預設會參與該TransactionScope所建立的交易,該交易即所謂環境交易[參考]),當環境交易Rollback(亦即TransactionScope未呼叫Complete()),子交易將會跟著Rollback。

由於信仰不夠堅定,無法100%確認自己的認知無誤,故設計實驗進行驗證:

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Transactions;
 
namespace TestTSqlCommit
{
class Program
    {
staticvoid truncateTable()
        {
using (SqlConnection cn = new SqlConnection(getDiffCnStr()))
            {
                cn.Open();
                var cmd = cn.CreateCommand();
                cmd.CommandText = "TRUNCATE TABLE LockLab";
                cmd.ExecuteNonQuery();
            }
        }
 
 
staticstring cs = "Data Source=...省略...;Application Name={0}";
//使用隨機Application Name建立連線字串
staticstring getDiffCnStr()
        {
returnstring.Format(cs, Guid.NewGuid());
        }
 
staticvoid Main(string[] args)
        {
//將LockLab資料表清空
            truncateTable();
//建立TransactionScope
using (TransactionScope tx = new TransactionScope())
            {
try
                {
//以隨機式連線字串建立連線,確保觸發分散式交易
//參考: http://blog.darkthread.net/post-2010-11-12-msdtc-2008.aspx
using (SqlConnection cn = new SqlConnection(getDiffCnStr()))
                    {
                        cn.Open();
                        var cmd = cn.CreateCommand();
                        cmd.CommandText = 
"INSERT INTO LockLab VALUES ('TEST','201302', 1)";
                        cmd.ExecuteNonQuery();
                    }
//第二段SQL 使用COMMIT TRAN
using (SqlConnection cn = new SqlConnection(getDiffCnStr()))
                    {
                        cn.Open();
                        var cmd = cn.CreateCommand();
                        cmd.CommandText = @"
BEGIN TRAN
INSERT INTO LockLab VALUES ('JEFF','201302', 1)
COMMIT TRAN
";
                        cmd.ExecuteNonQuery();
                    }
 
thrownew ApplicationException("刻意觸發錯誤!");
//tx.Complete()不會被執行,Transaction應Rollback
                    tx.Complete();
                }
catch (Exception ex)
                {
                    Console.Write(ex.ToString());
                }
            }
            Console.Read();
        }
    }
}

測試結果如同預期,TEST與JEFF兩筆資料均未寫入,證實TransactionScope中即便BEGIN TRAN/COMMIT TRAN仍會參與交易,可被Rollback。

最後補充,如果我們希望子交易不要依附於外層TransactionScope,以實現不論外層Transaction Rollback與否,子交易需能自由控制是否Commit的結果。為此,我們可宣告一個TransactionScopeOption.RequiresNew模式的TransactionScope將子交易包起來,則其中將自成一個獨立交易範圍,不受環境交易影響。沿用上例,如要將第二段獨立自成交易,可修改為:

using (TransactionScope tx2 = 
newTransactionScope(TransactionScopeOption.RequiresNew))
                    {
using (SqlConnection cn = new SqlConnection(getDiffCnStr()))
                        {
                            cn.Open();
                            var cmd = cn.CreateCommand();
                            cmd.CommandText = @"
BEGIN TRAN
INSERT INTO LockLab VALUES ('JEFF','201302', 1)
COMMIT TRAN
";
                            cmd.ExecuteNonQuery();
                        }
tx2.Complete();
                    }

如此,即便tx.Complete()未執行,JEFF資料也會寫入DB。

用FormCollection取代Request.Form,Why?

$
0
0

最近陸續在ASP.NET MVC書籍及Blog文章看到類似建議:

在ASP.NET MVC Controller運用前端傳入資料時,即便可透過Request.Form["..."]獲得相同結果,仍建議採用FormCollection["..."]取代之。

由於書籍及文章並未闡述該用FormCollection取代Request.Form的明確理由,不知有沒有朋友跟我一樣,不明究理就無法堅定信念,會陷入不知"為何而換"的迷惘? 為此爬了好些文章,沒能找到簡單扼要的理由,不過從中推敲,還是有點心得。依我的理解,建議改用FormCollection由主要考量是:

  1. 便於單元測試
    針對Controller的特定Action進行單元測試時,要模擬FormCollection物件作為輸入參數,遠比Request.Form簡單,基本上只需要
        var fc = new FormCollection();
        fc.Add("field_name", field_Value);

    就能輕鬆搞定[參考]。
    且由此衍生可得,不只Request.Form,在Controller還應避免直接存取Request、Response物件,可大幅降低單元測試的難度。
  2. 保留客製擴充彈性
    在Controller宣告public ActionResult Boo(FormCollection formCollection),ASP.NET MVC運行時會由預設ModelBinder解析用戶端傳入內容,轉換為FormCollection傳給Action處理。預設狀況下其內容與Request.Form["…"]一致,但ASP.NET MVC允許我們自訂FormCollection的ModelBinder,可修改產生FormCollection的邏輯,此時若直接存取Request.Form將導致客製邏輯失效,破壞ASP.NET MVC原有的擴充彈性。

以上心得,分享給有同樣疑惑的朋友參考。如果有人想到其他理由,也歡迎回饋補充。

CODE-透過WPF WebBrowser執行JavaScript

$
0
0

計劃在WPF內嵌WebBrowser元件,並透過JavaScript取得網頁元素資訊。經過一番研究,總算試出解法,簡單筆記如下: (以擷取Google新聞網站的焦點新聞為例)

  1. 在WPF加入WebBrowser,指定Source連向Google新聞URL,另一項重點則是要指定LoadCompleted事件,該事件會在網頁載入完成後觸發,某些動作(例如: 在網頁上執行JavaScript)必須在網頁載入後執行才不致出錯。
    <Windowx:Class="TestWebBrowser.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow"Height="350"Width="525"Loaded="Window_Loaded_1">
    <Grid>
    <WebBrowserx:Name="browser"
    VerticalAlignment="Stretch"HorizontalAlignment="Stretch"
    Source="http://news.google.com.tw/"
    LoadCompleted="bowser_LoadCompleted"/>
    </Grid>
    </Window>
  2. C#部分程式有幾個重點:
    1) 宣告一個ScriptHelper類別(必須為ComVisible),提供Method供網頁透過JavaScript呼叫
    2) 在Window Loaded事件中將ScriptHelper設為WebBrowser的ObjectForScripting物件
    3) 網頁載入完成時,可透過WebBrowser.InvokeScript(function_name, arg1, arg2…)呼叫網頁內的JavaScript函數。但因為我們要執行的是自訂程式作業,故藉用windows.eval()傳入要執行的JavaqScript程式字串當參數,如此達到執行任意指令的效果。
    4) 由於先前指定了ScriptHelper做為溝通物件,在JavaScript中可透過window.external.SendMessage()觸發ScriptHelper的SendMessage方法,將結果傳回.NET端。
    完整程式碼如下:
    using System;
    using System.Runtime.InteropServices;
    using System.Windows;
    using System.Windows.Navigation;
     
    namespace TestWebBrowser
    {
    publicpartialclass MainWindow : Window
        {
    public MainWindow()
            {
                InitializeComponent();
            }
     
    privatevoid bowser_LoadCompleted(object sender, NavigationEventArgs e)
            {
    //加入ProcMessage事件,處理傳回字串
                sh.ProcMessage = (s) =>
                {
                    MessageBox.Show(s);
                };
    //執行Script,取得熱門新聞,注意: 必須要在網頁載入完成後才可呼叫
                browser.InvokeScript("eval", @"
    var list = document.getElementsByClassName('top-stories-section')[0]
               .getElementsByTagName('h2');
    var news = [];
    for (var i = 0; i < list.length; i++) {
        news.push(list[i].getElementsByTagName('span')[0].innerHTML);
    }
    window.external.SendMessage(news.join('\n'));
    ");
            }
     
    #region供從WebBrowser網頁呼叫C#方法的溝通物件
            [ComVisible(true)]
    publicclass ScriptHelper
            {
    public Action<string> ProcMessage = null;
    publicvoid SendMessage(string msg)
                {
    if (ProcMessage != null)
                        ProcMessage(msg);
                }
            }
    private ScriptHelper sh = new ScriptHelper();
    #endregion
     
    privatevoid Window_Loaded_1(object sender, RoutedEventArgs e)
            {
    //指定ObjectForScripting,網頁可透過window.external存取該物件
                browser.ObjectForScripting = sh;
            }
        }
    }

執行程式,就能成功用WPF抓出網頁內容囉~

迴圈比對條件的陣列長度該不該用變數?

$
0
0

前幾天看到關於陣列跑迴圈時,比對條件裡陣列長度改用變數提升執行效率的討論。亦即

for (int i = 0; i < array.Length; i++) ...

若改成

int c = array.Length;
for (int i = 0; i < c; i++) ...

會不會變快?

支持變快的理由是比對條件會反覆執行,array.Length透過屬性取值會比直接存取變數耗時。

但有另一派說法:

Some programmers believe that they can get a speed boost by moving the length calculation out and saving it to a temp, as in the example on the right.

The truth is, optimizations like this haven't been helpful for nearly 10 years: modern compilers are more than capable of performing this optimization for you. In fact, sometimes things like this can actually hurt performance. In the example above, a compiler would probably check to see that the length of myArray is constant, and insert a constant in the comparison of the for loop. But the code on the right might trick the compiler into thinking that this value must be stored in a register, since l is live throughout the loop. The bottom line is: write the code that's the most readable and that makes the most sense. It's not going to help to try to outthink the compiler, and sometimes it can hurt.

意思是,當代編譯器早非吳下阿蒙,面對這種情境能判斷array.Length不會變,JIT產生的執行碼在比對條件時會直接用常數;刻意改用變數,編譯器反而被誤道以為其值可能變化而改用Regiter儲存,效率反而較差。

兩派主張都有道理,那就實測看看好了。

寫了一支簡單測試程式,宣告一個由65535個int組成的陣列,分別存入0到65535,使用迴圈進行累加,因累加一輪時間太短,連續跑5000回合做為計時單位,等同執行約32憶次迴圈所耗時間。分別用for迴圈+array.Length、for迴圈+長度變數、foreach三種做法各測一次,另外怕只測一次數據會受其他因素干擾失真,故三種測試都跑100次求平均減少誤差。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
 
namespace TestLoop
{
class Program
    {
staticvoid Main(string[] args)
        {
int[] ary = newint[65535];
for (int i = 0; i < ary.Length; i++)
                ary[i] = i;
int TIMES = 5000;
long sum = 0;
            Stopwatch sw = new Stopwatch();
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("ary.Length,count var,foreach");
 
for (int run = 0; run < 100; run++)
            {
                sw.Reset();
                sw.Start();
for (int t = 0; t < TIMES; t++)
                {
                    sum = 0;
//比對i < ary.length
for (int i = 0; i < ary.Length; i++)
                    {
                        sum += ary[i];
                    }
                }
                sw.Stop();
                Console.WriteLine("{1} ary.Length = {0:N0}ms", 
                                    sw.ElapsedMilliseconds, sum);
                sb.Append(sw.ElapsedMilliseconds + ",");
                sw.Reset();
                sw.Start();
for (int t = 0; t < TIMES; t++)
                {
int count = ary.Length;
                    sum = 0;
//將ary.Length存入count變數比對
for (int i = 0; i < count; i++)
                    {
                        sum += ary[i];
                    }
                }
                sw.Stop();
                Console.WriteLine("{1} count variable = {0:N0}ms", 
                                    sw.ElapsedMilliseconds, sum);
                sb.Append(sw.ElapsedMilliseconds + ",");
 
                sw.Reset();
                sw.Start();
for (int t = 0; t < TIMES; t++)
                {
                    sum = 0;
//使用foreach
foreach (int n in ary)
                    {
                        sum += n;
                    }
                }
                sw.Stop();
                Console.WriteLine("{1} foreach = {0:N0}ms",
                                    sw.ElapsedMilliseconds, sum);
                sb.AppendLine(sw.ElapsedMilliseconds.ToString());
            }
            File.WriteAllText("d:\\result.csv", sb.ToString());
            Console.Read();
        }
    }
}

由執行結果來看,三種做法各有勝負,並未出現一面倒:

2147385345 ary.Length = 1,274ms
2147385345 count variable = 1,260ms
2147385345 foreach = 1,701ms //foreach > Length > 變數
2147385345 ary.Length = 1,805ms
2147385345 count variable = 1,472ms
2147385345 foreach = 2,151ms //foreach > Length > 變數
2147385345 ary.Length = 1,266ms
2147385345 count variable = 1,238ms
2147385345 foreach = 1,621ms //foreach > Length > 變數
2147385345 ary.Length = 1,623ms
2147385345 count variable = 1,658ms
2147385345 foreach = 1,298ms //變數 > Length > foreach
2147385345 ary.Length = 1,214ms
2147385345 count variable = 1,271ms
2147385345 foreach = 1,310ms //foreach > 變數 > Length
2147385345 ary.Length = 1,358ms
2147385345 count variable = 1,125ms
2147385345 foreach = 1,404ms //foreach > Length > 變數
2147385345 ary.Length = 1,421ms
2147385345 count variable = 1,471ms
2147385345 foreach = 1,432ms //變數 > foreach > Length
2147385345 ary.Length = 1,801ms
2147385345 count variable = 1,475ms
2147385345 foreach = 1,848ms //foreach > Length > 變數
2147385345 ary.Length = 1,608ms
2147385345 count variable = 1,527ms
2147385345 foreach = 1,587ms //Length > foreach > 變數
2147385345 ary.Length = 1,554ms
2147385345 count variable = 1,392ms
2147385345 foreach = 2,057ms //foreach > Length > 變數
...略...

然而,約略感覺將array.Length存成變數的做法領先次數較多,100次測試時間取平均值也印證了這點,for+count變數最快,其次是for+ary.Length,foreach最慢! (下圖紅框為100次測試的耗時平均值)

【結論】

雖然由平均值來看,將陣列長度存成變數的時間最短,但是30億次只差0.14秒,且執行期間很可能因其他因素逆轉結果(由反覆測試時三種方法各有勝負得知,甚至確知較複雜的foreach寫法也可能最快)。
故用.NET程式寫陣列迴圈時,不必庸人自擾去求這"奈米級"的差別,用最直覺、易讀的方式撰寫更重要。我會選擇直接用Array.Length,甚至用foreach也無妨,程式碼易理解就好。

簽入TFS Service時發生SSL連線錯誤

$
0
0

臨上班前想把自己小專案的修改Check In到TFS Service,反覆試了好幾次,一直冒出以下錯誤,搞到差點遲到:

Multiple errors occurred during the operation, the first of which is displayed below. A full error list is available in the Output Window.

C:\TFS\Skype Tools\src\MSNCatgTool\MainWindow.xaml: The server encountered an unknown failure: The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

由於昨天晚上抓最新版、簽出時一切正常,加上檢視TFS Service網站功能正常,直覺懷疑是修改到什麼地方或某些操作有誤,誤踩TFS地雷出錯。查了好一陣子沒頭緒,最後在stackoverflow查到熱騰騰的討論,有沒有這麼巧? 剛好遇上TFS服務故障,白白查了老半天。

但由此次經驗學到,未來遇類似情況可先檢查Azure Service服務狀態: http://www.windowsazure.com/en-us/support/service-dashboard/
像這次大規模故障在Dashboard上即有張貼緊急公告,提到SSL憑證過期,比照錯誤訊息抱怨無法建立SSL連線,看起來就是導致我無法CheckIn的原因。

Skype MSN連絡人類別匯入工具Ver 1.0 Beta測試

$
0
0

大家都已經知道MSN(Live Messenger)將在今年3月退休的事(最近又聽說台灣地區下線時間會延到3/15之後),Skype雖然能把MSN連絡人整合進來,但是為連絡人設好的類別通通不見,造成很大困擾!! 以我自己為例,MSN有近300個連絡人,原本分成多組,例如: 專科同學、二技同學、前公司同事、A部門同事、B部門同事、廠商、MVP... 等等。要找尋特定連絡人,只需展開特定類別,每個分類最多十來個連絡人,很快就能找到。

這下子可好,Skype轉了MSN連絡人卻不把類別順便轉進來,想跟不常往來的MSN連絡人交談,記不得對方MSN名稱無法關鍵字檢索,而連絡人海茫茫,只能一邊滾輪子轉捲軸,一邊瞇著眼看名單如走馬燈流過,老人家眼花常看漏,三不五時氣到想捏碎滑鼠。而前些時候,看到"先將MSN擷取畫面以便日後對照分類"的建議,更是讓拎杯氣到想使出萬佛朝宗~~

身為程式魔人,該自力救濟的時間又到了!! 做了簡單研究,發現要在Skype裡設定連絡人群組不難,用Skype4COM就可輕鬆搞定,有挑戰性的部分是如何取得MSN設定的連絡人類別,之前玩過Live SDK,但細究後發現Live SDK沒有任何API可以取得連絡人類別資訊。退而求其次,我想到Windows Live Hotmail的連絡人網頁可使用類別篩選連絡人,是印象中MSN以外另一個有連絡人類別資料的地方,理論上找到其底層資料來源,就能抓出連絡人類別設定。
(題外話: 最近聽說Skype整合MSN時發生連絡人資料完全消失的杯具,提醒大家善用Live網站的連絡人匯出功能,把自己的連絡人清單備份到本機另存CSV檔,以求安心)

經過一番Hacking,成功地從Live連絡人網頁JavaScript Object偷到連絡人分類資料! 至此,餘下的工作便是如何把兩段Hacking整合在一起,包成工具程式讓程序一氣喝成!

我寫了以下的小工具--MSN Category Tool for Skype。

【準備工作】

  1. 確認Windows已安裝.NET Framework 4.0,一個簡單檢查方法是看系統碟有沒有以下目錄: C:\Windows\Microsoft.NET\Framework\v4.0.30319,若沒有,可參考安裝指南
  2. 請先確認Skype已啟動,並使用MSN帳號完成登入
  3. 已完成Skype與MSN帳號合併,連絡人清單已可看到MSN連絡人

【操作步驟】

  1. 執行程式後,按下【登入Live網站】,最下方有個內嵌瀏覽器會導向Windows Live Hotmail網站
  2. 請登入Hotmail網站
  3. 一旦下方進入連絡人頁面,稍等一下,程式會開始擷取連絡人類別資料
  4. 程式會將抓到的連絡人資訊顯示在中間的表格
  5. 此時按下【設定Skype連絡人類別】,程式會試著連上Skype開始設定連絡人類別
  6. 當Skype程式偵測到工具程式要連線會出現如下確認畫面,需按下【允許存取】才能成功設定類別。

設定完成後,即可看到Skype類別出現一堆以"MSN-"為首的新群組,成員即為原MSN類別下的連絡人,代表匯入完成。

【注意事項】

  1. 免責聲明: 本工具為免費提供,恕不對其可能造成的任何資料遺失、系統故障、財產損失(雖然以我的理解不致發生)負責,使用前請自行備份資料,評量風險後再使用,歡喜用、甘願受。
  2. 資安宣導: 使用前請確認程式來源安全無虞,若Skype誤授權給來路不明的惡意程式,可能導致資料遭竊、身分被盜等風險。
  3. 目前工具尚未加入防呆,請勿重複執行設定類別動作。重複執行可能會出現同一連絡人在群組出現兩次的問題,需手動清理。
  4. 評估程式測試尚不周全,計劃這幾天程式會先透過黑暗執行緒Facebook分舵讓群組成員試玩,彙整回饋後,幾天後再透過部落格提供經驗證較穩定的版本。換句話說,想等較穩定可靠版本程式的同學請再耐心等候,充滿熱血勇氣想當白老鼠的同學請往FB移動~

【茶包射手日記】看得到吃不到的Visual Studio專案參考程式庫

$
0
0

遇到一個怪異情境: 由他處取得的專案原始碼,編譯時出現錯誤訊息,抱怨專案沒有參考某個第三方元件--Quartz.dll。但如下圖範例,右側專案參考清單中明明有Quartz這顆元件,但左側using Quartz卻回應找不到Quartz命名空間,光視覺上就很矛盾!

其實過去已有類似經驗,問題多與.NET Framework版本有關,例如: .NET 2.0專案參考.NET 4.0元件、或是.NET 4.0 Client Profile專案參考.NET 4.0元件時就會產生這種結果。而詭異的是,該顆Quartz.dll元件在同一解決方案的其他專案中也有用到,卻能順利編譯無誤。

該專案的輸出目錄設定為X:\Boo\bin,屬性視窗顯示Quartz的Path為X:\Boo\bin\Debug\Quartz.dll,經確認檔案存在。試著移除Quartz參考重新加入參考,跟另一個專案採用的同一個Quartz.dll來源,如此Quartz參考屬性視窗的Path會指向新來源,但問題依舊。

靈光一現,打開Error List的Warning顯示切換(由於Warning常多到把Error淹沒,由於Warning多半不影響執行,故習慣關啟較能聚焦Error),冒出了以下Warning:

繞了大半天,還是.NET版本問題!! 原來Quartz.dll用了.NET 3.5,但有問題的專案是.NET 2.0,再仔細看那個可順利編譯專案--.NET 3.5!! 問題終究還是出在.NET Framework平台版本,但原先未料想同一解決方案中並存不同.NET版本專案,才一時迷惑! 且慢,還有一個謎團未解,若元件.NET版本不相容,前人為何能編譯成功呢? 最後,在.csproj找到答案:

<Reference Include="Quartz, Version=2.0.1.100, Culture=neutral, PublicKeyToken=f6b8c98a402cc8a4, processorArchitecture=MSIL">
  <SpecificVersion>False</SpecificVersion>
  <HintPath>..\..\..\..\src\Quartz.NET\bin\Release\Quartz.dll</HintPath>
</Reference>

真相大白,在前一位開發者環境裡專案會參照自行修改過的Quartz客製版,在我的電腦上自然沒有相對的目錄與檔案,於是Visual Studio很聰明地自行改參考\bin\Debug下的Quartz.dll,而該目錄下的Quartz.dll則為另一個專案寫入的2.0.100版,生出了這枚茶包。

【心得】

  1. 當Visual Studio遇到.csproj中DLL指定路徑不存在,會自動改指向bin目錄下的檔案。(今天才發現這點,慚愧!)
  2. 未來遇到"已參考元件,using時卻產生找不到"的情況:
    請立即檢查專案與元件的.NET版本是否相容
    請立即檢查專案與元件的.NET版本是否相容
    請立即檢查專案與元件的.NET版本是否相容
    (罰寫三遍)

TIPS-管理Skype API存取權限

$
0
0

網友詢問工具程式存取Skype API的權限設定問題。

狀況為工具程式第一次存取Skype時,在授權對話框誤按了【不允許存取】,之後Skype記下設定,工具程式的後續存取,不再詢問是否允許,一律拒絕,想重選【允許存取】存取都沒機會。

剛好稍早測試遇過相同狀況,順手分享一下修改API授權設定的方式。

步驟很簡單。開啟Skype的選項設定,找到【進階設定】,最下方有個【管理其他程式使用Skype的權限】。

點選後應可看到該工具程式在清單中,狀態為"不開放使用Skype",點選【變更】鈕後就可修改囉~

【資安提醒】授權其他程式存取Skype前,請務必確認該程式安全無虞,一旦授權給來路不明的惡意程式,將有個人資料遭竊,身分被冒用的風險。

Skype MSN連絡人群組匯入工具Ver 1.2.5

$
0
0

前情提要

原本只想透過Facebook群組進行封測,承蒙一些朋友轉載宣傳,沒想到演變成全民公測,正式還沒發佈,很多人已經轉完測過(估計已破上萬人次),對這個衛生紙性質的可抛式工具來說,有沒有出正式版好像也不重要,呵!

測試期間收到很多使用者的熱心回饋,給了我很難得的軟體開發經驗(讓我有在寫App的感覺),基於完整軟體產品生命週期的落實,在此宣告,正式發佈版本--Skype MSN連絡人群組匯入工具 Ver 1.2.5問市囉~
(PS: 正確術語應為連絡人類別,但大家口頭上幾乎都講群組,而Skype裡的用語也是群組,故工具命名從善如流)

【前言】

雖然Skype很早就能合併MSN帳號,但無法匯入MSN連絡人群組是我遲遲不肯使用Skype取代MSN(Windows Live Messenger)的主因! 要找個MSN連絡人得在數百人的清單裡搜尋,對照原本在MSN裡可以透過群組隨手可得的簡便,Skype肯定是要鍛練我心性來著,真是用心良苦~ orz

無法忍受在茫茫人海找MSN連絡人,沒耐性也不甘心在Skype裡為數百個MSN連絡人手工重新分群。(事實上我幹過一回,建了兩三個常用群組應急,手動分類了十來個連絡人,但卻不知何故設定全部消失,自此決定不再做這種悲情手工藝) 於是,我做了每個程式魔人在此情境都會做的事 --- 試著Hacking Live網站取出群組資料,再串接Skype API將群組資料注射進去,原本要耗費個把鐘頭的苦差事,開外掛只需幾秒就搞定,爽度破錶,自然也是程式魔人最大的樂趣所在。之前聽不少朋友有類似需求,就順手把程序包裝成更較簡易操作的工具程式分享出來。

簡單整理版本演進歷程:

Ver 1.0石破天驚第一版
Ver 1.0.1短短17分鐘後發布修正版,改正及強化完成訊息
Ver 1.1加入匯出/匯入CSV功能
Ver 1.2加入MSN-*群組自動清除,允許重複執行群組設定
Ver 1.2.5加入"MSN-未分類"收納未設群組之連絡人,修正程式在XP SP3無法執行問題
  

操作說明如下:

【準備工作】

  1. 確認Windows已安裝.NET Framework 4.0,一個簡單檢查方式是看系統碟有沒有以下目錄: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Web.Extensions.dll,若沒有目錄,表示沒裝.NET 4,若有目錄但沒有System.Web.Extensions.dll,表示你可能只裝了.NET Framework 4.0 Client Profile(精簡版),請安裝完整版(可參考安裝指南)。
  2. 請先確認Skype已啟動,並使用MSN帳號完成登入
  3. 已完成Skype與MSN帳號合併,連絡人Messenger群組下可看到MSN連絡人

【操作步驟】

  1. 執行程式後,按下【登入Live網站】,最下方有個內嵌瀏覽器會導向Windows Live Hotmail網站
  2. 請登入Hotmail網站
  3. 一旦下方進入連絡人頁面,請稍等一下,程式會開始擷取連絡人類別資料
  4. 程式會將抓到的連絡人資訊顯示在中間的表格
  5. 程式支援將連絡人及群組資料匯出成CSV檔,如需微調,可修改後再匯入調整後結果
  6. 此時按下【設定Skype連絡人類別】,程式會試著連上Skype開始設定連絡人群組
  7. 當Skype程式偵測到工具程式要連線會出現如下確認畫面,需按下【允許存取】才能成功設定類別。
  8. 如果不是第一次執行,Skype已存在先前轉入的"MSN-*"群組,程式將提示是否要全部刪除重新設定。(注意: 若選擇刪除,前次轉入MSN-*群組後手動修改的部分會遺失,若不刪除,重複轉入將會導致資料重複,需要手動清理)

設定完成後,即可看到Skype類別出現一堆以"MSN-"為首的新群組,成員即為原MSN類別下的連絡人,代表轉入成功。

【注意事項】

  1. 免責聲明:本工具屬免費提供,恕不對其可能造成的任何資料遺失、系統故障、財產損失(雖然以我的理解不致發生)負責,使用前請自行備份資料,評量風險後再使用,歡喜用、甘願受。
  2. 資安宣導:使用前請確認程式來源安全無虞,若Skype誤授權給來路不明的惡意程式,可能導致資料遭竊、身分被盜等風險。

【程式下載】

程式檔下載

【已知問題】

程式開放測試後已經歷經數千次執行測試,有信心在絕大部分環境可成功執行。唯陸續接到一些無法執行報告及功能改良需求,但因遠端偵錯能蒐集資料有限及模擬各式出錯環境的困難,對於某些朋友無法使用只能說抱歉;而對這支衛生紙性質的可抛式工具,原本並未打算投入太多精力去改良精進,故對耗時較多但評估效益有限的需求只能割捨。以下整理一些已知問題及建議處理方式:

  1. 程式一執行就當掉(Crash)!     
    如問題發生在XP SP3(錯誤訊息出現clr20r3及System.IO.FileFormatException),v1.2.5版已做過修正,請重新下載新版程式測試看看,否則另有隱情,建議重裝.NET Framework 4或換台電腦試試。(感謝網友李大華及Alex Lee協助破案!)
    有網友提供類似個案的錯誤訊息(訊息出現clr20r3字眼),推斷是.NET Framework 4.0安裝不正確或檔案損壞所致,建議移除並重新安裝.NET Framework 4.0(注意需安裝完整版,非精簡版,但已接獲重裝過完整版亦無效的案例)。如果還是不行,這類問題多得親機操作除錯,建議或請周遭熟悉.NET或Windows的朋友幫看或換台電腦試試。
  2. 程式一直反應"Skype is not running",設定類別按鈕無法使用
    請確定Skype程式已開啟執行(若為Windows 8,請確定要安裝執行"桌面專用的Skype",不是從市集下載的Skype App)。有網友回報: 確定Skype已在執行中仍彈出此訊息,因難以遠端偵測排除,建議重新安裝Skype或換台電腦試試。
  3. 程式彈出"這個網頁的指令碼發生錯誤",第4步驟無法抓出連絡人資料
    有熱心網友回報還看到nable to get value of the property 'SendMessage': object is null or undefined等訊息,研判是工具程式在整合內嵌瀏覽器時出錯,但原因不明,只能建議換台機器試試。
  4. 群組名稱以底線開頭或出現特定字元時會失敗
    有網友反應這種狀況,建議修改群組名稱避免之。
  5. Yahoo即時通連絡人每個人自成一個群組
    依網友提供資料,應是Yahoo即時通連絡人之資料格式不同,未在料想之內(今天才知道MSN這麼神通廣大,還串到即時通去了 XD)導致資料擷取出錯,因無法取得資料結構修正程式,建議透過修匯出CSV修正後再匯入因應。

【後記】

經統計這幾天已累積上萬人次測試,成功比例挺高,判斷已達可正式發行的程度,故決定視為正式版釋出。因本專案屬衛生紙性質的可抛式程式,以儘可能減少大家使用Skype的痛苦指數為目標,故後續將不再投入心力研發改良,對於一些零星的失敗案例,只能說聲抱歉,因遠端偵測排除不易(雖然我還蠻愛射茶包的),建議大家改換其他電腦試試可否避免。

如有問題回饋請在部落格或我的FB分舵留言,即使難以解決,也會整理成已知問題供其他網友參考。

【茶包射手日記】ICON導致程式在XP SP3無法執行

$
0
0

前幾天推出的Skype MSN群組工具,一些網友回報在XP環境無法執行,一執行程式便立刻以當掉(Crash)收場。由於網友回報多半只有"開不起來"、"當掉"等描述,一直蒐集不到錯誤細節,想解決也苦無頭緒。直到FB群組裡一位熱心網友,李大華同學,提供珍貴的當機畫面擷圖,另一位MVP Alex Lee則更進一步在手邊找到可以重現錯誤的XP SP3機器,錯誤資訊漸豐,總算有些眉目。

一開始被訊息中的clr20r3誤導,由於程式為.NET 4.0,錯誤卻由.NET 2.0的Runtime爆出來,直覺一直咬定跟.NET 4.0 Framework安裝有關,但經重裝.NET 4.0問題卻不見任何改善。瞎抓一陣子,才把焦點移到錯誤訊息中的另一條線索: System.IO.FileFormatException!

爬文找到一則高度相關案例,同樣是WPF程式,在XP SP2、Vista、Windows 7上執行都沒問題,獨獨在XP SP3一啟動就Crash,而錯誤來源也是System.IO.FileFormatException,跟我們遇到情境極為相似! 而同則討論中提到了ICON與PNG、JPG的問題,我想起專案中使用的ICON來自VS2010 Image Library的WinVista群組。

用Visual Studio 2012開啟ICO檔(沒想到Visual Studio連ICON檔都能直接編輯,不愧是地表上最強悍的開發工具),果然在其中發現了幾個大尺寸的PNG。

將PNG逐一移除,重Build程式再送請Alex測試,當機問題藥到病除~

推測這起當機疑雲跟ICON中內含PNG格式圖檔有關,但有趣的是,參考案例的程式在XP SP2正常,只有在XP SP3才出錯,而我們的案例中,程式使用.NET 4.0,但回報錯誤的來源卻是clr20r3,則是另一個謎。念在XP即將步上IE6後塵,快將被眾人放逐火星,茶包隱情已不值得用力追究。

唯獨得到一次寶貴經驗:

若程式在其他平台執行順利,唯獨在XP上一啟動就當機,請優先檢查是否源起ICON內含PNG?

再次感謝李大華及Alex Lee協助破案~

2013 萬金石馬拉松~

$
0
0

第六馬,萬金石馬拉松,也是2013三月三連馬的第一戰!

去年底失心瘋加手滑,三月連報了萬金石、櫻花馬、國道馬,每週一場(即江湖所說的連馬),外加海馬、山馬、公路馬大三元。週五氣溫還在26度,週日冷氣團來襲,氣溫驟降到13度,所幸原本預期的冷雨沒下,是乾爽的陰天,低溫無雨無日曬,可說是自己跑馬以來最好的天氣,加上萬金石賽道平坦寬敞,可謂破PB的絕佳良機!

俗話說: 天時地利百場難逢,不破PB天地不容! 無奈擔心暴衝受傷會影響隨後兩場,加上這陣子連夜化身程式魔人趕工,耗用不少真氣,體能狀態不佳,配速及成績目標該如何拿捏著實傷腦筋,最後決定回歸我最擅長的策略: Follow Your Heart! 就像LSD一般,不設目標,順著感覺跑就好,只求守住SUB 5。

起跑前,路協的老師再三叮嚀: "今年增設很多廁所,請選手們不要隨地便溺,尤其在涵洞用小水柱洗牆壁的行為非常不可取",想必去年鬧上新聞讓大會很在意呀!

6:30準時起跑,有沒有每次起跑時都會遇到永慢獅子頭大哥的八卦?

 

跑過不少山馬,海馬倒是頭一遭,迎著海風(但冒著13度吹風好冷啊~),聽著浪濤,擁抱海景在公路上奔跑的感覺真好! 而起跑前為了要不要單穿短袖短褲上場猶豫再三,最後決定穿外套比較保險,熱了挺多綁腰上,總比冷到發抖時無計可施來得好。這真是的明智的決定,一路跑來,不要說脫外套,全程拉鏈幾乎都拉到領口。

 

17K左右,遇到領先群折返,前5名全是黑人選手,18K附近,女子組黑人選手也回來了,非洲不愧是馬拉松界的霸主。今年的補給內容還不算差,有礦泉水、運動飲料、香蕉、小萫茄、迷你七七乳加巧克力(只是我牙口欠佳,硬綁綁的麥芽夾心嚼起來粉辛苦呀),途中還有幾站擺出數十罐肌樂類酸痛噴劑的大陣仗,讓人挺感動的。至於海綿站,今天生意明顯清淡,實在太冷了。唯一想抱怨的是,水站的垃圾桶數目太少也放得太近,常常邊喝邊吃邊走一小段後才發現無處可丟,巧克力包裝紙可以先塞口袋,但香蕉皮就麻煩了,總不能裝在口袋磨果泥或握在手裡滋潤肌膚吧? 不得已,我選擇放在分隔島的矮樹叢根部當堆肥,但好些人卻選擇抛在路中央,莫非這是擺脫追兵的斷後之計? (補聲暗) 最後我學乖了,乾脆站在水站垃圾桶旁,站三七步叉著腰,霸氣十足地啃完香蕉丟完皮,再繼續前進。

前30K差不多維持平日LSD水準,約3小時出頭跑完,旦之後疲勞度漸增,現階段罩門 -- 右腳跟與右膝開始傳來不適感,雖不到發作疼痛的程度,但已是警訊。深恐把腳玩殘後面兩場就甭玩了,便改變策略,放慢速度,不時切換到散步模式,放寬心情賞景吹海風,挺有詩意的,算是享受了萬金石馬的精華。

直到最後2K進了隧道,一度還意志消沈地想走完充數,卻在剩下不到1K時,發現拖鞋俠--荔枝大哥一路追來,莫名受到激勵,便一路直奔終點衝線,最後成績收在4:34:48,達成SUB 5,也守住了43X。

PACE記錄: 06:01 / 05:35 / 05:50 / 05:26 / 05:19 / 05:17 / 05:36 / 05:20 / 05:18 / 05:22 / 05:44 / 05:58 / 05:53 / 05:35 / 05:34 / 06:02 / 05:35 / 05:54 / 05:50 / 06:59 / 05:51 / 05:55 / 06:10 / 06:03 / 06:00 / 07:11 / 06:21 / 08:25 / 07:34 / 07:32 / 09:07 / 06:33 / 08:11 / 08:33 / 08:30 / 07:25 / 06:34 / 06:48 / 06:37 / 07:52 / 08:07 / 07:59 / 05:09

下一場準備重溫惡夢舊夢,回到害我跌入無底深淵讓我體悟馬拉松奧義的神聖初馬跑道。櫻花馬,我來了~

Viewing all 2458 articles
Browse latest View live


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