2019年1月16日

EPPlus讀取 xlsx,以 ExcelRange取值疑雲

客戶反應我們提供的報表表格有錯
最後追蹤到了是以 EPPlus讀取來源檔案 xlsx時,取 head row的範圍有錯

先看看兩個檔案的內容
測試用:


客戶用:



經由這段程式碼取 header row的內容:
// sheet.Cells[1,1,1,5]
foreach (var cell in sheet.Cells[sheet.Dimension.Start.Row, sheet.Dimension.Start.Column, sheet.Dimension.End.Row, sheet.Dimension.Start.Column])
{
    result.Add(GetAttributeModel(cell));
}

但是測試用檔案在 foreach執行取得的第一個 cell總是 "B1",客戶用檔案的 cell卻是 "A1"
到底是 EPPlus有問題還是檔案有問題 ...

因為追蹤原始碼工程浩大,決定先從檔案著手,解壓縮來比對看看
馬上抓到疑點!!

檔案位置:\{檔名}\xl\worksheets\sheet1.xml

很明顯在第 18行有鬼,左側是測試用檔案,因為沒有框線,在 xml內乾脆直接不存在了

好樣的 ...
因為我們自己測試用檔案都是 key完值就拿去上傳了,框線什麼的才沒空加呢,以至於之前完全沒碰到此種情境,也不知道程式其實是有 bug的,只是運氣不好才沒碰到

2018年10月16日

MvcSiteMapProvider PreservedRouteParameters於麵包屑的使用

在使用 MvcSiteMapProvider套件產生麵包屑時,如果使用 PreservedRouteParameters增加參數,則在子節點的 url中需要附帶相同的參數,麵包屑中父節點的 url才會帶入。 '

舉例,現在有一個父節點 /Order/List?groupId=1,一個子節點 /Order/Detail?orderId=3,程式需要這樣寫:

DynamicNodeProvider:
DynamicNode parentNode = new DynamicNode
{
    Title = "訂單列表",
    Key = "NodeA",
    Controller = "Order",
    Action = "List"
};

parentNode.PreservedRouteParameters.Add("groupId");

DynamicNode childNode = new DynamicNode
{
    Title = "訂單明細",
    ParentKey = "NodeA"
    Key = "NodeB",
    Controller = "Order",
    Action = "Detail"
};

OrderController:
public class OrderController : Controller
{
    public ActionResult List(int groupId)
    {
        ...
    }

    public ActionResult List(int orderId, int groupId)
    {
        ...
    }
}

Order List HTML:
<a href="/Order/Detail?orderId=3&groupId=1" ></a>

2018年3月22日

C# lock (typeof (Object)) CA2002

在 IBatisNet內看到這樣的用法:
lock (typeof (SqlMapper))
{
  ...
}

程式碼分析給出了 CA2002
不知道為什麼會寫出 lock typeof object的東西,這樣真的能鎖住東西?

C# 巢狀 Using 的程式碼分析警告 CA2202

使用 Visual Studio的程式碼分析(Code Analysis)功能,如果程式碼中有使用巢狀 using時:
using (StreamReader sr = new StreamReader(inputStream))
{
  using (CsvReader csvReader = new CsvReader(sr))
  {
    ..
  }
}
就會出現警告 CA2202:不要多次處置物件的 Dispose方法

但是在 MSDN的這篇 IDisposable.Dispose Method ()說明中寫道:如果一個物件被多次呼叫 Dispose方法,第一次之後的呼叫都會被忽略

 If an object's Dispose method is called more than once, the object must ignore all calls after the first one. The object must not throw an exception if its Dispose method is called multiple times. Instance methods other than Dispose can throw an ObjectDisposedException when resources are already disposed.

