前一篇中,我們設計了GridViewHandler及FormViewHandler,讓商業邏輯可以由主程式中抽離,放置於外部來動態選擇要載入那些商業邏輯,就該例而言,這個設計除了將原本該
置於Data Layout的商業邏輯與UI扯上關聯外,其實並無其它設計較為不當之處,而將商業邏輯與UI扯上關聯這點,其實也是為了讓範例更加簡單易懂而特意設計的,要將這種設計
移置Data Layout裡也很簡單。
                     
                    
	The Framework Designing (3) – Configurable Application
	 
	 
	文/黃忠成
	 
	 
	 
	re-designing or not?
	 
	  前一篇中,我們設計了GridViewHandler及FormViewHandler,讓商業邏輯可以由主程式中抽離,放置於外部來動態選擇要載入那些商業邏輯,就該例而言,這個設計除了將原本該
	置於Data Layout的商業邏輯與UI扯上關聯外,其實並無其它設計較為不當之處,而將商業邏輯與UI扯上關聯這點,其實也是為了讓範例更加簡單易懂而特意設計的,要將這種設計
	移置Data Layout裡也很簡單。
	  但,這個設計有一個設想未周全之處,那就是每個Element、也就是每個Table都只能掛載一個Handler,這點在開發初期並不會造成困擾,但在中後期當同系列的產品變多時,
	就會造成如圖1的困擾。
	 
	圖1
	
	如圖1所示,Product A、B、C屬於同一系列的產品,透過Handler來客製屬於該客戶的商業邏輯,但由於前篇所設計的延展點受限於每個Table只能掛載一個Handler,所以在產品線展開時,
	就會造成Handler中包含過多的重複程式碼,以圖1來說,每個Handler都擁有設定Company Name預設值的能力,但因為不同的產品有著不同數量的預設值需求,所以此處為了適應,
	只好將Company Name預設值的能力重複出現在之後延展的Handler裡,最好的設計應該如圖2。 
	
	圖2
	
	  圖2中假設當一個Table可以掛載多個Handler後的情況,在這種設計下,每個Handler都有各自的任務,可以透過組合來完成某個客戶的需求,這也是具延展性應用程式的主要設計架構。
	  圖2將原本GridViewHandler、FormViewHandler設計上的缺點揭露無遺,這使得我們必須思考是否要重新設計GridViewHandler及FormViewHandler,這也是當設計Framework或
	系統架構時設計師最常遇到的問題,如果這個缺點在設計之初即察覺到,那麼只要重新修改設計概念即可,但有些時候這類問題會出現在產品開發的中後期,此時要更改原設計所需
	付出的成本很大,因為得逐一調整開發完成的部分。
	  當修改原設計須付出的代價是無法或難以承受之時,此時就得往外科手術的方向著手,以本例而言,雖然每個Table只能掛載一個Handler,但這並不構成我們無法在一個Table掛載
	Handler的限制,因為只要將掛載的的這一個Handler設計成可以呼叫多個子Handler即可,簡略的說就是將延伸點往下放到Handler上,如圖3所示。
	 
	圖3
	
	在圖3中,原本CustomerExt是掛載至GridViewHandler的單一Handler,但如果這樣做就會造成圖1的問題,所以圖3中加入了一個SaveExtDispatcher,其掛載至GridViewHandler,
	並將呼叫下放至其動態掛載的CustomerExt1、CustomerExt2上,這種做法就可以達到圖2的架構,這是Dispatcher樣式的應用。
	  乍看之下,這樣的設計要達到圖2的架構似乎很簡單,不過裡頭潛在一個問題,讓我們先思考SaveExtDispatcher的設計。在SaveExtDispatcher中,會動態的由一個組態檔讀入要
	掛載的Handler,並將來自GridViewHandler的呼叫一一下放給這些Handler,實作上,SaveExtDispatcher一定會擁有類似於GridViewHandler中讀取組態檔及動態載入的程式碼,
	一切都沒有問題,直到將SaveExtDispacther掛到GridViewHandler中時,組態檔便會把問題點出來。
	 
	
		
			| 
				 
					<appSettings> 
				
					    <addkey="Customers"value="SaveExtDispatcher.CustomerExtDispatcher, SaveExtDispatcher"/> 
				
					  </appSettings> 
			 | 
		
	
	看出問題了嗎? 遷就於原本架構,每個Table都必須要有一個獨立的SaveExtDispatcher,所以,[會動態的由一個組態檔讀入要掛載的Handler,並將來自GridViewHandler的呼叫
	一一下放給這些Handler]這件事會出現在每一個Table所掛載的獨立SaveExtDispatcher中。
	  解決這個問題的方法很簡單,就直接把會重複的部分提出來寫成共用的Library即可,本例中就是DataHandlerDispacther.dll。
	 
	
		
			| 
				 
					DataHandlerDispatcher.cs (in DataHandlerDispatcher project) 
			 | 
		
		
			
				using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;
