Quantcast
Channel: Darkthread
Viewing all 428 articles
Browse latest View live

NuGet packages目錄舊檔大掃除

$
0
0

NuGet已是開Visual Studio寫專案的必備工具,用關鍵字找到項目就能安裝,有新版本點一下就更新,非常方便。

我們遇到一項困擾:NuGet在升級程式套件前會移除專案的舊版DLL,至於packages目錄下的舊版檔案若已無其他專案參照,也會一併刪除,理論上不會累積。依NuGet 文件說明,packages目錄之檔案移除原則如下:

  • Files in the solution folder. The folder for the package you removed is deleted from the packages folder. If it is the only package you had installed, the packages folder is also deleted.)

不過因為我們配合Build Service採取 packages簽入TFS策略,雖然NuGet升級時會刪除本機pacakges裡舊版檔案,但習慣上大家不會主動將package刪除操作簽入版控,而TFS版控裡的舊檔仍在,下回Get Latest就又通通跑回來,加上怕Build Service建置失敗,寧可錯加也不敢錯殺的心態,日積月累之下,版控packages裡的項目多如牛毛,一堆無用項目也拖累 Build Service 建置效率。(若Build Service設定成每次建置新開資料夾不覆寫,浪費的頻寬及儲存空間會更可觀)

最後,我們專案的 packages 宛如一座歷史博物館(以下圖),從毛公鼎、唐三彩、青花瓷到漢玉寶塔都有。NLog從2.1.0到4.2.3、Json.NET從5.0.7到7.0.1,各有七個版本。哪些有用哪些沒用,孰可殺誰該留則是個謎!讓開發小組興起「大掃除」的念頭。

爬文求解,網路上有人提到 Resolve Unused Reference套件,試用發現它主要用於修改 csproj 移除沒用到參照,移除 packages 屬於附加功能,而強行移除專案未用參照的做法有點粗暴,有違我只想清理 packages 舊版的原意,再加上一些被它誤殺導致無法編譯的Bug回報(提醒:要使用它前務必先 Check In 或備份,以免造成遺憾。),此一選項迅速被排除。

最後,想到一解,先手動將本機 packages 目錄清空,再借用 NuGet 還原功能重新下載(如果有多個 sln 共用 packages 目錄,務必逐一還原或重新編譯以防漏載),做完一輪,理論上 packages 就只會剩下各專案所需的必要版本的聯集。TFS 似乎沒有功能自動將本機缺少的檔案從版控刪除(實務上很少有此需求),我想到的愚公移山法是在 TFS Source Explorer 逐項巡視,若Local Path 未出現藍底連結表示資料夾不存在,可以安心刪除。(補充:文末有更有效率的做法)

最後共計刪掉55個無用舊版資料夾,packages 目錄總算清爽多了。

【2016-03-03更新】

抛磚引玉成功,Franma 大人開示使用 TFS 比對功能快速找出被刪檔案的妙計,下回就不用再愚公移山囉!特此感謝。

步驟為在 Solution Explorer packages 目錄叫出右鍵選單執行 Compare:

選擇只列出在來源路徑出現的檔案(TFS有,但實體路徑沒有):

比對結果就是所有被刪除的檔案,酷!


【茶包射手日記】只在CSHTML發生的編譯錯誤

$
0
0

在ASP.NET MVC專案新一個View,編輯CSHTML時Visual Studio爽快地賞了我一個錯誤:某個關鍵型別同時出現在兩顆組件(DLL)中!

看了兩顆組件名稱,Afa.WebApi是MVC網站的編譯結果,Afa.WebApi.Client則是先前嘗試不同做法時曾短暫加入,現已不再參照,二者不該並存。詭異點在於這個關鍵型別被Controller及Model廣泛使用,若重複出現在不同組件,MVC專案也該出錯,為何在加入CSHTML前編譯、執行完全正常?

檢查MVC的bin目錄,果然發現Afa.WebApi.Client.dll的蹤影,判斷為先前短暫加入參照時所殘留,而Afa.WebApi.dll與Afa.WebApi.Client.dll意外並存,就是CSHTML抱怨型別重複出現的根源。至於為什麼組件並存只影響CSHTML,原本MVC專案還是可以順利編譯?仔細想想不難理解-關鍵在於二者編譯方式不同!

ASP.NET MVC屬於Web Appplication專案,編譯時要參照哪些組件由csproj列舉項目決定;而CSHTML/Razor採取執行期間動態編譯,像Web Site Project時代的.aspx /.aspx.cs一樣,享有「改完存檔,重新整理網頁就生效」的即時性,而要實現這一點,編譯的參照來源也跟Web Site Project一樣,由放進bin目錄的組件決定。由此編譯行為差異,就不難解釋為何只有CSHTML出錯,Controller/Model未受影響。Case Closed.

新裝備入手-Nokia N1

$
0
0

陪伴兩年多的Nexus 7最近很不乖,常發生觸控沒反應或點擊位置誤差,尤以右上角最嚴重。最討厭的狀況是想關Chrome分頁關不掉,變成新増分頁,分頁愈關愈多令人光火;有時則是手指沒摸到螢幕,卻像有無形的手指亂點,畫面不斷放大縮小抖動不停,好氣又好笑。爬文在Mobile01上找到不少類似症狀的討論,觸控失靈貌似Nexus 7的常見問題,找來網友推薦的YAMTT測試工具,妖怪立即現形。如以下影片,手指一移到右上角,進入影分身模式,一點變五點,點擊頻率賽過加X鷹呀~ (YouTube上有加壓排線接頭解決觸控問題的教學,試過無效,而且我的失靈問題明顯集中在右上角,硬體損壞機率較高。)

眼見病發日益頻繁,醫生嘆了口氣,向滿面愁容的中年男子說道:「老爺,少奶奶的日子只怕不多了…」,只見那男人臉上表情忽然轉憂為喜,轉身對老僕說「來福,快把上回婚友社送來的候選資料拿來給我…」(喂)

已有手機筆電,平板只拿來傳LINE、供通勤或居家簡易上網用,另外也是要測試App或網頁的Android裝置代表。3C設備生命週期有限,即使配備攻頂,時間到了照樣得換,以消耗品心態找個中價位的可靠機型才是明智之舉。習慣了Nexus 7的輕巧,鎖定4:3 8吋尺寸,在網站上逛了一輪,發現Nokia去年出的Nokia N1,7.9吋4:3螢幕適合看網頁多過看影片的我,跟iPad mini 3相近的金屬外殼,2048x1536 IPS螢幕,Gorilla防刮玻璃,800萬像素後主鏡頭及500萬前鏡頭都很討喜,唯一可挑剔的就是少了NFC跟GPS。沿續對Nokia Lumia手機的好印象,加上網購價格下殺到六千有找,還送保護貼/耳機/觸控筆(到之貨才發現連保護皮套都內附了,真好),腦波一弱手一滑,東西就來了。

小插曲是網購網站上標明預購商品,下單14天後才會出貨,訂購確認通知也寫明兩週後的某月某日才出貨,心想Nexus還在,用等待換取優惠是值得的。人算不如天算,下單後Nexus 7病情突然惡化,讓我擔心等貨的這兩週會被觸控失靈搞瘋?萬萬沒想到,下單隔天一早就有人送貨上門… 嘖,真是一家沒制度不講章法胡亂出貨的網購公司,把14天預購搞成24小時到貨…不過,因為別人不講信用而嘴角上揚的經驗實在不多呀~ XD

試用心得,Nokia N1的質感一如預期(跟Limia 920一樣好),沒有硬塞一堆廠商自製軟體(這點我喜歡),唯一附上的Z Launcher還不錯用,主畫面顯示最近使用的App,據說還能觀察主人習性依不同時段提供「搭捷運常用App」、「蹲馬桶專用程式」等。畫面可直接寫字母搜尋App,可惜不支援中文,右側頁面則是以字母排序(有Windows Phone的味道,對我格外親切)的完整App清單,以我安裝App數量不多的慣例,說不定連多桌面跟資料夾分群管理都省了。