所以實際使用上是沒問題的
但是這樣在 Code Analysis就會卡著一個甚至多個警告訊息,看了很礙眼
那就在 function上加一個 SuppressMessage,讓這個警告不會再出現
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
public byte[] FunctionName(...) {

好啦這是在 StackOverflow上挖出來的解法
但是討論串內要直接用 SuppressMessage,還是用醜一點的寫法解掉這個訊息,還是有很多爭論

2018年1月10日

[JS] 字串轉數字使用 *1比 parseInt快

先驗證 parseInt和 *1轉化後的結果是否相同:
var strNum = "10000"
var parseNum = parseInt(strNum)
var multiplyNum = strNum * 1
typeof strNum
// "string"
typeof parseNum
// "number"
typeof multiplyNum
// "number"
parseNum
// 10000
multiplyNum
// 10000

接著比較兩種方式的效能
function testA(time, str) {
    for (i=0;i<time;++i) {
        var tmp = parseInt(str);
    }
}
function testB(time, str) {
    for (i=0;i<time;++i) {
        var tmp = str * 1;
    }
}

console.time('testA');
testA(10000000, "12345678");
console.timeEnd('testA');
// testA: 1226.073974609375ms

console.time('testB');
testB(10000000, "12345678");
console.timeEnd('testB');
// testB: 622.14208984375ms

console.time('testA');
testA(10000000, "82736491");
console.timeEnd('testA');
// testA: 1202.213134765625ms

console.time('testB');
testB(10000000, "82736491");
console.timeEnd('testB');
// testB: 619.800048828125ms

console.time('testA');
testA(10000000, "1");
console.timeEnd('testA');
// testA: 95.97900390625ms

console.time('testB');
testB(10000000, "1");
console.timeEnd('testB');
// testB: 67.47216796875ms

console.time('testA');
testA(10000000, "");
console.timeEnd('testA');
// testA: 719.873779296875ms

console.time('testB');
testB(10000000, "");
console.timeEnd('testB');
// testB: 235.06103515625ms

console.time('testA');
testA(10000000, "0");
console.timeEnd('testA');
// testA: 92.878173828125ms

console.time('testB');
testB(10000000, "0");
console.timeEnd('testB');
// testB: 74.76416015625ms

2017年12月26日

系統需求

Front-End 前端

  • 瀏覽器/版本
  • UI (framework、css)
  • 元件
        Datepicker
        下拉選單 (多選/搜尋)
        表格 (排序/編輯/分頁)
        分頁
        Popup Window
  • 輸入驗證/邊際值/顯示最大數量(筆數)
  • layout/partial view
        動態增減輸入區塊/欄位 (含驗證)
  • RWD
  • Report 報表
        Crystal Report
        SQL Server Reporting Services
  • 匯出檔案
        Excel
        Word (ContentControll替換文字)
        PDF (word轉 pdf)
  • Search Engine Optimization (SEO)
  • Logging all API/WCF/WebService called records

Middleware 中介

  • GraphQL
  • Message Queue (Apache Kafka、Redis<NoSQL DB>)

Backend 後端

  • Membership
  • Single Sign-On
  • Session:Read-only、Read/Write
  • ORM (SQL mapper Object)

Database 資料庫

  • Relational Database Management System
        Index
  • NoSQL
  • Read-Only DB、read/write DB

Server 伺服器

  • 前端、後端、資料庫
  • 數量
  • 環境
        Dev
        SIT
        UAT

Common 共通

  • Coding Style
  • 版本控制
        SVN
        Team Foundation Server
        Git
  • Issue Tracking System
        Team Foundation Service
        Redmine
  • 測試環境
        Dev
        SIT
        UAT
  • Migration
    功能測試 for 新資料 (SIT、UAT)
    功能測試 for 舊資料 (SIT、UAT)
  • Log
        to File
        to DB
        敏感資料 (個資)
  • Deploy (CI、CD ...)
        Rollback計畫
  • Security
        XSS (前端)
        SQL injection (後端)
        Authenticator 權限控管
        https
        Encrypt/de 加密/解密
            MD5
            RSA
        Exception Handler
  • 文件
        Word
        Excel
        PDF
  • 難字集
  • 效能要求
  • 批次作業
        Archive Log files

2017年5月17日

[CrystalReport] Crystal Report 視窗大小設定

使用者提出 Crystal Report視窗太小,看資料不方便,希望能放大
當初開發時雖然也這麼覺得,但是那時 google了相關的資料找不到解決方案,就先放著不管
現在使用者提出來就逃不掉了
但是一樣 google了一整天,都沒有滿意的方案,無法從 Crystal Report的元件或設定等地方做調整
最後索性使用 js直接調整視窗大小
done

(該不會就是因為解決方案太簡單所以網路上都沒人詢問或分享吧  囧)

window.resizeTo(screen.width, screen.height)

2017年3月31日

小工具開發紀錄

紀錄在工作上為了哪些事情個別寫程式去解決或縮短工作時間:
  • 台新 SPTS
    使用 iBatis.NET + log4net以文字檔儲存 sql紀錄,因為是用參數傳入的語法,若要人工剪下貼上參數,數量一多就很費時費力

    => 寫一支 iBatisLogParser,複製 iBatis的 log貼上,自動轉換成參數已經代入好的 sql句,本質上就是字串內容的辨識及取代
    .
  • 台新 SPTS
    開發環境封閉,無法連外網,有時需要看 Json格式但沒排版過的 log會很痛苦

    => 寫一支 JsonPrettyOffline,排版美化 Json格式的 log,易於閱讀
    .
  • 台新 SPTS
    檢查以 xml格式儲存 key/value的 wording,避免 key值重複取錯值

    => 寫一支 MessageListChecks,自動檢查
    .
  • 台新 SPTS
    每天要從 tfs抓取當天的 bug、CR數量等紀錄,並統計各 PG每天完成哪些 bug、CR

    => 寫一支 WorkItemTracer,自動產生統計表格及產生圖表
    .
  • 遠通 eTag Portal

    => 改寫以 vbs開發的壓縮 txt log程式

2017年1月16日

MS SQL專案 產生 script後使用 sqlcmd執行

利用 SQL專案的結構描述比較功能,在比較並產生指令碼後,產出的結果無法直接在 SSMS上直接執行
大概是長這個樣子
/*
TestDB 的部署指令碼

這段程式由工具產生。
...
*/
...
:setvar DatabaseName "TestDB"
:setvar DefaultFilePrefix "TestDB"
...

/* 偵測 SQLCMD 模式,如果不支援 SQLCMD 模式,則停用指令碼執行。
...
*/
...

使用 sqlcmd執行此 script的方式:
開啟 cmd,輸入下方指令後再按 Enter
sqlcmd -S TestDB -U userId -P password -i C:\TestDB_Update.publish.sql -o C:\output.txt

2016年12月5日

[VS] Visual Studio 更新服務參考過久的問題

開發的方案裡面有 FE、AP兩個專案
AP內有 N個 WCF給 FE使用
更新 FE的 Service Reference時會更新很久,甚至看起來跟當機有 87分像

最後找到問題是 FE專案參考的某些 dll會造成此情況 
此次案例的問題點是 FE有參考 DocumentFormat.OpenXml.dll

解決方式可以在設定 Service Reference時取消勾選重複使用所有組件
改成一個一個勾選且避開 DocumentFormal.OpenXml.dll

或是每次更新 Service Reference前先移除參考 DocumentFormal.OpenXml.dll,更新完再加入

2016年10月19日

金額 format string

var result = '0,0.' + Array(decimalPlace + 1).join('0')
result = result.replace(/\.\s*$/, "");
decimalPlace 0: "0,0"
decimalPlace 1: "0,0.0"
decimalPlace 2: "0,0.00"

參考:

Repeat Character N Times

2016年10月13日

懸浮按鈕 & 自動填入表單

懸浮按鈕
<input type="button" value="Import" style="positionL fixed; z-index: 10000; top: 95%; left: 5px;" onclick="$('#importFile').click()" />
<input id="importFile" type="file" style="position: fixed; z-index: 10000l top: -100px; left: -100px;" onchange="Import(this)" />
function Import(fileInput) {
    var reader;
    if(window.File && window.FileReader && window.FileList && window.Blob) {
        reader = new FileReader();
    }
    else {
        return;
    }

    var txtContent;
    if(fileInput.files && fileInput.files[0]) {
        reader.onload = function (e) {
            txtContent = e.target.result;
            console.log(txtContent);

            // Custom Code
            // End of Custom Code
        }
        reader.readAsText(fileInput.files[0]);

        //Reset
        fileInput.value = '';
    }
}

2016年9月10日

VS 專案 檔案異動沒有自動出現在包含的變更(Pending Changes)

前一陣子應客戶需求
獨立負責令一個專案開發
架構要跟原本開發的案子一樣
所以我直接複製整個專案,改名,再做一些加工後就丟上 TFS

但是修改過的檔案都不會自動出現在包含的變更(Pending Changes)清單
讓人困擾

解決方式:
檔案 → 原始檔控制 → 進階 → 變更原始檔控制 → 全選 繫結 → 簽入

2016年7月21日

Bootstrap modal prevent close (datepicker input in modal)

有個功能是 Bootstrap modal內有 datepicker input,還有一個 OK按鈕
如果不是按 OK就把 modal關閉的話,modal中輸入欄位的值要還原
如果是按 OK關閉 modal的話,檢核輸入值,檢核未過則 modal取消關閉

搜尋取消關閉 modal的範例後寫出大致如下的程式:
$('.modal').on('hide.bs.modal', function (e) {
  if (CheckClosedByBtnOK()) {
    if (!ValidInput()) {
      e.preventDefault();
      e.stopImmediatePropagation();
    }
  }
  else {
    ResetInput();
  }
});
但是發現使用 datepicker選完日期並關閉小視窗後也會觸發 hide.bs.modal,導致每次選完值就還原,有選跟沒選一樣

Bootstrap modal的官方文件中有說明 events還有一種 hidden.bs.modal,直接拿來套用,改成
$('.modal').on('hidden.bs.modal', function (e) { ...
現在不會選完值就還原了,也就是 hidden.bs.modal只有在 modal關閉時才會觸發,datepicker關閉時不會
但是另一個問題又來了,preventDefault、stopImmediatePropagation現在無效了

因為英文不好,對 Bootstrap modal官方文件的 Events說明琢磨了一會還是看不懂差異在哪:
hide.bs.modalThis event is fired immediately when the hide instance method has been called.
hidden.bs.modalThis event is fired when the modal has finished being hidden from the user (will wait for CSS transitions to complete).

只好寫程式來觀察:
$('.modal').on('hide.bs.modal', function (e) {
  console.log(this);
  console.log('hide');
}).on('hidden.bs.modal', function (e) {
  console.log(this);
  console.log('hidden');
});
得出 hidden.bs.modal是在 modal被關閉後才觸發,所以 preventDefault當然沒有效果
hide.bs.modal是關閉前觸發,但是 modal內的 datepicker關閉時也會觸發 hide.bs.modal

所以改成先判斷 hide.bs.modal是否為 datepicker被關閉時觸發即可

var isUsingDatepicker = false;

$('.modal').on('show.bs.modal', function (e) {
  isUsingDatepicker = ($(div.datepicker-dropdown).length > 0);
}).on('hide.bs.modal', function (e) {
  if (!isUsingDatepicker) {
    if (CheckClosedByBtnOK()) {
      if (!ValidInput()) {
        e.preventDefault();
        e.stopImmediatePropagation();
      }
    }
    else {
      ResetInput();
    }
  }
  isUsingDatepicker = ($(div.datepicker-dropdown).length > 0);
});