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

筆記:使用Google帳號登入ASP.NET MVC網站

$
0
0

新版ASP.NET改採OWIN架構,Middleware概念讓Request/Response處理流程變得模組化,允許抽換自由組裝,身分驗證也變得極富彈性,IIS時代ASP.NET只有匿名、Basic、Windows幾種選擇,改用OWIN後,整合Google、Facebook、Twitter、Microsoft Account… 等OAuth登入服務不再是難事,還有現成元件可用,輕輕鬆鬆實現Google、FB登入。(關於OAuth,小朱有系列文章介紹,包含原理與OAuth Client實作細節)

要做到Google、FB登入,使用ASP.NET 4.5.2的MVC專案樣版是捷徑,專案本身即內建Google、Facebook、Twitter、Microsoft登入整合功能。

新増專案時,Authentication選項預設為Individual User Accounts,採用ASP.NET自家的Membership機制,且支援使用Facebook、Twitter、Google、Microsoft帳號登入。

啟用外部帳號登入需修改程式碼,開關藏在App_Start/Startup.Auth.cs中:

有四段被註解掉的程式碼,分別是MicrosoftAccount、Twitter、Facebook跟Google的身分驗證設定,移除註解填入各家要求的API Key及Secret即可啟動,之後登入畫面會多出已啟用帳號服務的登入鈕,只要API相關設定妥當,按鈕後網頁會導向Google、FB網站,待使用者完成登入並授權後再回到MVC網站,ASP.NET端取得使用者的Google、FB帳號資料,再與系統自己的使用者資料結合完成身份識別,免除管理帳號密碼的負擔,非常方便。

感覺是小菜一碟,ASP.NET端只要填兩個值就搞定,但上面提到「只要API相關設定妥當」這句話暗藏玄機。因為缺乏經驗,加上網路服務改版迅速,現況與爬文所得常有出入,摸索好一陣子我才搞定Google API,而在整合FB花的時間更是可觀… 整理攻略如下,希望能幫大家節省一點時間。(ASP.NET官網上有篇文章介紹得挺詳細,但文章寫於2015年4月,有些地方已有變化)

這篇先介紹登入Google登入。

Google API Key要到Google API Console申請,第一件事請確定資訊主頁的API清單中有「Google+ API」,若沒有請使用上方的「啟用API」鈕新増。(一時糊塗,我在此挨了悶棍)

接著到「憑證」區,建議先設定「OAuth同意畫面」,填寫要顯示在授權網頁的資訊:

下一步,「建立憑證」,類別選「OAuth用戶端ID」:

在憑證設定畫面,應用程式類型請選「網路應用程式」,名稱無關緊要隨便敲,至於「已授權的JavaScript來源」要填入網站URL(不含路徑,localhost也接受),「已授權的重新導向URL」則填網站URL加上"/signin-google":

憑證建立後,Google很貼心地顯示用戶端ID(ClientId)、用戶端密碼(ClientSecret)並有按鈕直接複製字串到剪貼簿:

將ClientId及ClientSecret複製到Startup.Auth.cs

            app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
            {
                ClientId = "6847…7b6d.apps.googleusercontent.com",
                ClientSecret = "Ad1…pfZ"
            });

回到MVC網站的登入頁面,按下登入頁面的「Google」按鈕,網頁會切到Google登入及授權畫面:

按下「允許」回到MVC網站,系統已從Google取得Email資料,可直接註冊成會員。

註:測試過程學到一則經驗,「已授權的重新導向URL」在修改後似乎不會立刻生效,如在測試開發階段,重建憑證比較快。

下一集來談FB帳號整合,過程曲折許多,為了取得Email,我不但鑽了原始碼,還修改元件才過關,很硬斗!請待下回分解。


筆記:使用Facebook帳號登入ASP.NET MVC網站

$
0
0

前篇文章介紹過ASP.NET MVC 5內建Google、FB、Twitter、Microsoft Account外部帳號登入支援,只需設定API Key及API Secret即可啟用,十分方便。上回整完Google帳號,繼續處理Facebook登入。

一回生二回熟,第一步當然是到Facebook開發者網站申請API Key。在網頁按下「新増應用程式」:

類型選「網站」:

為應用程式取個名字:

填寫「顯示名稱」、「聯絡電子郵件」及「類別」:

下一步要填寫網站URL:

基本上這樣就算設定好了,「應用程式編號」跟「應用程式密碼」即程式所需的AppId及AppSecret。補充一點,Facebook API不像Google可以設多組URL,開發機、測試台、正式機URL不同需要多組AppId,可善用左側選單「建立測試應用程式」快速產生另一組AppId及AppSecret方便測試。

取得AppId及AppSecret後,在Startup.Auth.cs加入設定:(提醒:實務上appId及appSecret應由appSettings讀取,不宜寫死。)

            app.UseFacebookAuthentication(
               appId: "7360…1957",
               appSecret: "258d…8509");

實際測試,在MVC登入網頁按下Facebook按鈕,瀏覽器會導向Facebook網站,進行登入及要求授權:

待使用者同意授權後,會再導向MVC網站,此時ASP.NET已取得使用者的Facebook身分,拿到使用者的Email… 咦?並沒有!Email欄位是空的,跟Google帳號登入的情況不同。

依照ASP.NET會員管理需求,只要Facebook針對特定使用者提供唯一且固定的識別碼與會員資料連結就已夠用,Email交由使用者自由填寫不一定要與FB相同。但我有不同計劃,打算以Google、Facebook傳回Email作為權限管控依據,故還是得設法拿到Email。

只因為比一般應用情境多了一點要求,我離開寫兩行程式就搞定的高速公路,再次走上「抓Code看Code改Code」這條崎嶇蜿蜒的產業道路…(好討厭的感覺~)

使用Visual Studio偵錯發現,外部帳號登入成功後會使用AuthenticationManager.GetExternalLoginInfoAsync()方法取得使用者基本資料,DefaultUserName為使用者姓名、Email為電子郵件,另外有個Login屬性為UserLoginInfo型別,其中LoginProvider為"Facebook",另外有個ProviderKey,使用Facebook登入時,Email是空的。

而Google登入則可以抓到Email:

深入了解,得知Facebook從Graph API 2.0起改採App-Scoped User Id(中文翻成應用程式範圍編號)以強化個資保護,應用程式無法取得使用者真實User Id,改為首次登入應用程式時動態產生識別序號,該序號只對該應用程式有效,而應用程式只能取得有限的個人資訊,想取得更多資訊需經過審查。ProviderKey即是所謂的App-Scoped User Id。

依據官方文件,不需要審查可取得的資料範圍包含public_profile(姓名、連結、照片等)、email、user_friends。從CodePlex Katana專案拿到Microsoft.Owin.Security.Facebook的原始碼,發現Startup.Auth.cs UseFacebookAuthentication可以額外指定抓取email Scope:

            var opt = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
            {
                AppId = System.Configuration.ConfigurationManager.AppSettings["FBApiKey"],
                AppSecret = System.Configuration.ConfigurationManager.AppSettings["FBApiSecret"]
            };
            opt.Scope.Add("email");
 
            app.UseFacebookAuthentication(opt);

很不幸,加入email Scope後還是抓不到Email資料。逼得我得架設深入Microsoft.Owin.Security.Facebook內部偵錯並重現問題的測試環境(過程很繁瑣磨人,細節就不說了),費了一番手腳找出問題點。在Microsoft.Owin.Security.Facebook.FacebookAuthenticationHandler.AuthenticationCoreAsync()有一段程式呼叫 httqs://graph.facebook.com/me?access_token=… 取回App-Scoped User Id、姓名,舊版Graph API傳回結果預設即包含Email資料,新版API則需加上fields參數才會回傳Email,故程式需修改如下:(感謝小朱補充:fields參數需求是2.4版加入的規格)

歷經N個小時努力,Facebook登入終於也能抓到Email了,萬歲~

你的密碼被偷了嗎?

$
0
0

兩天前收到Dropbox的通知信,說我從2012年起就沒有變更過密碼,為了安全起見,下次登入時系統會提示進行更新。

信件與網頁強調這單純是預防性措施,帳戶並沒有被不當存取的跡象(實際登入Dropbox網站檢視存取記錄,的確也都正常),原因是Dropbox發現有一組舊的使用者登入資料 (電子郵件地址加上加密及雜湊的密碼)在2012年被偷走,Dropbox經由監測與分析相信未有帳戶遭到不當存取。不過為了安全起見還是要求所有 2012 年中之後就未曾更新的使用者,在下次登入時重設密碼。

