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

EF日期欄位之JSON序列化時區問題

$
0
0

註:閱讀本文章前有個先修課程,需知道.NET DateTimeKind如何影響Json.NET序列化結果,不熟悉的同學可以看補充教材補充教材2

依先前研究心得:將JsonSerializerSettings指定DateTimeZoneHandling.Utc可以避免DateTimeKind.Local被轉成「yyyy-MM-ddTHH:mm:ss.fffffff+08:00」,統一採用「yyyy-MM-ddTHH:mm:ss.fffffffZ」UTC時間格式方便前端處理,但遇上DateTimeKind.Unspecified,Json.NET轉換會出現時差(以台灣時區為例,會多八小時)。實務上碰到被標成Unspecified的台灣時間,我習慣用DateTime.ToUniversalTime()轉成UTC時間,JsonConvert.SerializeObject()的轉換結果才會正確。

平時取用DateTime.Now或DateTime.Today都有正確的DateTimeKind,較常遇到的狀況多發生在從資料庫讀取DATE型別。如以下Entity Framework實驗,建立一個Blah Entity物件,時間取DateTime.Now,以EF方式新増到資料庫,再查詢取回同一筆資料,表面上寫入與讀取的UpdateTime值應該一樣,事實不然:

publicstaticstring TestJson()
        {
            Blah toAdd = null;
string code = "JEFF";
using (var ctx = DataHelper.CreateDbContext())
            {
                ctx.Database.ExecuteSqlCommand(
"delete from blah where code={0}", 
                    code);
                toAdd = new Blah()
                {
                    Code = code,
                    UpdateTime = DateTime.Now
                };
                ctx.Blah.Add(toAdd);
                ctx.SaveChanges();
            }
            Blah fromDb = null;
using (var ctx = DataHelper.CreateDbContext())
            {
                fromDb = ctx.Blah.Single(o => o.Code == code);
            }
            JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
            {
                DateTimeZoneHandling = DateTimeZoneHandling.Utc
            };
return JsonConvert.SerializeObject(new
            {
                Now = DateTime.Now,
                NewItem = toAdd,
                FromDb = fromDb
            }, Formatting.Indented);
        }

結果為:

{
"Now": "2015-11-19T03:30:22.4362365Z",
"NewItem": {
"Code": "JEFF",
"UpdateTime": "2015-11-19T03:30:22.2832178Z"
  },
"FromDb": {
"Code": "JEFF",
"UpdateTime": "2015-11-19T11:30:22.283Z"
  }
}

可以發現由資料庫讀出的Blah.UpdateTime除了精確度不同,經JSON序列化會多8小時,就是前面所說「DateTimeKind.Unspecified時間的JsonConvert.SerializeObject()轉換誤差」,解決之道是UpdateTime = UpdateTime.ToUniversalTime(),或使用DateTime.SpecifyKind()將UpdateTime.Kind校正為Local。

每次從DB讀取資料必須逐一轉換日期資料轉JSON才不會爆炸,系統正確性全看開發者的紀律(和記性)有點危險。另一種解法是用程式偵測找出DateTime或DateTime?型別屬性,遇到DateTimeKind.Unspecified就自動轉成Local,程式不難,用Reflection就可實現:

static Dictionary<Type, List<PropertyInfo>> DatePropsCache = 
new Dictionary<Type, List<PropertyInfo>>(); 
/// <summary>
/// 掃瞄資料物件的DateTime型別屬性,將DateTimeKind.Unspecified改為DateTimeKind.Local
/// </summary>
/// <param name="entity">資料</param>
publicstaticvoid FixUnspecifiedDateKind(object entity)
        {
if (entity != null)
            {
                Type type = entity.GetType();
//以Reflection找出所有DateTime或DateTime?型別屬性
//每個型別只要做一次,使用Cache省去多餘運算
                List<PropertyInfo> dateProps = DatePropsCache.ContainsKey(type) ?
                    DatePropsCache[type] : null;
if (dateProps == null)
                {
lock (DatePropsCache)
                    {
//找出所有DateTime及DateTime?屬性的PropertyInfo
                        DatePropsCache[type] = type.GetProperties()
                            .Where(o =>
                                o.PropertyType == typeof(DateTime) ||
                                o.PropertyType == typeof(DateTime?)
                            ).ToList();
 
                    }
                    dateProps = DatePropsCache[type];
                }
foreach (var dateProp in dateProps)
                {
//取得目前屬性值
object curVal = dateProp.GetValue(entity);
if (curVal != null)
                    {
                        DateTime dt = dateProp.PropertyType.IsGenericType ? 
                            ((DateTime?)curVal).Value : (DateTime)curVal;
//若DateTimeKind為Unspecified,改設定為Local
if (dt.Kind == DateTimeKind.Unspecified)
                            dateProp.SetValue(entity, 
                                DateTime.SpecifyKind(dt, DateTimeKind.Local));
                    }
                }
            }
        }

而原來的程式要加上FixUnspecifiedDateKind(fromDb):

            Converter.FixUnspecifiedDateKind(fromDb);
return JsonConvert.SerializeObject(new
            {
                Now = DateTime.Now,
                NewItem = toAdd,
                FromDb = fromDb
            }, Formatting.Indented);

經過修正後JSON序列化結果符合預期。

{
"Now": "2015-11-19T03:55:25.4274737Z",
"NewItem": {
"Code": "JEFF",
"UpdateTime": "2015-11-19T03:55:25.4174634Z"
  },
"FromDb": {
"Code": "JEFF",
"UpdateTime": "2015-11-19T03:55:25.417Z"
  }
}

但是,即使有自動修正,每次由DB取回資料都要加上FixUnspecifiedDateKind(),還是很容易有疏漏,從源頭下手會是更理想的做法。這裡介紹一招: ObjectContext.ObjectMaterialized事件,會在從資料來源讀取資料產生物件後觸發,趁此時對 e.Entity 動手腳,外界永遠會拿到加工整形過的Entity。上回提過「建立DbContext應使用統一共用函式」,就是加入FixUnspecifiedDateKind()邏輯的好地方,如此可確保所有DbContext丟回的日期資料都經過修正,就不必再煩惱資料庫造成的JSON序列化時差囉!(這個例子也說明了「為什麼該用統一的DbContext建立函式,不要自己new一個DbContext」)

//使用統一的靜態函式建立DbContext物件,避免自行建構
publicstatic BBDPEntities CreateDbContext()
        {
//正式應用時,設定檔之連線字串應加密
//在此進行讀取設定並解密以建構DbContext,細節待日後介紹
            var ent = new BBDPEntities();
            ObjectContext objCtx = ((IObjectContextAdapter)ent).ObjectContext;
//由資料庫讀得資料後,進行DateTimeKind修正
            objCtx.ObjectMaterialized += (sender, e) =>
            {
                Converter.FixUnspecifiedDateKind(e.Entity);
            };
return ent;
        }

2015觀音山馬拉松

$
0
0

從小到大沒去過觀音山,又是大家不愛跑的硬斗山路,標準的小而美賽事,報了2015觀音山馬拉松,我的第31馬。

蘆洲區體育會(不過好像大家都講蘆洲慢跑,蘆慢)傾全會之力辦的這場比賽(據說動員了三四百位志工),辦得有聲有色,規模無法相比,但人情味與熱情不輸田中馬或府城星光馬。

會場在成蘆橋下,微風運河旁,第一次來。跑馬拓展了阿宅的人生,讓我去了好多本來不會到的地方。

 

主持大哥很風趣,一路細數蘆洲的特色,切仔麵(有兩百家)、神將(上圖),還有「觀音山有三多」,竹筍多、XX(忘了)多、先人(墳墓)多,叮嚀大家經過時要低調,吵到先人跑來幫你加油就不太好了。 XD

主持人還提到今天會有蘆慢十二金釵,穿著旗袍在終點幫你掛獎牌。而且,這群娘子軍卧虎藏龍,其中不乏全馬340的高手呢!呃… 光頭大哥你來湊什麼熱鬧啦?

7點整準時開跑,先繞運河一周約3K(路很小條,最多四人並排),微風運河辦過鐵人賽,河面不寬,比賽時會像下水餃吧?

今天氣溫21-26,濕度80%,多雲到晴,大約有1/3的時間曬到太陽,但風勢強勁扳回一城,坦白說是不錯的跑馬天。想想下半年運氣不錯,比賽當日天氣都不差,真是好狗運,不,好馬運。

跑完河濱,刺激的來了,超過20%的陡坡!當步兵剛剛好而已,本場總爬升超過1400公尺。

遇到也來跑全馬的狗狗Buddy,然後,我被牠海放了 海放了 海放了… orz

越過一座一百多米高的小山頭,壓軸的上場,前進觀音山!

坡好陡呀~但風景也開始壯觀起來。

接近全程最高點,看到硬漢嶺往前的指標嚇一跳,是說剛才爬的都不算什麼,更硬的1.4K在後面?幸好誤會一場,硬漢嶺步道要取右走登山步道攻山頭,我們爽爽地跑馬路下山就好。

越過山頭向下走,視望逐漸開闊,偶爾可以遠眺台北港。

過了八里療養院,攻上最後五百公尺的陡坡,總算來到折返點。拆返點有位志工大哥,熱心詢問每位跑者要不要跟折返點合照,於是出現跑友拿著手機排隊等排照的有趣畫面。

折返了也不用太高興,來的時候有上坡有下坡,越過兩個山頭,回程也是有上坡有下坡,還是要越過兩個山頭,但天晴風清,跑起來挺開心地。

越過最後的山頭只剩7K跟下坡的河濱段,心情愉快。決定來個創舉,最後7K,每次進水站都要喝一杯啤酒!

 

大會挺有心,每個水站都有裝扮主題,啦啦隊美眉、大廚、野人… 補給不講花俏,水、運動飲料、餅乾、香蕉(今秋香蕉產量少,跑了三場終於吃到了)、蕃茄、葡萄,折返點水站還有好吃的布朗尼蛋糕。我跑中後段班,每站都還喝得到啤酒,很讚!

某水站賤賤的加油標語,哈!

大推終點線前的號角加油棒啦啦隊。遠遠看到終點拱門,聽到一陣一陣的號角聲,原以為是附近打棒球的歡呼聲。靠近才發現是照片裡的加油團,只要有選手回來,一律吹號角敲加油棒再加上尖叫歡呼,熱情破表!讓人不自覺開心起來,讚!

旗袍美眉列隊迎接掛完賽獎牌耶,真心不騙吧。

又一場開心的馬拉松。

邏輯謬誤,那是什麼?能吃嗎?

$
0
0

看到網路轉貼的「24種常見的邏輯錯誤」,讓沒讀過正統邏輯學的邏輯狂如獲至寶。一路追源頭,找出中文版來自謝至理先生的翻譯,而原文則來自Thou shalt not commit logical fallacies (汝不該犯此等邏輯謬誤)網站(採CC BY-NC 3.0授權),決定以網站內容為基礎,蒐羅資料重新整理並加上自己的註解詮釋(姑且稱之黑註版吧)永久珍藏。

說是不該犯的邏輯謬誤,但因「常見」,生活周遭到處都是,從政治新聞到社群媒體戰文,隨手一抓就一大把。學會這些常見的謬誤典型,就像我們寫程式要搞懂Design Pattern,好處多多。跟人吵架時,能一眼看出對手出的奧步,大喊一聲「你這根本是滑坡謬誤,嚇不倒我的」;或者看對方邏輯不精人善可欺,來個誘導式提問挖坑等人跳。進可攻,退可守,做好人可防身,當壞蛋可殺人,真是居家旅遊、嗆聲吵架不可缺少的好兵刃~