最後,附上沒誠意新裝備展示照一張。

花園傳來嬉鬧聲,男子與少女在花叢間追逐嬉戲,「老爺別這様,夫人會看見…」(喂)

系統升級導致Android App閃退

$
0
0

新到手的Nokia N1設定好後收到升級通知,Adroid系統由5.0.2升級到5.1.1。重開機後立即冒出「Goolge書報攤」當機訊息,接著陸續發現Google圖書、萌典、Garmin Connect Mobile等App都無法使用,一啟動就閃退。心中暗叫不妙,是升級過程出了問題還是碰上機王?

一度懷疑是App不支援Nokia N1所致(先前用Google Nexus沒這疑慮,App跟Google自家平板不相容,一定是App的錯呀),但爬文未見類似案例,看來是人品問題 orz。新買平板有個App不能跑頗悶,興起恢復原廠設定重來一次的衝動。

動手前系統提示有幾項更新,其中有一項「Android System WebView」引起我的注意,雖然不熟Anroid App開發,約略知道這是在App顯示網頁的常用元件,莫非Google圖書、萌典、Garmin Connect Mobile這幾個App的共通點就是用了WebView,作業系統更新後元件沒更新才無法執行?

滿懷希望等待更新安裝完成,三個App的當機問題果真一次消失!答案揭曉,本茶包為「Android作業系統更新5.1.1後,Android Systm WebView也需配合更新才能運作」,而作業系統更新到WebView更新的這段時間,使用WebView的App就發生一執行就當機的現象。

好奇系統核心等級的元件怎麼沒跟作業系統一起升級?爬文找到一些線索,Android從Lollipop版本起將WebView拆成可獨立更新的套件(參考),較易於更新修正。基於安全理由,WebView需要較頻繁的更新,這個考量可以被理解。而網路上有WebView元件更新問題導致多個App不斷當機的案例,未來遇類似狀況,倒是可以優先考量此一因素,先更新或還原WebView版本試試。

將複合字串拆成多欄位-以ORACLE及SQL為例

$
0
0

先說說我的需求。某資料表使用複合欄位當Primary Key,例如:由OrgId、DeptId、UserId三欄組成唯一鍵值。當要查詢特定資料,理論上應寫成WHERE OrgId='…' AND DeptId = '…' AND UserId = '…'。為求簡便,在.NET程式端以及某些資料表我發明了一種複合代碼字串"OrgId-DeptId-UserId",只用一個參數或一個欄位就搞定關聯,省時又省力。不過,每個做法總有黑暗面,當複合代碼字串要拿來查資料時,得先拆解成OrgId、DeptId、UserId三個值,在C#裡用個.Split('-')可以輕鬆搞定,但如果要在SQL裡處理就麻纇多了。

有一種很鳥但絕對可行的解法:

SELECT * FROM BLAH WHERE OrgId + '-' + DeptId + '-' + UserId = 'OO-XX-orz'

保證你能正確查到資料,但你知道我知道獨眼龍也知道,遇到欄位加工再比對的寫法,DB必須逐筆處理無法靠索引加速,速度慢又不環保。
2016-03-11 補充,Oracle有Function-Based Index,欄位加工再比對也可使用Index。感謝Jumo補充。

最近在ORACLE再次與此問題狹路相逢,這回爬文學到一個神奇函數-REGEXP_SUBSTR,可在ORACLE用Regular Expression處理字串,做到類似Split()的效果。透過REGEXP_SUBSTR('FBI-IT01-12345', '[^-]', 1, 2),用'-'符號可將字串拆成三段,最後一個參數2可指定要取出第幾段。於是,老問題找到新解法了:

-- :pFindKey = 'FBI-IT01-12345'
--方法1
select * from BLAH where OrgId || '-' || DeptId || '-' || UserId = :pFindKey
--方法2
select * from BLAH where
       OrgtId = REGEXP_SUBSTR(:pFindKey, '[^-]+', 1, 1) and
       DeptId = REGEXP_SUBSTR(:pFindKey, '[^-]+', 1, 2) and
       UserId = REGEXP_SUBSTR(:pFIndKey, '[^-]+', 1, 3)

那那那那,在SQL Server遇到這個問題怎麼辦?SQL沒提供支援Regular Expression的內建字串函數,這類官方多半建議用威力強大的SQLCLR解決,但實務上一旦引用自訂Stored Procedure、User Defined Function會增加部署的需求,在我眼中,完全靠內建函式搞定才是王道呀!

找到一記妙招,將'FBI-IT01-12345'轉成'<n>FBI</n><n>IT01</n><n>12345</n>'後轉型成SQLXML型別,就可用.value('(/n)[2]', 'varchar(16)')取出'IT01',如此在SQL也能將複合字串拆成多欄位囉!

declare @pFindKey varchar(32) 
set @pFindKey = 'FBI-IT1-12345'
--方法1
select * from BLAH where OrgId + '-' + DeptId + '-' + UserId = @pFindKey
--方法2
select BLAH.* from BLAH
join (selectconvert(xml, '<n>' + replace(@pFindKey, '-','</n><n>') + '</n>') as x) Keys
on OrgId = Keys.x.value('(/n)[1]', 'varchar(16)') and
   DeptId = Keys.x.value('(/n)[2]', 'varchar(16)') and
   UserId = Keys.x.value('(/n)[3]', 'varchar(16)')

2016雙溪櫻花馬

$
0
0

路跑熱潮稍退,賽事報名不再場場秒殺,硬得要命的櫻花馬卻依然搶手。心知不一定搶得到,報名當天只想碰碰運氣,哪知忙起來整個忘光,等回神已錯過報名開始,報名網站早被眾人殺到暈頭轉向,怎麼喴都沒回應,混亂間看到「已額滿」訊息,心想大勢已去,罷了。幾分鐘後接獲線報,馬拉松LDS搭檔忠孝哥傳來還有名額的消息,雖然一下額滿一下可報有失誠信,但我完全不計較,再報一馬。

為配合火車時刻,櫻花馬照往例遲至8:30起跑,氣溫較高,但樂得睡飽一點也好。馬拉松跑多心態大不同,以前跑馬像出國,前一天就慎重地照Check List打包行囊,緊張到輾轉難眠;如今已如上菜市場,東西抓一抓就走,想想都覺好笑。

六點多出發,剛好趕上日出。

遇上氣溫24度以上的大熱天,跟前場渣打馬的4度相比,足足差了20度,不折不扣如洗三溫暖的跑馬人生。XD