今天看到Troy Hunt(有名的資安領域MVP)的文章,他拿到Dropbox流出的6800萬筆帳號、密碼雜湊檔,正巧包含他太太的Email帳號與密碼雜湊值。密碼由密碼管理工具(1Password)產生,是一組超過20字元的隨機字串,於是Troy做了簡單實驗,用密碼破解工具(hashcat)驗證Dropbox流出資料的雜湊碼的確是用該密碼計算出來的,換言之,檔案是真實資料無誤。

文章有提到Troy Hunt建立的資安服務網站「Have I been pawned?」,網站蒐集重大帳號密碼外流事件的資料整理成資料庫,不需註冊或登入,在網頁輸入Email或帳號名稱就可以查查自己的帳號密碼個資甚至信用卡資料是否曾被竊取或公開。

拿我的Email查了一下:

靠北,我中了!

我的Email名列本次Dropbox事件的高危險群(難怪會收到Dropbox的通知信),查詢結果顯示「Pwned on 1 breached site found no pastes.」代表帳號或Email出現在一個網站的外洩資料中,而Paste是指資料被公開散佈(例如:被貼到匿名公佈欄),而本次Dropbox的資料尚未公開流傳,但很可能已在駭客圈私下交流多年。我的因應之道是改用KeePass產生一組32字元的新密碼,並啟用兩階段驗證(加上手機簡訊驗證)。

未來再有帳號密碼外洩事件,未必每個廠商都像Dropbox一樣願意主動告知,Have I been pawned是可以馬上查證自己是否身陷險境的好地方~

【茶包射手日記】網站在IIS下無法讀取LocalDB

$
0
0

小問題一則。

為了測試Google登入整合,我將ASP.NET網站上傳Azure。經本機IIS Express測試無誤的網站,一掛到IIS下執行卻出現錯誤,在事件檢視器有以下訊息:

    • Unexpected error occurred while trying to access the LocalDB instance registry configuration. See the Windows Application event log for error details.
    • Windows API call SHGetKnownFolderPath returned error code: 5. Windows system error message is: Access is denied.
    • Cannot get a local application data path. Most probably a user profile is not loaded. If LocalDB is executed under IIS, make sure that profile loading is enabled for the current user.

ASP.NET網站使用LocalDB存放使用者註冊、角色等資料,推測是無法正確載入資料庫出錯。訊息明確與Profile有關,IIS使用Application Pool處擬帳戶,而IIS Express使用開發者登入Windows的帳戶執行,推測為導致錯誤的關鍵。所幸訊息明確,馬上查到相關說明

LocalDB需要載入User Profile才能運作,而IIS Application Pool預設不會載入,故需手動修改C:\Windows\System32\inetsrv\config\applicationHost.config,找到<system.applicationHost><applicationPools>裡該AppPool對應設定,將loadUserProfile及setProfileEnvironment設為true:

<add name=“OAuthTest“ autoStart=“true“ managedRuntimeVersion=“v4.0“ managedPipelineMode=“Integrated“>
<processModel identityType=“ApplicationPoolIdentity“ loadUserProfile=“true“setProfileEnvironment=“true“ />
</add>

註:LocalDB主打輕巧簡便及與正式SQL行為相近,但由於運作範圍僅限單一應用程式,多用於開發測試,正式及中大型應用時還是會使用正規SQL Server。關於LocalDB,保哥有一篇詳細介紹可以參考。

再談T-SQL複合字串鍵值比對-借用PARSENAME()

$
0
0

以下是我實際遇到的情境,複合鍵在某些資料表拆成兩個或三個欄位,但在某些資料表則用"."或"-"串接存成單一欄位。(不要問我為什麼搞出這種不一致的設計,誰沒有過去?)

如以下的例子,在JStock資料表的Market及Symbol欄位,在JReport則使用一個FullSymbol欄位,存成"Symbol.Market"。

問題來了,如果這兩個Table要JOIN怎麼辦?過去用過一種鳥方法:
JReport R JOIN JStock S ON R.FullSymbol = S.Symbol + '.' + S.Market格式。

這方法挺管用,但可能會有效能問題。以上面的例子,假設JStock有兩萬筆,JReport查詢結果只有四筆,為了找出這四筆對應的資料,SQL Server得把整個JStock每一筆的Market跟Symbol拿出來先相加再比對,無法善用JStock的Index加速。

先前曾介紹過用SQLXML拆字串的技巧,但XML轉換演算法太曲折且效率不佳,前幾天無意發現一個T-SQL內建函式-PARSENAME()(SQL 2008 R2起支援),原本用來將SvrName.DbName.SchemaName.ObjectName拆解成四個部分,例如以下範例:

只要複合字串的分隔符號是"."(若不是,可用REPLACE置換),就能用PARSENAME擷取指定一段落(最多能只解析四段,應能滿足絕大部分場合),不用寫自訂函數,效能又比XML轉換好。

最後補充一點,以上做法雖善用JStock Symbol、Market Index提升效能,但使用PARSENAME解析JReport FullSymbol仍會消耗效能。如果允許變動Schema以空間換取時間,可考慮為JStock建立計算型欄位(Computed Column)-FullSymbol = Symbol + '.' + Market,設為IsPersisted還能建立Index,直接使用FullSymbol = FullSymbol比對,效能可再大幅提升。

惱人的Managed ODP.NET ConfigSection問題

$
0
0

自從學會Managed ODP.NET,它馬上成為我的奧林匹克指定資料庫元件。不用額外安裝Oracle Client,管它x86還是x64,只要在主機設好TNSNAMES.ORA(我慣用的做法是用%TNS_ADMIN%環境參數提供路徑,一台主機只要設一次,部署到不同主機時不需改config),用NuGet下載安裝好一切搞定,十分方便。比起傳統ODP.NET常常糾結於x84與x64與Oracle Client版號高低,Managed ODP.NET高雅先進,用過之後就回不去了。

不過,我常遇到一個小問題,使用NuGet安裝Managed ODP.NET會自動在web.config加以下區段宣告:

<configSections>
    <section name="oracle.manageddataaccess.client" type="OracleInternal.Common.ODPMSectionHandler, Oracle.ManagedDataAccess, Version=4.121.2.0, Culture=neutral, PublicKeyToken=89b483f429c47342" />
</configSections>

在Visual Studio裡以IIS Express執行會出現以下錯誤:

Error Code       0x800700b7
設定錯誤       定義了重複的 'oracle.manageddataaccess.client' 區段
設定檔案       \\ ? \X:\TFS\src\Forms\web.config

研判machine.config已定義過oracle.manageddataaccess.client區段 ,我加上<!--把它註解掉,IIS Express即可正常執行。為了方便測試,我還習慣將同一專案路徑也掛在IIS下,好處是不需要在Visual Studio按F5/Run/Debug就能測試,還可開放其他同事預覽(IIS Express僅限localhost存取)。有趣的事發生了,網站在IIS出現以下錯誤: 

錯誤碼       0x80070032
設定錯誤       無法讀取設定區段 'oracle.manageddataaccess.client',因為它缺少區段宣告
設定檔案       \\ ? \X:\TFS\src\Forms\web.config

在IIS Express裡加oracle.manageddataaccess.client區段會出錯,拿掉區段宣告在IIS裡會出錯,你們搞得我好亂…

不過仔細一想,啊,machine.config!32與64!

IIS Express之所以抱怨區段重複宣告是因為machine.config已經宣告過,IIS抱怨缺少區段宣告則是因為machine.config沒宣告,到底machine.config有宣告還是沒有宣告?答案是一個有宣告,一個沒宣告!machine.config有分x86與x64版本,分別位於C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config 及 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config。IIS Express一律以32位元模式執行,IIS的執行環境由AppPool設定決定,預設為64位元模式。之前為了寫文章,執行過Managed ODP.NET安裝install_odpm.bat c:\oracle x86 true,只裝了x86版,導致只有32位元版machine.config加入oracle.manageddataaccess.client區段宣告,64位元版machine.config沒有,全案宣告偵破!

找出原因一切好辦,要解決有兩個做法:

  1. 執行install_odpm.bat c:\oracle both true,讓32/64 machine.config一致。
  2. NuGet之所以修改web.config加入區段宣告,是因為它加了一段範例設定:
      <oracle.manageddataaccess.client>
        <version number="*">
          <dataSources>
            <dataSource alias="SampleDataSource" descriptor="(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL))) " />
          </dataSources>
        </version>
      </oracle.manageddataaccess.client>
    如果不需要這段設定(像我就是用環境變數解決),將它連同一開始的configSection都移除也能避開問題。