依據網站列出的24項邏輯謬誤,參考維基百科(標題均附上連結),有些再加入自己的詮釋與範例,彙整如下:

    1. 稻草人

      扭曲別人的觀點或曲解別人的主張,以便發動攻擊。
      【範例】
      專案經理:專案進度有點落後,開發速度要再快一點。
      程式設計師:所以資安跟品質不重要囉?到時系統有安全漏洞或是有Bug,你要負責嗎?
    2. 因果謬誤

      因結果、數據的關聯性,輕率地推論二者的因果關係。
      【範例】
      *最危險的地方非醫院莫屬,大部分的人都從醫院出發去蘇州賣鴨蛋(倒因為果)
      *冰棒的銷量跟溺水人數成正比,禁止賣冰可減少溺水事件(二者皆為果,另有原因)
      *中秋烤肉製造二氧化碳,助長地球暖化(影響有限)
    3. 訴諸情感

      從情感面下手,企圖使對方偏離邏輯思維做成決定。
      【範例】
      *不要偏食,想想非洲有很多小孩連飯都沒得吃。(訴諸同情)
      *只有絕世高手才能破解它,除了黑大我第一個想到的就是你。(趁機自肥訴諸諂媚)
      *他一定在說謊,你忘記他上次怎麼陷害你的。(訴諸仇恨,幾乎是戰政治的起手式:飛彈、阿扁…)
    4. 謬誤論證

      因論述存在錯誤而否定整個論述。
      【範例】
      A:人有兩隻腳,黑大有兩隻腳,所以黑大是人。
      B:拜託,狗有四條腿,貓也四條腿,所以狗是貓?邏輯不是這様玩滴,你亂搞,所以黑大不是人。
    5. 滑坡謬誤

      宣稱A一定會導致B,B會導致C,C最終會導致可怕的D,所以不能讓A發生。
      【範例】 一天到晚玩手機就考不上好高中,不上了好高中就進不了好大學,進不了好大學就找不到好工作,這一生就毁了!(呃… 這招我好像對小閃光用過)
      【範例】包龍星:不關我事?我在妓院做,當然也希望母雞好,她心情不好,就會怠慢客人,客人會生氣,一生氣就不來,不來就關門,關門我就睡馬路,你敢說不關我的事?(吵架王必學)
    6. 因人廢言

      不依據其論述內容,而是依據對方的人格、外貌、地位、背景評斷真偽。
      【範例】你有打過150的球嗎?不過是個鍵盤球評,有什麼資格說三道四?(攻擊經驗)
      【範例】一整個就像死肥宅,教的把妹技巧能聽嗎?(攻擊外貌)
    7. 你還不是一樣(訴諸虛偽)

      不回應對方批評,而是回嗆「你自己不也Blah Blah...」。
      【範例】我抓MP3又怎樣,你就沒看過網路下載電影?
    8. 個人懷疑

      因本身知識不足或認知偏差而否定論述。
      【範例】煮飯加人工添加物,怎麼可能對人體無害?參考
    9. 片面辯護

      當論點被證明錯誤時,強調是特例加以開脫。
      【範例】所有的車子都不能超過速限 …(歐伊~歐伊~救護車飛馳而過)呃,救護車例外。
    10. 誘導性問題(挖坑給人跳)

      在問題加入誘導成分,誘使對方回答掉入陷阱。
      【範例】你已經戒毒了吧?你很久沒打老婆了吧?有聽過大豬搖頭小豬點頭嗎?
    11. 舉證責任

      提出觀點的人將舉證責任丟給質疑者。
      【範例】世界上一定有外星人,不然你證明沒有外星人給我看?
    12. 語義模糊

      使用雙關語或存有歧義語言,被質疑時當成擋箭牌。
      【範例】馮光遠的特殊性關係
      【範例】
      魔王:你喊破喉嚨吧,沒有人會來救你的
      公主:破喉嚨!破喉嚨!
      沒有人:公主,我來救你了
    13. 賭徒謬誤

      認定隨機事件有關聯性。
      【範例】連續丟10次銅板都是正面後,下一次是反面的機率比較高?
    14. 從眾效應

      因為大家都認同都相信,就認定是對的。
      【範例】中世紀歐洲深信太陽繞著地球轉。
    15. 訴諸權威

      因為主張者的權威性/地位,就認定觀點是對的。
      【範例】馬雲說、愛因斯坦說、郭董說…
    16. 合成謬誤

      將部分觀察所得沿伸套用到整體 。
      【範例】以偏概全,瞎子摸象。
    17. 不是真正的蘇格蘭人(訴諸純正)

      遭受攻擊時透過修改標準(縮小到一個更純淨、理想化的標準)來保住觀點。
      蘇格蘭人都ZZ->XX是蘇格蘭人但不ZZ耶->那XX就不是真正的蘇格蘭人
      【範例】又一夜,他們說相聲
      A:孔子說的巧言令色鮮矣仁影響以後的中國人,遇到事情要討論時就不太好意思開口,跑到旁邊惦惦。
      B:什麼惦惦,你看現在立法院議會為什麼會搞成那樣咧?
      A:我說的是比較有知識有修養的中國人。
    18. 基因謬誤

      基於事物出身去推斷好壞,類似人身攻擊,不正面回應攻擊,從其出身之負面印象下手。
      【範例】攻擊選舉對手出身權貴、突顯省籍。
    19. 非黑即白

      使用粗暴的二分法,掩蓋其他可能性。
      【範例】不支持反恐行動就是支持恐怖分子。
    20. 竊取論點

      先假設前題為真,利用循環論證法找出支持前題的舉證,做出前題為真的結論。
      【範例】黑大是最帥,所以沒有人比黑大更帥,既然沒人比黑大更帥,那黑大一定是最帥的。(沒有人:我比黑大更帥耶)
    21. 訴諸自然

      認定自然的就是合理的,就是必然的也是好的。
      【範例】天然A尚好,草藥一定必西藥好。
      【範例】自然界一向弱肉強食,所以人善被人欺也是剛好而已。
    22. 軼事證據

      提出罕見特殊個案,提供充分且生動的細節,誘導聽者相信其為一般現象。
      【範例】XX廟很靈驗,OO得了不治之症,醫生說活不過三個月,拜完病就好了,到現在人還活得好好的。
      【範例】我阿公每天煙酒不離手,完全不運動,一樣活到一百多歲,所以什麼養生保健全是騙人的 。
    23. 射箭畫靶(德州神槍手)

      典故:有個德州人朝著自己的穀倉射了許多子彈,在彈孔最密集處畫一個圈,自稱神槍手。從大量統計數據中只挑出有利的部分以支持主張。
      【範例】各政黨的自家民調、挑好看的數據整理成政績。
    24. 訴諸中庸

      假定從多個衝突觀點找出折衷觀點就是最佳解,但它並不是。
      【範例】你們兩個都說小孩是自己的,那就把小孩切一半平分好了。

邏輯謬誤並不只這些,維基百科有一份更完整的邏輯謬誤總表,有興趣的人可以詳讀。

看完這些邏輯謬誤,我有一項重要心得-日常生活中,要完全不犯任何邏輯謬誤是不可能的!有幾點理由:

  1. 不符人性:一些所謂的「邏輯謬誤」,早已是我們溝通與人際互動習以為常的方式,是人性的一部分。人不能只講邏輯,走極端會失去人性。當你慷慨陳詞講到熱淚盈眶,試圖動之以情,對方卻冷冷回應「你這是訴諸感情,是一種邏輯謬誤」,這嘴臉讓人很想巴下去吧?
  2. 真理未知:是否為邏輯謬誤有時是結果論。警察辦案未採信前科犯證詞,精準破案叫「斷案如神」,誤判方向叫「因人廢言」。當事實真相未明,有時很難斷定某個行為是否屬於邏輯謬誤。當知識拓展到另一層次,就會產生不同結果。現在大家普遍接受進化論,若有朝一日發現人類其實源於外星人,那麼此刻的我們都犯了從眾謬誤。
  3. 成本問題:若講每一句話前都要窮究可能的證據、資訊,所要耗費的時間精力無人可以負荷。回到第二點的例子,我們認定的事實都只能基於現有的知識,難道跟人討論人類起源前,不能引用專家學者的說法(避免訴諸權威),必須親自審閱所有科學論文(避免個人懷疑,避免從眾效應)才能發言,這樣的人生也太沈重了吧?
    面對眾多選擇,拿香跟著拜,省時省力,邏輯錯了又何妨,至少我跟大家一樣,這不就是人類天性。

