前些日子在整理硬碟時,在裡頭找到了許多以往於幾家公司任職顧問時所留下來的程式,有趣的是,我在這堆程式中找到了個簡繁轉換的ASP.NET例子
無縫式簡繁轉換
文/黃忠成
起
前些日子在整理硬碟時,在裡頭找到了許多以往於幾家公司任職顧問時所留下來的程式,這些程式當時都是為了解決該公司開發軟體上的問題而寫的,
那時由於時間上並不充裕,所以常常以WebApplication1、App2、Test1命名,結果現在嘗到苦果,根本不清楚這堆程式到底是為啥而寫的,只好一個個的開來看,
再一一重新命名了。
有趣的是,我在這堆程式中找到了個簡繁轉換的ASP.NET例子,當時會寫下這隻程式的原因是,客戶希望開發的網頁能在瀏覽器是簡體語系時顯示簡體,
在繁體時顯示繁體。唔!我想大多數人看到這,大概會想說我又開始重提幾百年前就有的ASP.NET多語系支援了吧?嘿!不要急,讓我把客戶的需求講完。
1、網頁必須於簡體瀏覽器上顯示簡體,繁體瀏覽器上顯示繁體(這超好解的)
2、資料必須來自於資料庫,顯示於網頁(嗯,這不難...等會!這有那麼一點點陷阱的味道)
3、我們的資料庫只存一種語系,所以,若使用者於簡體瀏覽器上輸入簡體時,實際要存成繁體(這...有點難度囉)。
第一個需求很簡單,也很易解,運用ASP.NET的多語系支援便可輕鬆過關,第二個問題就麻煩些了,因為除非有簡繁兩個資料庫,否則怎麼讓一個資料庫輸出
成兩種語言呢?正規的設計師在此多半會運用各個控件的DataBound事件,於內判斷語系進行轉換,比較偏門的設計師(我是其中之一...),可能就會搬出無差別格鬥技,
將整個網頁的Response硬轉,這招雖然很極端,效率上也有疑慮,但也不失為一解決方案。
重點在第三個需求,客戶要求的不僅是顯示,打簡還得存成繁,這就很麻煩了,正規的解法大概有兩種,一是從UI介面著手,在資料存進資料庫前進行轉碼,
二是由資料面著手,從TableAdapter的DB-Direct Access Method著手,一樣是在資料存進資料庫前動手腳。
總結以上所述,我們大概可以理出一個可行的解決方案,那就是在DataBound進行輸出時的轉碼,於TableAdapter中進行輸入轉碼。 聽起來這並不是件很難的事,
不過可以想見的,當網頁或是控件數目眾多,繁複一事暫且不提,漏網之魚是必然有的。
對此問題我想過許多種解法,一是替換所有的ASP.NET控件,為其訂製字串轉換機制,但是這不僅極端,且成效上也不符合經濟效益,因為很多字串的顯示
跟ASP.NET控件是沒關係的,例如Title。 二是配合Base Page,讓設計師於Init時呼叫一特定函式,為所有可以找到DataBound事件的控件掛上事件,然後於此
事件中進行轉換,當然!TableAdapter部份的寫入轉換仍然是不可少的。
我的解法
OK,總結以上的敘述,基本上需求是可以達成的,即使過程很繁複!不過我還是希望能找出一個既可達成需求,又不會對設計師造成過大負擔的解決方案。所以我開始由
根本上著手,也就是重新思考ASP.NET應用程式執行的過程,基本上!ASP.NET分為繪製與Post兩個大區塊,繪製部份是呼叫各個控件的Render函式,將輸出繪製到一個
HTMLTextWriter物件中,最後ASP.NET再將此物件內容輸出給瀏覽器。
因此,要進行簡繁轉換動作的最佳場所,就是在Page的PreRenderComplete事件,於此時,所有控件的準備動作皆已完成,接下來的動作便是將內容繪制到HTMLTextWriter中,
所以在此事件中,我們只要一一掃描Page上的所有控件,再一一轉換,等到真正的繪製發生時,就會依照我們所願的將轉好的資料輸出到瀏覽器,這樣一來既沒有無差別轉換法
的效能疑慮,又能夠達到我們的需求。
接著要處理的是輸入部份,對ASP.NET應用程式而言,輸入指的便是使用者於瀏覽器上的控件,如TextBox中輸入字串,此時這個字串會被存成參數,然後以Post方式送回到
ASP.NET頁面,因此!要對輸入字串做轉碼的時間點,就是Page的Init,只要在這個地方對Request的Params屬性內容進行轉換,接下來的動作我們便不需考慮TableAdapter了,
因為輸入的轉碼動作已在UI層就做好了。
問題是,Request.Params可是唯讀的呀,要怎麼才能改呢?其實,這有一個解法,那就是透過Reflection機制,當然!這是一個危險的動作,除非很了解ASP.NET的運作機制,
不然這個東西可是不能亂動的。
如果解決了以上的問題,那麼剩下的就是如何讓設計師更方便使用這個機制,最好的情況是,設計師完全不用管,只要掛上我們寫的類別,就可以讓網站中的所有網頁都納到此簡繁
轉換機制下。正巧!ASP.NET有個類別叫PageHandlerFactory,當使用者要求一個ASP.NET網頁時,ASP.NET會依據.aspx副檔名來建立PageHandlerFactory物件,此物件的工作就是
載入對應的.aspx並執行取得結果後返回給瀏覽器。因此,假如我們可以繼承此PageHandlerFactory,便能於使用者要求.aspx時,取得Page物件,再為其掛上PreRender、Init等事件,
這樣一來,豈不是神不知鬼不覺的將簡繁機制注入到一個原本不考慮此事的網站中了?
實作的程式碼
好了,現在就只剩下實作了,以下便是RenderConverter.cs的實作碼,此處簡繁轉換的部份,我運用了Visual Basic的StrConv函式達成,請注意,此函式的轉換存在一些問題,
某些字可能會轉成?號,在實際專案中,我們都是使用自己的簡繁碼對照表。
RenderConverter.cs |
using System; using System.Runtime.Serialization; using System.ComponentModel; using System.Reflection; using System.Collections; using System.Collections.Generic; using System.Text; using System.Security.Permissions; using System.Web; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; namespace Orphean.AspNetHelper.Big5GbConvert { public class ConvertPageHandlerFactory : PageHandlerFactory { public override System.Web.IHttpHandler GetHandler( System.Web.HttpContext context, string requestType, string virtualPath, string path) { IHttpHandler handler = base.GetHandler(context, requestType, virtualPath, path); if (handler is Page) { ReflectionPermission per = new ReflectionPermission(PermissionState.Unrestricted); per.Demand(); MethodInfo mi = typeof(Page).GetMethod("ApplyMasterPage", BindingFlags.Instance | BindingFlags.NonPublic); if (mi != null) mi.Invoke(handler, null); ((Page)handler).PreInit += new EventHandler(ConvertPageHandlerFactory_PreInit); } return handler; } void ConvertPageHandlerFactory_PreInit(object sender, EventArgs e) { RenderConverter.HookRender((Page)sender); } } public enum ConvertDirection { Forward, Reverse } public class BeforeConvertArgs : EventArgs { private bool _cancel; private ConvertDirection _direction = ConvertDirection.Forward; private object _control; public bool Cancel { get { return _cancel; } set { _cancel = value; } } public ConvertDirection Direction { get { return _direction; } } public object Control { get { return _control; } } public BeforeConvertArgs(ConvertDirection direction, object control) { _direction = direction; _control = control; } } public class RenderConverterArgs : EventArgs { private string _propertyName; private object _oldValue, _newValue; private bool _handled = false; public object OldValue { get { return _oldValue; } } public object NewValue { get { return _newValue; } set { _newValue = value; } } public string PropertyName { get { return _propertyName; } } public bool Handled { get { return _handled; } set { _handled = value; } } public RenderConverterArgs(object oldValue, string propertyName) { _oldValue = oldValue; _newValue = oldValue; _propertyName = propertyName; } } public delegate void RenderConverterHandler(object sender, RenderConverterArgs args); public class RenderConverter { private static Dictionary<Type, List<string>> _needConverterControls = null; private static object _lock = new object(); private static EventHandlerList _events = new EventHandlerList(); private static object _onConverter = new object(), _onReverseConvert = new object(), _onBeforeConvert = new object(); private static Type _pageType = null; private static MethodInfo _isSystemField = null; private static Type _hackNV = typeof(System.Collections.Specialized.NameObjectCollectionBase); private static PropertyInfo _hackNVReadOnly = null; private static FieldInfo _pageRequestValueCollectionProp = null; private static List<string> _convertedControls = new List<string>(); protected static Dictionary<Type, List<string>> NeedConverterControls { get { lock (_lock) { if (_needConverterControls == null) _needConverterControls = new Dictionary<Type, List<string>>(); } return _needConverterControls; } } public static event RenderConverterHandler Convert { add { _events.AddHandler(_onConverter, value); } remove { _events.RemoveHandler(_onConverter, value); } } public static event RenderConverterHandler ReverseConvert { add { _events.AddHandler(_onReverseConvert, value); } remove { _events.RemoveHandler(_onReverseConvert, value); } } public static event EventHandler<BeforeConvertArgs> BeforeConvert { add { _events.AddHandler(_onBeforeConvert, value); } remove { _events.RemoveHandler(_onBeforeConvert, value); } } private static bool IsSystemField(Page page, string key) { if (_pageType == null) _pageType = typeof(Page); if (_isSystemField == null) { ReflectionPermission per = new ReflectionPermission(PermissionState.Unrestricted); per.Demand(); _isSystemField = _pageType.GetMethod("IsSystemPostField", BindingFlags.NonPublic | BindingFlags.Static); } if (_hackNVReadOnly == null) _hackNVReadOnly = _hackNV.GetProperty("IsReadOnly", BindingFlags.NonPublic | BindingFlags.Instance); if (_pageRequestValueCollectionProp == null) _pageRequestValueCollectionProp = _pageType.GetField("_requestValueCollection", BindingFlags.NonPublic | BindingFlags.Instance); if (_isSystemField != null) return (bool)_isSystemField.Invoke(page, new object[] { key }); return false; } protected static void OnConvert(object control, RenderConverterArgs args) { RenderConverterHandler handler = (RenderConverterHandler)_events[_onConverter]; if (handler != null) handler(control, args); } protected static void OnReverseConvert(object control, RenderConverterArgs args) { RenderConverterHandler handler = (RenderConverterHandler)_events[_onReverseConvert]; if (handler != null) handler(control, args); } protected static void OnBeforeConvert(object sender, BeforeConvertArgs args) { EventHandler<BeforeConvertArgs> handler = (EventHandler<BeforeConvertArgs>)_events[_onBeforeConvert]; if (handler != null) handler(sender, args); } public static void RegisterControl(Type type, string propertyName) { lock (_lock) { if (!NeedConverterControls.ContainsKey(type)) NeedConverterControls.Add(type, new List<string>()); if (!NeedConverterControls[type].Contains(propertyName)) NeedConverterControls[type].Add(propertyName); } } public static void HookRender(Page page) { page.SaveStateComplete += new EventHandler(page_SaveStateComplete); page.InitComplete += new EventHandler(Page_InitComplete); page.PreRenderComplete += new EventHandler(page_PreRender); } static void page_PreRender(object sender, EventArgs e) { PreChildControlWorker((Control)sender); HiddenField field = new HiddenField(); System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formater = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); System.IO.MemoryStream ms = new System.IO.MemoryStream(); formater.Serialize(ms, _convertedControls); ms.Flush(); field.Value = System.Convert.ToBase64String(ms.GetBuffer()); field.ID = "__CONVERTED_CONTROLS__"; ((Page)sender).Form.Controls.Add(field); } static void Page_InitComplete(object sender, EventArgs e) { Page p = (Page)sender; if (p.Request.Params["__CONVERTED_CONTROLS__"] != null) { _convertedControls.Clear(); byte[] datas = System.Convert.FromBase64String(p.Request.Params["__CONVERTED_CONTROLS__"]); if (datas.Length > 0) { System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formater = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); System.IO.MemoryStream ms = new System.IO.MemoryStream(datas); _convertedControls = formater.Deserialize(ms) as List<string>; } } if (_convertedControls.Count > 0) { System.Collections.Specialized.NameValueCollection nv = null; if (_pageRequestValueCollectionProp != null) nv = _pageRequestValueCollectionProp.GetValue(p) as System.Collections.Specialized.NameValueCollection; foreach (string key in _convertedControls) { if (!IsSystemField(p, key)) { if (nv != null && _hackNVReadOnly != null && nv[key] != null) { _hackNVReadOnly.SetValue(nv, false, null); try { BeforeConvertArgs bcargs = new BeforeConvertArgs(ConvertDirection.Reverse, key); OnBeforeConvert(null, bcargs); if (!bcargs.Cancel) { RenderConverterArgs args = new RenderConverterArgs(nv[key], key); OnReverseConvert(null, args); if (args.Handled) nv[key] = args.NewValue.ToString(); } } finally { _hackNVReadOnly.SetValue(nv, true, null); } } } } _convertedControls.Clear(); } } static PropertyDescriptor ResolveProperty(object c, string propName, out object target) { string[] nodes = propName.Split('.'); target = c; if (nodes.Length == 0) return TypeDescriptor.GetProperties(c.GetType()).Find(propName, true); else { target = c; PropertyDescriptor pd = null; for (int i = 0; i < nodes.Length - 1; i++) { pd = TypeDescriptor.GetProperties(target.GetType()).Find( nodes[i], true); target = pd.GetValue(target); } return TypeDescriptor.GetProperties(target).Find( nodes[nodes.Length - 1], true); } } static bool IsRegisteredControl(object c, out Type realType) { Type t = c.GetType(); while (true) { if (NeedConverterControls.ContainsKey(t)) { realType = t; BeforeConvertArgs args = new BeforeConvertArgs( ConvertDirection.Forward, c); OnBeforeConvert(c, args); return !args.Cancel; } t = t.BaseType; if (t == null) { realType = null; return false; } } } static void ChildControlWorker(Control root) { Type realType = null; foreach (Control c in root.Controls) { if (c.Controls.Count > 0) ChildControlWorker(c); if (IsRegisteredControl(c, out realType)) { foreach (string prop in NeedConverterControls[realType]) { object target; PropertyDescriptor pd = ResolveProperty(c, prop, out target); if (pd != null) { object value = pd.GetValue(c); if (value is ICollection) CollectionProcess((IEnumerable)value); else { RenderConverterArgs args = new RenderConverterArgs( value, pd.Name); OnConvert(c, args); if (args.Handled) { if (!_convertedControls.Contains(c.UniqueID)) _convertedControls.Add(c.UniqueID); pd.SetValue(c, args.NewValue); } } } } } } } static void CollectionProcess(IEnumerable value) { IEnumerator enumerator = value.GetEnumerator(); while (enumerator.MoveNext()) { if (enumerator.Current is ICollection) CollectionProcess((IEnumerable)enumerator.Current); Type realType = null; if (IsRegisteredControl(enumerator.Current, out realType)) { foreach (string prop in NeedConverterControls[realType]) { object target; PropertyDescriptor pd = ResolveProperty(enumerator.Current, prop, out target); if (pd != null) { object objValue = pd.GetValue(enumerator.Current); if (objValue is ICollection) CollectionProcess((IEnumerable)objValue); else { RenderConverterArgs args = new RenderConverterArgs(objValue, pd.Name); OnConvert(enumerator.Current, args); if (args.Handled) pd.SetValue(target, args.NewValue); } } } } } } static void PreChildControlWorker(Control root) { Type realType = null; foreach (Control c in root.Controls) { if (c.Controls.Count > 0) PreChildControlWorker(c); if (IsRegisteredControl(c, out realType)) { foreach (string prop in NeedConverterControls[realType]) { object target; PropertyDescriptor pd = ResolveProperty(c, prop, out target); if (pd != null) { object value = pd.GetValue(c); if (value is ICollection) CollectionProcess((IEnumerable)value); else { RenderConverterArgs args = new RenderConverterArgs(value, pd.Name); OnConvert(c, args); if (args.Handled) { if (!_convertedControls.Contains(c.UniqueID)) _convertedControls.Add(c.UniqueID); pd.SetValue(c, args.NewValue); } } } } } } } static void page_SaveStateComplete(object sender, EventArgs e) { ChildControlWorker((Control)sender); } static RenderConverter() { RegisterStandardControls(); } static void RegisterStandardControls() { Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.TextBox), "Text"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.Button), "Text"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.LinkButton), "Text"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.DropDownList), "Items"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.TreeView), "Nodes"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.TreeNode), "Text"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.TreeNode), "ChildNodes"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.ListItem), "Text"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.RadioButtonList), "Items"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.BulletedList), "Items"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.CheckBox), "Text"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.HyperLink), "Text"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.RadioButton), "Text"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.TableCell), "Text"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.TableRow), "Text"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.LiteralControl), "Text"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.HtmlControls.HtmlHead), "Title"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.WebControl), "Tooltip"); Orphean.AspNetHelper.Big5GbConvert.RenderConverter.RegisterControl( typeof(System.Web.UI.WebControls.Label), "Text"); } } } |
在此類別中,我運用了PageHandlerFactory來掛載PreRender、Init等事件,然後透過Reflection來竄改Post上來的值!注意!這些動作並非是無差別對待每個控件及屬性,
仔細看PreChildrenWorker、ChildrenWorker、CollectionProcess,你會查覺到只有在末端以RegisterControl所註冊的類別及屬性才會被處理,這可以避免處理了一堆不
需要轉換的屬性值類別。
接著是負責轉碼的Big5GBConverter.cs
Big5GBConverter.cs |
using System; using System.IO; using System.Collections; using System.Collections.Generic; using System.Text; using Microsoft.VisualBasic; namespace Orphean.AspNetHelper.Big5GbConvert { public class Big5GbConvert { private static Big5GbConvert _instance; public static Big5GbConvert Instance { get { if (_instance == null) _instance = new Big5GbConvert(); return _instance; } } public bool IsBig5(string text) { byte[] ansiBuff = Encoding.GetEncoding("big5").GetBytes(text); string s = Encoding.GetEncoding("big5").GetString(ansiBuff); if (s.Equals(text)) return true; return false; } public bool IsGB(string text) { byte[] ansiBuff = Encoding.GetEncoding("x-cp20936").GetBytes(text); string s = Encoding.GetEncoding("x-cp20936").GetString(ansiBuff); if (s.Equals(text)) return true; return false; } public string Big5ToGb(string text) { return Big5ToGb(text, false); } public string Big5ToGb(string text, bool detectFirst, bool strongDetect) { if (strongDetect) { string s = string.Empty; foreach (char c in text) { string cstr = c.ToString(); if (IsBig5(cstr)) s += Big5ToGb(cstr, detectFirst); else s += cstr; } return s; } return Big5ToGb(text, detectFirst); } public virtual string Big5ToGb(string text, bool detectFirst) { if (detectFirst && !IsBig5(text)) return text; return Strings.StrConv(text, VbStrConv.SimplifiedChinese, 1033); } public string GbToBig5(string text) { return GbToBig5(text, false); } public virtual string GbToBig5(string text, bool detectFirst, bool strongDetect) { if (strongDetect) { string s = string.Empty; foreach (char c in text) { string cstr = c.ToString(); if (!IsBig5(cstr)) s += GbToBig5(cstr, detectFirst); else s += cstr; } return s; } return GbToBig5(text, detectFirst); } public virtual string GbToBig5(string text, bool detectFirst) { if (detectFirst && IsBig5(text)) return text; return Strings.StrConv(text, VbStrConv.TraditionalChinese, 1033); } } } |
當你將此兩個類別加入專案後,接著只要在web.config中加上以下字串,簡繁轉換機制便已注入至你的專案中。
Web.config |
<httpHandlers> <removeverb="*"path="*.asmx"/> <removeverb="*"path="*.aspx"/> <addverb="*"path="*.asmx"validate="false"type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <addverb="*"path="*_AppService.axd"validate="false"type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <addverb="GET,HEAD"path="ScriptResource.axd"type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"validate="false"/> <addverb="*"path="*.aspx"type="Orphean.AspNetHelper.Big5GbConvert.ConvertPageHandlerFactory, UltraBig5GB"/> </httpHandlers> |
注意,粗體字的UltraBig5GB字串,要改成你的專案名稱。
最後一個動作,就是進行實質的簡繁轉換,最適合寫這段程式碼的地方是Global.asax。
Global.asax |
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using Orphean.AspNetHelper.Big5GbConvert; namespace UltraBig5GB { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { RenderConverter.Convert += new RenderConverterHandler(RenderConverter_Convert); RenderConverter.ReverseConvert += new RenderConverterHandler(RenderConverter_ReverseConvert); } //輸入轉換 void RenderConverter_ReverseConvert(object sender, RenderConverterArgs args) { args.NewValue = Big5GbConvert.Instance.GbToBig5((string)args.OldValue, true); if (args.NewValue.ToString().Equals(args.OldValue.ToString())) return; args.Handled = true; } //輸出轉換 void RenderConverter_Convert(object sender, RenderConverterArgs args) { args.NewValue = Big5GbConvert.Instance.Big5ToGb((string)args.OldValue, true); if (args.NewValue.ToString().Equals(args.OldValue.ToString())) return; args.Handled = true; } protected void Session_Start(object sender, EventArgs e) { } protected void Application_BeginRequest(object sender, EventArgs e) { } protected void Application_AuthenticateRequest(object sender, EventArgs e) { } protected void Application_Error(object sender, EventArgs e) { } protected void Session_End(object sender, EventArgs e) { } protected void Application_End(object sender, EventArgs e) { } } } |
最後以一個網頁測試一下。
Default.aspx |
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="UltraBig5GB._Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>簡繁轉萬</title> </head> <body> <form id="form1" runat="server"> <div> <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>" DeleteCommand="DELETE FROM [PERSON] WHERE [PERSON_ID] = @PERSON_ID" InsertCommand="INSERT INTO [PERSON] ([PERSON_ID], [TYPE], [NAME]) VALUES (@PERSON_ID, @TYPE, @NAME)" SelectCommand="SELECT * FROM [PERSON]" UpdateCommand="UPDATE [PERSON] SET [TYPE] = @TYPE, [NAME] = @NAME WHERE [PERSON_ID] = @PERSON_ID"> <DeleteParameters> <asp:Parameter Name="PERSON_ID" Type="String" /> </DeleteParameters> <UpdateParameters> <asp:Parameter Name="TYPE" Type="Int32" /> <asp:Parameter Name="NAME" Type="String" /> <asp:Parameter Name="PERSON_ID" Type="String" /> </UpdateParameters> <InsertParameters> <asp:Parameter Name="PERSON_ID" Type="String" /> <asp:Parameter Name="TYPE" Type="Int32" /> <asp:Parameter Name="NAME" Type="String" /> </InsertParameters> </asp:SqlDataSource> <asp:LinkButton ID="LinkButton1" runat="server">測試</asp:LinkButton> <asp:GridView ID="GridView1" runat="server" AllowPaging="True" AutoGenerateColumns="False" CellPadding="4" DataKeyNames="PERSON_ID" DataSourceID="SqlDataSource1" ForeColor="#333333" GridLines="None"> <RowStyle BackColor="#F7F6F3" ForeColor="#333333" /> <Columns> <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" ShowSelectButton="True" /> <asp:BoundField DataField="PERSON_ID" HeaderText="PERSON_ID" ReadOnly="True" SortExpression="PERSON_ID" /> <asp:BoundField DataField="TYPE" HeaderText="TYPE" SortExpression="TYPE" /> <asp:BoundField DataField="NAME" HeaderText="NAME" SortExpression="NAME" /> </Columns> <FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" /> <PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" /> <SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" /> <HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" /> <EditRowStyle BackColor="#999999" /> <AlternatingRowStyle BackColor="White" ForeColor="#284775" /> </asp:GridView> <asp:FormView ID="FormView1" runat="server" AllowPaging="True" DataKeyNames="PERSON_ID" DataSourceID="SqlDataSource1"> <EditItemTemplate> PERSON_ID: <asp:Label ID="PERSON_IDLabel1" runat="server" Text='<%# Eval("PERSON_ID") %>' /> <br /> TYPE: <asp:TextBox ID="TYPETextBox" runat="server" Text='<%# Bind("TYPE") %>' /> <br /> NAME: <asp:TextBox ID="NAMETextBox" runat="server" Text='<%# Bind("NAME") %>' /> <br /> <asp:LinkButton ID="UpdateButton" runat="server" CausesValidation="True" CommandName="Update" Text="Update" /> <asp:LinkButton ID="UpdateCancelButton" runat="server" CausesValidation="False" CommandName="Cancel" Text="Cancel" /> </EditItemTemplate> <InsertItemTemplate> PERSON_ID: <asp:TextBox ID="PERSON_IDTextBox" runat="server" Text='<%# Bind("PERSON_ID") %>' /> <br /> TYPE: <asp:TextBox ID="TYPETextBox" runat="server" Text='<%# Bind("TYPE") %>' /> <br /> NAME: <asp:TextBox ID="NAMETextBox" runat="server" Text='<%# Bind("NAME") %>' /> <br /> <asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" CommandName="Insert" Text="Insert" /> <asp:LinkButton ID="InsertCancelButton" runat="server" CausesValidation="False" CommandName="Cancel" Text="Cancel" /> </InsertItemTemplate> <ItemTemplate> PERSON_ID: <asp:Label ID="PERSON_IDLabel" runat="server" Text='<%# Eval("PERSON_ID") %>' /> <br /> TYPE: <asp:Label ID="TYPELabel" runat="server" Text='<%# Bind("TYPE") %>' /> <br /> NAME: <asp:Label ID="NAMELabel" runat="server" Text='<%# Bind("NAME") %>' /> <br /> <asp:LinkButton ID="EditButton" runat="server" CausesValidation="False" CommandName="Edit" Text="Edit" /> <asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete" Text="Delete" /> <asp:LinkButton ID="NewButton" runat="server" CausesValidation="False" CommandName="New" Text="New" /> </ItemTemplate> </asp:FormView> <asp:TreeView ID="TreeView1" runat="server"> <Nodes> <asp:TreeNode Text="台北" Value="台北"></asp:TreeNode> <asp:TreeNode Text="天下" Value="天下"></asp:TreeNode> <asp:TreeNode Text="論檀" Value="論檀"></asp:TreeNode> </Nodes> </asp:TreeView> <asp:DropDownList ID="DropDownList1" runat="server"> <asp:ListItem>台北</asp:ListItem> <asp:ListItem>天下</asp:ListItem> <asp:ListItem>論壇</asp:ListItem> </asp:DropDownList> </div> </form> </body> </html> |
執行結果如下。
輸入資料時如下。
更新後如下。
實體存的資料如下。
Case Closed?
基本上,關於這個問題,我的任務就到此為止了,那時就把此類別及範例交給客戶,至於其如何使用或是之後所遭遇的問題及改動,
就不在我可以公開的範圍了。
另外,當年ASP.NET AJAX並未開始流行,所以此例子也沒考慮到這個,如果你是使用ASP.NET AJAX,雖然我做了些小測試沒有發生錯誤,
但這不代表沒問題。
最後,如果仔細看這個範例,你應該會查覺到,這不是一個僅能運用於簡繁轉換的技巧。
範例下載