提醒,類似的情境也會發生在部署到測試機與正式機時,需依部署對象machine.config狀況(記得區分32或64)決定是否加上configSection宣告。

Windows 2012 R2執行WCF出現HTTP 404.17錯誤

$
0
0

同事報案,.NET 4 WCF在一台新裝Windows 2012 R2主機執行,嘗試連上.svc時出現404.17錯誤:

HTTP Error 404.17 - Not Found
The requested content appears to be script and will not be served by the static file handler.
要求的內容似乎是指令碼,因此靜態檔案處理常式便不會對它進行處理。

依訊息推敲,應是IIS未安裝必要模組所致。

IIS自IIS7起改為模組化安裝,安裝選項分得很細,且盡量以預設不安裝為原則,好處是管理人員可依需要只安裝最精簡組合,用多少裝多少,避免一堆無用模組拖累效能或增加被攻擊風險。但缺點則是安裝時需要較多知識,得看懂選項並找出要安裝的項目。

檢查之後,發現同事裝了.NET Framework 4.5 Features / WCF Services,但忘了勾選 HTTP Activation,即.svc出現HTTP 404.17的主因,問題也在安裝該項目後排除。

面對多如牛毛的安裝選項如果你還是不知如何下手,以下是一些參考文件:

【茶包射手日記】VS2015無法啟動IIS Express

$
0
0

Visual Studio 2015 疑難雜症一枚。

ASP.NET 專案不知何故無法在 Visual Studio 裡執行及偵錯,一直跳出以下錯誤:

Unable to launch the IIS Express Web server.

試過刪除 .sln 檔所在目錄的.vs資料夾,無效。
註:.vs 是隱藏資料夾,VS2015 儲存開發環境設定的地方,刪除後會重新產生(但會遺失檔案開啟狀態、中斷點等設定),處理 VS2015 異常挺好用

爬文找到相關討論,有人建議刪除 %userprofile%/Documents/IISExpress/config 資料夾,重啟 Visual Studio,config 會重新產生,問題排除。

補充一點,IISExpress 雖多用於開發測試,但跟 IIS 一樣會寫 Log,IIS Log 位置就在上述 IISExpress 路徑的 Logs 資料夾,另外 TraceLogFiles則用來保留 Request 錯誤細節,需要深入偵察時很管用。


【茶包射手日記】System.Data.OracleClient更新中文變問號

$
0
0

同事報案,某支在Windows 2003主機運作良好的轉檔程式,移至開發機執行更新Oracle資料庫時,中文變成問號。

比較原本執行正常的環境為Windows 2003 x86英文版 + Oracle Client 9207(真實世界永遠不乏這種與時代嚴重脫節卻維繫日常營運的中流砥柱啊),開發機則為Windows 7 x64英文版 + Oracle Client 12.1,二者存在不少差異。

寫了一段測試程式嘗試驗證問題,在我的機器(Windows 8.1 x64中文版 + Oracle Client 12.1)更新中文正常,移到同事的機器執行更新中文… 也正常。登楞!

只好回到轉檔程式用Visual Studio展開偵錯,在同事機器上確實能重現中文變問號現象。仔細比對轉檔程式與測試程式,這才發現測試程式用的是ODP.NET,而寫於民國初年的轉檔程式用的則是將被淘汰的System.Data.OracleClient。將測試程式也換成System.Data.OracleClient,有趣的事發生了,在我的電腦更新中文OK,在同事電腦則可重現中文變問號。

測試程式如下:

//using Oracle.DataAccess.Client;
using System.Data.OracleClient;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
 
namespace OracleUpdateTest
{
class Program
    {
staticvoid Main(string[] args)
        {
            TestOracle();
        }
 
conststring cnStr = "Data Source=OracleServer;User Id=user;Password=ooxx;";
 
publicstaticvoid TestOracle()
        {
            System.Environment.SetEnvironmentVariable("ORA_NCHAR_LITERAL_REPLACE", "TRUE");
using (OracleConnection cn = new OracleConnection(cnStr))
            {
                cn.Open();
                OracleCommand cmd = cn.CreateCommand();
                cmd.CommandText = "UPDATE JEFFTEST SET NAME=N'中文測試' WHERE SEQNO = 1";
int r = cmd.ExecuteNonQuery();
                cn.Close();
            }
        }
 
    }
}

爬文求得一解,在連線字串加上"unicode=true"後問題排除。

依據文件說明,設定Unicode=true時,會指定 Oracle .NET Framework 資料提供者使用 UTF16 模式的 API 呼叫。很明顯這是一個 System.Data.OracleClient 專屬且只發生在特定Windows環境(Win7英文版?),由於 System.Data.OracleClient 已來日無多,不值得繼續深究,只記結論「System.Data.OracleClient 中文變問號,Unicode=true 連線參數不可少」,Case Closed。

使用Visual Studio Code開發Angular 2專案

$
0
0

Angular 2於9/13推出RC7,許多人還在懷疑該不會一直到RC18吧?說時遲那時快,Angular 2 Team忽然在兩天後中秋節這天,閃電宣佈Angular 2.0進入正式版!

身為開發老兵,近年已鮮少加入Beta、Tech Preview版先鋒部隊拓荒,習慣等正式版再認真以待,一則避免發展早期規格一變再變白走冤枉路,二則愈晚開始,規格、工具愈成熟,參考資源也愈多。雖依黑暗技術守則第15條:「早學搶先機,晚學撿便宜」,但NG2已成為工作專案的正式選項,只能乖乖面對新挑戰,看開發教學配月餅文旦,正式步上NG2學習之路。

Angular 2官網有個Angular 2 Quick Start 教學,其中有個My First Angular 2 App範例,算是新手村第一站,決定以它為對象,練習從無到有建立一個NG2專案。

初步評估,現階段Visual Studio Code對NG2開發的支援較VS2015成熟完整,是IDE的首選。加上體驗過用VSCode 偵錯node.js,也算有一丁點基礎(其實只有0.5公分高吧 Orz),就決定是它了!

在網路上找到一段VSCode Angular2 First App Example 影片教學,一步步跟著做,踩了一些雷,總算有驚無險在VS Code完成我第一個NG2專案。影片一鏡到底,從無到有建立網站進行測試,講得很完整,但NG2及VSCode在影片錄製之後有些小改變,另外開發環境差異可能導致錯誤,故我還是做了重點整理,提供想用VSCode開發NG2的捧油參考。

  1. 開始前請先安裝node.jsVisual Studio Code
  2. 建一個FirstApp空白資料夾
  3. 使用VSCode開啟資料夾
  4. 在VSCode加入package.json等檔案

    NG2教學網站的原始碼旁邊有個Copy Code按鈕,可省下選取及按Ctrl-C的步驟,請多加利用。
  5. 依序加入package.json、tsconfig.json、typings.json、systemjs.config.js(最後一個是js不是json哦)。
  6. 開啟DOS視窗,執行npm install,npm會依package.json列舉清單,下載所需的套件:

    npm套件安裝完成後,FirstApp目錄下會多出node_modules及typings資料夾:
  7. 在FirstApp建立app資料夾,並app資料夾中新増app.module.ts、app.component.ts、main.ts並貼入內容。(app.module.ts在範例有出現兩次,第二次多加入AppComponent才是最終版本)
  8. 在FirstApp建立index.html、styles.css並貼入內容。
  9. 設定啟動作業,依下圖操作並選取Node.js:

    VSCode會建立.vs/launch.json,其中program要改成"${workspaceRoot}/node_modules/lite-server/bin/lite-server",利用內建的簡易Web Server進行測試:
  10. 接著我們要指定tsc進行編譯,開啟「檢視/命令選擇區」:

    在命令清單中找到「工作:設定工作執行器」。由於項目蠻多的,可在最上方輸入關鍵字快速過濾:

    選擇「TypeScript-tscofig.json 編譯TypeScript專案」
  11. 如果一切順利,按下Ctrl-Shift-B或用命令選擇器找到「Task: Run Build Task」,app目錄應該會出現app.module.js與app.module.js.map…等編譯結果
  12. 按下F5執行,見證奇蹟的時刻,第一支NG2程式測試成功~

重點來了,以上宛如行雲流水,背後我卻撞牆流血,花了好大功夫才搞懂並解決tsc版本問題。我遇到的狀況是ts沒被編譯成js,VSCode看不到任何訊息,後來我找到tasks.json有個"showOutput"選項,預設為"slient",導致tsc即使編譯TypeScript出錯也不會顯示錯誤。