using System.Reflection;
using SimpleFramework;
namespace DataHandlerDispatcher
{
    public abstract class DataHandlerDispatcher:IDataHandler
    {
        private List _innerHandlers = null;
        protected abstract string GetConfigurationFileName();
        private void Initialize(string configurationFile)
        {
            string appPath = AppDomain.CurrentDomain.BaseDirectory + "\\" + configurationFile;
            if(!File.Exists(appPath))
                return;
            XDocument doc = XDocument.Load(appPath,LoadOptions.None);
            var result = from s1 in doc.Descendants("handler") select s1;
            _innerHandlers = new List();
            foreach (var item in result)
            {
                if (item.Attribute("type") != null)
                {
                    string[] partAssem = item.Attribute("type").Value.Split(',');
                    IDataHandler _instance = TypeLoader.CreateInstance(partAssem[1],
partAssem[0], "SimpleFramework.IDataHandler") as IDataHandler;
                    _innerHandlers.Add(_instance);
                }
            }
        }
        public void BeforeSave(System.Collections.Specialized.IOrderedDictionary values)
        {
            if (_innerHandlers == null)
                Initialize(GetConfigurationFileName());
            if (_innerHandlers != null)
            {
                foreach (var item in _innerHandlers)
                    item.BeforeSave(values);
            }
        }
    }
}
 
			 | 
		
	
	接著只要讓SaveExtDispatcher繼承自此類別即可。
	 
	
		
			| 
				 
					SaveExtDispatcher.cs (in SaveExtDispatcher project) 
			 | 
		
		
			
				using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DataHandlerDispatcher;
