整理 UOF WKF 外掛欄位整合 ERP 多筆明細資料至 Grid 列表的完整實作流程與開發規範,適用於 C# 5.0 (.NET Framework 4.5 包含) 開發環境
一、 架構與開發規範說明
在 UOF WKF 外掛欄位(VersionFieldUserControl_VersionFieldUC)開發中,自訂單身明細(Grid)的核心運作機制如下:
- 資料持久化機制:頁面透過隱藏的
txtFieldValue容器存取 XML 結構。表單送出與儲存時,UOF 框架會讀取FieldValue屬性回傳的 XML 字串。 - ERP 明細匯入流程:
- 透過 SQL 查詢取得 ERP
PURTA/PURTB資料集(DataTable)。 - 解析目前
txtFieldValue中的 XML。 - 將 ERP 多筆 DataRow 轉化為
<Item>節點批次寫入 XML。 - 呼叫
BindGrid()解析 XML 並綁定至前端Ede:Grid。
- 透過 SQL 查詢取得 ERP
- C# 5.0 相容限制:
- 不可使用 Null 條件運算子 (
?.):須改用傳統三元運算子(attr != null) ? attr.Value : string.Empty。 - 不可使用字串插值 (
$"..."):須改用string.Format(...)。 - DataRow 參數比對:
dt.Rows.Add()傳入的參數數量必須與DataTable.Columns欄位數量完全相等。
- 不可使用 Null 條件運算子 (
二、 前端頁面設計 (OptionField_NEW_PURTAB.ascx)
HTML
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="OptionField_NEW_PURTAB.ascx.cs" Inherits="WKF_OptionalFields_OptionField_NEW_PURTAB" %>
<%@ Reference Control="~/WKF/FormManagement/VersionFieldUserControl/VersionFieldUC.ascx" %>
<%@ Register Assembly="Ede.Uof.Utility.Component.Grid" Namespace="Ede.Uof.Utility.Component" TagPrefix="Ede" %>
<style type="text/css">
.tb-box { width: 100%; border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; border-radius: 4px; }
.tb-header { background-color: #f5f5f5; font-weight: bold; padding: 5px; margin-bottom: 8px; }
.tb-table { width: 100%; border-collapse: collapse; }
.tb-table td { padding: 4px 8px; }
</style>
<!-- ERP 查詢與手動新增區 -->
<div class="tb-box">
<div class="tb-header">明細資料操作</div>
<table class="tb-table">
<tr>
<td style="width: 50%;">
<strong>ERP 單號查詢:</strong><br />
<asp:TextBox ID="TextBox1" runat="server" Width="200px"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="載入 ERP 明細" OnClick="Button1_Click" />
<asp:Label ID="Label2" runat="server" ForeColor="Green"></asp:Label>
</td>
<td style="width: 50%;">
<strong>手動新增 TB005:</strong><br />
<asp:TextBox ID="txtTB005Input" runat="server" Width="200px"></asp:TextBox>
<asp:Button ID="btnAddDetail" runat="server" Text="新增" OnClick="btnAddDetail_Click" />
<asp:Button ID="btnDeleteDetail" runat="server" Text="刪除勾選列" OnClick="btnDeleteDetail_Click" />
</td>
</tr>
</table>
</div>
<!-- Grid 明細展示區 -->
<div class="tb-box">
<Ede:Grid ID="Grid1" runat="server" DataKeyNames="ID" AutoGenerateColumns="false" Width="100%">
<Columns>
<asp:BoundField HeaderText="ID" DataField="ID" Visible="false" />
<asp:BoundField HeaderText="單別 (TA001)" DataField="TA001" HeaderStyle-Width="15%" />
<asp:BoundField HeaderText="單號 (TA002)" DataField="TA002" HeaderStyle-Width="20%" />
<asp:BoundField HeaderText="序號 (TB003)" DataField="TB003" HeaderStyle-Width="15%" />
<asp:BoundField HeaderText="品號 (TB004)" DataField="TB004" HeaderStyle-Width="25%" />
<asp:BoundField HeaderText="品名/規格 (TB005)" DataField="TB005" HeaderStyle-Width="25%" />
</Columns>
</Ede:Grid>
</div>
<!-- 訊息提示與 XML 持久化儲存控制項 -->
<div id="MESSAGES">
<asp:Label ID="lblMessage" runat="server" ForeColor="Red"></asp:Label>
</div>
<div style="display: none;">
<asp:TextBox ID="txtFieldValue" runat="server" TextMode="MultiLine"></asp:TextBox>
</div>
<!-- UOF 框架標準控制項 -->
<asp:Label ID="lblHasNoAuthority" runat="server" Text="無填寫權限" ForeColor="Red" Visible="False"></asp:Label>
<asp:Label ID="lblToolTipMsg" runat="server" Text="不允許修改(唯讀)" Visible="False"></asp:Label>
<asp:Label ID="lblModifier" runat="server" Visible="False"></asp:Label>
三、 後端邏輯實作 (OptionField_NEW_PURTAB.ascx.cs)
C#
using System;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml.Linq;
using Ede.Uof.WKF.Design;
using Ede.Uof.WKF.VersionFields;
public partial class WKF_OptionalFields_OptionField_NEW_PURTAB : WKF_FormManagement_VersionFieldUserControl_VersionFieldUC
{
#region ============== 公開屬性 ==============
private bool m_ShowGetValueButton = true;
public bool ShowGetValueButton
{
get { return this.m_ShowGetValueButton; }
set { this.m_ShowGetValueButton = value; }
}
#endregion
protected void Page_Load(object sender, EventArgs e)
{
SetField(m_versionField);
}
#region ============== UOF 框架標準覆寫方法 ==============
public override string ConditionValue => string.Empty;
public override bool IsModified => false;
public override string DisplayTitle => string.Empty;
public override string Message => string.Empty;
public override string RealValue
{
get => string.Empty;
set => base.m_fieldValue = value;
}
public override string FieldValue
{
get
{
if (string.IsNullOrEmpty(txtFieldValue.Text) || txtFieldValue.Text == "<FieldValue/>")
{
return "<FieldValue/>";
}
return txtFieldValue.Text;
}
set => base.m_fieldValue = value;
}
public override bool IsFirstTimeWrite
{
get => false;
set => base.IsFirstTimeWrite = value;
}
/// <summary>
/// 控制控制項啟用/停用(檢視/編輯/簽核模式)
/// </summary>
public void EnabledControl(bool enabled)
{
TextBox1.Enabled = enabled;
Button1.Enabled = enabled;
txtTB005Input.Enabled = enabled;
btnAddDetail.Enabled = enabled;
btnDeleteDetail.Enabled = enabled;
Grid1.Enabled = enabled;
}
/// <summary>
/// 初始化欄位狀態與綁定資料
/// </summary>
public override void SetField(VersionField versionField)
{
FieldOptional fieldOptional = versionField as FieldOptional;
if (fieldOptional != null)
{
if (string.IsNullOrEmpty(fieldOptional.FieldValue))
{
txtFieldValue.Text = "<FieldValue/>";
}
else
{
txtFieldValue.Text = fieldOptional.FieldValue;
BindGrid();
}
// 權限控制判斷
if (!fieldOptional.IsAudit)
{
EnabledControl(fieldOptional.HasAuthority);
}
else
{
if (fieldOptional.Filler != null)
{
bool isCurrentSiteFiller = base.taskObj != null &&
base.taskObj.CurrentSite != null &&
base.taskObj.CurrentSite.SiteId == fieldOptional.FillSiteId &&
fieldOptional.Filler.UserGUID == Ede.Uof.EIP.SystemInfo.Current.UserGUID;
EnabledControl(isCurrentSiteFiller ? fieldOptional.HasAuthority : fieldOptional.AllowModify);
}
else
{
EnabledControl(fieldOptional.HasAuthority);
}
}
if (fieldOptional.FieldMode == FieldMode.Print || fieldOptional.FieldMode == FieldMode.View)
{
EnabledControl(false);
}
if (fieldOptional.Modifier != null)
{
lblModifier.Visible = true;
lblModifier.ForeColor = System.Drawing.Color.FromArgb(0x52, 0x52, 0x52);
lblModifier.Text = System.Web.Security.AntiXss.AntiXssEncoder.HtmlEncode(fieldOptional.Modifier.Name, true);
}
}
}
#endregion
#region ============== ERP 資料查詢與 XML 操作邏輯 ==============
/// <summary>
/// 查詢 ERP PURTA / PURTB 表單
/// </summary>
public DataTable FIND_ERP_PURTA_PURTB(string TA002)
{
string connectionString = ConfigurationManager.ConnectionStrings["ERPconnectionstring"].ConnectionString;
Ede.Uof.Utility.Data.DatabaseHelper m_db = new Ede.Uof.Utility.Data.DatabaseHelper(connectionString);
string cmdTxt = @"
SELECT PURTA.TA001, PURTA.TA002, PURTB.TB003, PURTB.TB004, PURTB.TB005
FROM [TK].dbo.PURTA
INNER JOIN [TK].dbo.PURTB ON TA001 = TB001 AND TA002 = TB002
WHERE TA002 = @TA002";
m_db.AddParameter("@TA002", TA002);
DataTable dt = new DataTable();
dt.Load(m_db.ExecuteReader(cmdTxt));
return (dt != null && dt.Rows.Count > 0) ? dt : null;
}
/// <summary>
/// 解析 XML 並重新繫結 Grid (C# 5.0 相容)
/// </summary>
private void BindGrid()
{
if (string.IsNullOrEmpty(txtFieldValue.Text)) return;
XElement xe = XElement.Parse(txtFieldValue.Text);
DataTable dt = new DataTable();
dt.Columns.Add("ID");
dt.Columns.Add("TA001");
dt.Columns.Add("TA002");
dt.Columns.Add("TB003");
dt.Columns.Add("TB004");
dt.Columns.Add("TB005");
var items = xe.Elements("Item");
foreach (var item in items)
{
XAttribute attrId = item.Attribute("id");
XAttribute attrTA001 = item.Attribute("TA001");
XAttribute attrTA002 = item.Attribute("TA002");
XAttribute attrTB003 = item.Attribute("TB003");
XAttribute attrTB004 = item.Attribute("TB004");
XAttribute attrTB005 = item.Attribute("TB005");
string id = (attrId != null) ? attrId.Value : string.Empty;
string ta001 = (attrTA001 != null) ? attrTA001.Value : string.Empty;
string ta002 = (attrTA002 != null) ? attrTA002.Value : string.Empty;
string tb003 = (attrTB003 != null) ? attrTB003.Value : string.Empty;
string tb004 = (attrTB004 != null) ? attrTB004.Value : string.Empty;
string tb005 = (attrTB005 != null) ? attrTB005.Value : string.Empty;
// 參數個數必須與 DataTable 欄位數量一致 (6個)
dt.Rows.Add(id, ta001, ta002, tb003, tb004, tb005);
}
Grid1.DataSource = dt;
Grid1.DataBind();
}
/// <summary>
/// 按鈕事件:載入 ERP 多筆明細至 XML
/// </summary>
protected void Button1_Click(object sender, EventArgs e)
{
lblMessage.Text = string.Empty;
Label2.Text = string.Empty;
string TA002 = TextBox1.Text.Trim();
if (string.IsNullOrEmpty(TA002))
{
lblMessage.Text = "請輸入單號!";
return;
}
DataTable dt = FIND_ERP_PURTA_PURTB(TA002);
if (dt == null || dt.Rows.Count == 0)
{
lblMessage.Text = "查無對應的 ERP 單據資料!";
return;
}
XElement xe = (string.IsNullOrEmpty(txtFieldValue.Text) || txtFieldValue.Text == "<FieldValue/>")
? new XElement("FieldValue")
: XElement.Parse(txtFieldValue.Text);
int addedCount = 0;
foreach (DataRow row in dt.Rows)
{
// C# 5.0 安全轉型 (防範 DBNull 與 null)
string TA001Value = (row["TA001"] != DBNull.Value && row["TA001"] != null) ? row["TA001"].ToString().Trim() : string.Empty;
string TA002Value = (row["TA002"] != DBNull.Value && row["TA002"] != null) ? row["TA002"].ToString().Trim() : string.Empty;
string TB003Value = (row["TB003"] != DBNull.Value && row["TB003"] != null) ? row["TB003"].ToString().Trim() : string.Empty;
string TB004Value = (row["TB004"] != DBNull.Value && row["TB004"] != null) ? row["TB004"].ToString().Trim() : string.Empty;
string TB005Value = (row["TB005"] != DBNull.Value && row["TB005"] != null) ? row["TB005"].ToString().Trim() : string.Empty;
if (!string.IsNullOrEmpty(TB005Value))
{
XElement newItem = new XElement("Item",
new XAttribute("id", Guid.NewGuid().ToString()),
new XAttribute("TA001", TA001Value),
new XAttribute("TA002", TA002Value),
new XAttribute("TB003", TB003Value),
new XAttribute("TB004", TB004Value),
new XAttribute("TB005", TB005Value)
);
xe.Add(newItem);
addedCount++;
}
}
if (addedCount > 0)
{
txtFieldValue.Text = xe.ToString();
BindGrid();
Label2.Text = string.Format("已成功匯入 {0} 筆明細", addedCount);
}
else
{
lblMessage.Text = "查詢結果中的 TB005 欄位皆為空值!";
}
}
/// <summary>
/// 手動新增單筆資料
/// </summary>
protected void btnAddDetail_Click(object sender, EventArgs e)
{
lblMessage.Text = string.Empty;
string tb005Value = txtTB005Input.Text.Trim();
if (string.IsNullOrEmpty(tb005Value))
{
lblMessage.Text = "請輸入 TB005 資料!";
return;
}
XElement xe = (string.IsNullOrEmpty(txtFieldValue.Text) || txtFieldValue.Text == "<FieldValue/>")
? new XElement("FieldValue")
: XElement.Parse(txtFieldValue.Text);
XElement newItem = new XElement("Item",
new XAttribute("id", Guid.NewGuid().ToString()),
new XAttribute("TA001", "MANUAL"),
new XAttribute("TA002", "MANUAL"),
new XAttribute("TB003", "0000"),
new XAttribute("TB004", "MANUAL"),
new XAttribute("TB005", tb005Value)
);
xe.Add(newItem);
txtFieldValue.Text = xe.ToString();
txtTB005Input.Text = string.Empty;
BindGrid();
}
/// <summary>
/// 刪除 Grid 中被勾選的資料列
/// </summary>
protected void btnDeleteDetail_Click(object sender, EventArgs e)
{
string[] selectedIds = Grid1.GetSelectedRowGUIDs();
if (selectedIds == null || selectedIds.Length == 0) return;
XElement xe = XElement.Parse(txtFieldValue.Text);
foreach (string id in selectedIds)
{
XElement targetItem = xe.Elements("Item")
.FirstOrDefault(x => x.Attribute("id") != null && x.Attribute("id").Value == id);
if (targetItem != null)
{
targetItem.Remove();
}
}
txtFieldValue.Text = xe.ToString();
BindGrid();
}
#endregion
}
四、 常見開發錯誤與 Debug 排查清單
| 錯誤現象 | 常見原因 | 排查與修復方式 |
|---|---|---|
System.ArgumentException | dt.Rows.Add() 的引數數量與 DataTable.Columns 數量不吻合。 | 確認 dt.Columns.Add() 建立的欄位總數,必須與 dt.Rows.Add(...) 傳入的變數數量完全一致。 |
CS1061: 'XAttribute' does not contain a definition for '?' | 在舊版 Visual Studio / C# 5.0 環境使用了 C# 6.0 的 ?. 語法。 | 改用 (attr != null) ? attr.Value : string.Empty 判斷。 |
System.NullReferenceException 讀取資料庫 | 資料庫欄位回傳 NULL(DBNull.Value),直接使用 .ToString() 引發例外。 | 加入判斷式:(row["Col"] != DBNull.Value && row["Col"] != null)。 |
| 表單送出後 Grid 明細遺失 | 未將 XML 結構回填至 txtFieldValue.Text,或 FieldValue 屬性沒有回傳 txtFieldValue.Text。 | 確保每次對 XML 新增/刪除節點後都有執行 txtFieldValue.Text = xe.ToString()。 |
自我LV~