將參數調整為"always"後,得知無法編譯TypeScript是因為 error TS5023: Unknown compiler option 'p'.  錯誤,爬文得知這是tsc版本不對造成,再費了好大力氣,搞懂幾件事:

  1. tsconfig.json是新版tsc提供的功能,如果你的tsc版本太舊,就會出現error TS5023: Unknown compiler option 'p'. 
    VSCode理應使用npm下載的tsc,會出錯多半是因為PATH殘留1.5以前版本TypeScript路徑所致,因新版已不需在PATH設定路徑,故請將其刪除(請刪除,不要改指向新版),詳情可參考保哥的文章
  2. 記用npm install -g typescript在node.js環境安裝TypeScript編譯元件
  3. tsc有兩種!上述PATH路徑放在C:\Program Files (x86)\Microsoft SDKs\TypeScript\1.x\的是tsc.exe,C:\Users\User\AppData\Roaming\npm下的則是tsc.cmd,以JavaScript寫成,依附於node.js執行,二個是不同的東西。tsc.exe目前已經到了1.8.34,而tsc.cmd還在1.8.10,二者行為也有別。例如我就發現tsc.cmd編譯時會自動省略node_modules,故tsconfig.json不需要透過exclude排除,加了反而會有錯誤。而實測發現,NG2官網範例專案使用tsc.exe –p .方式執行,ts不會被編譯也不會有錯誤訊息,故建議將PATH中tsc.exe的路徑移除,並確認node.js的tsc元件安裝妥當。
  4. 如何檢查方法tsc版?在CMD視窗下執行where tsc及tsc -v,確認其路徑為AppData\Roaming\npm,且版本為1.8.10

就這樣,我完成了VSCode NG2首航!祝大家NG2 Coding不NG~

潛盾機-網頁版COPY指令產生器

$
0
0

工作環境有不少Web Farm主機,部署程式時需要將一或多個檔案同步複製到多台主機上,依上線流程需產生指令檔交給OP人員執行。這種需求用COPY、XCOPY或ROBOCOPY指令寫成批次檔是最直覺有效的做法,但以「將四個檔案複製到八台主機」為例,需寫成32條指令,沒營養又躲不掉的枯躁苦工,交給機器人才是王道。

輸入IP清單轉成一串指令的小工具,印象裡我寫過WinForm版也寫過WebForm版,最近有類似需求,卻不幸遇到記憶斷層,怎麼也想不起程式放哪… Orz 心一橫,索性拿它當成練功題材,花了點時間寫成純前端網頁版,順手分享出來。

操作介面如上圖,設好來源項目與目標IP按個鈕即展開成批次指令,亦提供複製到剪貼簿的功能。Script Template是產生指令的樣版字串,{0}代表來源清單的項目,{1}則是對象機器IP,另外有個{0:path}語法可以只取來源項目資料夾路徑,方便組裝COPY指令。主機清單通常是固定的,右上方我放了快捷鈕,可直接帶入預先定義的主機IP清單,IP資料由一個JavaScript物件提供,使用時請自行調整成實際在用的群組名稱與IP。

var groups = {
"GRP1": "191.168.1.1\n192.168.1.2",
"GRP2": "10.10.100.1\n10.10.100.2\n10.10.100.3\n10.10.100.4"
}   

程式靠Angular MVVM配合JavsScript輕鬆搞定,沒什麼技術含量,寫成網頁的好處是只需單一檔案,丟上網路人人可用,懂JavaScript就能修改,差不多是路邊奉茶的概念(笑)。

程式碼如下,同時我也放上JSBin了,有需要的朋友請自取。

<!DOCTYPEhtml>
<htmlng-app="app">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width">
<title>Copy Script Generator</title>
<style>
    .deploy {
        width: 1024px;
    }
 
    legend {
        font-size: 9pt;
    }
 
    .list {
        width: 100%;
        margin: 6px 0;
    }
 
        .list td {
            padding-right: 6px;
        }
 
    textarea {
        max-width: 100%;
        width: 100%;
        height: 100px;
    }
 
    input {
        max-width: 100%;
    }
 
    .op {
        margin: 12px 0;
        background-color: #ccc;
        padding: 3px;
    }
 
    .result {
        height: 200px;
    }
 
    .show-hide {
        transition: all linear 0.5s;
    }
 
        .show-hide.ng-hide {
            opacity: 0;
        }
 
    .txt-button {
        color: blue;
        text-decoration: underline;
        cursor: pointer;
        margin-right: 3px;
    }    
</style>
</head>
<body>
<divng-controller="ctrl"class="deploy"ng-cloakafa-loading-indicator="vm.IsBusy">
<h3>
        Copy Script Generator by darkthread
</h3>
<div>
<tableclass="list">
<tr>
<td>Source Item</td>
<td>
                    Source IPs
<spanstyle="float: right">
                        IP Groups:
<spanng-repeat="(k,v) in vm.Groups"
class="txt-button"ng-click="vm.SetDest(k)">{{k}}</span>
</span>
</td>
</tr>
<tr>
<td>
<textareang-model="vm.SrcList"></textarea>
</td>
<td>
<textareang-model="vm.DstList"></textarea>
</td>
</tr>
</table>
</div>
<divclass="op">
<div>Script Template</div>
<textareang-model="vm.ScriptTmpl"style="height: 50px"></textarea>
<buttonng-click="vm.GenScript()">Generate Script</button>
<buttonng-click="vm.CopyScript()">Copy to Clipboard</button>
<spanclass="show-hide"ng-bind="vm.Msg"ng-show="vm.ShowMsg"></span>
</div>
 
<div>
<fieldset>
<legend>Scripts</legend>
<textareang-model="vm.Script"class="result"></textarea>
</fieldset>
</div>
</div>
<scriptsrc="https://code.jquery.com/jquery-3.1.0.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js">
</script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular-animate.min.js">
</script>
<script>
//REF: http://stackoverflow.com/a/30810322/4335757
function copyTextToClipboard(text) {
var textArea = document.createElement("textarea");
  textArea.style.position = 'fixed';
  textArea.style.top = 0;
  textArea.style.left = 0;
  textArea.style.width = '2em';
  textArea.style.height = '2em';
  textArea.style.padding = 0;
  textArea.style.border = 'none';
  textArea.style.outline = 'none';
  textArea.style.boxShadow = 'none';
  textArea.style.background = 'transparent';
  textArea.value = text;
  document.body.appendChild(textArea);
  textArea.select();
try {
var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
  }
catch (err) {
    console.log('Oops, unable to copy');
  }
  document.body.removeChild(textArea);
}
 
var groups = {
"GRP1": "191.168.1.1\n192.168.1.2",
"GRP2": "10.10.100.1\n10.10.100.2\n10.10.100.3\n10.10.100.4"
}    
angular
    .module("app", ["ngAnimate"])
    .controller("ctrl", function($scope) {
function myViewModel() {
var self = this;
            self.IsBusy = false;
            self.SrcList = "scripts\\common.js\nscripts\\common.min.js\ncss\\style.css";
            self.DstList = "192.168.1.1\n192.168.1.2";
            self.ScriptTmpl = "copy d:\\WWW\\blah\\{0} \\\\{1}\\d$\\WWW\\blah\\{0:path} /Y";
            self.ShowMsg = false;
            self.GenScript = function() {
var self = this;
var srcAry = self.SrcList.split('\n');
var dstAry = self.DstList.split('\n');
var cmds = [];
                $.each(srcAry, function(i, src) {
var p = src.split('\\');
                  p.pop();
                  var srcPath = p.length ? p.join('\\') : src;
                  $.each(dstAry, function(j, dst) {
                        cmds.push(self.ScriptTmpl
                                  .replace(/[{]0[}]/g, src)
                                  .replace(/[{]0:path[}]/g, srcPath)
                                  .replace(/[{]1[}]/g, dst));
                    });
                });
                self.Script = cmds.join('\n') + "\n";
            };
            self.GenScript();
            self.CopyScript = function() {
var self = this;
                copyTextToClipboard(self.Script);
                self.Msg = "Copied!";
                self.ShowMsg = true;
                setTimeout(function() {
                    self.Msg = "";
                    self.ShowMsg = false;
                    $scope.$digest();
                }, 2000);
            };
            self.Groups = groups;
            self.SetDest = function(grpName) {
var self = this;
                self.DstList = groups[grpName];
            };
        }
        $scope.vm = new myViewModel();
    });    
</script>
</body>
</html>

後記:小工具寫完沒幾天,NG2正式版就閃電問市惹… 所以,敬請期待「COPY指令產生器 in NG2」,Coming Soon~