namespace SaveExtDispatcher
{
    public class CustomerExtDispatcher:DataHandlerDispatcher.DataHandlerDispatcher
    {
        protected override string GetConfigurationFileName()
        {
            return "SaveExtDispatcher.config";
        }
    }
}
 
			 | 
		
	
	SaveExtDispatcher會讀取執行目錄下的SaveExtDispatcher.config,本例中如下所示。
	 
	
		
			| 
				 
					SaveExtDispatcher.config(in web/winform project) 
			 | 
		
		
			| 
				 
					<?xmlversion="1.0"encoding="utf-8"?> 
				
					<handlers> 
				
					  <handlertype="SaveExt.CustomerExt, SaveExt"/> 
				
					<!-- 
				
					  <handlertype="SaveExt2.CustomerExt, SaveExt2"/> 
				
					--!> 
				
					</handlers> 
			 | 
		
	
	最後只要將SaveExtDispatcher掛到GridViewHandler/FormViewHandler即可,你可以於CreateStep_1目錄中找到整個完整範例。
	 
	writing configuration section handler
	 
	  當然,前節的做法是不得已下的產物,改寫架構才是根本的解決之道,問題的根源是前例就簡使用了只能夠指定單值的appSetting,透過.NET Framework的
	Configuration Framework,我們可以自訂一個專屬的Section來儲存Handlers的設定,如下所示。
	 
	
		
			| 
				 
					app.config 
			 | 
		
		
			| 
				 
					<?xmlversion="1.0"encoding="utf-8"?> 
				
					<configuration> 
				
					  <configSections> 
				
					    <sectionGroupname="simpleFramework"> 
				
					      <sectionname="dataHandlers" 
				
					 type="SimpleFramework.Configuration.DataHandlerConfigurationHandler, SimpleFramework" 
				
					               allowLocation="true"   allowDefinition="Everywhere"/> 
				
					    </sectionGroup> 
				
					  </configSections> 
				
					  <connectionStrings> 
				
					    <addname="WindowsFormsApplication1.Properties.Settings.NorthwindConnectionString" 
				
					        connectionString="Data Source=127.0.0.1;Initial Catalog=Northwind;Integrated Security=True" 
				
					        providerName="System.Data.SqlClient"/> 
				
					  </connectionStrings> 
				
					  <simpleFramework> 
				
					    <dataHandlers> 
				
					      <items> 
				
					        <dataItemkey="Customers"> 
				
					          <handlers> 
				
					            <handlertype="SaveExt.CustomerExt, SaveExt"/> 
				
					          </handlers> 
				
					        </dataItem> 
				
					      </items> 
				
					    </dataHandlers> 
				
					  </simpleFramework> 
				
					  <appSettings> 
				
					  </appSettings> 
				
					</configuration> 
			 | 
		
	
	DataHandlerConfigurationHandler的原始碼如下。
	 
	
		
			| 
				 
					DataExtensionConfigurationHandler.cs(in SimpleFramework) 
			 | 
		
		
			
				using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace SimpleFramework.Configuration
{
    public class DataHandlerConfigurationHandler:ConfigurationSection
    {
        [ConfigurationProperty("items", IsDefaultCollection = true)]
        [ConfigurationCollection(typeof(DataHandlerSectionElementCollection),
AddItemName = "dataItem")]
        public DataHandlerSectionElementCollection Items
        {
            get
            {
                return (DataHandlerSectionElementCollection)this["items"];
            }
        }
        public static List GetHandlers(string key)
        {
            DataHandlerConfigurationHandler handler =               (DataHandlerConfigurationHandler)System.Configuration.ConfigurationManager.GetSection(
"simpleFramework/dataHandlers");
            foreach (DataHandlerSectionElement item in handler.Items)
            {
                if (item.Key == key && item.Handlers.Count > 0)
                    return item.Handlers.GetHandlers();
            }
            return null;
        }
    }
    public class DataHandlerSectionElement : ConfigurationElement
    {
        [ConfigurationProperty("key", IsRequired = true, IsKey = true)]
        public string Key
        {
            get
            {
                return (string)this["key"];
            }
        }
        [ConfigurationProperty("handlers", IsDefaultCollection = true)]
        [ConfigurationCollection(typeof(DataHandlerElementCollection), AddItemName = "handler")]
        public DataHandlerElementCollection Handlers
        {
            get
            {
                return (DataHandlerElementCollection)this["handlers"];
            }
        }
    }
    public class DataHandlerSectionElementCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new DataHandlerSectionElement();
        }
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((DataHandlerSectionElement)element).Key;
        }
    }
    public class DataHandlerElement : ConfigurationElement
    {
        [ConfigurationProperty("type", IsRequired = true, IsKey = true)]
        public string Type
        {
            get
            {
                return (string)this["type"];
            }
        }
        public IDataHandler GetHandler()
        {
            if (!string.IsNullOrEmpty(Type))
            {
                string[] partAssem = Type.Split(',');
                return TypeLoader.CreateInstance(partAssem[1], partAssem[0],
"SimpleFramework.IDataHandler") as IDataHandler;
            }
            return null;
        }
    }
    public class DataHandlerElementCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new DataHandlerElement();
        }
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((DataHandlerElement)element).Type;
        }
        public List GetHandlers()
        {
            List result = new List();
            foreach (DataHandlerElement item in this)
                result.Add(item.GetHandler());
            return result;
        }
    }
}
 
			 | 
		
	
	隨著DataExtensionConfigurationHandler的加入,GridViewHandler與FormViewHandler也要做些修改來適應。
	 
	
		
			| 
				 
					GridViewHandler.cs 
			 | 
		
		
			
				using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Configuration;
namespace SimpleFramework.WinForms
{
    public class GridViewHandler
    {
        private List _instances;
        private DataGridView _view;
        public void Initialize(string key, DataGridView view)
        {
            view.RowValidating += new DataGridViewCellCancelEventHandler(view_RowValidating);
            _view = view;
            _instances =
SimpleFramework.Configuration.DataHandlerConfigurationHandler.GetHandlers(key);
        }
        void view_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
        {
            OrderedDictionary values = new OrderedDictionary();
            foreach (DataGridViewCell item in _view.Rows[e.RowIndex].Cells)
            {
                if(item.Value is DBNull)
                    values.Add(_view.Columns[item.ColumnIndex].DataPropertyName, null);
                else
                    values.Add(_view.Columns[item.ColumnIndex].DataPropertyName, item.Value);
            }
            foreach (var item in _instances)
                item.BeforeSave(values);
            foreach (DataGridViewCell item in _view.Rows[e.RowIndex].Cells)
                item.Value = values[_view.Columns[item.ColumnIndex].DataPropertyName];
        }
    }
}
 
			 | 
		
		
			| 
				 
					FormViewExt.cs 
			 | 
		
		
			
				using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using System.Configuration;