過於嚴謹刻板會逼死人,但搞懂邏輯謬誤理論真的有好處。來現學現賣,找出之前兩篇討論小朋友科展免洗筷毒性研究的找碴篇,以前只能憑藉直覺,感覺某處怪怪的不合邏輯,至於違反什麼邏輯原理,說不出個所以然,現在不一樣了:

  • 免洗筷的可怕--找碴篇

      1. 筷子泡水一週臭得不得了,一定有毒 –> 訴諸情感
      2. 拿筷子水養綠豆,綠豆死掉,故對人體有害 –> 合成謬誤
      3. 筷子燃燒後的煙溶於水呈酸性—>因果謬誤
  • 【茶包射手日記】又來了,免洗筷有多毒--找碴篇

      1. 缺乏對照組 –>因果謬誤
      2. 動植物實驗結果的代表性—>合成謬誤
      3. 煽情演出–> 訴諸情感
      4. 缺乏根據的推論—>因果謬誤
      5. 未善用相關文獻—>個人懷疑

    【結論】

    了解邏輯謬誤,並不代表以後凡事都該用邏輯剖析,走火入魔只會盡失人性,眾叛親離。我們還是得包容日常生活裡無所不在的邏輯謬誤,畢竟它是自然的一部分(在此容我訴諸自然 XD),不需反應過度。只要在需要明辨真理的關鍵時刻,記得用科學角度分析不被蒙蔽,不跌入別人的陷阱,就夠了。至於許多該裝傻該圓滑的時刻… 邏輯謬誤,那是什麼?能吃嗎?

    AssemblyInformationVersion踩雷記

    $
    0
    0

    前幾天回答網友QOO提問,又多學會「AssemblyFileVersion不支援自動跳號,未設定時會引用AssemblyVersion」新知識,知道這些就夠了嗎?並沒有,今天又踩一雷,再獲冷知識一枚。

    同事升級專案的Newtonsoft.Json.dll到7.0.1版,NuGet顯示版本為7.0.1版:

    由於NuGet安裝時沒自動加入bindingRedirect設定,手動加入時為了找出newVersion的正確版號,參考了檔案資訊:

    檔案版本是AssemblyFileVersion,那麼產品版本就是AssemblyVersion囉?加上NuGet Package說現在的版本是7.0.1,所以開開心心填上newVersion="7.0.1.18622":

    <dependentAssembly>
    <assemblyIdentityname="Newtonsoft.Json"publicKeyToken="30AD4FE6B2A6AEED"culture="neutral"/>
    <bindingRedirectoldVersion="0.0.0.0-6.0.0.0"newVersion="7.0.1.18622"/>
    </dependentAssembly>

    不料,程式抱怨找不到Newtonsoft.Json 4.5.0版,顯示繫結重新導向沒生效。爬文發現Json.NET 7.0.1相關導向範例都是寫newVersion="7.0.0.0",乖乖改成7.0.0.0後,系統正常運作。

    問題來了,7.0.1、7.0.1.18622、7.0.0.0,這麼多版號搞得我好亂,將來遇到同樣狀況,到底要以哪一個為準?

    做完功課,老狗又學會新把戲,除了AssemblyVersion、AssemblyFileVersion,還有所謂AssemblyInformationVersion!

    1. AssemblyVersion

      為必要屬性,是參照組件的重要依據,一旦版號改變,參照該組件的程式必須更新(重新編譯或設定組件繫結重新導向)。
    2. AssemblyFileVersion

      主要用於部署管理,可以每次部署時遞增以便區隔,是安裝程式常用的版本依據,實務上可由MSBuild產生(但要自己寫邏輯)。如果想不驚動相關程式悄悄換版,可以維持同一個AssemblyVersion,只編譯成AssemblyFileVersion不同的DLL檔。若未指定AssemblyFileVersion,系統會用AssemblyVersion版號當作AssemblyFileVersion。 AssemblyFileVersion可以用檔案總管檢視(即前面附圖所示的「檔案版本」)。
      注意:用檔案總管是看不到AssemblyVersion的!最簡便的查詢方式是透過Visual Studio:

    3. AssemblyInformationalVersion

      產品版本。它不像AssemblyVersion跟AssemblyFileVersion限定格式為四節數字,允許寫成"0.9 Beta Version"、"1.0 Release Candidate"之類的可讀文字,但過去曾出現過相關工具處理非數字AssemblyInformationalVersion的相容問題(例:Issue1Issue2)。當組件未指定AssemblyInformationalVersion時,系統會使用AssemblyVersion代替。

    參考來源:stackoverflow

    經過這次教訓,下回遇到要寫bindingRedirect時,就知道要乖乖查Visual Studio的組件屬性視窗,不會再用檔案總管亂查了。

    為EF連線字串加密的簡單範例

    $
    0
    0

    依一般資安準則,在設定檔使用明碼儲存連線字串是不被允許的,連線字串加密是基本要求。

    雖然用反組譯.NET組件破解加密字串不是什麼難事,但駭客至少得先找到檔案拿到組件檔(DLL)才能動手,相較之下明碼連線字串就簡單多了,用關鍵字掃掃硬碟、備份媒體就能抓出一大把,不費吹灰之力蒐集到資料庫帳號密碼,很可怕吧!

    ASP.NET 2.0起,web.config內建connectionStrings加密功能,但它綁死機器,必須解決Web Farm機器間的同步,且只能透過aspnet_regiis工具程式操作,部署管理較為麻煩。因此,我還是偏好自己處理連線字串加密。

    ADO.NET時代,SqlConnection/OracleConnection由自己建立,連線字串怎麼取也由程式決定,要加密很簡單。到EF時代,連線字串由元件背後的機制處理,new BlahEntities()就連上資料庫做事,開發者不需要也不容易介入取得連線字串的過程。早期的EF版本還有個可傳入連線字串的DbContext建構式,到後來,DbContext只剩一個無參數的公開建構式,使用時想指定連線字串都沒辦法。

    所幸,這問題不難克服,利用partial class技巧在專案裡多加個BBDPEntities.cs(記得namespace要跟EF的BBDPEntities相同),補上吃連線字串參數的建構式,搞定。

    namespace BBDPWeb.Models
    {
    publicpartialclass BBDPEntities
        {
    public BBDPEntities(string cnStr) : base(cnStr)
            {
            }
        }
    }

    加密解密演算法很多,就連線字串而言,不必動用什麼高深莫測堅不可破的演算法,避免帳號密碼被人一眼看穿就夠。以下是簡單的DES加解密函式範例:

    //很陽春的加解密函式,改寫自http://goo.gl/sos1J
    //程式以示意為主,只適用小型字串處理,未包含參數檢核,防錯邏輯
     
    publicclass MyCipher
        {
    byte[] rgbKey = newbyte[8];
    byte[] rgbIv = newbyte[8];
            DESCryptoServiceProvider des = new DESCryptoServiceProvider();
    public MyCipher(string key)
            {
                var hash = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(key));
                Array.Copy(hash, 0, rgbKey, 0, 8);
                Array.Copy(hash, 8, rgbIv, 0, 8);
            }
    publicstring Encrypt(string rawString)
            {
    using (var ms = new MemoryStream())
                {
    using (var cs = new CryptoStream(ms, 
                        des.CreateEncryptor(rgbKey, rgbIv), CryptoStreamMode.Write))
                    {
    byte[] buff = Encoding.UTF8.GetBytes(rawString);
                        cs.Write(buff, 0, buff.Length);
                    }
    return Convert.ToBase64String(ms.ToArray());
                }
            }
     
    publicstring Decrypt(string encString)
            {
    using (var ms = new MemoryStream(Convert.FromBase64String(encString)))
                {
    using (var cs = new CryptoStream(ms, 
                        des.CreateDecryptor(rgbKey, rgbIv), CryptoStreamMode.Read))
                    {
    using (var sr = new StreamReader(cs))
                        {
    return sr.ReadToEnd();
                        }
                    }
                }
            }
        }

    為DbContext增加了傳入連線字串的建構式,也準備好解密函式,接著只需將建立DbContext的程序改成:由設定檔讀取加密後的連線字串、解密、使用連線字串建立DbContext,如此就做到連線字串加密,提升系統安全性。另外奉送一則我常用的小技巧:啟用加密後,修改或比對連線主機、帳號、密碼設定會變得很麻煩,而這在測試開發階段屬於不必要的負擔。故我習慣讓程式聰明一點,遇到連線字串是明碼就直接用,若是加密版就解開再用,明碼加密兩相宜,「測試開發用明碼,正式上線再加密」,魚與熊掌兼得。加入一點關鍵字比對,就可以實現這點。

    static MyCipher cipher = new MyCipher("加密金鑰");
    //使用統一的靜態函式建立DbContext物件,避免自行建構
    publicstatic BBDPEntities CreateDbContext()
            {
    //設定檔之連線字串應加密
                var cnStr = ConfigurationManager.ConnectionStrings["BBDPEncCnnStr"].ConnectionString;
    //自動偵測,支援加密及未加密的連線字串,測時不加密,上線時再加密
    //在連線字串找到metadata字樣表示為加密字串
    if (!cnStr.Contains("metadata")) 
                {
                    cnStr = cipher.Decrypt(cnStr);
                }
                var ent = new BBDPEntities(cnStr);
    return ent;
            }

    要加密連線字串一樣靠MyCipher,var encCnnStr = cipher.Encrypt("metadata=res://*/BBDP……"),加密結果是一長串Base64編碼,用它替換掉原本的明碼連線字串即可。實務上多半會寫個通用加解密工具,輸入加密Key跟來源字串,按鈕執行加密或解密,用起來比較方便。

    把這招學起來,以後別再讓帳號密碼脫光光躺在設定檔做日光浴囉!

    【茶包射手日記】Chrome釘選到Windows 10開始畫面的圖示消失之謎

    $
    0
    0

    等了好久,Sony終於在11月下旬開放VAIO T13筆電升級到Windows 10(Sony較謹慎,官方堅持要等驅動程式及軟體測試OK再開放使用者升級)。就像賽馬前方的柵欄拉起,二話不說馬上衝了,將筆電從Windows 8.1升級到Windows 10。

    Windows自動下載Windows 10安裝程式(約2.6GB),按下一步下一步,等了近一個小時,電腦就呼嚕呼嚕地直上Windows 10。跟前次升級家中PC的經驗差不多,過程很無腦很順暢,升級後程式幾乎都能用,唯一被擋下的只有Classic Shell。不過,Windows 10總算肯把「開始選單」還給我了,有完整的「所有應用程式」清單(下圖左側的直排清單),可以自由組合排列的開始畫面(下圖右方一大片的動態磚牆),也有搜尋功能,該有的都有,Classic Shell可以安心退役了,謝謝你這兩年的陪伴,再會~

    不過我遇到一個挺怪異的問題,我習慣將應用程式的動態磚縮成小圖示緊密排列,而Chrome被釘選到開始畫面時,一開始大圖示還正常:

    調整為小尺寸後,動態磚有縮小也可正常使用,但變成灰黑一片,神奇寶貝球Chrome圖示不見了!

    原以為這是某些傳統應用程式釘選到開始畫面的問題,爬文無所獲。使出萬事問臉書的大絕,蒐集到更具體的情資:「只有Chrome會這樣」,縮小爬文目標,找到一篇superuser論壇文章揭開謎底。原來是Chrome特別貼心,在Program Files(x86)\Google\Chrome\Application目錄下放了一個VisualElementsManifest.xml自訂「當Chrome被釘選到開始畫面時的視覺效果」,其中可看到它指定了圖示檔、背景色,這也說明為什麼一般應用程式在大尺寸時都是小圖示配藍底,Chrome卻是大圖示配灰黑底(#323232)。但顯然這組設定與Windows 10有點不相容,因此當縮成小尺寸動態磚時,圖示會搞失蹤。

    對VisualElementManifest.xml沒研究,找不出縮小後圖示消失的原因,但發現最簡單的做法是將設定檔更名成VisualElementManifest.xml.bak(或者直接刪掉也成),取消釘選再重新釘選Chrome到開始畫面,Windows 10會將Chrome視為一般應用程式,畫面不如原先精美,但縮小時圖示就不會再搞失蹤囉~

    筆記:Angular 2介紹 by Brad Green

    $
    0
    0

    影片

    Brad Green (Google Angular開發團隊的主管) 在微軟MVP年會講了一場Angular 2(微軟的人到蘋果發表會站台,Google主管跑到微軟MVP年會做簡報,世界真奇妙 :P),完整影片前陣子放到Channel 9,雖然沒去美國參加MVP年會,基於已開始靠Angular吃飯又是來自「原廠」的第一手資訊,就認真當好學生把影片看完,當然筆記也是一定要的:(這場屬概觀式介紹,很多東西沒深入細講,先了解關鍵字就好)

    • NG1在全世界已經有1100萬名開發者。 https://www.madewithangular.com/ 蒐集許多用Angular開發的有名網站,當然,包含不少Google自己的專案
    • 在發展NG2的同時,NG1也在持續開發,一些新功能同時被加進NG1及NG2,例如:元件路由強化(Nested、Parallel、Modal)、多語系支援、ngAnimate動畫效果…
    • 速度是NG2專注的重點之一,NG2在Load、Compile、Render、Re-render四個階段都下了功夫
      • Load
        Angular Universal:簡單地說就是把一部分產生網頁的工作移到Server端,再跟Client端程式無綘接軌。除了能縮短從網頁載入到使用者看到完整內容的時間,也有利於SEO。(不需JavaScript就得到最終的網頁內容,方便搜尋引擎抓取)
      • Compile
        調整Compile順序提升效率 (從Compile->Remder->Compile->Render改成Compile->Render->Render),甚至可選擇在網站編譯階段就先Compile好,如此啟動時間可比NG1快3倍,且程式庫更小(啟動速度加快三倍) 
      • Render
        異動偵測加入新技術以提升速度:VM inlining、Rx & Immutable、記憶體效率提升
        NG2比NG1快2.5倍
      • Re-render
        View Caching技術:Virtual scroll(捲動到下方法再載入)、Repeater & routes改良、記憶體效率提升
        NG2比NG1快4.2倍
    • Meteor效能測試中,Angular 2大幅領先Blaze與React
    • NG2支援Web Workers:讓網頁能善用多CPU的優勢,重度運算可另開Web Worker執行,確保網頁介面操作不被卡住。影片中有段神奇的展示,在多個瀏覽器裡的Web Worker可以彼此溝通。
    • NG2在語法上了大幅度改良
      <input [value]="firtName"> 屬性Binding
      <button (click)="buy($event)"> 事件Binding
      <input [(ng-model)]="userName"> 雙向Binding
    • NG2將Template Directive跟Controller融合在一起,變成Component,再結合TypeScript,語法精簡得嚇人:(常寫Directive寫到手酸的人默默飄過)
      @Component({ 
        selector: 'display', 
        template: '<p>My name: {{myName}}</p>'
      }); 
      class DisplayComponent { 
        myName:string; 
        constructor() { 
      this.myName='Alice'; 
        } 
      } 

    • Binding改採Unindirection Data Flow概念
      比NG1更快,更容易Debug、可結合React及Flux(Facebook推的Framework,這兩年很紅)
    • 開發語言可以選ES5(不用編譯)、ES6(要編譯,更OOP)或TypeScript(強型別)。對我而言,想都不用想,當然要選TypeScript呀!沒有強型別,李技安抓鬼?
    • 工具:Batarangle Chrome擴充套件,方便偵錯NG2問題
    • 跨平台:Desktop/Mobile Web/Mobile,針對行動裝置網頁,Brad提到了這幾年成長很快的元件Ionic(不過我會繼續跟著Kendo UI <3)
    • NG1傳統做法:
      HTML Template + Data = DOM View
      NG2推出:
      XML Template + Data = Native View (用產生網頁的概念產生手機APP UI,NativeScript
      Web GL Template + Data = 3D View(未來式)
    • ngUpgrade
      NG1.x與NG2的Controller可並存於同一網頁,再一部分一部分換成NG2(Thanks God,不用砍掉重練了)
    • NG2仍會支援IE9… (這… 大家團結起來吼伊細不好嗎?)
    • NG2文件:http://angular.io/docs本場投影片:http://g.co/ng/mvp-angular

    為Kendo UI NumericTextBox加上超出範圍警示

    $
    0
    0

    網頁要用到數字輸入欄位我習慣用Kendo UI的NumericTextBox解決,可以設上下限,有調整鈕,還能用上下鍵調數字,該有的都有,但有個小問題-當使用者輸入值超過上下限,NumericTextBox只默默將它改成Max或Min,沒有任何提示,使用者可能沒察覺輸入數字被改掉,導致爭議。

    在Telerik討論區找到相關討論,Telerik RD確認NumericTextBox目前版本不提供超出範圍事件。由於沒察覺數字被改可能造成的爭議不小(跟錢有關),專案規格提出「數字超過範圍時需提醒使用者」的需求,因此又到了天助自助,自立自強的時刻。追了原始碼,有個_adjust()函式負責檢查使用者輸入的值是否大於Max或小於Min,超過時強制改為Max及Min,我想到做法抽換掉_adjust,換成加料的版本-修改數值前先觸發自訂outOfRange事件(借用jQuery的自訂事件),傳入value、max、min參數,以便加入自訂邏輯,例如:alert顯示警告訊息等。由於不希望事件卡住原本的運作流程,故我用了setTimeout的技巧,等1ms才觸發。

    程式範例以下(Demo),供有類似需要的同學參考:(註:這屬於Hacking解法,未來若Kendo UI改版有可能導致做法失效)

      $(document).ready(function() {
    // create NumericTextBox from input HTML element
        $("#numeric").kendoNumericTextBox();
    var ntb = $("#numeric").data("kendoNumericTextBox");
    //hacking, trigger event when out of range
        ntb._adjust = function(value) {
    var that = this,
              options = that.options,
              min = options.min,
              max = options.max;
     
    if (value === null) {
    return value;
          }
          element = this.element;
    var triggerEvent = function() {
    var evtData = { value: value, max: max, min: min };
            setTimeout(function() {
              element.trigger("outOfRange", evtData);
            }, 1);
          };
    if (min !== null&& value < min) {
            triggerEvent();
            value = min;
          } elseif (max !== null&& value > max) {
            triggerEvent();
            value = max;
          }
    return value;
        };
        $("#numeric").bind("outOfRange", function(e, data) {
    if (data.value > data.max) 
            alert("Value " + data.value + " is greater than Max: " + data.max);
    elseif (data.value < data.min)
            alert("Value " + data.value + " is less than Min: " + data.min);
        });
      });

    【茶包射手日記】無法使用本機帳號存取C$網路分享

    $
    0
    0

    某伺服器最近由Windows 2003升級到Windows 2008 R2,原本複製檔案到該伺服器的排程發生存取被拒錯誤,經偵察獲得以下線索:

    1. 主症狀為使用該伺服器的本機管理者帳號(該伺服器未加入網域)從遠端機器存取其C$、D$等系統內建網路分享出現Access Denied
    2. 同一排程作業在升級Windows 2008 R2前可行
    3. 確定帳號密碼無誤,因為使用同一帳號可以Terminal Service登入問題伺服器
    4. 若另開一個網路分享並授與該帳號讀取權限,可順利存取無誤

    同事提供情資一則,原來這是Windows Vista起(亦適用Windows 2008+)加入的新政策-UAC Remote Restriction(詳情請參考KB951016):

    概念很簡單,啟用UAC之後,每次動用管理者權限都要額外提示及確認,若假冒來自遠端的存取就能繞過UAC直接以管理者權限存取機器,有可能淪為安全漏洞。故Windows預設會一併啟用UAC Remote Restriction,落實以下原則:

    本機帳號 (Security Account Manager,SAM使用者帳號)

    當使用隸屬本機管理者群組(Local Administrators)帳號連上管理用的網路分享,如:\\ remotecomputer \Share$,該連線將不會被賦與完整管理者權限,如需使用管理權限執行作業,必須以遠端協助(Remote Assistance)或遠端桌面(Remote Desktop)方式互動式登入。

    網域使用者帳號 (Active Directory 使用者帳號)

    當使用AD帳號遠端連線管理用的網路分享,若該帳號屬於本機管理者群組,則不受上述UAC Remote Restriction限制。

    用Windows 2008跟C$/D$做法已好一段時間,之前一直沒遇到問題是因為都是用AD帳號,這台機器性質特殊沒加入網域,只能使用本機管理者帳號,一升級到Windows 2008立刻爆炸。

    解決方法有兩種:

    1. 避用 C$、D$,另開網路分享滿足檔案傳輸需求,如此可繼續享有UAC Remote Restriction的保護(建議做法)
    2. 透過Registry關閉UAC Remote Restriction,位置:HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\LocalAccountTokenFilterPolicy REG_DWORD –> 1。(安全性較差,不建議)

    診斷Visual Studio 2015安裝卡住問題

    $
    0
    0

    這篇文章背後有個悲慘故事,微軟在11/30釋出了VS2015 Update 1,隔天我就迫不及動手要升級,最後卻足足花了四天,晚上開夜車研究,清晨四點多想到點子爬起來繼續射茶包(沒搞定睡不著,射手的宿命 orz),直到剛剛才正式把VS2015U1裝好~

    小小的升級安裝搞成天堂路,內情自然不單純,別擔心,我的痛苦遭遇源自系統環境特殊,大家應不會遇到。但網路上看到很多人抱怨Visual Studio 2015安裝很慢,一裝數小時,停在某個安裝項目久久沒進展,搞不清楚是當掉還是安裝中?不知道該耐心等候還是果決中斷不浪費生命?有鑑於我可能是全台灣安裝VS2015 Update 1次數最多的記錄保持人,過程搞懂一些Visual Studio安裝原理跟偵錯技巧,抱著捨我其誰的精神,分享偵察VS2015安裝卡住問題的小技巧。

    Visual Studio的安裝方式有兩種,網路安裝或安裝光碟(VS2015U1的安裝媒體高達5.89GB,已超過普通DVD的容量,通常都是下載後掛成虛擬光碟機)。網路安裝檔很小,只有737KB,會依你勾選的安裝項目下載安裝檔回來。第一次使用網路安裝失敗後,無法釐清是網路問題還是安裝錯誤,決定由MSDN下載光碟檔,排除網路下載因素。之後還是歷經安裝特定項目卡住不動,等數小時也無進展的狀況。學會看Log後才知道,某些時候該項目的安裝程式早已出錯中止,是Visual Studio安裝程式未接到通知才讓使用者傻等,這種情況等到海枯石爛也不會有結果的… orz

    要區別Visual Studio是在安裝中還是已經當掉卡死,Log檔是最好的觀察指標。開啟檔案總管,輸入"%TEMP%"進入暫存資料夾,安裝VS時應該要看到一堆dd_vs_*_yyyyMMddHHmmss_****.log檔案。

    用修改日期排序,健康的安裝過程應該要一直冒出新檔案,再不然是最新的一筆Log檔持續變大。如果你發現檔案超過十分鐘沒變化,就像沒聽到心跳,八成大事不妙。此時可以挑最新的幾個檔案查詢安裝項目的處理狀態,像我就遇到在Microsoft .NET Framework 4.5 Multi-Targeting Pack卡住超過一小時:

    打開當時最新的Log檔 dd_vs_enterprise_20151203220108_027_netfxdtp_48.log,就知道發生了什麼事:

    MSI (s) (24:C8) [22:10:21:955]: Machine policy value 'DisableUserInstalls' is 0
    MSI (s) (24:C8) [22:10:21:962]: Warning: Local cached package 'C:\Windows\Installer\1641d83.msi' is missing.
    MSI (s) (24:C8) [22:10:21:962]: User policy value 'SearchOrder' is 'nmu'
    MSI (s) (24:C8) [22:10:21:962]: User policy value 'DisableMedia' is 0
    MSI (s) (24:C8) [22:10:21:962]: Machine policy value 'AllowLockdownMedia' is 1
    MSI (s) (24:C8) [22:10:21:962]: SOURCEMGMT: Looking for sourcelist for product {56E962F0-4FB0-3C67-88DB-9EAA6EEFC493}
    MSI (s) (24:C8) [22:10:21:962]: SOURCEMGMT: Adding {56E962F0-4FB0-3C67-88DB-9EAA6EEFC493}; to potential sourcelist list (pcode;disk;relpath).
    MSI (s) (24:C8) [22:10:21:962]: SOURCEMGMT: Now checking product {56E962F0-4FB0-3C67-88DB-9EAA6EEFC493}
    MSI (s) (24:C8) [22:10:21:962]: SOURCEMGMT: Attempting to use LastUsedSource from source list.
    MSI (s) (24:C8) [22:10:21:962]: SOURCEMGMT: Processing net source list.
    MSI (s) (24:C8) [22:10:21:962]: SOURCEMGMT: Trying source C:\ProgramData\Package Cache\{56E962F0-4FB0-3C67-88DB-9EAA6EEFC493}v4.5.50710\Redistributable\4.5.50710\.
    MSI (s) (24:C8) [22:10:21:962]: Note: 1: 1402 2: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer 3: 2
    MSI (s) (24:C8) [22:10:21:962]: Note: 1: 2203 2: C:\ProgramData\Package Cache\{56E962F0-4FB0-3C67-88DB-9EAA6EEFC493}v4.5.50710\Redistributable\4.5.50710\netfx45_dtp.msi 3: -2147287037
    MSI (s) (24:C8) [22:10:21:962]: SOURCEMGMT: Source is invalid due to missing/inaccessible package.
    MSI (s) (24:C8) [22:10:21:962]: Note: 1: 1706 2: -2147483647 3: netfx45_dtp.msi
    MSI (s) (24:C8) [22:10:21:962]: SOURCEMGMT: Processing media source list.
    MSI (s) (24:C8) [22:10:21:963]: SOURCEMGMT: Trying media source G:\.
    MSI (s) (24:C8) [22:10:21:963]: Note: 1: 2203 2: G:\netfx45_dtp.msi 3: -2147287038
    MSI (s) (24:C8) [22:10:21:963]: SOURCEMGMT: Source is invalid due to missing/inaccessible package.
    MSI (s) (24:C8) [22:10:21:963]: SOURCEMGMT: Trying media source D:\.
    MSI (s) (24:C8) [22:10:21:963]: Note: 1: 2203 2: D:\netfx45_dtp.msi 3: -2147287038
    MSI (s) (24:C8) [22:10:21:963]: SOURCEMGMT: Source is invalid due to missing/inaccessible package.
    MSI (s) (24:C8) [22:10:21:964]: Note: 1: 1706 2: -2147483647 3: netfx45_dtp.msi
    MSI (s) (24:C8) [22:10:21:964]: SOURCEMGMT: Processing URL source list.
    MSI (s) (24:C8) [22:10:21:964]: Note: 1: 1402 2: UNKNOWN\URL 3: 2
    MSI (s) (24:C8) [22:10:21:964]: Note: 1: 1706 2: -2147483647 3: netfx45_dtp.msi
    MSI (s) (24:C8) [22:10:21:964]: Note: 1: 1706 2:  3: netfx45_dtp.msi

    問題出在安裝程式找不到netfx45_dtp.msi的安裝檔,但VS2015主安裝程式沒有察覺此一狀況,讓我也跟著痴心守侯數小時(我寶貴的青春吶),直到耐心耗盡,怒砍安裝程式為止。(砍安裝程式會造成安裝不完整,事後得確實修復,否則無法使用某些功能)

    【結論】

    遇到Visual Studio安裝進度卡在某個項目很久,可善用安裝Log檔確認狀態及找出原因。至於如何排除問題有賴見招拆招,恕無萬用大絕可分享。(其實有啦,重灌Windows… XD)

    【後記】

    回到我的悲慘故事,花了很久時間,終於找到問題癥結在於先前為了騰出C槽空間,我搬動了Installer及Package Cache目錄。請不要誤會,移動這兩個目錄基本上是安全的,並非致命因素。但很不幸地,上週剛好才將筆電原機升級Windows 10,Windows 10很貼心地保留原本的Windows目錄放在Windows.old,另外開了一個Windows目錄,允許使用者後悔退回原本的作業系統版本。大家有猜到問題了嗎?升級過程Installer及Package Cache檔案沒有順利搬到Windows 10,陰錯陽差,炸得我遍體鱗傷!補齊兩個資料夾後,我的Visual Studio 2015 Update終於裝好了… (淚)

    取得NLog檔案路徑

    $
    0
    0

    NLog已是平日寫.NET專案的標準配備(另一個是Json.NET,每個專案都要加,恨不得.NET能把它納入內建),但偶爾需要確認Log檔路徑(不確定NLog.config寫法是否有錯,設定是否生效),每次遇到每次重新爬文,記性之差,讓網友直呼太誇張,只好寫篇筆記救救自己。

    例如有NLog.config寫法如下:(順便筆記我最常用的NLog檔案設定樣式)

    <?xmlversion="1.0"encoding="utf-8" ?>
    <nlogxmlns="http://www.nlog-project.org/schemas/NLog.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <targets>
    <targetxsi:type="File"name="f"fileName="e:/AfaConsoleLog/${logger}/${shortdate}.log"
    layout="${longdate} ${uppercase:${level}} ${message}"/>
    </targets>
    <rules>
    <loggername="*"minlevel="Debug"writeTo="f"/>
    </rules>
    </nlog>

    要取得f Target產生的檔案路徑,做法是先用FindTargetByName取得 f Target轉型為FileTarget型別,但由於Log檔路徑與Logger名稱及日期有關,故要用FileName.Render()傳入日期及Logger名稱參數才能得到結果。以上可濃縮成一行:

    (LogManager.Configuration.FindTargetByName("f") as NLog.Targets.FileTarget).FileName
    .Render(new LogEventInfo() { TimeStamp = DateTime.Now, LoggerName = "loggerName" })

    趁著偵錯中斷,丟進Visual Studio的立即執行視窗可當場取值,例如:"e:/AfaConsoleLog/loggerName/2015-12-08.log"

    若要省事,可以寫成共用函式丟進通用程式庫,呼叫方法可再簡化為 MyUtility.ProbeNLogFilePath("f");

    /// <summary>
    /// 偵測NLog檔案路徑及名稱 REF: http://stackoverflow.com/a/11629408
    /// </summary>
    /// <param name="targetName">目標名稱</param>
    /// <param name="loggerName">Logger名稱</param>
    /// <returns></returns>
    publicstaticstring ProbeNLogFilePath(string targetName, string loggerName = "LoggerName")
    {     
    return (NLog.LogManager.Configuration.FindTargetByName(targetName) as NLog.Targets.FileTarget)
        .FileName.Render(new LogEventInfo() { TimeStamp = DateTime.Now, LoggerName = loggerName });
    }

    Open Live Writer-球來就打,有蟲就殺,開源萬歲!

    $
    0
    0

    講到部落格離線寫作軟體,王者非Windows Live Writer(WLW)莫屬(其實是「拔劍四顧心茫然」,根本沒半個對手),支援的部落格平台繁多,有API可以自已寫外掛套件,要整合Flicker、YouTube都不是問題。隨著Live Messenger吹熄燈號,身為Live家族一員的Live Writer陷入尷尬情境,政策上微軟不太會對Live系列投入開發及維護資源,於是WLW從2012年起就不再更新,但市場偏偏又沒有任何替代軟體,面對技術規格不斷演進(例如:部落格平台改版、HTML5、CSS3、Markdown語法),WLW一路走來,始終如一,原廠無力改版,社群無權改版(沒有原始碼亦缺少授權)。今天五月的時候,Scott Hanselman在一篇部落格文章分享用Dropbox同步Live Writer草稿目錄的小技巧,文末提到:2014年六月Scott曾參與Live Writer未來發展的討論,「I'll be sure to let you know about our plans with Windows Live Writer as soon as I know more. 」,頗令人期待,有網友在猜或許會以Open Source方式釋出,這應該是最美妙的發展了~

    近半年後,今早看到好消息,微軟內的一群志願開發者為 Windows Live Writer 衍生出一個分支(Fork)-Open Live Writer(簡稱OLW好了),正式開放原始碼!跟WLW相比,Open Live Writer移除跟增加了一些功能,像是:

    1. 移除拼字檢查
      第三方套件或Windows 8都已提供很好的拼字檢查功能
    2. 移除Blog This API
      跟IE/Firefox整合的古老COM+玩意兒,就讓它隨風而逝吧~
    3. 即將加入OAuth2支援
      OLW即將支援Blogger平台使用的OAuth2,而WLW不會加入,串接Blogger只能用OLW

    另外,OLW的外掛套件功能仍待開發,大家可以到回饋問卷投票,催生自己最需要的套件。但這意味OLW套件與WLW套件的規格不同,既有套件得重寫,往好處想,新版的套件API會更符合.NET風格,應會比WLW時代帶點COM+味的規格好寫。

    Scott提醒,OLW的程式碼始於.NET 1.0/1.1時代已有10年歷史,裡面的程式語法、風格可能與當今主流格格不入,大家如果有心參與開發,別急著把.NET 4.6的async await一股腦塞進去,一心想砍掉重練,穩穩地維護與更新才能駛得萬年船。若要貢獻程式修改,請先跟開發團隊充分溝通,不要憑著滿腔熱血埋頭寫完卻不符合OLW發展規劃,讓開發團隊接受也不是,拒絕也不是。

    Scott也透露,OLW計劃從2013年4月就啟動,但由於涉及複雜的法律與技術問題,加上Open Source過去對微軟是一塊沒接觸過的領域,歷經許多堅持與努力,才成功將Windows Live Essentials的一部分開源化。

    OLW目前為0.5版,依據它的Roadmap,1.0版會支援多語系,提供測試組件(方便接受程式碼貢獻),1.1版會再支援Azure AD、在Facebook/Twitter PO文、Markdown… 等,預期WLW變身OLW之後,會再以地表最強部落格寫作工具之姿,再戰十年。

    興奮地裝了 Open Live Writer,介面跟WLW幾乎一樣,但到設定部落格階段無法擷取更新部落格樣式,印象中在WLW時代就發生過,似乎是某次修改部落格版型後發生,後來我就只能靠手動 Copy CSS 解決。如今局面不同了,「Use the source, Luke!」

    從Github下載原始檔,用Visual Studio 2015開啟,毫無阻礙地編譯、執行,很快地就靠Line by Line Debug抓出問題癥結,在偵測部落格樣式時,Live Writer會上傳一篇暫時文章(如下圖),文章標題跟內文會包含幾個GUID,Live Writer抓取網頁比對GUID後擷取網頁的HTML、CSS及圖檔。

    不知什麼原因,Live Writer沒抓到我的單篇文章URL,而是抓回首頁(可能跟我自訂URL路由有關),由首頁應該也要能抓到暫時文章,只是首頁在某次改版後調了規則,文章內容只會擷取前100個字元,超過部分以「...」表示,所以HTML長這樣:

    <DT><A href="/blogs/darkthreadtw/archive/2015/12/10/12139.aspx">Temporary Post Used For Theme Detection (dc450109-c337-47a4-8aec-c35f2a63b163 - 3bfe001a-32de-4114-a6b4-4005b770f6d7)</A>
    <DD>This is a temporary post that was not deleted. Please delete this manually. (1ef7f77f-b221-443a-a7d7...
    <TABLE width="100%">

    文章內容內嵌GUID的一部分換成「…」,造成Title GUID比對吻合,Body GUID抓不到,HTML解析邏輯發生未預期錯誤。不想為此調整部落格,我想到的解法是小調OLW程式,將GUID移到內文的最前方,避免被截掉:

    就這樣,在OLW開源的第一天,我就靠Source Code解決了自己遇到的Bug。想起鋒哥的名言:

    寫程式也是這樣,有Bug就殺,沒什麼特别的,如果有原始碼的話… 開源萬歲!

    克服Java Applet封鎖問題

    $
    0
    0

    在過去,許多HTML/JavaScript做不到的事,在網頁上只能交給Flash或Java Applet解決,但Flash或Applet具有較高本機資源存取權限,容易形成資安漏洞。隨著資安意識抬頭,加上HTML5快要無所不能,Flash與Java Applet的不可取代性下降,兩位老英雄晚景淒涼,Flash將逐步走入歷史,而Chrome及Edge則不再支援Java Applet。即使Java Applet在IE或Firefox仍可使用,也受到嚴格限制。

    最近小木頭在瘋魔術方塊,讓我忍不住重溫七年前習得的雜技,回頭查當年的教學網站,已經看不到Java Applet圖解動畫了,即使改用IE瀏覽也不行。

    查了資料,得知從 Java 7 Update 51開始,Java不允許使用者執行未經簽署 (未簽署)、自行簽署 (不是由信任授權單位簽署) 或是遺漏權限屬性的應用程式[參考] 。這意味著除非開發者花錢為自己寫的Java Applet加上數位簽章,否則會被瀏覽器禁用。許多Applet屬佛心提供,怎麼可能要求開發者為自己多年前寫的程式買憑證加簽章?

    由於Java不允許調降安全規格,將網站加入例外網站是唯一解法,針對特定網站法外開恩,允許未簽署的Applet也能執行。但要提醒:開放執行未簽署元件形同拆掉鐵窗關閉防火牆,若Applet包藏惡意程式(例如:病毒、木馬、後門、加密勒索程式…),此舉形同邀強盗到家裡泡茶,開啟前請三思。為求謹慎,我選擇另外開一台VM,在VM裡啟用Applet,降低被攻擊風險。再次強調,以下做法存在風險,請審慎評估是否要冒險前進。

    設定例外網站的方法如下。先找到「設定Java」:

    在安全頁籤的例外網站清單加入要啟用未簽署Applet的網站:

    記得要重新啟動IE才會生效。

    設定後,Java Applet總算回來了…

    Coding4Fun-羅輯思維錄音檔快捷下載

    $
    0
    0

    大約半年前,我迷上聽「羅輯思維」,一位「歪嘴胖子」的說書節目。節目名稱有哏,取「羅輯」而非「邏輯」,依羅胖自己的說法,是因為文科生不敢妄談邏輯,自嘲只是個姓羅的胖子編輯整理自己的思維罷了。但這絕對是自謙之言,依邏輯狂的標準,節目內容思路清晰,邏輯脈絡分明,即使部分論述流於片面證據,恐有詭辯謬誤之嫌,但其邏輯推演流暢,絕對合乎邏輯法則,總能自圓其說,聽來毫無違和感,非常適合我這種一發現邏輯不通就會皺眉頭的邏輯偏執者,每每是拍案叫絕,大呼過癮。

    羅胖的口號是「死磕自個兒,愉悅大家」(死嗑是北京話,意思差不多是「不達目的誓不罷休,跟你沒完沒了」),以說書人自居,節目內容主要是深度研讀某個領域的一本或多本書籍,擷取精華,以極有條理的方式探討五花八門的議題,從不同角度剖析,常出現新奇且獨特的觀點。我平日極少接觸工作以外的書藉資料,即使有對「課外知識」很有學習熱誠,但你知道的,沒排進生活作息,沒有吸收管道就無緣進入腦海。聽羅輯思維像吃維他命,不用狂啃一堆蔬菜水果,直接攝取別人提煉好的養分精華,花不到一個小時就吞下原本得啃好幾天書才能得到的知識。我承認速食取巧永遠無法取代親自咀嚼消化,但換個角度想,反正已註定蔬菜水果吃得少,吞顆維他命多少有效。

    節目取材範圍廣泛,但以中外歷史為大宗,其中不乏新鮮有趣的軼事或頗具顛覆性的論點,舉幾個例子:

    • 人類能源枯竭的一天永遠不會來臨
    • 機車的費馬在草稿賣了個關子,花掉後人三百年才破解
    • 無法回答學生的問題怎麼辦?叫同學們把他弄死吧!(畢達哥拉斯解法)
    • 歐洲古代的平民住宅很少有窗子,目的是為了避稅
    • 秦儈沒想像中可惡,岳飛比你想的白目
    • 倭寇裡只有少部分是日本人,是不當貿易政策的產品
    • 海盗船比商船更民主更講人性化管理
    • 你有權保持沉默,否則你所說的一切將成為呈堂證供,你有權找律師… 這段話有個專有名詞:「米蘭達宣告」
    • 辛普森殺妻案為什麼判無罪?
    • 大航海時代航向偉大航道一點也不浪漫瀟灑,而是九死一生又狼狽,直到泡菜跟航海鐘登場
    • 清官林則徐也送禮也收紅包,否則日後根本沒有機會禁煙燒鴉片

    精彩故事多不勝數,有興趣的朋友不妨自己慢慢發掘。

    節目推薦完了,回到Coding4Fun。撥時間看影片太花時間,我習慣抓MP3丟進運動耳機,慢跑時邊跑邊聽。在喜馬拉雅FM網站可以找到羅輯思維的各集錄音,網站支援「下載到手機」,卻沒有下載到PC功能。在瀏覽器用F12開發工具不難找到MP3檔(實際格式為m4a)下載連結,但手續有點繁瑣,又到了寫程式自立自強的時候。

    我的解決方案是在網站加一顆按鈕「產生下載連結」:

    「產生下載連結」會在每個標題後方加上一個下載連結,在「下載」按右鍵選「另存連結為」就能下載MP3檔案,方便多了。

    程式碼如下:


    function genDownloadLinks() {
        $(this).remove();
      $(".miniPlayer3 .title").each(function() { 
    var a = this;
    var id = a.href.split('/')[5];
        $.getJSON("http://www.ximalaya.com/tracks/" + id + ".json")
        .done(function(res) { 
    var title = res.title;
    var path = res.play_path_128 || res.play_path_64 || res.play_path_32;
    var ext = path.match("[.]m.+$")[0];
            $(a).after("<a href='" + path + "' download='" + title + ext + "' " + 
    "style='margin-left: 6px; color: blue;'>下載</a>");
        });
      });
    }
     
    $("<button style='margin-left:6px;border:1px solid gray;background-color:#ccc'>" + 
    "產生下載連結</button>")
    .appendTo(".body_top .left").click(genDownloadLinks);

    程式已寫成Greasemonkey外掛已放上GreasyFork,有需要的朋友可以參考。安裝說明

    在Windows驗證網站設定部分匿名存取

    $
    0
    0

    最近接連遇到幾次的需求:供內部使用的ASP.NET網站,全站使用Windows驗證,使用者以網域AD帳號登入,但網站包含少數API性質的ASHX、ASPX或MVC Action,提供其他系統呼叫整合。

    此時問題來了,既為API性質,要求呼叫端程式用網域帳號登入徒增部署與管理的不便(需申請程式專用AD帳號、程式要綁特定式執行身分、每次改密碼時要記得修改)。對API而言,較理想的做法是開放Web API網頁匿名存取,改限制呼叫來源IP或採API Key等驗證機制進行安全管控。在不拆分Web Application前題下要達成這個目標有個基本要求:必須在啟用Windows驗證的IIS網站開放部分資料夾或路徑接受匿名存取。

    方法不難,用下面的專案示範:(文章都寫完了,才發現MixAuth被我寫成FixAuth,XD)

    有某個MVC專案,網站採Windows驗證,圖中黃色底線項目需開放匿名存取。我設計了幾個典型,開放匿名存取對象包含:MyApiController所有Action,HomeController的AllowAnonymous() Action,ForEveryone目錄下的Test.aspx(傳統WebForm)及Test.html。為驗證結果,可使用以下程式偵測是否為匿名,匿名時為False,Windows驗證時為True:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
     
    namespace FixAuth.Controllers
    {
    publicclass HomeController : Controller
        {
    // GET: Home
    public ActionResult Index()
            {
    return Content("IsAuthenticated=" + Request.IsAuthenticated);
            }
     
    public ActionResult AllowAnonymous()
            {
    return Content("IsAuthenticated=" + Request.IsAuthenticated);
            }
        }
    }

     

    <%@ Page Language="C#" AutoEventWireup="true" %>
    <scriptrunat="server">
    void Page_Load(object sender, EventArgs e)
        {
            Response.Write("IsAuthenticated=" + Request.IsAuthenticated);
            Response.End();
        }
    </script>

    首先,網站必須「同時」開放匿名驗證及Windows驗證,


    IIS設定方式

     


    IISExpress設定方式

    接下來在web.config動點手腳,由於網站接受匿名存取,故先在<system.web>中加入<authorization><deny users="?" /></authorization>,預設整個網站拒絕匿名存取。針對要開放匿名存取的部分,則使用<location path="URL路徑">包住<authorization><allow users="?" /></authorization>,針對特定URL正向表列宣告接受匿名存取。

    基本上,這樣就已滿足「部分網頁採Windows驗證,部分網頁開放匿名存取」的要求。但有一點需要注意,當網站同時啟用匿名及Windows兩種驗證,靜態檔案(如:html、js、css、jpg、png、zip、pdf…)將套用匿名存取規則,不需要登入就能下載。若此一行為不符合專案的資安要求,則要再動點手腳。設定<system.webServer><module runAllManagedModulesForRequests="true">是最簡單的解法,但所有靜態檔案都透過ASP.NET管線處理效能稍差,在效能要求較高的情境,可參照以下方法針對特定路徑加入Handler設定,可對該路徑的靜態檔案啟用Windows驗證。

    以上就是在Windows驗證網站部分開放匿名存取的做法,提供大家參考。


    RadAsyncUpload有時無法上傳

    $
    0
    0

    接獲報案,使用Telerik RadAsyncUpload上傳元件的WebForm網頁有時成功有時失敗,上傳失敗的檔案多試幾次有時會成功,用多台機器測試失敗率有高有低,難以捉摸。使用F12開發者工具檢視,錯誤發生在瀏覽器以POST傳送包含檔案內容的multipart/form-data資料到/MyWeb/Telerik.Web.UI.WebResource.axd?type=rau,但伺服器端傳回{ "message" : "RadAsyncUpload handler is registered succesfully, however, it may not be accessed directly." }。

    由於檔案上傳時好時壞,初步排除RadAsyncUpload Handler安裝或註冊問題。反覆實驗多次,由蒐集到的成功及失敗案例,歸納出一個關鍵現象:當Request Header出現Authorization: Negotiate TlRMTVNTUAADAAAAGAAYAH…就會失敗;Request Header未包含Authorization則會成功。

    依經驗,瀏覽器之所以回傳Authorization Header,在於回應伺服器端的驗證要求,由於F12開發者工具的網路擷取功能不包含供認證細節。到了HTTP茶包重兵器表現的時刻-Fiddler登場!

    用Fiddler側錄上傳成功與失敗的網路傳輸,馬上看出關鍵差異。

    上圖的編號46跟47是一次失敗上傳,第一次伺服器HTTP 401要求瀏覽器提供登入認證,第二次的Request增加Authorization Header,伺服器回傳HTTP 200,但在這個情況下,RadAsyncUpload Handler未正確接收上傳檔案,吐回錯誤。(透過反組譯追進RadAsyncUpload Handler,當 Request.Files.Length == 0 就會傳回 …it may not be accessed directly 訊息)

    編號50則是一次成功上傳,Request不包含Authorization Header,伺服器爽快傳回HTTP 200,完成上傳動作。

    有了這個線索,鎖定IIS認證方向偵辦:網站採用Windows驗證,依觀察結果研判Telerik.Web.UI.WebResource.axd在Windows驗證模式運作不正常。(但我無法解釋同樣是不含Authorization Header的Request,為何有時收到401,有時會收到200?)

    在網路上查到一些開放WebResource.axd匿名存取的做法(參考),情境不完全相同但原理類似,官方建議在web.config加入以下設定:

    <locationpath="Telerik.Web.UI.WebResource.axd">
    <system.web>
    <authorization>
    <allowusers="*"/>
    </authorization>
    </system.web>
    </location>

    設定匿名存取後問題排除。留下一個謎團:同樣未傳Authorization Header,為何有時WebResource.axd要求登入認證,有時又能直接傳回結果?先歸入X檔案,日後再破解。

    NuGet Package簽入TFS時勿忘非程式庫Package

    $
    0
    0

    公司因為TFS Build Service無法存取網際網路還原NuGet Package,故我們採取將NuGet Packages一律簽入TFS的策略。近日再發現TFS Build Service行為特性一則,筆記之。

    某專案使用Build Service編譯時出現NuGet無法還原錯誤,想起專案本次改版時更新NLog版本到4.2.3,趕緊手動補簽入NLog,但編譯持續出錯,疑惑NLog.dll已補為何還是編譯失敗,追了一陣找出答案,專案除了更新NLog,還增加了兩個Package,在專案加入NLog.config以及NLog.xsd:
    <package id="NLog.Config" version="4.2.3" targetFramework="net45" />
    <package id="NLog.Schema" version="4.0.0" targetFramework="net45" />

    當我們講「Build Sevice」、「編譯」,腦中只想到該有的DLL要有,參照不到會編譯失敗。實際上,還原NuGet Packages是建置.NET專案的必要程序,即使cs編譯成DLL的過程用不到NLog.config跟NLog.xsd,但因為它是建置的先期步驟,遇上缺少Package又無法網頁下載補足,就會被判定錯誤導致建置程序中止。

    從不覺得NLog.Schema會與編譯有關,遺漏沒簽入導致本次的狀況,問題則在補上NLog.Schema後消失。

    補充:由Build Log可以判斷缺少NuGet Package,但不會指出缺少哪一個Package,感覺該寫個小工具協助檢查,嗯,排入ToDo Job Queue,排隊序號57… XD

    12>RestorePackages:
         All packages listed in packages.config are already installed.
    13>D:\Works\1\XAPP\XAPPWeb-Test\src\.nuget\NuGet.targets(89,9): error : Package restore is disabled by default. To give consent, open the Visual Studio Options dialog, click on Package Manager node and check 'Allow NuGet to download missing packages during build.' You can also give consent by setting the environment variable 'EnableNuGetPackageRestore' to 'true'. [D:\Works\1\XAPP\XAPPWeb-Test\src\Web\XAPPDbService\XAPPDbService.csproj]
    13>D:\Works\1\XAPP\XAPPWeb-Test\src\.nuget\NuGet.targets(89,9): error MSB3073: The command ""D:\Works\1\XAPP\XAPPWeb-Test\src\.nuget\NuGet.exe" install "D:\Works\1\XAPP\XAPPWeb-Test\src\Web\XAPPDbService\packages.config" -source ""  -NonInteractive -RequireConsent -solutionDir "D:\Works\1\XAPP\XAPPWeb-Test\src\ " " exited with code 1. [D:\Works\1\XAPP\XAPPWeb-Test\src\Web\XAPPDbService\XAPPDbService.csproj]
    13>Done Building Project "D:\Works\1\XAPP\XAPPWeb-Test\src\Web\XAPPDbService\XAPPDbService.csproj" (default targets) -- FAILED.

    【茶包射手日記】EDMX ADO.NET Provider錯誤疑雲

    $
    0
    0

    為調查TFS Build Service建置失敗,登入TFS Build Service主機使用Visual Studio 2013偵察(在TFS Build Service主機安裝VS2013的原委參見TFS Build Service筆記),Visual Studio回報找不到Oracle.ManagedDataAccess.Client ADO.NET Provider錯誤。

    問題根源不難理解,TFS Build Service主機沒有安裝及註冊Managed ODP.NET,只靠NuGet下載相關DLL到專案。依之前的研究

    如果要執行Entity Framework,應使用install_odpm.bat,第一個參數為安裝資料夾(將解壓縮內容複製到指定路徑再註冊),第二個參數為x86、x64、both三者擇一,第三個參數則需設為true,將元件加入GAC及在machine.config加入設定。

    沒有註冊Managed ODP.NET導致錯誤很合邏輯,但吊詭的部分來了:發現建置失敗原因是缺少NuGet Package,補上後Build Service就可編譯成功,但用VS2013編譯仍維持相同錯誤!

    這意味「即使沒有註冊Managed ODP.NET,MSBuild也能成功建置使用Oracle.ManagedDataAccess.Client的EDMX」,但Visual Studio建置則會出錯!為了佐證,在Build Service主機上開啟「VS2013開發者命令提示字元」,使用MSBuild.exe(參考)建置EDMX專案,也可成功編譯。

    由以上觀察研判-「按下建置鈕後冒出的錯誤未必是建置錯誤」。深入調查找到兩項佐證:

    1. 由輸出視窗查看建置輸出訊息,建置已順利完成(Rebuild All: 2 successed, 0 failed)
    2. 刪除專案bin目錄下的DLL,即使出現錯誤訊息,Visual Studio仍會順利產生DLL檔。

    結論:使用Visual Studio建置專案時在錯誤清單出現的錯誤不一定代表建罝失敗。除了MSBuild作業外,Visual Studio還有其他處理程序也可能產生錯誤(先前有類似案例),但要使用VS偵錯程式,就得乖乖排除。

    筆記-Evernote自動更新失敗

    $
    0
    0

    Evernote無法更新問題之前曾處理過,但一直沒徹底解決,每次動更新找不到evernote.msi的問題就要上演一回。先前直接執行%temp%下的Evernote.msi可以克服,但今天手動執行這招失效,安裝程式繼續抱怨無法移除5.8.13版。(不確定是否跟我的作業系統從Windows 8.1升級到Windows 10有關?)

    爬文想找Evernote 5.8.13版Evernote.msi,意外發現Evernote討論區(https://discussion.evernote.com/forum/224-windows-desktop-product-feedback/)保留了每一版安裝程式。找到5.8.13版程式執行,原本想看看會不會解壓出Evernote.msi,或是提供解除安裝選項,不料程式跑完Evernote就直接降級成5.8.13版。使用Evernote的自動更新功能想從5.8.13更新到5.9.6版,有趣的狀況來了-安裝程式改成抱怨找不到5.9.1 Evernote.msi。

    最後,我從討論區下載最新版5.9.6的安裝程式手動執行,終於如願更新完成。

    2015塗鴉牆回顧

    $
    0
    0

    去年年終玩過FB塗鴉牆回顧。訊息貼FB如馬桶沖水,轉眼間便不知去向,依照慣例,將一整年的碎碎念值得保存的回憶扔進部落格 醃起來 風乾,老的時候 下酒。(偽文青模式)

    【通勤奇遇】

    • 竟在一天之內在捷運陸續看到三次,潮男們穿著右上背標註「極度乾燥」中文字樣的外套,推測是為防止太潮引發濕疹的防護裝備。 (誤) 以我外行眼光,覺得它的標語式中文字體跟擺放位置特別歸特別,稱不上有美感,就算把「極度乾燥」改成「有夠潮濕」應該也不會差太多 XD (顯示為外行中的外行)

    • 公車上遇到一對母子,媽媽催促著小學生模樣的小孩吃蛋餅,小子嘟嚷了兩句,媽媽開口:「快點吃,不要嫌東嫌西」「還有,以後不要再跟阿媽說,你考試考不好都是因為我沒讓你吃早餐」 這對話讓人五味雜陳…

    【跑步人生】

    • 到運動場才發現GPS充電沒充好,電力耗竭,只好靠體育館前的大鐘計時,回到古老年代的手指計圈法,8跑道十公里要跑22圈,微微握拳,跑完第一圈姆指按在食指位置,第二圈姆指改按中指,第三圈按無名指,五圈時重置,六的按法與一相同。
      這可是改良過的版本呢! 一開始試過比一般的數字手勢,搞得像邊跑邊划酒拳,七巧呀七巧,八仙呀八仙,有點蠢又不符合人體工學 XD 新指法接近跑步應有的微握拳手掌姿勢,我該去申請專利的 (誤)
      精彩回文節錄: 有人建議我改用二進位,一隻手就可以記到31,但也有人提醒,用二進位跑到第四圈就要被揍了 XD

    • 六點多,12度,無風,跑道中央的足球場出現異象。草皮上方覆著一層薄薄的「雲」,厚度大概五十公分,密度不高,但隱約看得出水氣凝結的形狀停滯在空中。奇景讓場邊拉筋的阿姨們議論紛紛,卻定不是我眼花。

    • 雨勢不斷,只能去政大環山道繞兩圈暖腳充數。第二圈上坡途中,望著雲霧中忽隱忽現的樟山寺出了神... 腦波一弱,20分鐘後,人就在貓空產業道路上吞雲吐霧淋著大雨,充滿詩(濕)意。只有兩種解釋:1) 這人對跑步有愛 2) 這人根本有病。

    • 清晨六點的運動場,一大群成年男子集結,穿著印有公司字樣的蘋果綠活動排汗衫,短褲,運動鞋,人數估計近百,在司令台前成連集合隊形精神講話,做暖身操,呼口號,帶隊繞了兩圈跑道後往環山道跑去。退伍後,已好久沒見過此等軍容,這般陣仗。
      部隊做操時,趁著慢跑經過,我偷偷打量隊伍成員,看到不少或茫然或惺忪的臉孔。呼口號時我已跑在跑道另一端,聽不清口號內容,但我相信是「五斗米!五斗米!」

    • 這是一個小發財停在路肩看著藍寶堅尼車隊呼嘯而過的概念…


    【夢醒時分】

    身為清醒夢特異功能人士,2015仍與周公交手多次,但次數變少,莫非特異功能漸失?(憂心中)

    • 旅遊時脫隊自己去探險,跑進沒關門的鄉間民宅亂逛,被發現了還厚臉皮跟屋主老阿媽哈啦。晚上回到旅行團住宿飯店,才發現今天排的旅遊行程是考期末考,我全部錯過了,正想找考卷補考,小木頭說不用了,監考老師說沒到的直接零分還遞上寫有我姓名及大大紅字「0」的空白考卷。當下一整個驚慌,後來一想,都畢業多久了,還考什麼試怕什麼零分,大笑了起來,被女王白眼說教壞小孩大小... 這麼無厘頭的夢不是我的菜,頗不習慣,該寫意見表向周公客訴

    • 在公車站等車,一名年約30多歲的男子騎著速克達,在離車站約十來公尺停車,對著路旁咆哮接著掏出一把槍胡亂射擊,候車乘客頓時四散逃亡,我也機緊地衝進旁邊的小巷卧倒,匍伏前進找尋更安全的隱匿處所,一邊爬一邊碎唸,地上黑黑的襯衫都弄髒了…(命都快沒還在意這個?)
      爬進一處室內以為安全了,才發現剛才的乘客全都擠在這裡,而且槍手就站在前方,沒人敢動。槍手發現才爬進來的我,說道:那個穿襯衫的,給我過來!
      我走上前,但不甘束手就擒,趁槍手邊說話邊揮槍之際,雙手握住手槍扭向他處,呼喊「大家快制服他」,現場十餘人一臉驚恐,全像木頭人般站著不動,讓我感嘆社會的冷漠… 然後就醒了

    • 在公寓樓梯間遇到樓上的H太太,跟我抱怨她們家的大門對講機又壞了,得叫人來修很麻煩… 我心想,不就五六條線而已?(電源、電鈴、門鎖、對講)
      一進門馬上拆了家裡的對講機研究。媽啊,怎麼會有十幾條線?一時腦袋抽風,我竟把線全剪了,打算用電錶一條一條測試用途。線多又沒電路圖,有一半電線的顏色都是藍色,就算剪線前有拍照存證也沒用,搞到一塌糊塗。 後悔沒事亂剪線,不知為什麼,腦中想到的竟是「Ctrl-Z」
      在心中「Ctrl-Z」了好幾次也回不到之前的狀況,覺得這個夢境系統超爛,拎杯不想玩,接著我就醒了…

    • 電腦用起來有點卡卡頓頓,趁著購物網站有結帳84折優惠,看了幾款就挑好一台下單。下完單才覺得怪怪的,買3C不做功課就出手不是我的風格,再者家裡電腦一堆我幹嘛又買新的?而且還忘了確認送貨時間有人收貨… 先取消好了。
      擔心貨已經在路上,卻找不到地方取消訂單,焦急萬分,但愈想愈不對勁,終於意識到又是場夢,決定不再浪費時間跟精神,隨它去吧!Let it be, let it be, let it be, let it be...

    • 買早餐遇到同事,又遇到同事的朋友,一併受邀到他的豪宅玩。現場聚集一堆國內外明星藝人(其中有一位名字三個字的台灣歌手,去大陸發展以一首三個字的歌爆紅,也演過武俠戲),大家語言不通竟排練起話劇。
      玩了一陣子才覺不妙,看了錶已十點二十八,還要不要上班了?跟同事說該走了,那知所有人聞言紛紛起身,頓時鳥獸散,我在門口還沒穿好鞋,全部的人早不見人影(還包含同事,暗!沒義氣),只好一個人在眷村似的巷弄找路,記得來時搭了一段公車,要回去有點挑戰,手機留在公司沒Google Map可查,幸好有帶皮夾,但要搭小黃也得回到大馬路上,繞了許久還繞不出去,開始覺得焦躁,但也逼迫我認真思考要怎麼排除困境。
      射手魂一發作就立刻發現不對勁,果斷張開眼睛,結束這回合。

    【人到中年】

    • 繼想不起天天在用的專案訊息軟體(HipChat)的名稱後,今天又發生無法憶起某位臨近同事(雖然平時很少交流)名字的事件,好可怕... (抖)
      PS: 寫這則訊息時,終於想起來了,耗時15分鐘,鳴。但依昨天看到的商週文章,最後終究能想起是健忘,再也想不起是失智,好險好險 orz

    • 在FB看到關於電阻色碼的討論,我腦中立刻浮現「白紅黑黃紫,藍橙綠棕灰」,不假思索就能說出口訣,記憶牢不可破,卻完全忘記是什麼鬼?Google後才喚回記憶。懷念起那二年「唸化工卻跑去修電信交換機」的軍旅歲月,人生境遇之奇妙,總能超越你的想像~

    • 整理舊單據,挖出時代的眼淚,想起那個一條1G DDR RAM八千五,插滿記憶體就噴掉一萬七的年代... (用重金換取在自己的PC跑VM做Lab,無價!)

    • 在九歲高齡的T43上裝Windows 10 Preview,但見硬碟燈狂閃,安裝節奏卻是「下...一...步......下...一...步......」,慢歸慢,終究還是裝好能跑,Win10的硬體門檻沒想像中高。心中浮現一幕:解甲歸田的老將軍被徵召再赴沙場,動作遲緩,步伐蹣跚,遇摔了兩次才爬上馬鞍,直叫人鼻酸... 沒想到隔了個把時辰,還真看他拎了顆敵人首級晃回來 XD

    • 過了中秋,天氣明顯轉涼,我的黃金傳說「一整個夏天睡覺不開冷氣」也宣告挑戰成功,本次能達成目標,麻將涼蓆居功甚偉,不愧為節能環保寢具之首
      友人回應:古有「臥薪嚐膽」,今有「臥蓆度夏」…

    • 每天一餐大燕麥政策邁入第七年,趁特價進貨飼料一批(總重6.6公斤),再繼續吃下去會變成牛嗎?

    【水電工日記】

    • 三塊玩具太陽能光板,蒐集客廳省電燈泡吊燈的光線點亮一顆LED,完全不符經濟效益,但看在此舉純為科學夏令營器材廢物利用,以及小木頭量電壓量得很開心的份上,還是可以宣告實驗成功 (傳說中的非蓄電型太陽能手電筒雛型來著 XD)

    • 公寓鐵門的鑰匙孔怪怪的,前陣子有兩三次轉好幾次才成功的經驗,今天更慘,鑰匙無法插到底,完全轉不動,幸好家裡有人在才沒被鎖在外面。以為又要上演大門換鎖整棟公寓重打鑰匙的戲碼。某工程師抱著死馬當活馬醫的心情帶著WD40下樓,只噴了兩下,意外就讓鑰匙孔如新品般滑順好轉!哦,WD40,我要為你輕輕唱首歌~

    • 下班前往岳父母家游修,日光燈閃爍,更換起動器無效,判斷為安定器故障,正準備以缺料為由撤退之際,冷不防岳母大人拿出一套閒置的新燈具(登楞...) XD
      折開研究接線,發現沒看過的兩款新式接線頭(我的知識還停留在剝皮絞線纏電氣膠帶的時代),安定器上的接線頭螺絲看來需要特殊起子(還沒查到),我需要補充知識與工具,擇日再戰。水電工,也需要與時俱進!

    • 解除「更換車內保險絲」的成就!(雖然只有一塊蛋糕等級)
      跑腿油錢 3元
      15安培保險絲 5元
      DIY的成就感 無價

    【病得不輕】

    • 聽到神人開示:
      Q:如何檢測自己對某個信仰有足夠熱情,有資格成為Evangelist ?
      A:當你到世界的盡頭時, 會想拿出相關物品拍個紀念照就及格了。
      (默默翻出在日本高山雪地的TechEd背包及MVP帽照片…)

    • 刷悠遊卡看到餘額數字512,心頭出現莫名的驚喜感,連嘴角都微微上揚... 這八成是病入膏肓的前兆,再來就是四處景物變成綠色的0跟1 orz

    • 終極密碼驚喜續集:下車刷悠遊卡,第一次出現無法感應,刷第二次顯示404,忍不住會心一笑 (顯示為毒性已入任督...)

    • 木瓜版Github,這是一個Fork出Branch,各自發展到開花結果的典範

    【時事觀點】

    • 奇文共欣賞  http://news.ltn.com.tw/news/opinion/paper/854500
      造句練習:所以呢?讓開發者不需要吃飯睡覺不會抱怨,就不需要增加人力也能應付暴增十倍的專案了。想到這裡,往往覺得責任已了,剩下的就是專業人士的功課了,他們得去思考用什麼血清、要怎麼讓開發人員變成僵屍。

    • 阿帕契案藏了困惑我一輩子的深奧問題:
      如果你是勞乃成的長官,該怎麼告誡誘導他別惹麻煩又不傷和氣,省得他惱羞成怒找機會捅你?如果你是601的阿兵哥,長官叫你搬梯子舖紅毯違紀招待來賓,你該抗命、事後當抓耙子還是裝做不知?

      1) 這是什麼白痴問題?這種敗類當然給他死
      2) 這... (陷入沈思,額頭滲出兩滴冷汗)
      3) 這樣啊?呵呵

      人生處於不同境界的不同反應...

    • 沿續前天談的阿帕契案隱藏人生抉擇,今天就有悲慘個案,門口衛哨要被懲處了。看吧,世間難解的問題真的很多。
      不照規定來(照潛規則來),運氣不好才會倒大楣;照規定來,馬上倒大楣。你說大家會怎麼選?
      結論:平安退伍,在職場打滾,很多事只能看老天爺如何發落,也就是所謂的看人品,靠福報。不多說了,先下線來去扶老太太過街要緊。

    • 覺得合約被事後插入一頁很扯,合約要有騎縫章不是常識嗎?怎麼感覺還有很多人不知道。
      等等,這個生活知識好像不在學校通識教育範圍,得靠長輩傳承、工作接觸、跟有經驗的交易對象往來才會學到,若第一次簽約就遇到壞人就要吃大虧。說來,知不知道要看個人造化,國民教育也該涵蓋這類法律知識跟實戰技巧才對。
      PS:今天回去跟小木頭小閃光機會教育一下

    • 頂新案讓大家好激動,果不其然,有人開始把帳算到法官身上了,開始PO法官照片,不意外地,「嘴臉」一詞也出籠了…
      我也好激動,但激動的點不太一樣,這邏輯分明不對呀?
      如果「現行法律容許違背良心但不違背食品科學或罪證舉證不足的廠商」不符合所有人民的期待,就要加緊催促立法讓法官有辦法懲治遊走法律邊緣的黑心廠商,而不是期待法官當包青天,只要能大快人心,管他有沒有證據法條怎麼寫,把狗頭鍘當理髮剪刀耍就對了。
      想想覺得好笑,在這類事件裡還在意邏輯,我可能是瓦肯星人。

    • 推廣福利聯盟這種名稱,懷舊古樸風格DM,標榜網路服務卻沒附上公司網址,怎麼看都覺得怪怪的,不是詐騙就是做黑的

    • 在某PO文看到「放水流管理」有感… 有些現存事物你明知是錯的,違背自己的信念,甚至潛藏風險,但評估全面鏟除問題需投入的成本與風險,睜隻眼閉隻眼或許才是上策。「放水流」暗黑技可避免自己陷入「被逼瘋vs逼瘋別人」死亡對決,為職場必備求生心法之一,並有助於每個人脫離「憤青」,邁向下一人生階段。
      但革命有風險,放水流也有風險,何時該堅持?何時該鄉愿(XD)?分寸拿捏全靠智慧(其實也看造化,哈)

    • 有時會覺得困惑,像這樣無時無刻提醒自己活在毒窟,惶惶不可終日,到底最後會換到什麼?(而且倡議者還常意無意抹黑別人一把,暗捅他人一刀)
      尤其,許多論點並沒有紮實的科學證據(無法證實,但也不容易被推翻,所以才有存在空間),搞不好依老天爺的BigData分析,這麼搞剛恐懼謹慎過一輩子,確實有利增加壽命,平均可增加... 2分48.5秒。哈!

    【兩代之間】

    • 沒事翻看小閃光的生物作業,意外引發「基因突變是否為物競天擇關鍵?」論證。攻防摘要如下:

      甲方:基因突變不會遺傳(舉輻射造成突變不會遺傳為例)*論點A
      乙方:發生在生殖細胞突變就會遺傳,其亦屬基因突變範圍 *論點B
      甲方:物競天擇源於長頸長頸鹿與長頸交配形成脖子更長的長頸鹿 *論點C
      乙方:基因突變為長頸鹿物種出現脖子長短有別的根源,配種是讓差異益加明顯 *論點D

      最後乙方的「基因突變可促進物種多樣式,有利物競天擇」論述,以邏輯性取勝。事後查資料補強先前的不專業論點,筆記如下:
      演化發生必然具備兩項條件:
      1.在同一種個體之間,必須先發生可遺傳變異 
      2.必須發生自然淘汰
      遺傳變異有兩個來源:
      1.基因發生突變 (*論點D)
      2.對偶基因減數分裂後重新組合,產生新的遺傳性狀 (*論點C、D)
      而基因突變又可分為:
      1.自然突變:機率約十萬分之一 (*論點B、D)
      2.誘發性突變:以人為方式導致突變,如X─RAY,紫外光,亞硝酸鹽等 (*論點A)

    • 小閃光洗好澡,見我還在打電腦,擺出流氓樣,粗聲吼道:「快點去洗澡啦!」
      楞了一秒,我不甘示弱回嗆「你在大聲什麼啦?」
      一秒後,出現軟弱回應「啊啊啊… 不要起爭議啦」
      一搭一唱,合作無間,父女都是鄉民無誤~

    • 小閃光獲得人生的第一支智慧型手機!(註:需依「中學生消費性電子產品管制條例」於指定時段及處所使用,違者主管當局得視情節暫時或永久沒收設備,或處以拘役,併科或易科罰金)

    • 暑假每天跳繩1000下,小竹筍小木頭一個月就長高2.3公分... (我也好想再長高2公分)

    • 等了14年,終於解除「女兒請吃父親節大餐」的成就,用的還是自己努力得到的獎學金,格外讓人開心。

    • 角色介紹:「鋼普拉零件助手」-一種協助他人組裝鋼彈模型,只負責剪修零件,不動手組裝的角色。即使組裝者技術不佳組得歪七扭八,助手也必須按捺不出手,只從旁指導與建議,以免傷害當事人幼小心靈,故一般只有心理素質過人或為人父者得以勝任。


    • 目擊蘇利文(怪獸電力公司)、長頸鹿、斑馬、恐龍跟皮卡丘聚餐。吃飽喝足,驚見皮卡丘點起一根煙~ (用打火機點的,沒用十萬伏特)

    【專頁貼文整理】

    【生涯里程碑】

    某人曾經說過:身為程式魔人居然不會在自己手機上寫Code將是一生的污點…
    2015年,我又意外解除另一項成就:在自己的 GPS 錶上寫程式!
    接著迎接一段奇妙經歷:
    *第一次收到德文、西班牙文、俄文、法文寫的使用者回饋
    *跟來自德國、西班牙、鳥克蘭、克羅埃西亞、北京、美國、泰國、印度網友在Facebook用英文溝通
    *發現我的英文並不比非英語系的歐洲朋友差,哈
    *首次體驗在16KB記憶體限制下寫程式,SDK更新後還變胖吃掉原本就少得可憐的記憶體空間,原本跑得動的程式出現記憶體不足。我也第一次知道「將模組化變數改為寫死數字」竟然也是一種重構方法,就像在奈米世界物理特性會劇變,寫微型程式時軟體原則也會被扭曲
    *App作者應該很難忍受在Store上收到負評吧?歷經鍥而不捨的查問題調程式,從三顆星給到一顆星的網友,終於給了五顆星。咦,這程式不是要寫給自己用的,怎麼會變成這樣?只能說,捍衛自己作品的評價,無法忍受程式有Bug,應該是每個程式魔人的天性。 但後來我也看開了,有人漏看說明書不會安裝或遇到狀況,並不會試著連絡作者,給完負評後就跟你分道揚鑣,人生不再有交集,Let It Be吧!
    *在Mobile01的發文被置頂一個月
    *程式一度躍上App商店的分類榜首

     

    祝大家2016新年快樂,事事順心~

    Viewing all 2337 articles
    Browse latest View live