TypeScript 1.4 - 2.0 改版整理

$
0
0

TypeScript 2.0 已於 9/22 正式推出,想起從1.4版起已好久沒有深入了解改版差異,順勢做個重點整理。

TypeScript 改版歷程

TypeScript 1.0 推出時,由於具有支援強型別、介面、繼承等物件導向語言特性,提供編譯期錯誤檢查,再加上完整 IDE 支援,很適合開發大型且複雜的 JavaScript 程式,獲得許多前端開發者青睞,TypeScript 也自此成為我開發網站的首選。

TypeScript 在 1.1 時選擇重寫編譯器,提供四倍速度,同時也移上 Github,鼔勵開源社群參與協作。

1.4 TypeScript 加入大量 ES2015/ES6 支援以及新特性,包含:

  • Union Types ★★
    例如: x: number | number[],x 的型別可以是數字或是數字陣列
  • Type Alias
    為型別取別名,例如:type NgScope = ng.IScope, type Callback = () => void
  • Const Enums
    編譯時直接置換為數字,在 JavaScript 端完全隱形
  • 支援 ES6 的 Let/Const
    let 的用法與 var 相同,差別在於 let 嚴格限定變數存活範圍,杜絕干擾區塊外變數的可能性。const 則可用於宣告後定義好就不容更改的變數。
  • Template String ★★★★
    直接看範例:
    var rectangle = { height: 20, width: 10 };
    var areaMessage = `First line,
    Rectangle area is ${rectangle.height * rectangle.width}`;
    樣版字串使用「`」符號取代單引號或雙引號,在字串可嵌入變數,還像C# @"…"可支援換行,串接HTML標籤接到落落長時格外好用,大推!
    延伸閱讀:TypeScript Template Strings

TypeScript 1.5陸續加入更多 ES6 支援,包含:

  • ES6 Module概念
    在 ES6 習慣以模組概念拆解成多個程式碼檔案,使用 export 決定對外公開範圍,要用時以 import 匯入引用。前幾天初試 Angular 2已體驗過這種新做法。
  • Destructing
    使用陣列形式快速指定變數值
    const iterable = ['a', 'b'];
    var [x, y] = iterable; // x = 'a'; y = 'b'
  • Spread運算子 ★★
    用法 function drawText(x: number, y: number, ...strings: string[]) { … }
    概念相當於 C# 的 params,例如:void DrawText(decimal x, decimal y, params string[] strings)
    故寫成 drawText(10,20,"hello") drawText(10,20,"hello","world")都通,但Spread運算子還可加於呼叫參數前方,例如:
    var pos=[10,20], strings=["one","two"];
    drawText(…pos, "hello",…strings,"world");
    等同drawText(10,20,"hello","one","two","world");
  • for … of 語法
    類似 C# foreach (var … in …):
    for (var v of expr) { }
    等同於
    for (var _i = 0, _a = expr; _i < _a.length; _i++) {
        var v = _a[_i];
    }
  • 支援ES6內建Symbol 參考
  • ES6 Computed Property
  • Module 輸出選擇
    除了 AMD、CommonJS 外,再新增 SystemJS、UMD
  • 支援使用 tsconfig.json 設定專案以及編譯選項
  • Decorator
    與 Angular、Ember、Aurelia 開發團隊合作,TypeScript 1.5 融入 ES7 的 Decorator 特性,它也是 Angular 2 開發的重要關鍵:(如以下程式的 @Component 及 @View )
    import {Component, View, NgFor, bootstrap} from “angular2/angular2”;
    import {loadFile} from “audioFile”;
    import {displayAudioFile} from “displayAudio”;
     
    @Component({selector: ‘file-list’})
    @View({template: `
    <select id=”fileSelect” size=”5″>
    <option *ng-for=”#item of items; #i = index”
          [selected]=”selected === item”(click)=”updateSelection()”>{{ item }}</option>
    </select>`,
      directives: [NgFor]
    })
     
    class MyDisplay {
      items: string[];
      constructor() {
    this.items = [“item1”, “item2”];
      }
     
      updateSelection() { … }
    }

TypeScript 1.6-1.8 陸續再加入改良。

TypeScript 1.6

  • 支援 React Typing 及 JSX
  • Class Expression
    一列寫完類別宣告,例如:
    class StateHandler extends class { reset() { return true; } } {
  • 自訂型別檢核
    例如以下程式,若 a 是 Dog,在編譯時期就會出錯
    function isCat(a: Animal): a is Cat {
      return a.name === ‘kitty’;
    }
  • Intersection Type
    在 JavaScript 裡有時會使用 Mixin 概念或 jQuery.extended()方法融合兩個不同型別物件同時具備兩種型別的介面,過去在這種情況下要實現強型別,需宣告一個新介面或類別以兼容兩種型別的介面,1.6 起可用 T & U 代表融合 T 與 U 屬性方法的混合型別,例如:
    function extend<T, U>(first: T, second: U): T & U
  • 支援抽象類別(Abstract Class)
  • 支援泛型別別名

TypeScript 1.7

  • 支援 async / await
  • Polymorphic this Typing
    多形概念的 this,主要用於 Fluent 串接式 API,看範例比較好懂:
    interface Model {
        setupBase(): this;
    }
    interface AdvancedModel extends Model {
        setupAdvanced(): this;
    }
    declare function createModel(): AdvancedModel;
    newModel = newModel.setupBase().setupAdvanced(); // fluent style works
  • ES6 Module Emitting
    新增 module 參數,面對 Node.js v4 不支援 ES6 模組但支援 ES6 特性的情境,可以 target 參數設 es6,但模組用 commonjs。
  • ES7 Exponentiation
    ES7 規格,let cubed = 2 ** 3 –> cubed = 2 * 2 * 2,取代 Math.pow()

Type Script 1.8

  • Module Augmentation
    允許 import Module 後再擴充其介面加入新屬性、方法
  • String Literal Types ★★
    限定字串變數只能使用列舉的字串值,例如: easing: "ease-in" | "ease-out" | "ease-in-out"; 若 easing = "out" 會出現Error: Type '"out"' is not assignable to type '"ease-in" | "ease-out" | "ease-in-out"'
  • 流程分析更加智慧化
    例如:return 後馬上換行陷阱偵測,未 return 等同傳回 undefined 警告。

至於 TypeScript 2.0,台灣 MSDN 部落格有篇文章有 Beta 版的詳細介紹,這裡簡單條列:

  • --strictNullChecks 限定 string、number 等型別變數不允許被設為 null 或 undefined,除非使用 string | null 或 string[] | undefined,如必要可加上「!」排除變數為 null 或 undefined 情況以避開編譯錯誤。例如:
    let lowerCased = strs!.map(s => s.toLowerCase());
  • 編譯器能更精準掌握變數在某段程式碼位置時是什麼型別
  • 模組宣告簡化
    在 TS1.x 為描述外部程式庫,我們可能需要寫成以下宣告
    declare module "foo" {
        var x: any;
        export = x;
    }
    在 TS2.0 只要寫成 declare module "foo" 即可

2.0 RC加入的改良如下:

  • Tagged Unions ★★
    Tagged Unions(又稱為 Discriminated Unions, Disjoint Unions, 或 Algebraic Data Types)已是 F#, Swift, Rust, JavaScript 常用的設計模式。用個例子來說明,Circle 與 Square 都有 kind 屬性,但在 Circle 寫死為 "circle",在 Square 則為 "square":
    interface Circle {
        kind: "circle";
        radius: number;
    }
     
    interface Square {
        kind: "square";
        sideLength: number;
    }
     
    type Shape = Circle | Square;

    在 TS1.8 裡,取得 Shape 聯集型別(Union Type)後必須轉型成 Circle 才能取得半徑,轉成 Square 才能讀取邊長:
    function getArea(shape: Shape) {
    switch (shape.kind) {
    case"circle":
    // Convert from 'Shape' to 'Circle'
                let c = shape as Circle;
    return Math.PI * c.radius ** 2;
     
    case"square":
    // Convert from 'Shape' to 'Square'
                let sq = shape as Square;
    return sq.sideLength ** 2;
        }
    }

    TS2.0 變聰明了,知道何時 Shape 是 Circle,何時是 Square:
    function getArea(shape: Shape) {
    switch (shape.kind) {
    case"circle":
    // 'shape' is a 'Circle' here.
    return Math.PI * shape.radius ** 2;
     
    case"square":
    // 'shape' is a 'Square' here.
    return shape.sideLength ** 2;
        }
    }
  • 1.8 推出的 String Literal Type 廣受好評,2.0 再擴大到 boolean, number ★★
    type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
    let nums = Digit[];
    nums.push(16); <= 出錯,因為16不在列舉範圍內
  • tsconfig.json 支援萬用字元
    例如:
    {
    "include": [
    "./src/**/*.ts"
        ],
    "exclude": [
    "./src/tests/**"
        ]
    }

    其中:*代表0或多個非分隔符號字元、? 代表一個非分隔符號字元、**/ 代表任意層的子目錄

再補上幾條 TypeScript 2.0 革新:

  • 簡化宣告檔(d.ts)的引用程序
    在 VS Code 等應用中,使用 npm install –s @types/lodash 安裝 Scoped Package後,在專案中遇到需要 lodash 定義時將自動找到適合的版本下載。
  • 增加 readonly 修飾字
    用法與 C# readonly 相同,只允許由建構式指定值,之後不得再變動。

註:標上 ★★的是我覺得特別好用的功能,黑大嚴選,大力推薦!

要使用 TypeScript 2.0,VS2015 必須更新到 Update 3,並安裝 TypeScript 2.0 for Visual Studio 2015;Visual Studio Code 則可透過 npm install –g typescript@2.0 安裝新版。

.NET Standard 2.0 是什麼?可以吃嗎?

$
0
0

這幾天(9/26-30) Microsoft Ignite 2016(去年起由 Ignite 取代 TechEd)正在美國如火如荼召開,乍見一個新名詞-.NET Standard 2.0。我得了一種聽到新名詞就會焦慮的病,趕緊查資料壓壓驚…

.NET Team Blog 同步貼了一篇介紹文,可說是目前最詳細最權威的資料來源,抱著懂個大概就好的心態,整理重點如下。

先用兩張圖說明為什麼沒事要搞出一套 .NET Standard 新標準?(註:圖片取自 MSDN Blog 文章

.NET Standard 的核心使命在於解決 .NET 跨平台時基礎程式庫不一致的問題。.NET 發展至今,已初步實現跨平台, 用 C# 就可以寫 Windows、macOS、Linux、iOS、Android 程式。但如下圖所示,.NET Framework BCL、.NET Core Library 與 Xamarin 的 Mono Class Library 各自發展,缺乏統一的介面標準,像是System.Collections, System.IO, System.Xml… 這類性質的基礎類別,各家支援程度不一,某些 API 可能在某個平台不存在,又或者 API 介面存在差異。

如果你只專注一種平台,當然可以無視各平台基礎程式庫的差異,專心學好一種就好。但如果系統被要求跨平台,差異那怕再小,都會跑出來咬你屁股。首先,你必須搞懂不同平台的差異,第二,差異提高「跨平台共同程式庫/元件」開發的難度。(腦海中出現一堆噁心的 #if NETCORE … #elif XAMARIN … #endif) 。

過去針對跨平台共用程式庫的主推做法是PCL(Portable Class Library),取多個平台的交集,篩選保留各平台都支援的 API,但開發者仍需知道不同平台的差異。.NET Standard 則試圖規範一套標準基礎程式庫 API 介面,各平台依此介面實作出一致的程式庫,如此程式碼不需修改即可針對不同平台編譯、執行。

即使有 .NET Standard,還是無法逃避各平台支援度不一的現況。.NET Standard 版本號碼與 API 完整度成正比,與支援平台範廣度成反比。.NET Standard 2.0 提供的 API 數目一定比 1.0 多,但如果想涵蓋 Windows Phone 8.1,就只能選擇 .NET Standard 1.0 – 1.2。

隨著 .NET Standard 2.0 制定,.NET Core 與 Xamarin 將在新版加入支援,而 .NET Framework 4.6.1 則已符合 2.0 標準。有沒有注意上表的玄機?.NET Standard 1.4 對應到 .NET Framework 4.6.1、1.5 對 4.6.2,結果 2.0 又倒車回到 4.6.1,原來是基於部署普及率考量,.NET Standard 2.0 拿掉 1.5/1.6 增加但應用不廣的 API,好讓 .NET Framework 4.6.1 符合 .NET Standard 2.0。各位同學,今天我們要介紹的成語是-「削足適履」~(笑 )

以下是 .NET Standard 2.0 的主要涵蓋範圍,細節則參考 github 上的文件。 .NET Standard 2.0 仍在發展中,未來可能還會有變動。

至於一些與平台高度相關的 API,例如:只有 Windows 才有的 Registry、Reflection Emit 功能不適用 .NET Native、UWP、Xamarin iOS。.NET Standard 採取「需額外安裝 NuGet Package 才能使用,在不適用平台執行時拋出例外」的原則,不同的 API 處理方式不一。

如果你需要開發跨平台程式庫,官方建議改走 .NET Standard,以降低平台相依性,並允許混合參照 PCL 及 .NET Framework,但 PCL 仍適用特定場合,例如:某些平台不在 .NET Standard 支援之列,PCL 是唯一解。實作上有個 API Port可偵測程式適用的最低 .NET Standard 標準,再配合檢查目標平台是否在該標準支援範圍,以便決定標準版本。

完整的 .NET Standard 工具支援將內建於下一版本 Visual Studio "Dev 15",以 NuGet Package 方式加入參照,而未來 Visual Studio、VSCode、Xamarin Studio 均會提供一線支援。

報告完畢。

網路抓來的圖能不能用,可不可以修改?淺談創用CC授權

$
0
0

之前只模糊知道 CC 是一種授權方式,常在網站看到下方這種標誌或 CC-BY-NC 之類的註記:

但對於 BY NC SA ND 這堆縮寫與符號一知半解,總搞不清楚我在網路上找到的圖或影片,到底能不能用,可不可以修改,要不要標示作者…

Lag 了很久,最近終於搞懂「創用 CC 授權條款」是怎麼回事,寫篇筆記兼分享。

時至今日,相信大家都已有著作權觀念,知道網路上看到的創作(照片、文章、繪畫、音樂、影片…)都有著作權,觀賞自用沒啥爭議,若要在自己的作品裡引用,必須徵求作者同意。不過,寫部落格或做投影片為了一張照片或一段音樂,得連絡原作者協商取得授權(如果作者有留連絡方式的話…)即曠日費時又有些本末倒置。尤其這年頭很流行以照片為主體的投影片風格,試想如果投影片用到數十張網路上的攝影作品,光逐一連絡作者取得授權大概就飽了…

從另一個角度,不少創作者很歡迎別人引用及推廣他的作品,但其他人卻害怕觸犯著作權法不敢使用,阻礙了知識與藝術交流以及人類文明演進(謎之聲:有這麼嚴重嗎?)。於是,在 2001 年由美國法律學者推動的 Creative Commons 公眾授權條款,鼓勵大家在作品上主動標示授權範圍,明確作品開放給公眾利用,促進內容資源的散佈與流通,讓創作者與使用者同時受惠。

關於 Creative Commons 的細節,中研院科技創新研究中心推動的「台灣創用 CC 計劃」網站有非常詳細的說明,很值得一讀(其中還有 FAQ 解答常見的疑惑,例如:用 CC 授權素材做成文宣有沒有觸法?參加比賽得獎算不算商業行為?)。以下則是我簡單整理的重點:

CC 授權有四大要素,就是一開始圖檔上出現的小圖示,每個要素另有兩個字母縮寫:


圖片來源:台灣CC創用計劃

四項要素分別是 BY 姓名標示、NC 非商業性、ND 禁止改作、SA 相同方式分享,除了圖示,也可使用「CC BY-SA」這種格式表示。四個要素共有六種組合:CC BY、CC BY-NC、CC BY-NC-SA、CC BY-ND、CC BY-NC-ND、CC BY-SA。

CC 網站有圖示可供下載,而應用時多半還會加上連結指向授權說明文件,有個簡便做法是使用授權選擇網頁經由簡單問答產生適用的授權標誌嵌入程式碼,圖示與連結一次搞定,放進作品網頁就算完成 CC 授權宣告。

搞懂這些,未來在網路尋找照片、影片,如果看到 CC 授權,就代表作者同意授權使用,但要留意其限制,有 BY 代表必須標記作者,若有 ND 代表不能進行任何修改,出現 NC 代表不允許用於商業行為謀利,若有 SA 代表引用後你的作品也要採相同授權分享出去。

在所有可利用資源中,有一種佛心到極致(差不多是萬佛朝宗的等級)的授權模式叫 Public Domain (公眾領域),標示為 CC0 (公眾領域貢獻宣告)與 PDM (Public Domain Mark,公眾領域標章),CC0 象徵著作權人自願拋棄著作權,不保留任何權利,將作品貢獻給公眾,任何人可以任何方法自由運用,不論公益或營利;而 PDM 主要應用於圖書館、博物館將文物資產與公眾共享。

換言之,CC0 授權允許自由修改且不需標註出處,使用起來全無限制,是創作素材中的極品,網路上可以找到很多 CC0 圖檔甚至有 CC0 免費圖庫搜尋引擎,大家可以善加利用,別再甘冒侵犯著作權風險亂抓亂用圖片。

最後,補充一點小技巧,Google 圖片搜尋功能的「搜尋工具」有個「使用權限」篩選工具,提供「不限使用權」、「標示為允許再利用且可修改」(相當於 CC BY)、「標示為允許再利用」(相當於 CC BY-ND)、「標示為允許非商業用途再利用且可修改」(相當於 CC BY-NC)、「標示為允許非商業用途再利用」(相當於 CC BY-NC-ND)五種篩選等級,能依應用方式快速找到符合授權範圍的圖片。但要留意,除了 CC0 之外,只要有 BY 就需依要求標註創作者姓名或來源。

除了 Google 查詢,許多專業的搜尋服務都有授權過濾選項,有了創用 CC 授權的知識,下回再看到篩選器裡的成串選項,應該就不會再一頭霧水囉~

2016烏來馬拉松

$
0
0

步入秋季,九月跑了兩場10K路跑暖身,下半年跑馬由烏來馬揭開序幕。週六比賽,週二、週三因梅姬連放兩天颱風假,媒體傳來的烏來實況照片挺嚇人,原本擔心山區受創會取消或延期。所幸一切無恙比賽照常,但主辦人邵老師肯定已洗完一輪心情三溫暖。(據説這次辦比賽瘦了17公斤 XD)

凌晨四點趕到捷運新店站,接駁遊覽車花了30分鐘到鳥來,在觀光大橋附近換小巴再開10分鐘到會場-勇士廣場。時間不到5點天色仍暗,但現場已人聲鼎沸。

天色漸亮,慢慢看清會場全貌,舞台就在瀑布正前方。

馬場聲樂家許文龍老師登場,獻唱一曲「You Raise Me Up」,感染力十足~

近六點整集結出發,大會介紹幾位特別的參賽者,分別有跑友要在今天挑戰百馬、兩百馬、三百馬、五百馬、六百馬… 媽呀,六百馬是什麼狀況?每個週末跑兩場全年無休,還要夠多比賽可報才能達標。台灣這幾年路跑風行,每年路跑賽事高達三百場,才造就了另類「台灣奇蹟」。

出發後先繞行老街,接著一路爬升,7K左右到達海拔四百多公尺的賽道最高點(勿高興,後面會再爬回四百米外加一路上上下下)。下點最高點水站照片裡有位穿整套原住民服飾(還是印地安風?)的女跑友,手拿小石斧頭加誇張羽毛頭飾十分吸晴,速度頗快,我還想一路當成明顯目標緊跟,肯定成績不會太差。不過,才過高點沒多久,我就被海放 海放 海放~~~

過最高點的下坡又陡又濕滑,沒人敢跑,大家通通緩步前進以防仆街。

10K左右繞完山頭再回到會場附近,看到瀑布已在下方。

美景

多雲到晴的好天氣,狀闊的峽谷,讓孤陋寡聞的阿宅重新認識美麗的烏來~

 

 

 

瀑布

幾天前下完大雨,山區處處都是肥壯而有怒氣的大小瀑布,一口氣把一年份的瀑布看完,過癮。XD

   

   

   

美食

現在本紹本場戶外餐會的菜單,餐前水果有「海鹽田園檸檬」、「厚切當季鮮蕉」。

另外「洋石榴玉女蕃茄佐梅粉」的絕妙搭配令人驚豔。開胃菜則有兩道,分別是「黃金凝脂糖心蛋」:

以及「福爾摩莎火腿佐香蒜」:

前菜來了,「辣味洋葱拌炒野味山豬肉」放在芭蕉葉上格外好吃。邵老師就在山豬肉水站外熱情招呼跑友,遇到熟識跑友,還飆出「王八蛋,為什麼不吃山豬肉?」,當場笑翻。

主菜登場,巴巴巴巴馬乾酪嫩雞潛艇堡佐蜂蜜芥末,別懷疑,就是Subway的潛艇堡三明治沒錯,誰叫邵老師開Subway餐廳呢?連手工餅乾都登場了。

飲料也很豐富,喝到山粉園、仙草蜜、舒跑、維大力、可樂… (嗝~)

雜記

身處峽谷間,GPS訊號不佳,12K左右配速數字亂跳,還天外冒出三分速,而全程里程也誤差近2公里。事後看GPS軌跡,有段通過隧道的軌跡猶如鬼畫符。

折返點在福山部落大羅蘭溪,又一個如果不是跑馬我不會來的地方。

5K一個里程板,我蒐集到四塊。

   

   

大會開放自訂號碼布姓名,所以,薑薑薑薑~~~

自訂姓名帶來額外趣味,最後幾K我盯上一位「奔跑的茼萵」小姐,由於不想輸給蔬菜,跑得格外賣力,成績比預期好。另外,路上也被讀者跑友認出,小聊了兩句。

最後5h40m18s完賽,本來以為會破六,比想像好很多,但這回穿練跑鞋登場,透氣性及重量有差,上下坡路段不少,最後10K腳趾發痛,估計會領幾塊黑指甲當紀念獎牌。

就這樣,開心結束一馬,順利搭上接駁車,還及時趕上原本以為會大遲到的同學會。

賽道風景優美(雖屬山路,但與櫻花馬、石碇馬相比難度並不高),補給豐盛,接駁周到,完賽獎牌別緻,參賽背心及完賽紀念T-Shirt都很好看,小而美賽事口袋名單再添一筆!

 


Oracle故障後續處理經驗一則

$
0
0

不經一事不長一智,以下經驗價值1.5小時。

接獲回報,部分 ASP.NET 網頁出現資料庫錯誤,錯誤指向某 Oracle 資料庫,使用 Telnet oracel_server_ip 1521 測試無反應,通報系統人員,查出為資料庫主機網路異常,並在隨後修復。

真正的茶包在 Oracle 資料庫主機恢復後才現身,部分使用者通報他們還是無法使用網頁,但我測試是成功的,而有問題的使用者「多試幾次」也會成功。網站為 Web Farm 架構,參雜使用者連上主機可能不同的因素,歷經一番追查彙整,才理頭緒:

  • 網頁連線 Oracle 資料的動作時好時壞,失敗時出現連線錯誤或 Timeout
  • 問題集中在 Web Farm 其中兩台主機
  • 事件檢視器有 EntityFramework 連線錯誤 The underlying provider failed on Open

由於該網頁使用者不多,推測 Oracle 出錯時兩台問題主機剛好有使用者在線上,當時用的 OracleConnection 無法連線出錯,而出錯連線殘留在 Connection Pool。之後 Oracle 資料庫恢復正常,但若是網頁從 Connection Pool 中取出的是曾出錯的連線,便會產生 The underlying provider failed on Open 錯誤。由於 Connection Pool 部分連線是好的,部分是壞的,造成時好時壞,多試幾次就會成功的現象。而重啟 Application Pool 後,問題宣告排除。

事後才想起七年前 ODP.NET 9207 時代我也遇過 Oracle Connection Pool 殘留錯誤連線問題,當時還研究過 ClearPool() ClearAllPool() 指令,可惜未及時想起白走了冤枉路,扼腕!

教重訂 SOP 如下:

遇 Oracle Server 出錯恢復後,應重啟 AppPool 或程序,以清除 Connection Pool 中的有毒連線。 

【茶包射手日記】OLE DB讀取CSV時文字變空白

$
0
0

同事回報一個鬼問題,某段古老 ASP.NET 程式使用 OLEDB 讀取 Excel/CSV 檔案(寫法範例,屬民國初年流行做法,現多會改用 EPPlusClosedXML),原本運作良好,自從 Windows 2003 EOS 退役移至 Windows 2012R2 x64 後出現狀況。情境為使用者上傳CSV給網頁解析,其格式如下:
SN,Data
1,1234
2,AB12
3,9527
4,5978

Data欄位有可能是純數字或英數字,當Data為英數字有時會讀不到,而且是全部英數字都被忽過,數字部分正常,但有時在下方多加一筆英數字就又可全部讀到,頗為詭異。修改程式碼將讀取結果逐筆印出後有了較明顯線索:發現在「某種條件」下,從Data欄位讀入的英數字會全部變成空白,但數字部分正常。英數字資料的筆數、位置似乎會影響結果,但多次實驗仍未理出頭緒。

爬文查到保哥的一篇老文章提到 TypeGuessRows 機碼及 Jet Engine 由前 8 列資料猜測欄位型別的行為。換言之,是「欄位格式自動偵測」惹的禍,由於  Data 欄位混雜純數字與英數字,Jet 引擎自動偵測決定將其視為純數字或是英數字,若被判定成數字,其中的英數字資料就會被當成NULL,轉為字串後變成空字串。在德瑞克的另一篇文章,則有更詳細的情境範例描述:

如果您在同一欄中混合使用數值和文字值,會發生嚴重的問題。
Jet 和 ODBC 提供者都會傳回主要類型的資料,但是次要資料類型則會傳回 NULL (空) 值。
如果同一欄中兩種類型混合使用的比例相同,提供者會選擇數值而非文字值。
例如:
(1) 在 8 個已掃描的列中,如果欄位中包含 5 個數值和 3 文字值,提供者會傳回 5 個數值和 3 個 Null 值。
(2) 在 8 個已掃描的列中,如果欄位中包含 3 個數值和 5 文字值,提供者會傳回 3 個 Null 值和 5 個文字值。
(3) 在 8 個已掃描的列中,如果欄位中包含 4 個數值和 4 文字值,提供者會傳回 4 個數值和 4 個 Null 值。

但有個大疑點:我確認 Registry 中 TypeGuessRows 為 8,ImportMixedTypes 為 Text,實測就算前 8 筆都是英數字,欄位仍可能被判定為數字導致英數字空白,而這種混雜型別狀況依 ImportMixedTypes='Text' 明明要為文字才對。

最後,我才驚覺 Jet 解析 CSV 時使用的 Registry 與 Excel 有別,ImportMixedTypes 預設 Majority Type,而 MaxScanRows為 25,「為什麼前 8 筆全是英文仍會導致英數字變 NULL」有了完美解釋。


補充:Text Jet Engine Registry設定說明

又長了知識。

【2016-10-08更新】感謝網友張峰銘補充,若不想讓Jet Engine瞎猜一通,可使用Scehma.ini強制指定欄位型別(參考)。

【延伸閱讀】

中文亂碼「嚙踝蕭嚙踝蕭」是怎麼來的?

$
0
0

在 FB  看到 91 貼了一張照片,提到某廠商的電子報一直存在亂碼問題,寄信人與信件主旨出現一堆嚙調客、嚙踝蕭… 之類的怪字亂碼。

有種畫面上到處是老鼠的感覺… (嚙是的異體字)

連 Chrome 也想吶喊「有老鼠!Encoding 碰上麻煩了。」 XD

之前寫過一篇中文亂碼"蕞蕞蕞蕞"是怎麼來的?,猜想同樣是錯用編碼解析的結果,雖然最近案子忙到火燒屁股,但就看到球滾進了我的守備範圍,還是忍不住研究起老鼠從哪來… XD

用一小段程式驗證問題來自「錯用 UTF8、BIG5 解碼」:

首先將「嚙踝蕭嚙踝蕭」做一次 BIG5 逆轉回 UTF8,得到 ����,� (EF-BF-BD)是 UTF8 無法解碼時使用的無效字元符號。由此推測問題來自非 UTF8 字串卻誤用 UTF8 解碼,產生大量�及少量英數字交雜的亂碼字串,接著該 UTF8 字串再被誤當成 BIG5 再解碼一次,「嚙踝蕭嚙踝蕭」就這麼誕生了!結案。

「以排程方式呼叫Word/Excel注意事項」補充包

$
0
0

程式搬家出錯的老梗又來了~ 古老的 VB.NET 傳真傳送程式,內部用 CreateObject("Word.Application") 呼叫 Office Word 2003,借重其傳真發送功能。不意外地,原本在 Window 2003 執行得好好的程式,移到 Windows 2008 R2 x64 後水土不服。新主機裝的是 Word 2010,程式出錯,為避免踩到 Word 2003 / Word 2010 差異的地雷,決定在 Windows 2008 R2 上裝 Word 2003。雖然官方文件已言明不支援,實際安裝及測試倒沒啥問題,決定這様頂著先,要改的東西太多,所以就改天吧!

換裝 Word 2003 後程式可手動執行成功,但移入排程後出現錯誤:

System.Exception: 無法建立 ActiveX 元件。 於 Microsoft.VisualBasic.Interaction.CreateObject(String ProgId, String ServerName)   
System.Exception: Cannot create ActiveX component. at Microsoft.VisualBasic.Interaction.CreateObject(String ProgId, String ServerName)".

Office 相關程式桌面互動執行OK,移入 Windows Service 或排程出錯,過去已有不少經驗,關鍵是要手動建立 c:\windows\syswow64\config\systemprofile\desktop 資料夾。快手快腳建完目錄,錯誤依舊… 這下糗了,黔驢技窮。

思索了一陣子,由 CreateObject("Word.Application") 得到靈感,建立 ActiveX 物件依賴 Registry,而 Registry 體系 32 位元 64 位元有別,莫非問題與 32/64 有關?使用 corflags 工具檢查程式的目標平台為 AnyCPU,懷疑問題出在排程中程式是在 64 位元模式執行,用 corflags 將程式改為 x86 再試一次,問題排除!特此筆記。

【延伸閱讀】

檔案部署指令實戰技巧整理

$
0
0

比起滑滑鼠,我更喜歡開DOS視窗敲鍵盤下指令解決問題,效率高,操作程序可以保存並加以優化,成果可反覆使用並散佈供他人使用。當手指飛快在鍵盤上躍動,還有種自己正忙著拯救世界,或是藝術家沈醉在表演中的錯覺 XD

前陣子分享過產生批次指令部署檔案的小工具,在最近上線過程耍得虎虎生風,習得實戰技巧幾則,特筆記備忘。

COPY 三寶

COPY、XCOPY、ROBOCOPY 三者各有無法被取代的特色,視場合使用,俗稱 COPY 三寶(喂!不要亂取名字啦)。完整參數介紹:COPYXCOPYROBOCOPY

複製檔案時如何覆寫被設為唯讀的檔案?

XCOPY 有個 /R 參數,例如:xcopy d:\src\blah\a.js d:\dst\blah\a.js /y /r。(記得還要加上/y,省略確認直接覆寫。

如何找出某個時點之後更新的檔案?

上線程式時若想挑出有更動的檔案,ROBOCOPY 有個好用參數 /xo /maxage,可以搜尋整個目錄挑出指定時間後才更新的檔案,並依原資料夾結構擺放,用它直接對目的資料夾進行覆寫,一個動作完成更新。

例如以下指令可挑出2016/10/01之後有異動的檔案:robocopy d:\WWW\Oasis d:\Set9527\Oasis /MAXAGE:20161001 /XO /S

接下來執行 xcopy d:\Set9527\Oasis \\192.168.1.1\d$\WWW\Oasis /s /y就可用整個資料夾覆寫目的資料夾,但只更新有異動的檔案。

如何將 DIR 結果轉為檔案清單?

部署指令產生器使用檔案清單跟主機 IP 清單展開成 COPY 指令,要將 DIR 結果轉成文字清單,可使用 PowerShell Get-ChildItem取得檔案物件陣列,從物件FullName屬性取得完整路徑再去除父資料夾部分轉成相對路徑。例如:Get-ChildItem -Path E:\Set9527\Oasis -Recurse –File | % {$_.FullName.Replace("E:\Set9527", "") }

實際應用時,有時 Get-ChildItem 的結果還需加上篩選條件或一些花式變化,透過 Pipeline 串接可以輕易達成。參考

如果想將執行結果複製到剪貼簿,不要再傻傻滑滑鼠選範圍或輸出到檔案再取回,後方用 Pipeline 串接 clip.exe 工具,一次搞定。

[2016-10-16更新] 感謝安德魯補充,dir /b /s 也可快速產生檔名清單,另外 for 也能產生相同結果且功能更強大。

複製檔案時如何自動建立資料夾?

遇到複製整個資料夾(或以萬用字元指定多檔)而目的路徑不存在時,XCOPY 會詢問目標路徑為檔案還是資料夾:

要避免 XCOPY 詢問,可加上 /i 參數,可指示 XCOPY 自動建立資料夾並複製檔案。 若複製來源為單一檔案便不適用 /i 參數,一定跳出詢問:

最簡單的解法是在目的路徑後方加上一個"\"搞定。

Viewing all 2311 articles
Browse latest View live