首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> 關於ASP.NET

ASP.NET 物件成員資料繫結器實作:從 MonoRail 啟發的表單自動映射框架

     編輯:關於ASP.NET

背景動機

MonoRail(Castle Project 早期 MVC 框架)提供強大的 DataBinder:自動將 `Request.Form`/`QueryString` 依照慣例映射至物件屬性,支援巢狀物件、集合、型別轉換、驗證。ASP.NET WebForms 缺乏同等機制,開發者常手寫大量 `entity.Prop = Request["Prop"]` 程式碼。本文實作輕量級繫結器填補此缺口。

核心介面設計

// 型別轉換器介面
public interface IStringConverter {
    object ConvertTo(string value, out bool succeeded);
}

// 內建轉換器範例
public class Int32Converter : IStringConverter {
    public object ConvertTo(string value, out bool succeeded) {
        return int.TryParse(value, out int result) ? (succeeded = true, result) : (succeeded = false, 0);
    }
}

public class DateTimeConverter : IStringConverter {
    public object ConvertTo(string value, out bool succeeded) {
        return DateTime.TryParse(value, out DateTime result) ? (succeeded = true, result) : (succeeded = false, DateTime.MinValue);
    }
}

public class ByteArrayConverter : IStringConverter {  // HttpPostedFile → byte[]
    public object ConvertTo(string value, out bool succeeded) {
        // 實際實作需從 HttpContext.Current.Request.Files 取得
        succeeded = false; return null;
    }
}

繫結上下文與命名慣例

public class BindingContext {
    public HttpRequest Request { get; }
    public IDictionary Converters { get; } = new Dictionary();
    public string Prefix { get; set; }  // 如 "order."、"user.Address."

    public BindingContext(HttpRequest request) => Request = request;

    // 命名對應規則:支援 PropName、PrefixPropName、Prefix_PropName、Prefix.PropName
    public string[] GetPossibleKeys(string propertyName) {
        var keys = new List { propertyName };
        if (!string.IsNullOrEmpty(Prefix)) {
            keys.Add(Prefix + propertyName);
            keys.Add(Prefix + "_" + propertyName);
            keys.Add(Prefix + "." + propertyName);
        }
        return keys.ToArray();
    }
}

核心繫結演算法

public static class ObjectBinder {
    public static T BindObject(BindingContext ctx) where T : new() {
        var obj = new T();
        BindObject(ctx, obj);
        return obj;
    }

    public static void BindObject(BindingContext ctx, object target) {
        var props = target.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
                          .Where(p => p.CanWrite);

        foreach (var prop in props) {
            var keys = ctx.GetPossibleKeys(prop.Name);
            string rawValue = null;
            foreach (var key in keys) {
                rawValue = ctx.Request[key];
                if (rawValue != null) break;
            }
            if (rawValue == null) continue; // 未提供值,保留預設

            // 尋找轉換器
            if (ctx.Converters.TryGetValue(prop.PropertyType.FullName, out var converter) ||
                ctx.Converters.TryGetValue(prop.Name, out converter)) {
                if (converter.ConvertTo(rawValue, out bool ok) is object val && ok) {
                    prop.SetValue(target, val);
                }
            } else if (prop.PropertyType.IsPrimitive || prop.PropertyType == typeof(string) || prop.PropertyType == typeof(decimal)) {
                // 內建型別直接轉換
                var converted = Convert.ChangeType(rawValue, prop.PropertyType);
                prop.SetValue(target, converted);
            } else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) {
                // 巢狀物件遞迴繫結
                var nestedCtx = new BindingContext(ctx.Request) { Prefix = prop.Name + "." };
                var nestedObj = Activator.CreateInstance(prop.PropertyType);
                BindObject(nestedCtx, nestedObj);
                prop.SetValue(target, nestedObj);
            }
        }
    }
}

使用範例

// 註冊全域轉換器 (Application_Start)
BindingContext.GlobalConverters[typeof(DateTime).FullName] = new DateTimeConverter();
BindingContext.GlobalConverters["BirthDate"] = new DateTimeConverter(); // 指定屬性專用

// 頁面中使用
protected void btnSave_Click(object sender, EventArgs e) {
    var ctx = new BindingContext(Request);
    var order = ObjectBinder.BindObject(ctx);
    var customer = ObjectBinder.BindObject(ctx, "cust"); // Prefix="cust."
    OrderService.Save(order, customer);
}

進階功能擴展

  • 集合繫結:支援 `Order.Items[0].ProductId`、`Order.Items.Index.Quantity` 索引語法。
  • 驗證整合:繫結後執行 `Validator.TryValidateObject`,收集 `ValidationResult` 回顯錯誤。
  • ModelState 相容:輸出 `ModelStateDictionary` 供 MVC 風格驗證摘要使用。
  • 安全性:白名單 `BindAttribute` / `Include`/`Exclude` 防止過度張貼攻擊。

與 ASP.NET Core Model Binding 比較

總結

自訂物件繫結器以約定優於配置、介面導向型別轉換、遞迴巢狀繫結,大幅減少手動賦值程式碼。雖 ASP.NET Core 內建更強大的模型繫結,但在維護舊版 WebForms 專案、漸進式遷移、特殊格式解析(如舊版加密參數)場景仍具實用價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
特性自訂繫結器ASP.NET Core Model Binding
架構整合獨立庫,需手動呼叫內建管道,自動執行
型別轉換IStringConverter 介面IModelBinder / TypeConverter
驗證手動整合 DataAnnotations內建 ModelState + DataAnnotations
巢狀/集合遞迴實作,支援基本語法完整支援複雜巢狀、字典、陣列
效能反射為主,可快取 PropertyInfo編譯運算式樹,極高效能