using System.Reflection;
using SimpleFramework;
namespace SimpleFramework.Web
{
    public class FormViewHandler
    {
        private List _instances;
        public void Initialize(string key, FormView view)
        {
            _instances =
SimpleFramework.Configuration.DataHandlerConfigurationHandler.GetHandlers(key);
            view.ItemInserting += new FormViewInsertEventHandler(view_ItemInserting);
            view.ItemUpdating += new FormViewUpdateEventHandler(view_ItemUpdating);
        }
        void view_ItemInserting(object sender, FormViewInsertEventArgs e)
        {
            foreach (var item in _instances)
                item.BeforeSave(e.Values);
        }
        void view_ItemUpdating(object sender, FormViewUpdateEventArgs e)
        {
            foreach (var item in _instances)
                item.BeforeSave(e.NewValues);
        }
    }
}
 
			 | 
		
	
	CreateStep_2中可找到此例的原始碼。
	 
	using dispatcher pattern
	 
	  照上節的設計,GridViewHandler/FormViewHandler都要做出修改,那有沒有更簡單的方法呢?有的,只要應用先前用過的Dispatcher pattern即可。
	 
	
		
			| 
				 
					DataExtensionConfigurationHandler.cs(in SimpleFramework) 
			 | 
		
		
			
				using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace SimpleFramework.Configuration
{
    public class HandlerDispatcher : IDataHandler
    {
        private List _innerHandlers = null;
        public HandlerDispatcher(List handlers)
        {
            _innerHandlers = handlers;
        }
        public void BeforeSave(System.Collections.Specialized.IOrderedDictionary values)
        {
            foreach (var item in _innerHandlers)
                item.BeforeSave(values);
        }
    }
    public class DataHandlerConfigurationHandler:ConfigurationSection
    {
        [ConfigurationProperty("items", IsDefaultCollection = true)]
        [ConfigurationCollection(typeof(DataHandlerSectionElementCollection), AddItemName = "dataItem")]
        public DataHandlerSectionElementCollection Items
        {
            get
            {
                return (DataHandlerSectionElementCollection)this["items"];
            }
        }
        public static IDataHandler GetHandler(string key)
        {
            DataHandlerConfigurationHandler handler =
               (DataHandlerConfigurationHandler)System.Configuration.ConfigurationManager.GetSection(
"simpleFramework/dataHandlers");
            foreach (DataHandlerSectionElement item in handler.Items)
            {
                if (item.Key == key && item.Handlers.Count > 0)
                    return item.Handlers.GetHandler();
            }
            return null;
        }
    }
    public class DataHandlerSectionElement : ConfigurationElement
    {
        [ConfigurationProperty("key", IsRequired = true, IsKey = true)]
        public string Key
        {
            get
            {
                return (string)this["key"];
            }
        }
        [ConfigurationProperty("handlers", IsDefaultCollection = true)]
        [ConfigurationCollection(typeof(DataHandlerElementCollection), AddItemName = "handler")]
        public DataHandlerElementCollection Handlers
        {
            get
            {
                return (DataHandlerElementCollection)this["handlers"];
            }
        }
    }
    public class DataHandlerSectionElementCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new DataHandlerSectionElement();
        }
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((DataHandlerSectionElement)element).Key;
        }
    }
    public class DataHandlerElement : ConfigurationElement
    {
        [ConfigurationProperty("type", IsRequired = true, IsKey = true)]
        public string Type
        {
            get
            {
                return (string)this["type"];
            }
        }
        public IDataHandler GetHandler()
        {
            if (!string.IsNullOrEmpty(Type))
            {
                string[] partAssem = Type.Split(',');
                return TypeLoader.CreateInstance(partAssem[1], partAssem[0],
"SimpleFramework.IDataHandler") as IDataHandler;
            }
            return null;
        }
    }
    public class DataHandlerElementCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new DataHandlerElement();
        }
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((DataHandlerElement)element).Type;
        }
        public IDataHandler GetHandler()
        {
            List result = new List();
            foreach (DataHandlerElement item in this)
                result.Add(item.GetHandler());
            return new HandlerDispatcher(result);
        }
    }
}
 
			 | 
		
	
	整個架構如圖4。
	
	你可以在CreateExt_Step4找到完整的範例程式碼。
	 
	configuration providers
	 
	  當提出Simple Framework這種可抽換式的架構時,我常聽到的回應多半不是這些東西怎麼實作出來,而是組態檔所放置的位置,例如不想放在app.config/web.config裡,
	想放在例如Database、另一個Server、或是由Web Service取得等等的需求,但實際上要達到這些需求並不難,比起設計一整個可抽換式架構來說,這簡直就是小兒科,
	本文末尾將Simple Framework修改成如圖5所示。
	圖5
	
	簡單的說,將讀取組態檔的部分應用抽換式架構,平移至一個外部的Assembly中,形成可抽換式的組態檔來源。
	 
	
		
			| 
				 
					DataExtensionConfigurationHandler.cs(in SimpleFramework) 
			 | 
		
		
			
				using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace SimpleFramework.Configuration
{
    public interface IDataHandlerConfigurationProvider
    {
        IDataHandler GetHandler(string providerConnectionString, string key);
    }
    public class HandlerDispatcher : IDataHandler
    {
        private List _innerHandlers = null;
        public HandlerDispatcher(List handlers)
        {
            _innerHandlers = handlers;
        }
        public void BeforeSave(System.Collections.Specialized.IOrderedDictionary values)
        {
            foreach (var item in _innerHandlers)
                item.BeforeSave(values);
        }
    }
    public class DataHandlerConfigurationHandler:ConfigurationSection
    {
        [ConfigurationProperty("items", IsDefaultCollection = true)]
        [ConfigurationCollection(typeof(DataHandlerSectionElementCollection),
AddItemName = "dataItem")]
        public DataHandlerSectionElementCollection Items
        {
            get
            {
                return (DataHandlerSectionElementCollection)this["items"];
            }
        }
        [ConfigurationProperty("provider")]
        public string Provider
        {
            get
            {
                return (string)this["provider"];
            }
        }
        [ConfigurationProperty("providerConnectionString")]
        public string ProviderConnectionString
        {
            get
            {
                return (string)this["providerConnectionString"];
            }
        }
        public static IDataHandler GetHandler(string key)
        {
            DataHandlerConfigurationHandler handler =
               (DataHandlerConfigurationHandler)System.Configuration.ConfigurationManager.GetSection(
"simpleFramework/dataHandlers");
            if (!string.IsNullOrEmpty(handler.Provider))
            {
                string[] partAssem = handler.Provider.Split(',');
                IDataHandlerConfigurationProvider provider =
                    (IDataHandlerConfigurationProvider)TypeLoader.CreateInstance(partAssem[1], partAssem[0],
                             "SimpleFramework.Configuration.IDataHandlerConfigurationProvider");
                return provider.GetHandler(handler.ProviderConnectionString, key);
            }
            foreach (DataHandlerSectionElement item in handler.Items)
            {
                if (item.Key == key && item.Handlers.Count > 0)
                    return item.Handlers.GetHandler();
            }
            return null;
        }
    }
    public class DataHandlerSectionElement : ConfigurationElement
    {
        [ConfigurationProperty("key", IsRequired = true, IsKey = true)]
        public string Key
        {
            get
            {
                return (string)this["key"];
            }
        }
        [ConfigurationProperty("handlers", IsDefaultCollection = true)]
        [ConfigurationCollection(typeof(DataHandlerElementCollection),
AddItemName = "handler")]
        public DataHandlerElementCollection Handlers
        {
            get
            {
                return (DataHandlerElementCollection)this["handlers"];
            }
        }
    }
    public class DataHandlerSectionElementCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new DataHandlerSectionElement();
        }
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((DataHandlerSectionElement)element).Key;
        }
    }
    public class DataHandlerElement : ConfigurationElement
    {
        [ConfigurationProperty("type", IsRequired = true, IsKey = true)]
        public string Type
        {
            get
            {
                return (string)this["type"];
            }
        }
        public IDataHandler GetHandler()
        {
            if (!string.IsNullOrEmpty(Type))
            {
                string[] partAssem = Type.Split(',');
                return TypeLoader.CreateInstance(partAssem[1], partAssem[0], "SimpleFramework.IDataHandler") as IDataHandler;
            }
            return null;
        }
    }
    public class DataHandlerElementCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new DataHandlerElement();
        }
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((DataHandlerElement)element).Type;
        }
        public IDataHandler GetHandler()
        {
            List result = new List();
            foreach (DataHandlerElement item in this)
                result.Add(item.GetHandler());
            return new HandlerDispatcher(result);
        }
    }
}
 
			 | 
		
	
	CreateExt_Step5目錄中就是應用此架構的範例,你可於其中找到XmlConfigurationProvider Project,其將會由另一個XML檔案中讀取組態檔。
	 
	
		
			| 
				 
					XmlProvider.cs(in XmlConfigurationProvider project) 
			 | 
		
		
			
				using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Reflection;
