前言:
.NET中常見讀取Excel的元件有:
● Microsoft.Office.Interop.Excel
● NPOI
● EPPlus
● ClosedXML
但Interop.Excel在使用上,問題比較多,如多執行緒時,會出現檔案鎖定等問題,
至於EPPlus跟ClosedXML只能處理xlsx格式,所以就拿NPOI來試試了,
而且NPOI可以商業用,只要掛上copyright與license notice即可(Apache License 2.0)。
作法:
初次使用NPOI,發現不同Excel格式有不同類別處理,如:
● HSSFWorkbook用來處理xls
● XSSFWorkbook用來處理xlsx
但這樣不就要先判斷附檔名,再用不同類別處理?
似乎不夠漂亮!?
後來找到NPOI提供WorkbookFactory類別,提供IWorkbook抽象,
只要呼叫WorkbookFactory.Create方法,即可依Excel格式回傳該Workbook。
不過在WorkbookFactory.Create(string file)實作方法裡,
竟然是先用HSSFWorkbook開看看,開不起來再用XSSFWorkbook開,
沒問題就回傳,出乎我意料之外...
後來想到,如果只是讀取Excel,用LinqToExcel就可以了,
使用LinqToExcel讀取Excel非常方便,並可代入自訂物件結構,使用強型別處理資料。
但記得要安裝Microsoft Access Database Engine 2010 Redistributable,
以免出現The 'Microsoft.ACE.OLEDB.12.0' provider is not registered on the local machine.'例外。
參考資料:
https://github.com/tonyqus/npoi
https://github.com/paulyoder/LinqToExcel
2019年3月23日 星期六
2018年3月3日 星期六
[Tool] OpenQA.Selenium.DriverServiceNotFoundException: The geckodriver.exe file does not exist...
說明:
測試專案加入Selenium Web Driver 3.9.1,使用FirefoxDriver,
第一次啟動測試後出現錯誤:
OpenQA.Selenium.DriverServiceNotFoundException: The geckodriver.exe file does not exist in the current directory or in a directory on the PATH environment variable. The driver can be downloaded at https://github.com/mozilla/geckodriver/releases.
解法:
這是因為Firefox(FF) 48後的engine改為gecko driver了,
因此Selenium 3.0後,就使用gecko driver取代FF預設WebDriver的實作,
至於為何會改用gecko driver?
那要先介紹Gecko,Gecko是:
● It's a free and open source project.
● 內容是Layout engine,提供許多API讓application可以顯示web page或當使用者介面。
● 相容於W3C定義的WebDriver標準。
● 後來由Mozilla基金會與公司維護。
參考資料:
https://stackoverflow.com/questions/40657443/how-to-set-up-selenium-3-0-getting-error-the-geckodriver-exe-file-does-not-exi
http://qavalidation.com/2016/08/whats-new-in-selenium-3-0.html/
https://en.wikipedia.org/wiki/Gecko_(software)
https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox
https://blog.mozilla.org/futurereleases/2016/08/02/whats-next-for-multi-process-firefox/
測試專案加入Selenium Web Driver 3.9.1,使用FirefoxDriver,
第一次啟動測試後出現錯誤:
OpenQA.Selenium.DriverServiceNotFoundException: The geckodriver.exe file does not exist in the current directory or in a directory on the PATH environment variable. The driver can be downloaded at https://github.com/mozilla/geckodriver/releases.
解法:
這是因為Firefox(FF) 48後的engine改為gecko driver了,
因此Selenium 3.0後,就使用gecko driver取代FF預設WebDriver的實作,
至於為何會改用gecko driver?
那要先介紹Gecko,Gecko是:
● It's a free and open source project.
● 內容是Layout engine,提供許多API讓application可以顯示web page或當使用者介面。
● 相容於W3C定義的WebDriver標準。
● 後來由Mozilla基金會與公司維護。
而Gecko有支援multi-processing(能讓UI在不同process處理)架構,
當時整個FF 47還跑在同一個process,因此48版後改用gecko driver。
所以現在啟動測試,使用FirefoxDriver需透過gecko driver來啟動FF,
正如錯誤訊息所示,到https://github.com/mozilla/geckodriver/releases下載,
目前最新版是v.0.19.1,下載後解壓縮到特定資料夾,
我直接放到FF安裝資料夾內,然後到系統:
找到環境變數
在Path,新增一筆gecko driver的放置路徑,放在使用者變數也行。
最重要的是,設定後要重開VS。
當時整個FF 47還跑在同一個process,因此48版後改用gecko driver。
所以現在啟動測試,使用FirefoxDriver需透過gecko driver來啟動FF,
正如錯誤訊息所示,到https://github.com/mozilla/geckodriver/releases下載,
目前最新版是v.0.19.1,下載後解壓縮到特定資料夾,
我直接放到FF安裝資料夾內,然後到系統:
最重要的是,設定後要重開VS。
參考資料:
https://stackoverflow.com/questions/40657443/how-to-set-up-selenium-3-0-getting-error-the-geckodriver-exe-file-does-not-exi
http://qavalidation.com/2016/08/whats-new-in-selenium-3-0.html/
https://en.wikipedia.org/wiki/Gecko_(software)
https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox
https://blog.mozilla.org/futurereleases/2016/08/02/whats-next-for-multi-process-firefox/
2018年2月28日 星期三
[Tool] 初探Katalon Recorder搭配MSTest建構Web UI測試教學
前言:
近幾年來,測試成為顯學,去年3月在.Net社群研討會,
見識到91哥介紹了3套測試工具,最有印象的就是Selenium IDE了,
直到近期才研究使用,以下是我的使用紀錄。
正文開始:
首先,Selenium IDE是Firefox的附加元件(add-on)裡的擴充套件(Extension),
可用來錄製、編輯、回放與偵錯測試。
遺憾的是,Selenium IDE只支援到Firefox 54.x版,
因此,若要使用IDE,可使用54.0.1或以下的免安裝版本。
而且預設沒有C#+MSTest的範本(雖然可使用91哥的MSTest範本),
加上用舊版的Firefox難免有些不便,因此改用Katalon Recorder來試試。
先拿Chrome來測試,到chrome應用程式或google搜尋Katalon Recorder結果第一筆,
找到後,點選加到CHROME。
Chrome右上角應該會出現圖示,點擊打開Katalon Recorder,點選New,輸入Test Case's name。
打開要測試的網頁,再點選Record,使用錄製功能來產生測試腳本,
再微調Command,這樣的方式較快。
那什麼是Command呢? 以Selenese來講,有以下3種:
有以下6種方式(簡單範例):
只要點選Record,可自動產生上述說的Command、Target與Value(It depends.)
以下是實際錄製的過程。另assertText等Command,跟Selenium一樣,
在元素上右鍵,就可看到相關Command。
產出測試腳本後,可以再微調command,例:將click 表單欄位的command移除。
接著可將測試腳本Export出來,選擇要跑測試的語言與測試framework,
本例選擇C#+MSTest,即可將產生的程式碼貼進專案,
如果點選Save As File...,會以Test case名稱去掉底線命名,再加進專案即可。
另外,Katalon儲存是以Test Suite為單位。Export則是Test Case為單位。
測試專案需參考3個package:
to be continued...
參考資料:
https://addons.mozilla.org/zh-TW/firefox/addon/selenium-ide/
https://www.guru99.com/locators-in-selenium-ide.html
近幾年來,測試成為顯學,去年3月在.Net社群研討會,
見識到91哥介紹了3套測試工具,最有印象的就是Selenium IDE了,
直到近期才研究使用,以下是我的使用紀錄。
正文開始:
首先,Selenium IDE是Firefox的附加元件(add-on)裡的擴充套件(Extension),
可用來錄製、編輯、回放與偵錯測試。
遺憾的是,Selenium IDE只支援到Firefox 54.x版,
因此,若要使用IDE,可使用54.0.1或以下的免安裝版本。
加上用舊版的Firefox難免有些不便,因此改用Katalon Recorder來試試。
先拿Chrome來測試,到chrome應用程式或google搜尋Katalon Recorder結果第一筆,
找到後,點選加到CHROME。
Chrome右上角應該會出現圖示,點擊打開Katalon Recorder,點選New,輸入Test Case's name。
打開要測試的網頁,再點選Record,使用錄製功能來產生測試腳本,
再微調Command,這樣的方式較快。
那什麼是Command呢? 以Selenese來講,有以下3種:
- Actions:對element操作,常見的有type、open、clickAndWait等。
- Accessors:儲存element的value。
- Assertions:驗證測試的條件是否滿足。有Assert、Verify與WaitFor,其中Assert跟Verify差異在Assert fail,則測試中斷,Verify則是log起來繼續執行。
有以下6種方式(簡單範例):
| Locator | Html | Target |
| ID | <input id="email"> | id=email |
| Name | <input name="email"> | name=email |
| Link Text | <a href="#">Mail</a> | link=Mail |
| CSS Selector | <input class="form-group"> <span data-toggle="collapse"></span> |
css=input.form-group css=span[data-toggle=collapse] |
| DOM | <input id="email"> | document.getElementById("email") |
| XPath | <html><body> <table> <tr> <td>123</td> <td>456</td> </tr> </table> </body></html> |
//html/body/table/tr/td[2] |
只要點選Record,可自動產生上述說的Command、Target與Value(It depends.)
以下是實際錄製的過程。另assertText等Command,跟Selenium一樣,
在元素上右鍵,就可看到相關Command。
產出測試腳本後,可以再微調command,例:將click 表單欄位的command移除。
接著可將測試腳本Export出來,選擇要跑測試的語言與測試framework,
本例選擇C#+MSTest,即可將產生的程式碼貼進專案,
如果點選Save As File...,會以Test case名稱去掉底線命名,再加進專案即可。
另外,Katalon儲存是以Test Suite為單位。Export則是Test Case為單位。
測試專案需參考3個package:
- Selenium.WebDriver
- Selenium.Support
- Selenium.WebDriver.ChromeDriver(若使用Chrome測試,預設是Firefox)
to be continued...
參考資料:
https://addons.mozilla.org/zh-TW/firefox/addon/selenium-ide/
https://www.guru99.com/locators-in-selenium-ide.html
2017年12月26日 星期二
[.Net Tool] 使用bootstrap-multiselect實作帶checkbox與optGroup的DropDownList(使用ajax with json或model binding)
說明:
下拉式選單(DropDownList)是網頁常見的元素(element),
如果要做出帶有群組(optGroup),且項目前都有核取方塊(checkbox),如下圖:
該怎麼做呢?
這裡使用bootstrap-multiselect套件,使用BSD 3-Clause License,
商業使用須加上作者的版權聲明。
本例資料來源從Localdb產生,table為Country,如下圖:
並將資料整理成bootstrap-multiselect需要之格式,
如:
var data = [{
label: 'America',
children: [{ label: 'Canada', value: '2042101B-A6AF-4603-B04A-10D9257821CD' },
{ label: 'American', value: '7E35A8AC-D415-4ED3-B572-725ACF7074FB' }]
}, {
label: 'Asia',
children: [{ label: 'Taiwan, ROC', value: 'EB9A0E48-A541-430B-AAEA-C85D98DBA384' },
{ label: 'Singapore', value: '0A5D7D14-9698-4AAA-B3F6-586EED21D0CD' }]
}];
定義GetAll方法來做這件事:
以下兩張圖是自訂的類別(轉換成Json用)
Controller部分,其中LoadAllCountries取得CountryService中的GetAll,
Update則是取得選擇的country Ids:
View部分,使用jQuery.ajax取得下拉式選單的資料,
使用form表單post選擇的country Ids:
呈現結果:
送出表單後,可取得多選的Ids:
to be continued...
下拉式選單(DropDownList)是網頁常見的元素(element),
如果要做出帶有群組(optGroup),且項目前都有核取方塊(checkbox),如下圖:
該怎麼做呢?
這裡使用bootstrap-multiselect套件,使用BSD 3-Clause License,
商業使用須加上作者的版權聲明。
本例資料來源從Localdb產生,table為Country,如下圖:
並將資料整理成bootstrap-multiselect需要之格式,
如:
var data = [{
label: 'America',
children: [{ label: 'Canada', value: '2042101B-A6AF-4603-B04A-10D9257821CD' },
{ label: 'American', value: '7E35A8AC-D415-4ED3-B572-725ACF7074FB' }]
}, {
label: 'Asia',
children: [{ label: 'Taiwan, ROC', value: 'EB9A0E48-A541-430B-AAEA-C85D98DBA384' },
{ label: 'Singapore', value: '0A5D7D14-9698-4AAA-B3F6-586EED21D0CD' }]
}];
定義GetAll方法來做這件事:
以下兩張圖是自訂的類別(轉換成Json用)
Controller部分,其中LoadAllCountries取得CountryService中的GetAll,
Update則是取得選擇的country Ids:
View部分,使用jQuery.ajax取得下拉式選單的資料,
使用form表單post選擇的country Ids:
呈現結果:
to be continued...
2016年11月16日 星期三
[.NET/Tool] Common.Logging與Log4Net的差異與使用
問題:
專案中會看到同時參考Common.Logging與Log4Net,Log4Net就可以輸出Log了,為何還需要Common.Logging?
說明:
專案是可以互相參考的,專案A使用Log4Net輸出log,專案B卻使用NLog,造成管理的不便,
此時使用Common.Logging可提供抽象,由Log4net等dll來實作這些方法,
可以整合Log4net或NLog等.NET常見的Log函式庫,讓專案間可以使用Common.Logging來當Interface。
使用NuGet加入Common.Logging後,using與建立log物件,如下圖:
在app.config加入Logging Configuration後(使用ConsoleOutLoggerFactoryAdapter),
即可輸出Console。
搭配Log4Net,使用NuGet加入Common.Logging.Log4Net1215(目前最新),NuGet就會自動加入對應的Log4Net版本1.2.15(package version為2.0.5),
一樣需設定Logging Configuration,即可輸出Console。
Logging Configuration需注意的是:
● factoryAdapter內的type屬性,最後的Common.Logging.Log4Net1215版本號需正確。
● factoryAdapter下的ConfigType屬性設為INLINE表示讀取專案app.config內設定,若沒設定該屬性,則須有外部設定檔。
輸出外部log,可使用RollingFileAppender。
附上Log4Net版本號與NuGet package version對應,如1.2.15對應NuGet 2.0.5。
參考資料:
http://netcommon.sourceforge.net/docs/2.1.0/reference/html/ch01.html
http://logging.apache.org/log4net/release/config-examples.html
https://cmatskas.com/extend-the-common-logging-api-with-log4net/
https://github.com/net-commons/common-logging/wiki/Common.Logging-Packaging-and-Versions
http://stackoverflow.com/questions/37171988/nuget-package-version-not-matching-reference-version
專案中會看到同時參考Common.Logging與Log4Net,Log4Net就可以輸出Log了,為何還需要Common.Logging?
說明:
專案是可以互相參考的,專案A使用Log4Net輸出log,專案B卻使用NLog,造成管理的不便,
此時使用Common.Logging可提供抽象,由Log4net等dll來實作這些方法,
可以整合Log4net或NLog等.NET常見的Log函式庫,讓專案間可以使用Common.Logging來當Interface。
使用NuGet加入Common.Logging後,using與建立log物件,如下圖:
在app.config加入Logging Configuration後(使用ConsoleOutLoggerFactoryAdapter),
即可輸出Console。
搭配Log4Net,使用NuGet加入Common.Logging.Log4Net1215(目前最新),NuGet就會自動加入對應的Log4Net版本1.2.15(package version為2.0.5),
一樣需設定Logging Configuration,即可輸出Console。
Logging Configuration需注意的是:
● factoryAdapter內的type屬性,最後的Common.Logging.Log4Net1215版本號需正確。
● factoryAdapter下的ConfigType屬性設為INLINE表示讀取專案app.config內設定,若沒設定該屬性,則須有外部設定檔。
輸出外部log,可使用RollingFileAppender。
附上Log4Net版本號與NuGet package version對應,如1.2.15對應NuGet 2.0.5。
參考資料:
http://netcommon.sourceforge.net/docs/2.1.0/reference/html/ch01.html
http://logging.apache.org/log4net/release/config-examples.html
https://cmatskas.com/extend-the-common-logging-api-with-log4net/
https://github.com/net-commons/common-logging/wiki/Common.Logging-Packaging-and-Versions
http://stackoverflow.com/questions/37171988/nuget-package-version-not-matching-reference-version
2016年9月28日 星期三
[.NET] 使用HtmlAgilityPack與HAPXPathFinder擷取網頁上的資料
說明:
想擷取網頁上的資料,該怎麼做?
作法:
寫.NET的可以使用HtmlAgilityPack這個套件,可透過NuGet或自行下載後加入參考,
這裡以擷取統一發票中獎號碼為例(網址:http://invoice.etax.nat.gov.tw/)。
假設想擷取特別獎號碼,需要知道它的XPath,何謂XPath呢?
XPath(XML Path Languange),用來尋找XML檔案中的節點,詳細語法可參閱[1][2]。
一般而言,網頁的XPath大多像這樣:/html/body/div[1]/table[1]/tr[1]/td[1]。
找尋XPath主要有兩種方法:
● 檢視網頁原始碼來找尋XPath,或按F12開發者工具,再點選「選取元素」自行剖析XPath,Chrome可右鍵->Copy->Copy XPath。
● 使用HAPXPathFinder[3],曾自行剖析某網頁XPath,程式卻一直找不到該節點,使用HAPXPathFinder發現,該XPath沒有html tag...。因各瀏覽器產生的DOM有所差異,
使用HAPXPathFinder可以準確找出HtmlAgilityPack DOM的XPath。
打開HAPXPathFinder,在「Browser」tab輸入URL後,點選「DOM Tree」tab,
在TextBox輸入尋找內容->點選「Find」->點選右方Text區塊的搜尋結果
(->點選左方區塊的標示節點)->中間區塊即有節點的XPath(/#text[1]不需要)。如:
有了URL與XPath,就可以使用HtmlDocument.DocumentNode.SelectSingleNode方法的InnerText屬性來取得號碼了。 若找不到資料,可能是網頁編碼的問題,該網頁編碼使用UTF8。
參考資料:
[1] http://www.w3schools.com/xsl/xpath_syntax.asp
[2] https://msdn.microsoft.com/zh-tw/library/ms256086(v=vs.120).aspx
[3] https://hapxpathfinder.codeplex.com/
想擷取網頁上的資料,該怎麼做?
作法:
寫.NET的可以使用HtmlAgilityPack這個套件,可透過NuGet或自行下載後加入參考,
這裡以擷取統一發票中獎號碼為例(網址:http://invoice.etax.nat.gov.tw/)。
假設想擷取特別獎號碼,需要知道它的XPath,何謂XPath呢?
XPath(XML Path Languange),用來尋找XML檔案中的節點,詳細語法可參閱[1][2]。
一般而言,網頁的XPath大多像這樣:/html/body/div[1]/table[1]/tr[1]/td[1]。
找尋XPath主要有兩種方法:
● 檢視網頁原始碼來找尋XPath,或按F12開發者工具,再點選「選取元素」自行剖析XPath,Chrome可右鍵->Copy->Copy XPath。
● 使用HAPXPathFinder[3],曾自行剖析某網頁XPath,程式卻一直找不到該節點,使用HAPXPathFinder發現,該XPath沒有html tag...。因各瀏覽器產生的DOM有所差異,
使用HAPXPathFinder可以準確找出HtmlAgilityPack DOM的XPath。
打開HAPXPathFinder,在「Browser」tab輸入URL後,點選「DOM Tree」tab,
在TextBox輸入尋找內容->點選「Find」->點選右方Text區塊的搜尋結果
(->點選左方區塊的標示節點)->中間區塊即有節點的XPath(/#text[1]不需要)。如:
有了URL與XPath,就可以使用HtmlDocument.DocumentNode.SelectSingleNode方法的InnerText屬性來取得號碼了。 若找不到資料,可能是網頁編碼的問題,該網頁編碼使用UTF8。
參考資料:
[1] http://www.w3schools.com/xsl/xpath_syntax.asp
[2] https://msdn.microsoft.com/zh-tw/library/ms256086(v=vs.120).aspx
[3] https://hapxpathfinder.codeplex.com/
2016年8月21日 星期日
[.NET] 使用Sandcastle製作軟體help file的sample code
說明:
軟體開發完成後,需要有說明文件方便使用者查詢使用,
微軟有提供CHM(Compiled HTML)的說明檔格式,
使用Sandcastle與GhostDoc(professional版以上)都可產出CHM help file,
但如果還想要加入sample code的頁面,該如何做呢?
作法:
可使用Sandcastle Help File Builder(SHFB)這套免費工具,又是獨立的編譯器,使用上也較方便。
● 先下載SHFB(http://shfb.codeplex.com/)並安裝,安裝後執行Sandcastle Help File Builder GUI。
● 上方選單列->File->New Project->輸入專案名稱並存檔。
● 在右方Project Explorer中的Documentation Sources上右鍵,選擇Add Documentation Sources...,加入軟體專案編譯後的dll與XML,(專案XML需在專案屬性->建置->輸出區塊,勾選XML文件檔案)。
● 接著在專案名稱上右鍵->Add->New Item,加入Code Snippets與Content Layout,可更改檔名後存檔。
● 點開Code Snippets,將item的id改成需要的名稱,例:SampleCode#NewInstance, sampleCode tag中則是要顯示的程式碼,預設有C#與VB,<可用<、>用>取代。
● 點開Content Layout,點選主畫面左上角的Add a new topic as a sibling of the selected topic,選擇Standard Templates的Sample,填入檔名後Add。
● 點開上一步加入的Sample.aml,在section中的content tag中加上<codeReference>item id</codeReference>,例:
<section address="Section1">
<title>Create Instance</title>
<content>
<para>建立實體物件</para>
<codeReference>SampleCode#NewInstance</codeReference>
</content>
</section>
● 調整想要的Project Properties後,上方選單列->Documentation->Build Project,建置結束後,選單列->Documentation->View Help File即可。
參考資料:
http://www.codeproject.com/Articles/141766/Creating-documentation-for-a-NET-component-with-Sa
http://community.submain.com/blogs/tutorials/archive/2014/03/05/conceptual-content-adding-custom-content-to-api-documentation-created-with-ghostdoc.aspx
http://huan-lin.blogspot.com/2009/02/use-sandcastle-file-builder-to-create.html
http://www.codeproject.com/KB/XML/csharpcodedocumentation.aspx?msg=1679454
軟體開發完成後,需要有說明文件方便使用者查詢使用,
微軟有提供CHM(Compiled HTML)的說明檔格式,
使用Sandcastle與GhostDoc(professional版以上)都可產出CHM help file,
但如果還想要加入sample code的頁面,該如何做呢?
作法:
可使用Sandcastle Help File Builder(SHFB)這套免費工具,又是獨立的編譯器,使用上也較方便。
● 先下載SHFB(http://shfb.codeplex.com/)並安裝,安裝後執行Sandcastle Help File Builder GUI。
● 上方選單列->File->New Project->輸入專案名稱並存檔。
● 在右方Project Explorer中的Documentation Sources上右鍵,選擇Add Documentation Sources...,加入軟體專案編譯後的dll與XML,(專案XML需在專案屬性->建置->輸出區塊,勾選XML文件檔案)。
● 接著在專案名稱上右鍵->Add->New Item,加入Code Snippets與Content Layout,可更改檔名後存檔。
● 點開Code Snippets,將item的id改成需要的名稱,例:
● 點開Content Layout,點選主畫面左上角的Add a new topic as a sibling of the selected topic,選擇Standard Templates的Sample,填入檔名後Add。
● 點開上一步加入的Sample.aml,在section中的content tag中加上<codeReference>item id</codeReference>,例:
<section address="Section1">
<title>Create Instance</title>
<content>
<para>建立實體物件</para>
<codeReference>SampleCode#NewInstance</codeReference>
</content>
</section>
● 調整想要的Project Properties後,上方選單列->Documentation->Build Project,建置結束後,選單列->Documentation->View Help File即可。
參考資料:
http://www.codeproject.com/Articles/141766/Creating-documentation-for-a-NET-component-with-Sa
http://community.submain.com/blogs/tutorials/archive/2014/03/05/conceptual-content-adding-custom-content-to-api-documentation-created-with-ghostdoc.aspx
http://huan-lin.blogspot.com/2009/02/use-sandcastle-file-builder-to-create.html
http://www.codeproject.com/KB/XML/csharpcodedocumentation.aspx?msg=1679454
2013年12月17日 星期二
[.NET] .NET Reflector
說明:可用來反組譯.NET平台的dll或exe,目前最新版本為Reflector 8。
官網:http://www.red-gate.com/products/dotnet-development/reflector/
參考資料:
http://mitblog.pixnet.net/blog/post/34558377-.net-reflector-7
官網:http://www.red-gate.com/products/dotnet-development/reflector/
參考資料:
http://mitblog.pixnet.net/blog/post/34558377-.net-reflector-7
訂閱:
文章 (Atom)