起跑前的「公主徹夜未眠」聲樂表演盪氣迴腸,現場聽超級震憾!(謎之聲:誰叫你站在音響喇叭前面?)沒機會聽到主持人介紹演出者,爬文猜想是在藍鯨坪林超馬表演過的許文龍老師。(影片

司令台旁獎典組帳篷,閃閃發亮的金馬獎盃真漂亮,自知腿弱體衰,此生與凸台算是無緣了,跑健康就好,跑健康就好。今年意外櫻花馬有項好傳統--捐衣活動,家裡一堆沒穿過的路跑排汗衫有了好歸宿。

五千人的規模真盛大,到起跑區時間稍晚一點,所在位置已看不到起跑拱門。

天氣也太好了,氣象預報說好的多雲呢?今年氣候特殊,花開得不整齊,沒能目睹萬花齊放盛況,而三月初大部分櫻花已謝,所幸一路仍有幾株「大器晚成」的櫻花相挺,不愁無花可賞。

人多路窄,加上難以完全封路,前10K不太好跑,水站還出現高難度的人車交錯表演。

虎豹潭附近的這間公廁是我的奧林匹克指定水費繳交處,與貓空半馬的明德宮洗手間齊名,是我每回跑櫻花馬一定要來報到的打卡點,解放一下身心舒暢。

鄉間農舍旁有一棵盛開櫻花,在樹下發現野生山貓路跑團成員。

本場開LDS模式,與忠孝哥邊跑邉聊,心跳超標就立刻減速,以「跑健康」為最高指導原則。

前半馬花了三小時,差不多在中段班。回程收容車已在路旁待命,站在車旁招呼的志工很壞心:「快上車哦,天氣那麼熱,上車就可以休息吹冷氣囉!」哈。而回程只要聽到後方傳來小巴士引擎聲,就知道有一台「坐好坐滿」的冷氣回容車回去了。 XD

倒數的某個水站,只見滿地的雞精空瓶… 我們跑太慢,雞精發完惹,水站志工急中生智:「沒喝到沒關係,跑步喝雞精沒用啦」(哈)

沒喝到雞精讓人沮喪,但吾等必須坦誠面對此一挫敗,拿不到選票雞精全因自己努力不夠,無法獲得大會的認同,但我們不會懷憂喪志,必將深刻檢討自省,會在下次比賽重新嬴回信任與支持… 抓抓~~~(瓦斯氣笛聲)

回程路過蘭平千里石碑,趁人少拍照一張留念。(想起去年Kuso版由「雙膝硬化馬」題字的「勿忘在雙溪」 碑)

來到下個水站,還有雞精耶!嘿,不用檢討反省囉~

最後9K是爽爽的下坡,但跑快還是會爆心跳,慢慢來,健康就好。

勉強挺前SUB 6,事後查成績,分組排名約落在50%,若依總排名計,則還有近2900人要在40分鐘內趕回來才能擺脫落馬命運。

領完寄物遇上雙溪國小的小朋友:「叔叔,要買我們的手工皂嗎?」,明知這是行為的藝術裡所說的「擬人化」戰術,還是很沒抵抗力地拿退晶片的100元換了手工皂,回家一看,居然是螃蟹造型,蠻可愛的。

喀完便當準備回家路過終點拱門,剛過關門時間,但桌上還有成堆獎牌苦等主人歸來。大會宣告6:40之後回來的跑友有獎牌有毛巾,但不會有成績。依大會統計,約有15%,約670名跑友落馬,高溫豔陽是幫凶,沒關係,雙溪的硬斗山路永遠都在,明年再來。

去年題字的雙膝硬化馬先生今年一躍成為完賽獎牌的主角,很有意思:

 

Nokia N1麥克風無聲問題經驗一則

$
0
0

Nokia N1新入手用了幾天,感覺一切都好,心想「網購七天退貨大絕」是用不到了,貼上保護貼正式啟用。

昨天想到試試語音輸入才發現大事不妙,麥克風全無反應!試了影片錄製,有影無聲,檢查確認App有「錄製音訊」授權。更!我放棄七天退貨權才發現自己拿到機王,心中滿是狂奔羚羊~

既然無法退貨上網查查送修資訊,發現一則N1送修的鬼故事令我冷汗直冒,雖然故事喜劇收場,且現在維修服務應已改善,但要奔波送修短期無機可用總是麻煩,即使能七天退貨,搬回Nexus 7也得花功夫。遇上硬體問題,怎麼都少不了折騰,難道全因為我的人品?orz

平撫情緒,換上甲種射手服裝,來個基本故障分析。找來耳機麥克風對照測試,以確認問題出在硬體還是軟體。插上耳麥,進入Google App語音輸入模式按下麥克風鍵,發現用耳麥可以正常輸入。所以,這代表內建麥克風硬體是壞的?並不是!移除耳麥後,N1就能正確收音了,拍影片收音也正常,再測了LINE通話,判定麥克風功能正常。

結論,Nokia N1 Anroid平板內建麥克風要用耳麥開光才能解除封印。(誤)

雖不知其所以然,還是留文一篇,期望能幫到有緣人。

T-SQL使用逗號分隔字串當作WHERE IN條件

$
0
0

寫Stored Procedure時有一個麻煩情境是由外界傳入參數當作WHERE IN條件,由於參數數量不定,難以事先寫成WHERE … IN (@val1, @val2, @val3),開發者往往會走上用傳入參數組裝SQL指令的險路,稍有不慎就搞出SQL Injection,導致難以想像的災難。(是的,SQL Injection不只會出現在ASP/ASP.NET/PHP/Java/C++,也可能藏在Stored Procedure裡)

最常見的例子是開放使用者勾選一個到多個類別作為篩選條件,例如使用者選取了「主機」、「螢幕」與「耗材」,要轉換成SELECT … FROM Products WHERE Category IN ('主機','螢幕','耗材')。如果你會Dapper,這需求絕對是小菜一碟,cn.Query<T>("SELECT … FROM Products WHERE Category IN @categories", new { categories = "主機,螢幕,耗材".Split(',') })搞定收工,而且是以SqlParameter方式傳遞字串參數,完全沒有SQL Injection疑慮。然而同樣場景搬到T-SQL,卻沒有類似的現成簡便解法可用。

前幾天學會用SQLXML拆解字串的技巧,剛好可以拿來解決難題,將CSV逗號分隔字串先轉成XML型別,再使用.nodes()拆成多筆,就能當成WHERE IN的比對陣列來源囉,如以下範例:

DECLARE @deptIds VARCHAR(128)
SET @deptIds = '1,3,5,7'
DECLARE @x XML
SET @x = CONVERT(XML, '<n>' + replace(@deptIds, ',', '</n><n>') + '</n>')
SELECT * FROM HumanResources.Department
WHERE DepartmentID IN (
SELECT T.n.value('.','varchar(5)') FROM @x.nodes('n') T(n)
)

拿SQL Server的AdventureWorks範例資料庫來練槍,真的可以用"1,3,5,7"字串查出DepartmentID是1,3,5,7的部門資料!

實務應用時,大家應該會選擇將CSV拆多筆資料的邏輯包成函式,方便重覆利用。

CREATEFUNCTION [dbo].[SplitCsv]
(
    @csvString nvarchar(2048),
    @delimiter nchar(1)
)
RETURNS @valuesTABLE (value nvarchar(2048))
AS
BEGIN
DECLARE @x XML
SET @x = CONVERT(XML, 
'<n>' + replace(@csvString, @delimiter, '</n><n>') + '</n>')
INSERTINTO @values
SELECT T.n.value('.','varchar(2048)') FROM @x.nodes('n') T(n)
RETURN
END
 
--使用範例
SELECT * FROM HumanResources.Department
WHERE DepartmentID IN (
SELECTvalueFROM dbo.SplitCsv('1,3,5,7', ',')
)

學會這招,以後就不用再因為WHERE IN挺而走險組SQL字串囉~ (補充:以上方法不考慮CSV字串夾帶XML內容的罕見情境,如不幸遇上請自行克服)

溫馨小提醒:因為你因為不可抗力因素必須走上組SQL這條路,請用生命擔保它沒有SQL Injection風險,以免造業。

有小道消息指出,閻羅王去年研發了一批新刑具,專門對付寫出SQL Injection的程式設計師,很可怕,不要問!


無法使用Windows帳號登入防火牆內的SQL Server

$
0
0

要穿過防火牆連上一台SQL(1433 Port有開,網路芳鄰NETBIOS封閉),發現用SQL帳號登入(SQL Authentication)可成功登入,若用AD帳號(Windows Authentication)則會出錯。

錯誤訊息為:

已超過連接逾時的設定。在嘗試使用登入前的信號交換確認時超過逾時等待的時間。這可能是登入前的信號交換發生失敗,或伺服器無法及時回應所造成。

Connection Timeout Expired. The timeout period elapsed while attempting to consume the pre-login handshake acknowledgement. This could be because the pre-login handshake failed or the server was unable to respond back in time.

 

爬文找到線索,有人回報ADO.NET連線字串使用Integrated Security=SSPI時發生相同狀況,但較常原因是SQL Server的Named Pipe或TCP/IP協定未啟動所致,與我受防火牆阻隔的情境不同。心生一計,Server Name用機器名稱取代IP,就登入成功了。推測使用Windows Authentication需由IP解析機器名稱,但因防火牆阻擋無法反查,故造成操作逾時,直接輸入機器名稱避開此一無法完成的程序。以上僅限揣測,還是PO文一篇備忘,順便供給遇到這些罕見情境的有緣人參考。

【2016-03-18 補充】

文章貼出後,於FB獲網友高錦川先生提供MSKB一則,文中提到:

當用戶端的 SQL Server 驅動程式使用整合式安全性連線至 SQL Server,用戶端驅動程式的程式碼會嘗試解析執行 SQL Server 電腦的完整格式 DNS 網域名稱,方法是使用 WinSock 網路 API。為了執行這項作業,驅動程式的程式碼會呼叫 gethostbyname 及 gethostbyaddr WinSock API。即使 IP 位址或主機名稱通過執行 SQL Server 的電腦驗證,如果該電腦使用整合式安全性,SQL Server 驅動程式仍然會嘗試解析連線電腦的 DNS 完整格式。

算是證實使用Windows驗證(整合式安全性)會觸發由IP反查機器名稱的動作,由於出問題的主機身處防火牆內無法用NETBIOS反查機器,再因DNS設定問題從IP反查FQDN也失敗,造成以上的結果。依此原理,在hosts中設定SQL的IP與機器對應後,用IP也能用AD帳號登入了。

KB提供一招檢測技巧,使用「ping –a xxx.xxx.xxx.xxx」檢查解析名稱是否成功,例如以下例子,192.168.32.11解析失敗,192.168.32.12則成功解析為sqlserver01,結果為用AD帳號可登入192.168.32.12,登入192.168.32.11則會失敗。

C:\Windows\System32\drivers\etc>ping -a 192.168.32.11

Ping 192.168.32.11 (使用 32 位元組的資料):
回覆自 192.168.32.11: 位元組=32 時間=3ms TTL=116
回覆自 192.168.32.11: 位元組=32 時間=3ms TTL=116
回覆自 192.168.32.11: 位元組=32 時間=3ms TTL=116
回覆自 192.168.32.11: 位元組=32 時間=3ms TTL=116

192.168.32.11 的 Ping 統計資料:
    封包: 已傳送 = 4,已收到 = 4, 已遺失 = 0 (0% 遺失),
大約的來回時間 (毫秒):
    最小值 = 3ms,最大值 = 3ms,平均 = 3ms

C:\Windows\System32\drivers\etc>ping -a 192.168.32.12

Ping sqlserver01 [192.168.32.12] (使用 32 位元組的資料):
回覆自 192.168.32.12: 位元組=32 時間=2ms TTL=116
回覆自 192.168.32.12: 位元組=32 時間=2ms TTL=116
回覆自 192.168.32.12: 位元組=32 時間=2ms TTL=116
回覆自 192.168.32.12: 位元組=32 時間=2ms TTL=116

192.168.32.12 的 Ping 統計資料:
    封包: 已傳送 = 4,已收到 = 4, 已遺失 = 0 (0% 遺失),
大約的來回時間 (毫秒):
    最小值 = 2ms,最大值 = 2ms,平均 = 2ms

學會這招,下回再遇SQL帳號可登,AD帳號無法登入的狀況,可以先用「ping -a」技巧初步偵斷一下。

【茶包射手日記】Java內嵌IE網頁疑案

$
0
0

接獲報案:某支Java開發的程式以內嵌IE方式顯示特定網頁,在特定機器執行時網頁出現異常。

初步蒐集情報如下:

  • 於問題機器單獨使用IE或Chrome可正常顯示該網頁
  • 問題機器之IE版本為IE11
  • 問題僅出現在特定機器,同一Java程式於其他機器執行正常
  • 於異常網頁按右鍵檢視HTML原始碼完整,異常部分推測為JavaScript出錯導致
  • IE在內嵌模式下無法使用F12開發者工具,偵錯困難

沒有F12可用,改用Fiddler側錄往來封包,找出後半段Request未發送證據,推測最大可能是JavaScript程式在某階段中止執行。

反覆推敲程式邏輯,包含簡單jQuery Deferred串接數個AJAX呼叫、ViewModel屬性設定、變數比對if分支、console log偵錯,未見可疑之處。無法使用F12偵錯,只好拿出石器時代狩獵技巧,在程式碼各階段埋下alert,確認程式執行進度。

流傳千古的愚公移山法原始歸原始,效果倒不容懷疑,很快把搜索範圍縮小,我忽然眼睛一亮,console.log(blah)!大膽妖孽,還不現形?

讓我們回顧三年前的文章

IE8/IE9要先按F12開啟IE Dev Tools才能存取console物件啦! 笨蛋!

將console.log註解掉,網頁果真恢復正常,證明凶手是console.log無誤!還原案情, 推測是IE被內嵌於Java程式時因某種原因啟動了IE相容模式,而JavaScript在使用console前未確認console是否存在,於是… 轟!

所以我們該寫成window.console && console.log()解決問題嗎?當然不是,從2016/1/12起,微軟只對最新版本的IE(目前是IE11)提供技術支援及安全更新,理論上IE6/7/8/9/10該馬上從地球消失!(理論上啦~前陣子處理一起網頁異常,調查後驚覺使用者還在用他X的IE8,詢問才知受限某個不支援IE9+的重要系統,只好含淚不升級… 被老舊系統壓迫的使用者快站出來反抗吧!)

所以我心目中最好的解法是「把程式寫成不升級新版IE不能跑,把因舊系統不能升級的使用者逼上絕路督促無法升級的使用者向死抱老IE的廠商施壓」。建議做法如下:

  1. 在<head>加上<meta http-equiv="X-UA-Compatible" content="IE=edge">防止IE進入相容模式
  2. 加上一小段<script>window.console || console.log("你媽知道你還沒升級IE嗎?");</script>

全案偵結,收隊!

對付SQL Injection,換掉單引號到底夠不夠?

$
0
0

雖然現在遇到使用者輸入條件查詢DB,我一律都用參數化查詢(順推超好用的Dapper)不再偷懶組裝SQL指令,但關於SQL Injection,我心中始終藏著一個疑問:流傳千古的… WHERE Col = '" + input.Replace("'", "''") + "'"換單引號大法,人人都知它不夠安全,但網路流傳一種說法,指稱換單引號只是自欺欺人根本無效,奇怪的是卻很少看到它被打穿的實例。我完全同意置換法不夠安全,黑名單法一旦漏列就會破功,但既然要把它說得不堪一擊,拿點證據出來很難嗎?很難嗎?很難嗎?

我試了,還真的不簡單…

爬文找到幾種主張置換法無效的說法:

  • 遇到WHERE NumberCol = " + input.Replace("'", "''")時置換無效
    如果欄位是數字型別,將input字串轉成數字才嚴謹,寫成這様是自己跳坑,不列入考慮
  • 利用編碼轉換偷渡單引號
    字串置換函式、資料庫驅動程式或資料庫採用非Unicode編碼,透過巧妙安排讓傳入的Unicode字串轉換後形成單引號。若以SQL Server為例,Client與Server幾乎都已是Unicode,以C# System.String.Replace()、內建SqlClient程式庫、SQL2005-2012為例,置換單引號的做法不致留下轉碼漏洞(參考),但在其他環境則很難說。
  • 利用單引號等效字元
    爬文發現一種傳說中的神奇字元0x02bc(Modifier Letter Apostrophe),串接在SQL指令效果等同單引號。這讓我為之一震,可以替代單引號的神祕字元?好一隻黑天鵝

眾多說法中最吸引我目光的莫過0x02bc字元,尤其不少人說這招在SQL Server及MySQL都管用,甚至有範例程式:參考

string badValue = ((char)0x02BC).ToString();
badValue = badValue + ";delete from widgets--";
string sql = "SELECT * FROM WIDGETS WHERE ID=" + badValue.Replace("'","''");
TestTheSQL(sql);

迫不及待想驗證,很可惜,對SQL Server Express 2014做了類似測試,攻擊失敗!0x02bc被視為一般字元,安全過關。

如果SQL不行,那MySQL呢?特地安裝了MySQL 5.7 Community,如法泡製拔出0x02bc進攻,再次鎩羽而歸。

由以上實測,至少證明0x02bc已不具備打穿SQL Server Express 2014跟MySQL 5.7的神奇功效。或許在以前的某些SQL或MySQL版本上是可行的,但至少在我測的兩種DB版本已不存在。無心再去找活化石老DB試玩,就此打住。

意思是我們可以安心繼續組SQL字串再用單引號置換法嗎?當然不是,遇上特定資料庫、程式庫版本,只置換單引號必然存在等效特殊字元偷渡做亂的可能性,無論換掉多少可疑的字元,只要有漏網之魚就會破功,這是黑名單法無法被補強的本質缺陷。所以,乖乖使用參數化查詢,不要組裝SQL指令才是治本之道!

【結論】我百分之百贊同「置換單引號不能杜絕SQL Injection,在某些特殊情境下會被攻破」,但在沒有強力佐證的情況下要說「哼!換掉單引號根本沒屁用」則略嫌浮誇。

以上看法屬爬文及實驗後的心得,受限個人所見所學,就當拋磚引玉吧,以下開放想打臉、補刀的朋友提出實證討論。(我真的好想看到單引號置換法被打穿)

TFS Build Queue卡單排除經驗一則

$
0
0

今早使用TFS Build Service建置部署時,建置作業卡在Queue裡遲遲不開始,等了五分鐘感到不對勁,展開調查。

狀態顯示我排在第二順位,似乎在等待其他建置執行完畢,但超過5分鐘頗為異常,決定查查前面的烏龜車是哪一台。

登楞!整個Queue裡只有我一人。夾緊擴約肌在廁所門口苦等十分鐘,忍不住破門想抓出是誰著佔茅坑,結果沒人?

推測是TFS Build Service故障,啟動3R(Restart, Reboot, Reinstall)第一步,遠端登入Build Service主機開啟TFS管理主控台,原本想重啟Build Controller或Build Agent就好,但兩個服務的Restart鈕沒反應,最後按Build Service的Restart鈕重啟服務後問題排除。

開啟TFS Build Service Log

$
0
0

前天提過的TFS Build Queue卡單今天再度上演,一樣又是重啟TFS Build Service才解決,由於已非偶發罕例,決定展開調查。由事件檢視器查到一筆發生在卡單前一刻的可疑錯誤,Build 1257號,正好是被卡住1258號的前一筆,訊息為An error occurred while calling tracking participants causing the instance to be aborted. 而回頭檢查前天出錯前也有一筆一模一樣的錯誤訊息,推測極可能是導致Queue停止運作的元凶。

我沒找到方法由數字1257反查是哪個專案及編譯結果,只好回歸土方法-逐一偵訊詢問關係人,最後找出失敗建置的Log,確認建置的部署步驟發生檔案複製錯誤,但單純建置失敗為何導致Build Service故障則是個謎。想進一步調查卻苦無線索,爬文得知TFS Build Service可以開啟Log以保留更多偵錯資訊。網路上找到不少教學(),教導修改TFSBuildServiceHost.exe.config設定BuildServiceTraceLevel及TeamFoundationTextWriterTraceListener 的方法,我找到config,發現已內附被<!-- -->註解掉與教學文相同的<system.diagnostics>,立即取消註解,指定Log路徑,但試了好久都不成功,Log檔始終沒出現。再爬文,才發現被擺了一道,Microsoft Connect上有一則Issue Report

This is a feature that had been improved with TFS 2012, and the MSDN documentation for it can be found at
http://msdn.microsoft.com/en-us/library/dd723544.aspx#events
(Manage Your Build System, Use event logs to diagnose problems)

In short, all logging is now available in the Event Viewer, and this can be accessed from the Team Foundation Server Administration Console in the Build Configuration section. As described in the referenced MSDN page, increasing levels of detail can be found in the recent events, operational log, and analytic log (which must first be enabled). Please take a look and let us know if you have any further problems.

Our apologies for the confusion regarding this change. Obviously the leftover comments in the TFSBuildServiceHost.exe.config file regarding trace logging were misleading, and we’ll have those removed in a future release.

意思是從TFS2012起,<system.diagnostics>只用來控制Log詳細度,Log不再寫成檔案而是改存入Windows事件(說實在話,寫成Event是比Log檔方便很多)。TFSBuildServiceHost.exe.config 設定檔殘留被註解掉的TeamFoundationTextWriterTraceListener 是個陷阱,真是抱歉!orz

照著MSDN文件說明,在事件檢視器開啟「Show Analytic and Debug Logs」:

找到Event Viewer/Application and Service Logs/Microsoft/Team Foundation Server/Build Services/Analytic按右鍵選單「Enable Log」:

重啟TFS Build Service後就會啟用記錄功能,我試著排入一個編號1258的建置,在事件中果然找到1258號對應到的建置定義資訊。下回再遇到狀況就會有多一點線索可追,至於能不能抓出卡單之謎?就讓我們拭目以待。

Chrome的Button Click行為差異

$
0
0

同事報案網頁在IE與Chrome表現不同,依稀記得遇過,但沒寫成沒找到明確記錄,花了時間回想、研究、實驗,得到結論後才恍然憶起,從HipChat對談翻出以前的辦案記錄。明明是前科犯還重啟調查,記憶力壞掉好可怕,也懊悔浪費了時間。由此得一結論-「勿以茶包小而不記,永遠別信任中年人的記憶力」,故寫此文。

用範例程式說明如下: Live Demo

<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width">
<title>Button Click</title>
<style>
    button { padding: 6px; }
    span { 
      background-color: yellow; 
    }
</style>
</head>
<body>
<button>
<span>CLICK</span>
</button>
<scriptsrc="https://code.jquery.com/jquery-2.1.4.js">
</script>
<script>
    $("button").click(function(e) { 
      console.log('Button Clicked'); 
      console.log("e.target=" + e.target.outerHTML);
    });
    $("span").click(function(e) {
      console.log("Span Clicked");
    })
</script>
</body>
</html>

在<button>裡放了一個<span>,二者都掛上Click事件。在Chrome,點擊文字部分,Span跟Button的Click事件都會被觸發,而在Button Click事件裡e.target指向Span;若點擊文字以外的區域,則只有Button Click被觸發。

IE以及Firefox行為則不同,藏在Button中的Span不接受Click事件,也不會成為e.target,無論點擊哪裡都只觸發Button Click。換句話說,對IE跟Firefox來說,整個Button是一體的,內部的元素不會獨立回應Click事件。

顯然這又是各家瀏覽器對Button Click行為的詮釋差異,但這回不合群的是Chrome同學,在跨瀏覽器之路撒了一顆小石頭…:P

讓Windows輾轉難眠的臉書

$
0
0

Windows,你為什麼不睡覺?一文中,我學到播放影片軟體會佔用音效裝置,導致Windows閒置自動睡眠設定失效,而powercfg /requests指令則可用來快速查詢資源是否處於使用中。此後,每次讓電腦睡眠前,我會特別關掉播放中的影片,避免半夜Windows自動更新或其他排程甦醒後不再自動睡眠,平白空轉一整天。

前幾天接連兩次,電腦半夜醒來後一直醒著,用powercfg /requests一查,果然音效狀置在使用中,問題是我只有瀏覽器跟Visual Studio沒關,瀏覽器沒上YouTube看影片,那會是誰卡著音效裝置不放?

經過一番調查,居然只因Chrome開著臉書!我錄了一段操作重現問題:

如以上影片所示,當開啟黑暗執行緒臉書專頁,使用powercfg指令檢查還正常;一旦開啟Microsoft專頁,powercfg立即顯示USB Audio Device在使用中。可以預期,當此現象發生,Windows就不會啟動閒置後自動睡眠,凶手現形。

結論是:當瀏覽器開啟特定臉書網頁會佔用Audio Device。經過簡單對照,我推測關鍵在臉書的影片自動播放,特别挑Microsoft專頁示範,就因為它的前幾則貼文剛好有內嵌影片。

知道這點,下回叫Windows上床前,就知道該把瀏覽器的臉書網頁都關一關,省得電腦半夜爬起來偷看影片不肯回去睡。


【茶包射手日記】解決舊作業系統檔案無法刪除問題

$
0
0

問題情境如下:整理原屬舊作業系統碟的硬碟,在刪除檔案時常遇到系統檔被設定只有TrustedInstaller等系統帳號才能刪除的狀況,此時需修改NTFS進階安全設定克服。

如下圖所示,檔案被設成TrustedInstaller才能完全控制權限,連Administrators都無法修改權限。唯一解法是先將檔案擁有者設成自己再加入修改及刪除權限。

手工調整成千上萬個檔案讓人發狂,上網爬文找到批次指令

警告:本案例討論對象為舊作業系統檔案,請勿用於更動現行作業系統檔案權限,以免造成系統損壞。

先用takeown工具取得檔案擁有權:

takeown /A /R /F "Windows"

再用icacls工具將Windows下所有子目錄改成Jeffrey可以完全存取:

icacls "Windows" /grant:r jeffrey:F /t

搞定,收工!

Visual Studio編譯小技巧:工具程式一檔搞定

$
0
0

我經常寫小工具程式,不用安裝程式,單一EXE檔隨Copy隨用是最理想的部署設計。不過,程式稍稍複雜就難免依功能屬性拆分多個專案,有時需用到跨專案的共享程式庫,至於引用Json.NET、Dapper、NLog等必備套件的情況更是普遍。例如以下專案,Tool為Console Application(EXE)專案,引用Model類別程式庫專案,還參考了Json.NET:

編譯後會產生三組組件檔:Tool.exe、Model.dll與Newtonsoft.Json.dll,得一起部署到客戶端才能正確執行。

要實現單一EXE檔搞定,.NET有個好用工具-ILMerge,可將多個DLL、EXE檔合併成單一檔案(ILMerge使用方式可參考保哥的介紹文),原本想花點時間研究怎麼安排AfterPost事件執行ILMerge,驚喜地發現已有好心人包成MSBuild的Task!

在NuGet使用msbuild.ilmerge查詢,可以找到MSBuild.ILMerge.Task,二話不說,安裝到專案。

安裝後專案會多出ILMerge.props、ILMergeOrder.txt,但大部分情況下不需修改,直接編譯就好。

重新編譯可發現Model.dll及Newtonsoft.Json.dll不見了,只剩一個變胖的Tool.exe,使用時只需Copy這個檔案就行了。

用JustDecompile解析,可以看到Tool.exe裡藏了Model跟Newtonsoft.Json組件裡的所有型別。

用這種做法即可輕鬆一檔搞定小工具程式的部署,非常方便。

補充:MSBuild.ILMerge.Task預設會將參照到的DLL都包進EXE檔,如果想略過特定DLL,可將DLL的Copy Local屬性設為False即可排除。

2016鳳梨馬(八卦山台地馬拉松)

$
0
0

清明時節,返鄉掃墓兼跑馬的時刻又到了,連續第三年的鳳梨馬。

今年安排較充實的行程,前一天去了台灣地理中心碑、埔里酒廠跟紙教堂。

行前功課沒做足,對於哪個才是「台灣地理中心碑」有點迷惘,先在山下看到一個感覺不夠威,加上Fenix 3高度計顯示的海拔與資料上的555公尺差約100公尺,爬階攻上山頂找到另一座呈現魔法陣概念的石柱群,海拔吻合顯然才是真正的地理中心。

      
      

回頭在入口處看到介紹牌,原來地理中心碑的確指的是山腳刻有山清水秀,白色弧牆拱抱立有長桿的褐色石碑,為日據時間所立。光復後經重新測量發現中心碑位置有誤,真正的地理幾何中心在虎頭山頂。簡單地說,地理中心碑不在地理中心,真的地理中心沒有碑,報告完畢。而這個故事也告訴我們「RTFM」的重要性。

埔里酒廠比想像大,展售中心有各式各樣的酒類衍生商品,從冰棒、巧克力、蜜餞到生技化妝品都有,傳說中買不到的花雕雞泡麵不意外地高掛缺貨牌。

     

展售中心樓上有酒廠沿革及製酒介紹,意外學到二鍋頭的由來:指傳統製酒蒸餾換上第二鍋冷凝水時得到的成品。下圖是女兒紅展示區,兩個站在酒罈後的金髮西服模特兒像路人亂入,噗!

數大便是美的酒罈牆,拍起照來很有fu~

傍晚去了紙教堂,天色漸暗原以為看不到什麼好風景。

沒想到紙教堂愈夜愈美麗,點燈後更美! 

第二天照例一早到南投家樂福等接駁車,發現連我只有五位跑友要搭車,還發生人車各在兩頭枯等的小插曲,在下一站又接了兩名跑友,七人獨享一台大巴士前往會場。

鳳嗚國中校舍改建中,司令台跟教室大樓不見了。會場安排與動線依舊維持一貫的專業水準,在我心中是名列前茅的優質賽事。

大會開放加購的紀念品,鳳梨娃娃。

連下了一週的雨,週六才開始放晴,氣溫降到11度左右,老天爺賜了一個乾冷好天氣,那就認真一點好了。:P

賽前注意到會場有三位黑人選手,猜想就是傳說中的肯亞軍團,長期駐台四處參賽討生活,這也是台灣馬拉松賽事密集產生的新行業,據說年薪可破百萬,但這行飯不是人人吃得起就是了。三人小組兩位跑全馬一位跑半馬,里程已過大半後方仍有台灣選手緊咬的態勢,估計油門只踩八分,畢竟在他們的規劃裡這是日常上班不比參加歌唱選秀,使出渾身解數搞到氣力放盡明天還要不要上班?XD

    
    

139縣道的好風景依舊,可惜小葉欖仁新葉未萌,不然就更美了~

好久不見滾鐵圈的大哥,速度驚人,拍照時我大概已落後2K以上!倒是折返後在對向遇到馬拉松狗黃檸檬,落後我約1K,心中燃起小宇宙,難得有機會,這回我不要再跑輸狗了。

    

鳳梨馬的交管很到位,每個路口都有交警、志工管制車輛,經過時心中對受影響的車輛偷偷說聲抱歉。

最近氣候反常,去年開得無法無天的九重葛今年盛況不再。

遇到在路旁寫生的畫家,裝扮很有藝術家的fu。

台地看出去的好風景,可惜空氣品質不佳,展望不好。

鳯梨服裝展示,今年流行的主軸偏向多樣化,我們可以看到拉丁美洲風大圓帽,以及中世紀歐洲風格鵝黃網紗,以及展現熱帶風情的葉片素材包法… (純唬爛,勿砲)

   

鳳梨馬的特色:鳳梨跟跑者相映成趣~(還有喝不完的土鳳梨汁)

「吃很好」也是鳳梨馬的傳統,香蕉、檸檬、小蕃茄、巧克力、小饅頭、鹹蛋、火腿、麵條、貢丸湯、炸(假)干貝、薯條、蠻牛… 多到記不住,把地瓜球攤子推來現炸這招很殺 XD 而可以喝到飽的土鳳梨汁是我的最愛。

拜氣溫低所賜,前30K跑了3h20m,成績尚可,偷偷訂下SUB 5目標(還有,不要被黃檸檬刷卡),近10點太陽開始發威,掛上墨鏡戴起MP3聽快節奏舞曲,開啟小宇宙燃燒模式… (其實只是少走一點,多跑一些,均速還是很難看)

34K經過終點拱門,過家門而不入體驗+1,最後8K倒數。順道一提,賽道上每公里的里程標示跟我的GPS錶測得距離誤差均小於200公尺,非常精準。

除了鳳梨田,還有壯觀的茶園。

天空之橋到了,倒數6K。

最後卯足全勁,保4成功,4:56:20完賽,考量賽道有超過500公尺爬升以及老爺車年年增長的車齡,成績不算差,多虧天氣幫忙。終點有Show Girl為選手掛獎牌,賽前沒宣佈害我一點心理準備都沒有,不過這種驚喜多多益善。XD

一條龍服務很讚,瞬間領完成績單、伴手禮、海鮮鹹粥(現場搭辦桌棚子現煮,好吃!),領物後還有更衣室可以沖澡更衣,很貼心。

離開前在教室走廊巧遇黃檸檬,正吸引三個小女生圍著牠(狗帥真好),這才想起:對耶,我終於跑嬴黃檸檬了,YA!

跟狗主黃大哥小聊幾句,大哥感慨八歲的黃檸檬今年體力明顯衰退。屈指一算,黃檸檬差不多是男甲組,我居然跟六十歲的阿公級跑者比成績,好丟臉。被小女生摸頭時,黃檸檬幾度低頭閤眼顯露疲態,再無當年40K就遇到牠逆跑排乳酸的煥發,心中浮起歲月不饒人(狗)的感慨。無論如何,恭喜雷夢80馬完賽,並祝早破百馬。

回程順道去看了八卦山大佛,這才知道跑了三回的139縣道,一路跑下去就能跑到大佛。童年來過,如今再見,第一個感覺是訝異與記憶中的高大相差甚多,仔細想想原因有二,一是是兒時身高感受的空間感不同,二是隨時代演進高樓林立,對於「高」的定義也變了。

小攤在賣鵝蛋跟鴕鳥蛋,很新奇。我們買了鵝蛋嚐鮮,蛋殼差不多有信用卡的厚度,蛋白相當彈牙,很新鮮的口感。

大佛前有個環狀步道,彰化市區一覽無遺。

發現微笑單車已經部署到彰化了,不知有沒有人挑戰從台北騎下來。

補上完賽獎牌照片。

    

很巧合地,賽前疑似被小木頭傳染感冒,有輕微症狀,跑完不意外地變嚴重,咳了一週才好,跟去年情節幾乎完全相同,為「跑全馬影響扺抗力人體實驗」再添一例 orz。

改良式GetCachableData可快取查詢函式

$
0
0

多年前發展過一種可快取查詢:呼叫GetCachableData函式時傳入Cache Key、查詢或產生資料Callback函式、Cache保留期限(或指定閒置未用多久自動清除)三個參數,GetCachableData會依「若Cache有資料就直接沿用;若Cache無資料則當場產生並存入Cache」原則聰明處理,從此不需操心何時該查資料何時用Cache,應用起來挺方便的。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace GetCachable
{
publicstaticclass CacheManager
    {
/// <summary>
/// 取得可以被Cache的資料(注意:非Thread-Safe)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">Cache保存號碼牌</param>
/// <param name="callback">傳回查詢資料的函數</param>
/// <param name="cacheMins"></param>
/// <param name="forceRefresh">是否清除Cache,重新查詢</param>
/// <returns></returns>
publicstatic T GetCachableData<T>(string key, Func<T> callback, 
int cacheMins, bool forceRefresh = false) where T : class
        {
            ObjectCache cache = MemoryCache.Default;
string cacheKey = key;
 
            T res = cache[cacheKey] as T;
//是否清除Cache,強制重查
if (res != null&& forceRefresh)
            {
                cache.Remove(cacheKey);
                res = null;
            }
if (res == null)
            {
                res = callback();
                cache.Add(cacheKey, res, 
new CacheItemPolicy() {
                        SlidingExpiration = new TimeSpan(0, cacheMins, 0)
                    });
            }
return res;
        }
 
 
/// <summary>
/// 取得可以被Cache的資料(注意:非Thread-Safe)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">Cache保存號碼牌</param>
/// <param name="callback">傳回查詢資料的函數</param>
/// <param name="absExpire">有效期限</param>
/// <param name="forceRefresh">是否清除Cache,重新查詢</param>
/// <returns></returns>
publicstatic T GetCachableData<T>(string key, Func<T> callback, 
            DateTimeOffset absExpire, bool forceRefresh = false) where T : class
        {
            ObjectCache cache = MemoryCache.Default;
string cacheKey = key;
//取得每個Key專屬的鎖定對象
            T res = cache[cacheKey] as T;
//是否清除Cache,強制重查
if (res != null&& forceRefresh)
            {
                cache.Remove(cacheKey);
                res = null;
            }
if (res == null)
            {
                res = callback();
                cache.Add(cacheKey, res, new CacheItemPolicy()
                {
                    AbsoluteExpiration = absExpire
                });
            }
return res;
        }
    }
}

不過原本的設計有個小問題,例如:有個網站透過GetCachableData由資料庫讀取五千筆員工資料並Cache住一小時,以便後續能快速地用員編查姓名。想像一個場景,尖峰時刻Cache逾時被清除(或是網站因故重啟),線上一百名使用者同時瀏覽某一網頁使用到員工姓名查詢,於是GetCachableData同時被100條Thread呼叫,MemoryCache本身為Thread-Safe多執行緒讀寫不致出錯,但Cache不存在觸發100個資料庫查詢,對形成一波完美的DDoS攻擊!接著資料庫忙碌、網頁卡住、使用者無助、老闆暴怒、開發者想哭…

以下範例可展示此問題,同時開啟三條Thread呼叫GetCachableData,則Callback動作也會同時三份(Callback執行時會印出Thread n Start/Stop Job訊息以利觀察)。這三次查詢動作只有一次是必要的,其餘兩次將取得相同結果覆寫同一Cache,平白消耗資源,在極端案例中甚至可能讓系統崩潰。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace GetCachable
{
class Program
    {
staticvoid Main(string[] args)
        {
            var tasks = new List<Task>();
for (var i = 0; i < 3; i++)
            {
                tasks.Add(Task.Factory.StartNew(() =>
                {
                    var data = CacheManager.GetCachableData<string>("KEY",
                        () =>
                        {
                            Console.WriteLine("Thread {0} Start Job",
                                Thread.CurrentThread.ManagedThreadId);
                            Thread.Sleep(3000);
                            Console.WriteLine("Thread {0} Stop Job",
                                Thread.CurrentThread.ManagedThreadId);
return"OK";
                        }, 10);
                    Console.WriteLine("Data:" + data);
                }));
            }
            tasks.ForEach(t => t.Wait());
 
            Console.WriteLine("Done");
            Console.ReadLine();
        }
    }
}

執行結果如下,可觀察到三條Thread同時執行Callback:

Thread 13 Start Job
Thread 11 Start Job
Thread 12 Start Job
Thread 13 Stop Job
Data:OK
Thread 11 Stop Job
Thread 12 Stop Job
Data:OK
Data:OK
Done

要改良此一缺點,可在多執行緒查詢時加入Lock機制,相同Key值的查詢單一時間只允許一組Callback執行,執行完成後其餘等待的Thread可直接取用Cache結果,省下無效益的Callback動作。程式範例如下,依Key值建立Object作為鎖定對象,即能實現一Key值不會有兩份以上Callback同時執行:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Text;
using System.Threading.Tasks;
 
namespace GetCachable
{
publicstaticclass BetterCacheManager
    {
//加入Lock機制限定同一Key同一時間只有一個Callback執行
conststring AsyncLockPrefix = "$$CacheAsyncLock#";
/// <summary>
/// 取得每個Key專屬的鎖定對象
/// </summary>
/// <param name="key">Cache保存號碼牌</param>
/// <returns></returns>
staticobject GetAsyncLock(string key)
        {
            ObjectCache cache = MemoryCache.Default;
//取得每個Key專屬的鎖定對象(object)
string asyncLockKey = AsyncLockPrefix + key;
lock (cache)
            {
if (cache[asyncLockKey] == null) cache.Add(asyncLockKey,
newobject(),
new CacheItemPolicy() {
                        SlidingExpiration = new TimeSpan(0, 10, 0)
                    });
            }
return cache[asyncLockKey];
        }
 
/// <summary>
/// 取得可以被Cache的資料(注意:非Thread-Safe)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">Cache保存號碼牌</param>
/// <param name="callback">傳回查詢資料的函數</param>
/// <param name="cacheMins"></param>
/// <param name="forceRefresh">是否清除Cache,重新查詢</param>
/// <returns></returns>
publicstatic T GetCachableData<T>(string key, Func<T> callback, 
int cacheMins, bool forceRefresh = false) where T : class
        {
            ObjectCache cache = MemoryCache.Default;
string cacheKey = key;
 
//取得每個Key專屬的鎖定對象
lock (GetAsyncLock(key))
            {
                T res = cache[cacheKey] as T;
//是否清除Cache,強制重查
if (res != null&& forceRefresh)
                {
                    cache.Remove(cacheKey);
                    res = null;
                }
if (res == null)
                {
                    res = callback();
                    cache.Add(cacheKey, res, 
new CacheItemPolicy() {
                            SlidingExpiration = new TimeSpan(0, cacheMins, 0)
                        });
                }
return res;
            }
        }
 
 
/// <summary>
/// 取得可以被Cache的資料(注意:非Thread-Safe)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">Cache保存號碼牌</param>
/// <param name="callback">傳回查詢資料的函數</param>
/// <param name="absExpire">有效期限</param>
/// <param name="forceRefresh">是否清除Cache,重新查詢</param>
/// <returns></returns>
publicstatic T GetCachableData<T>(string key, Func<T> callback, 
            DateTimeOffset absExpire, bool forceRefresh = false) where T : class
        {
            ObjectCache cache = MemoryCache.Default;
string cacheKey = key;
//取得每個Key專屬的鎖定對象
lock (GetAsyncLock(key))
            {
                T res = cache[cacheKey] as T;
//是否清除Cache,強制重查
if (res != null&& forceRefresh)
                {
                    cache.Remove(cacheKey);
                    res = null;
                }
if (res == null)
                {
                    res = callback();
                    cache.Add(cacheKey, res, new CacheItemPolicy()
                    {
                        AbsoluteExpiration = absExpire
                    });
                }
return res;
            }
        }
    }
}

改用BetterCacheManager後,同時三條Thread呼叫GetCachableData()只會觸發一次Callback,可減少高承載系統產生重複查詢的壓力:

Thread 9 Start Job
Thread 9 Stop Job
Data:OK
Data:OK
Data:OK
Done

以上私房做法,提供大家參考。

關於IIS整合式Windows驗證的冷知識

$
0
0

在企業內部寫ASP.NET的人都用過整合式Windows驗證吧?來個小測驗:

  1. 沒加入網域的PC可以用AD帳號登入隸屬該網域的IIS嗎?
  2. 在測試環境建立一測試網域,與公司正式AD網域間無信任關係。正式網域PC是否可以用測試網域帳號登入測試網域的IIS?
  3. 承上,正式網域PC需將DNS指向測試網域DC才能用測試網域帳號登入嗎?

依據實際使用經驗,我知道的答案是:Yes、Yes、No,但不知所以然。最近因工作需要做了粗淺研究,整理筆記備忘兼分享。(如有謬誤,有請各方高人指正)

IIS實現Windows驗證的方式有兩種,NTLM與Kerberos。NTLM如其名,是NT時代就有的Challenge/Response驗證方式(不透過網路傳輸密碼本身);Windows 2000起改用Kerberos作為預設認證協定,主要有以下優點:

  • 驗證速度快
    使用NTLM時,若資源伺服器本身不是DC(Domain Controller,網域控制器),則轉向DC求證身分真偽(稱之Pass-Through Authentication);而資源伺服器取得Kerberros Tickets(或稱Authenticator)時就已取得足以驗證客戶端的資訊,較有效率。
  • 交互驗證
    Kerberos驗證時,不只客戶端向伺服器證明自己身分,伺服器也可要求伺服器驗明正身,社絕NTLM可能被冒牌伺服器騙取身分的風險。
  • Kerberos為開放標準
    由於Kerberos為開放標準,故有機會整合Windows與其他作業系統達成單一登入(Single Sign On)。
  • 支援驗證委派
    資源伺服器取得代表使用者身分的Kerberos Ticket後,可直接代表該使用者存取其他資源伺服器。例如:使用者登入Web後,Web以該使用者身分登入SQL。 
  • 支援Smart Card登入
    可整合Smart Card做到雙因子登入,登入時需擁有實體卡片並知道密碼,降低身分盜用風險。

當我們設定IIS使用Windows驗證時,預設的提供者為Negotiate,包含Kerberos及NTLM兩種驗證方式,而其選用規則為「與瀏覽器協商,先嘗試使用Kerberos,若條件不符則改用NTLM」。

IIS採用NTLM或Kerberos則有以下區別:

NTLM

  • 客戶端不管有沒有加入網域都適用
  • 可使用網域帳號或本機帳號
  • 使用網域帳號時,只有IIS主機需要連線DC
    註:忙碌的NTLM Server (IIS, Exchange, TMG/ISA)可能產生大量NTLM請求,對DC形成負擔
  • 客戶端只需連上IIS,不用連到DC
  • 可以穿透支援HTTP Keep-Alives的Proxy
  • 認證過程需多次往返(HTTP 401 –> HTTP 401 –> HTTP 200)
  • 不支援Double-Hop Authenticataion(即驗證委派,將使用者認證用於另一台電腦執行的服務)
  • 支援Windows 2000以下的舊版系統(註:此點已可忽略)
  • 易受LM Auth Level不一致影響(lmcompatibilitylevel不吻合)
  • 為Negotiate提供者的備援方案,若 Kerberos不成功就轉用NTLM

Kerberos

  • 只適用已加入網域的客戶端
  • 客戶端必須自己連上AD DC(TCP/UDP 88 Port)
  • 不易穿透Proxy
  • 憑證效期長(10小時),DC負荷較輕
  • 認證過程為HTTP 401 –> 傳送Token(6-16KB) –> HTTP 200
  • 認證身分可以轉用。例如:使用者登入IIS後,Web使用同一AD帳號登入SQL
  • Negotiate提供者的優先選項
  • 必須搞定SPN註冊(手續挺複雜)
  • 非常容易失敗,如果你…
    • 在URL使用IP而不是機器名稱
    • 沒註冊SPN
    • 重複註冊SPN
    • SPN註冊錯帳號 (KRB_ERR_AP_MODIFIED)
    • 客戶端無法連線DNS/DC
    • Proxy/Local Intranet Zone沒設好

理解以上知識,一開始的三個問題就有了答案,而我才這發現平日使用的Windows驗證,幾乎都是走NTLM,理由包含:使用測試AD帳號、使用IP URL、未特別為網站註冊SPN、啟用Web Farm或負載平衡設備無法用機器名稱… 換言之,Kerberos雖然更安全,但成立條件嚴苛,稍有不慎就會掉回NTLM。

活到老學到老,今天又上了一課。

參考資料:

Viewing all 428 articles
Browse latest View live