using System.IO;
using SimpleFramework;
using SimpleFramework.Configuration;
namespace XmlConfigurationProvider
{
    public class XmlProvider:IDataHandlerConfigurationProvider
    {
        public SimpleFramework.IDataHandler GetHandler(string providerConnectionString, string key)
        {
            XDocument doc = XDocument.Load(AppDomain.CurrentDomain.BaseDirectory+"\\"+providerConnectionString);
            var keyRoot = (from s1 in doc.Descendants("dataItem") where s1.Attribute("key").Value == key select s1).FirstOrDefault();
            if (keyRoot != null)
            {
                var items = keyRoot.Descendants("handlers").FirstOrDefault().Descendants("handler");
                List list = new List();
                foreach (var item in items)
                {
                    string[] partAssem = item.Attribute("type").Value.Split(',');
                    list.Add((IDataHandler)TypeLoader.CreateInstance(partAssem[1], partAssem[0], "SimpleFramework.IDataHandler"));
                }
                return new HandlerDispatcher(list);
            }
            return null;
        }
    }
}
 
			 | 
		
	
	以下為web.config的組態內容。
	 
	
		
			| 
				 
					<?xmlversion="1.0"?> 
				
					  
				
					<!-- 
				
					  For more information on how to configure your ASP.NET application, please visit 
				
					  http://go.microsoft.com/fwlink/?LinkId=169433 
				
					  --> 
				
					  
				
					<configuration> 
				
					  <configSections> 
				
					    <sectionGroupname="simpleFramework"> 
				
					      <sectionname="dataHandlers"type="SimpleFramework.Configuration.DataHandlerConfigurationHandler, SimpleFramework" 
				
					               allowLocation="true"   allowDefinition="Everywhere"/> 
				
					    </sectionGroup> 
				
					  </configSections> 
				
					    <connectionStrings> 
				
					        <addname="NorthwindConnectionString" 
				
					connectionString= 
				
					"Data Source=127.0.0.1;Initial Catalog=Northwind;Integrated Security=True" 
				
					            providerName="System.Data.SqlClient"/> 
				
					    </connectionStrings> 
				
					  <simpleFramework> 
				
					    <dataHandlers 
				
					provider="XmlConfigurationProvider.XmlProvider, XmlConfigurationProvider"providerConnectionString="handler.config"/> 
				
					  </simpleFramework> 
				
					    <system.web> 
				
					        <compilationdebug="true"targetFramework="4.0"/> 
				
					    </system.web> 
				
					</configuration> 
			 | 
		
	
	handler.config的內容如下:
	 
	
		
			| 
				 
					<?xmlversion="1.0"encoding="utf-8"?> 
				
					<items> 
				
					  <dataItemkey="Customers"> 
				
					    <handlers> 
				
					      <handlertype="SaveExt.CustomerExt, SaveExt"/> 
				
					    </handlers> 
				
					  </dataItem> 
				
					</items> 
			 | 
		
	
	 
	本文後記
	 
	  說實在的,當你掌握了動態載入Assembly及設計組態檔的技巧後,要設計一個簡單的可抽換式架構就不難了,難得是當這些元件載入後彼此間的互動,
	這又是另一個層次的問題了,未來本系列文章會持續地討論這些。
	範例下載: http://code6421.myweb.hinet.net/Framework/Arch_2.zip