ASP.NET 內建 MaxLength 屬性僅限制字元數,但資料庫 varchar(n) 欄位限制的是位元組數。中文字在 UTF-8 佔 3 bytes、Big5/GB2312 佔 2 bytes,英文數字佔 1 byte。直接存入會導致截斷或例外。MaxLengthValidator 解決此問題:雙端驗證(伺服器+客戶端),支援非 Unicode 欄位位元組長度計算。
///
/// 計算字串在非 Unicode 欄位 (varchar/char) 的位元組長度
/// 中文字元計 2 bytes,英數字元計 1 byte
///
public virtual int GetLengthForNonNationnal(string text)
{
if (string.IsNullOrEmpty(text)) return 0;
int length = 0;
foreach (char c in text)
{
length++;
if (c > 255) length++; // 非 ASCII 字元視為雙位元組
}
return length;
}
[Description("對 MaxLength 進行驗證")]
[ToolboxData("<{0}:MaxLengthValidator runat=server></{0}:MaxLengthValidator>")]
public class MaxLengthValidator : BaseValidator
{
#region 屬性
[DefaultValue(true)]
public bool ValidateForNonNationalField
{
get => ViewState["ValidateForNonNationalField"] as bool? ?? true;
set => ViewState["ValidateForNonNationalField"] = value;
}
#endregion
#region 伺服器端驗證
protected override bool EvaluateIsValid()
{
var target = NamingContainer.FindControl(ControlToValidate) as TextBox;
if (target == null) return true;
int maxLength = target.MaxLength;
string value = GetControlValidationValue(ControlToValidate);
if (string.IsNullOrEmpty(value)) return true;
int actualLength = ValidateForNonNationalField
? GetLengthForNonNationnal(value)
: value.Length;
return actualLength <= maxLength;
}
#endregion
#region 客戶端腳本註冊
protected override void OnPreRender(EventArgs e)
{
string script = @"
function MaxLengthValidator_GetNonNationalLength(text) {
if (!text) return 0;
var length = 0;
for (var i = 0; i < text.length; i++) {
length++;
if (text.charCodeAt(i) > 255) length++;
}
return length;
}
function MaxLengthValidator_Validate(val) {
if (!val || !val.TargetControlID) return true;
var target = document.getElementById(val.TargetControlID);
if (!target) return true;
var maxLength = parseInt(val.maxlength, 10);
if (isNaN(maxLength)) return true;
var value = target.value;
if (!value) return true;
var realLength = val.ValidateForNonNationalField === 'true'
? MaxLengthValidator_GetNonNationalLength(value)
: value.length;
return realLength <= maxLength;
}";
ScriptManager.RegisterClientScriptBlock(this, GetType(),
"MaxLengthValidator_Validate", script, true);
base.OnPreRender(e);
}
#endregion
#region 渲染屬性傳遞給客戶端
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
if (RenderUplevel)
{
var target = NamingContainer.FindControl(ControlToValidate) as TextBox;
writer.AddAttribute("TargetControlID", GetControlRenderID(ControlToValidate));
writer.AddAttribute("ValidateForNonNationalField", ValidateForNonNationalField ? "true" : "false");
writer.AddAttribute("evaluationfunction", "MaxLengthValidator_Validate");
if (target != null) writer.AddAttribute("maxlength", target.MaxLength.ToString());
}
}
#endregion
}
<%@ Register Assembly="YourAssembly" Namespace="YourNamespace" TagPrefix="cc" %>
<asp:TextBox ID="txtTitle" runat="server" MaxLength="100" Columns="50" />
<cc:MaxLengthValidator
ID="valTitle"
runat="server"
ControlToValidate="txtTitle"
ValidateForNonNationalField="true"
ErrorMessage="標題長度超過限制 (中文算 2 個字元)"
Display="Dynamic"
CssClass="text-danger" />
| 資料庫欄位 | ValidateForNonNationalField | 說明 |
|---|---|---|
| varchar(n) / char(n) | true | 非 Unicode,中文佔 2 bytes (Big5/GBK) 或 3 bytes (UTF-8) |
| nvarchar(n) / nchar(n) | false | Unicode,每字元固定 2 bytes (UTF-16) |
| text / ntext | false | 通常不限長度,建議不驗證 |
若資料庫使用 UTF-8 編碼,中文佔 3 bytes,可繼承並覆寫計算方法:
public class Utf8MaxLengthValidator : MaxLengthValidator
{
public override int GetLengthForNonNationnal(string text)
{
if (string.IsNullOrEmpty(text)) return 0;
return Encoding.UTF8.GetByteCount(text); // 精確 UTF-8 位元組數
}
}
MaxLength (字元數上限) + Validator 驗證位元組長度ScriptManager.RegisterClientScriptBlock 自動去重ValidationGroup 配合送出按鈕MaxLengthValidator 以 30 行核心邏輯 解決了 ASP.NET 開發中極常見卻常被忽視的「字元數 vs 位元組數」不匹配問題。其設計亮點:繼承 BaseValidator 获得完整驗證生命週期、雙端同步邏輯避免繁瑕維護、可配置的計算模式適應不同資料庫編碼。建議納入專案共用控制項庫,所有涉及 varchar 欄位的表單統一套用。