先前介紹過用單一 .aspx 檔在線上環境測試 MSDTC 是否正常的小技巧,今天使用時發現一個小缺點:當有多台 SQL 需要驗證分散式交易功能,因為連線字串寫死在程式裡,需反覆修改程式儲存重測,有點麻煩。

順手把 DTC 測試程式改寫成通用版,將連線字串裡的 Data Source (SQL IP)、User Id、Password 改為人工輸入,如此,部署一次程式即可測完多台 SQL。

若測試成功將顯示測試時間、兩次 GetDate() 內容、 Transaction.Current.TransactionInformation 的 LocalIdentifier 及 DistributedIdentifier,DistributedIdentifier 為非空白 GUID 表示有成功啟用分散式交易。

失敗時則會顯示紅字錯誤訊息:

既然是單檔 .aspx,網頁,我直接用 ASP.NET WebForm TextBox、Button + Server Event 簡化程式碼,只花了一百行就搞定,WebForm 在某些應用場合還是很有優勢。

最後,評估安全性是一定要的:測試查詢寫死 SELECT GETDATE() AS D,不致有被注入 SQL 指令的風險,唯一風險來自被用來猜測帳號密碼(例如:試連限定該網站 IP 才能存取的 SQL 主機),為降低風險,我再多加了一層逾期鎖定,在程式碼寫死有效期限,逾時即無法使用。即使忘記刪除遺留在伺服器上,連線功能也會自動失效避免被拿來做壞事。當然,最嚴謹的做法還是每次測試完就從伺服器移除。

以下是完整程式碼,提供有類似需要的朋友參考:

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Transactions" %>
<script runat="server">
    const string expireTime = "201911161200";
    protected void btnTest_Click(object sender, EventArgs e)
    {
        try
        {
            if (DateTime.Now.ToString("yyyyMMddHHmm").CompareTo(expireTime) > 0)
                throw new Exception("Tool Expired");
            TestDtc(txtDataSource.Text, txtUserId.Text, txtPassword.Text);
        }
        catch (Exception ex)
        {
            preDisplay.Attributes["class"] = "err";
            preDisplay.InnerHtml = "**ERROR**\n" + ex.ToString();
        }
    }
    void TestDtc(string dataSrc, string uid, string pwd)
    {
        Action<string, string> validate =
            (v, n) => { if (string.IsNullOrEmpty(v)) throw new ArgumentException(n + " is null or empty."); };
        validate(dataSrc, "Data Source");
        validate(uid, "User Id");
        validate(pwd, "Password");
        var scsb = new SqlConnectionStringBuilder();
        scsb.DataSource = dataSrc;
        scsb.UserID = uid;
        scsb.Password = pwd;
        TestDtc(scsb.ConnectionString);
    }
    void TestDtc(string cnStr)
    {
        using (var tx = new TransactionScope())
        {
            var css = "normal";
            var sb = new StringBuilder();
            sb.AppendFormat("Test DTC at {0:HH:mm:ss.fff}\n", DateTime.Now);
            sb.AppendLine(querySqlServer(cnStr));
            sb.AppendLine(querySqlServer(cnStr));
            var txInfo = Transaction.Current.TransactionInformation;
            sb.AppendLine("Local Id = " + txInfo.LocalIdentifier);
            sb.AppendLine("Distributed Id = " + txInfo.DistributedIdentifier);
            if (txInfo.DistributedIdentifier != Guid.Empty)
            {
                sb.AppendLine("*** TEST PASSED ****");
                css = "pass";
            }
            preDisplay.Attributes["class"] = css;
            preDisplay.InnerHtml = sb.ToString();
            tx.Complete();
        }
    }
    private string querySqlServer(string cnStr)
    {
        cnStr =  cnStr.TrimEnd(';') + ";Application Name=" + Guid.NewGuid().ToString();
        using (SqlConnection cn = new SqlConnection(cnStr))
        {
            SqlCommand cmd = new SqlCommand("SELECT getdate() as D", cn);
            cn.Open();
            SqlDataReader dr = cmd.ExecuteReader();
            dr.Read();
            var res = "GetDate = " + dr["D"];
            cn.Close();
            return res;
        }
    }
</script>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>DTC Test</title>
    <style>
        pre { background-color: #eee; padding: 6px; }
        pre.pass { background-color: #c9e488; }
        pre.err { color: brown; }
    </style>
</head>
<body>
    <form runat="server">
        <table>
            <tr>
                <td>Data Source</td>
                <td><asp:TextBox runat="server" ID="txtDataSource"></asp:TextBox></td>
            </tr>
            <tr>
                <td>User Id</td>
                <td><asp:TextBox runat="server" ID="txtUserId"></asp:TextBox></td>
            </tr>
            <tr>
                <td>Password</td>
                <td><asp:TextBox runat="server" ID="txtPassword" TextMode="Password"></asp:TextBox></td>
            </tr>
        </table>
        <asp:Button ID="btnTest" Text="Test DTC" runat="server" OnClick="btnTest_Click" />
        <pre runat="server" id="preDisplay"></pre>
    </form>
</body>
</html>

A simple universal WebForm .aspx for testing distributed transaction to SQL servers.


Comments

Be the first to post a comment

Post a comment