asp.net|對象|資料
用過MonoRail的朋友應該知道它提供的對象成員資料繫結功能非常方便,通過標記參數屬性或方法就可以自動把提交回來的資料和對象成員進行綁定;有了這些方便的功能的確可以節省大量的set代碼。不過這些功能只是MonoRail提供,於是實作類別似的功能方便自己開發。
實現目標:可以靈活方便地實現資料繫結。
OrderSearch search = FormContext.BindObject<OrderSearch>();
Orders order = FormContext.BindObject<Orders>("order");
制定規則和約束
首先確定WEB提交的資料和成員屬性的映射關係,可以通過名稱約定的方式:
<input id="Text1" name="companyname" type="text" />
xxxx.LastName、xxxx_LastName或xxxxLastName等。在綁過程可以指定首碼進行對象成員的綁定;不過在webForm控制項的Name是asp.net產生的,在關係分析上就相對複雜些。
類型轉換介面的定義
因為轉換的情況是很難確定;除了。NET的基礎類型外實際應用中還會存在其他轉換方式,如:HttpPostedFile到byte[],序列化String到Object等。因此制定轉換介面就可以方便實現可擴充和可配置。
public interface IStringConverter
{
object ConvertTo(string value, out bool succeeded);
}
由於Web提供的資料大部份是以string的方式提供,因此定義一個基於string轉換描述。基於介面的實也很簡單:
public class ToSbyte :IStringConverter
{
#region IStringConverter 成員
object IStringConverter.ConvertTo(string value, out bool succeeded)
{
sbyte nvalue;
succeeded = sbyte.TryParse(value, out nvalue);
return nvalue;
}
#endregion
}
IStringConverter工廠的實現
由於轉換情況的不確定性,因此通過工廠的方式來達到代碼對外的封閉性和良好的擴充性。可以通過目標類型來擷取相關轉換執行個體,在.NET中IDictionary就能夠達到應用的要求。
static IDictionary<Type, IStringConverter> mConverters;
public static IDictionary<Type, IStringConverter> Converters
{
get
{
if (mConverters == null)
{
lock (typeof(ConverterFactory))
{
OnInit();
}
}
return mConverters;
}
}
static void OnInit()
{
if (mConverters != null)
return;
mConverters = new Dictionary<Type, IStringConverter>();
mConverters.Add(typeof(byte), new ToByte());
LoadConfig();
}
//從設定檔載入轉換器映射,如果配置存在相同類型轉器就取代原有轉換器
static void LoadConfig()
{
//Load Config
// <converter type="System.Int32",value="HFSoft.Binder.ToByte"
}
為了方便使用可以在工廠中寫入程式碼配置內部基礎類型;因為轉換情況的不確定,所以允許通過設定檔的方式引入不同情況的類型轉換器。
可以擴充性的Custom Attribute
雖然工廠可以達到轉換介面的可配置性,但實際上很難達到應用要求;在某些情況下類型轉換器只是在某些對象成員中有效(雖然設定檔也可以達到期要求,但在設定檔中定義這麼小的粒度並不是好的選擇);通過Attribute給相關Property指定類型轉換器非常適合。
/// <summary>
/// 用於特殊情況下描述對象具體成員的轉換器
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ConverterAttribute : Attribute, IStringConverter
{
public ConverterAttribute(Type convertertype)
{
mConverterType = convertertype;
}
public ConverterAttribute(Type convertertype, string defvalue)
{
mConverterType = convertertype;
mDefaultValue = defvalue;
}
private Type mConverterType;
public Type ConverterType
{
get
{
return mConverterType;
}
}
private String mDefaultValue;
public String DefaultValue
{
get
{
return mDefaultValue;
}
set
{
mDefaultValue = value;
}
}
protected IStringConverter CreateInstance()
{
if (mConverters.ContainsKey(ConverterType))
return mConverters[ConverterType];
lock (typeof(ConverterAttribute))
{
if (!mConverters.ContainsKey(ConverterType))
{
mConverters.Add(ConverterType, (IStringConverter)Activator.CreateInstance(ConverterType));
}
return mConverters[ConverterType];
}
}
static IDictionary<Type, IStringConverter> mConverters = new Dictionary<Type, IStringConverter>();
#region IStringConverter 成員
public object ConvertTo(string value, out bool succeeded)
{
string newvalue = value != null ? value : DefaultValue;
return CreateInstance().ConvertTo(newvalue, out succeeded);
}
#endregion
}
通過ConverterAttribute可以方便制定粒度更小的配置
private byte[] mFileStream;
[Converter(typeof(FileStreamConverter),"IconPhoto")]
public byte[] FileStream
{
get
{
return mFileStream;
}
set
{
mFileStream = value;
}
}
以上定義可以上傳檔案流轉成byte[]到FileStream屬性中。
功能整合實現
現在就把所有東西整合起來,滿足目的的要求。
public object Bind(System.Collections.Specialized.NameValueCollection values, string prefix)
{
object newobj = Activator.CreateInstance(ObjectType);
if (prefix == null)
prefix = "";
object value;
foreach (PropertyInfo item in Properties)
{
value = values[prefix + "." + item.Name];
if(value == null)
value = values[prefix + "_" + item.Name];
if(value == null)
value = values[prefix + item.Name];
BindProperty(newobj, item, (string)value);
}
return newobj;
}
private void BindProperty(object obj, PropertyInfo property, string value)
{
IStringConverter stringconver;
object nvalue;
bool confirm = false;
Object[] cas = property.GetCustomAttributes(typeof(ConverterAttribute), true);
if (cas.Length > 0)
{
nvalue = ((ConverterAttribute)cas[0]).ConvertTo(value, out confirm);
if (confirm)
mPropertiesHandle[property].SetValue(obj, nvalue);
}
else
{
if (ConverterFactory.Converters.ContainsKey(property.PropertyType))
{
stringconver = ConverterFactory.Converters[property.PropertyType];
nvalue = stringconver.ConvertTo(value, out confirm);
if (confirm)
mPropertiesHandle[property].SetValue(obj, nvalue);
}
}
}
因為Web提交的資料幾乎可以通過HttpRequest.Params得到,只需要根據屬性名稱和相關首碼進行匹配尋找就可以了。這裡實現的匹配方式並不理想,其實可以在相關page第一次請求就可以分析到關係存在IDictionary中,後期直接使用就可以了。
以上功能是在編寫一個MVC組件的資料繫結功能,其實完全可以移植傳統的WebForm下工作;有更好想法的朋友請多提交意見。