Popular Posts
Enable SSL connection for Jsoup import org.jsoup.Connection; import org.jsoup.Jsoup; import javax.net.ssl.*; import java.io.IOException; import java.security.KeyManagement... JDateField package bruce.lib.swing; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.A... Get files name in batch Windows Batch : get filename using [%%~ni] Linux Shell Script : get filename using [awk, sed] windows sample : rename all *.htm file to ...
Stats
Window service : dynamic load dll module and execute

Project(DLL) : ServicePlugin

Configuration

Duration.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Configuration
{
    public enum Duration
    {
        Day,
        Week,
        Month
    }
}
Logger.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;

namespace ScheduleService.Configuration
{
    public class Logger : ConfigurationElement
    {
        /// <summary>
        /// DLL檔案位置
        /// </summary>
        [ConfigurationProperty("binary")]
        public string Binary
        {
            get { return (string)this["binary"]; }
            set { this["binary"] = value; }
        }
        /// <summary>
        /// 實體名稱
        /// </summary>
        [ConfigurationProperty("assembly", IsRequired = true)]
        public string Assembly
        {
            get { return (string)this["assembly"]; }
            set { this["assembly"] = value; }
        }
    }
}
MemoryUsage.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;

namespace ScheduleService.Configuration
{
    /// <summary>
    /// 已不使用
    /// </summary>
    public sealed class MemoryUsage : ConfigurationElement
    {
        [ConfigurationProperty("interval", IsRequired = false, DefaultValue = 60)]
        public int Interval
        {
            get { return (int)this["interval"]; }
            set { this["interval"] = value; }
        }

        [ConfigurationProperty("monitor", IsRequired = false, DefaultValue = false)]
        public bool Monitor
        {
            get { return (bool)this["monitor"]; }
            set { this["monitor"] = value; }
        }
    }
}
Module.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Runtime.Serialization;
using ScheduleService.Remoting;

namespace ScheduleService.Configuration
{
    public sealed class Module : ConfigurationElement
    {
        /// <summary>
        /// 模組名稱
        /// </summary>
        [ConfigurationProperty("name", IsKey = true)]
        public string Name
        {
            get { return (string)this["name"]; }
            set { this["name"] = value; }
        }
        /// <summary>
        /// 介面名稱
        /// </summary>
        [ConfigurationProperty("type", IsRequired = true)]
        public string Type
        {
            get { return (string)this["type"]; }
            set { this["type"] = value; }
        }
        /// <summary>
        /// DLL檔案位置
        /// </summary>
        [ConfigurationProperty("binary")]
        public string Binary
        {
            get { return (string)this["binary"]; }
            set { this["binary"] = value; }
        }
        /// <summary>
        /// 實體名稱
        /// </summary>
        [ConfigurationProperty("assembly", IsRequired = true)]
        public string Assembly
        {
            get { return (string)this["assembly"]; }
            set { this["assembly"] = value; }
        }
        /// <summary>
        /// 執行間隔(秒)
        /// </summary>
        [ConfigurationProperty("interval", IsRequired = false)]
        public int Interval
        {
            get { return (int)this["interval"]; }
            set { this["interval"] = value; }
        }

        /// <summary>
        /// 執行週期
        /// </summary>
        [ConfigurationProperty("duration", IsRequired = false, DefaultValue = Duration.Day)]
        public Duration Duration
        {
            get { return (Duration)this["duration"]; }
            set { this["duration"] = value; }
        }

        /// <summary>
        /// 執行時間點(格式tt:MM:dd)
        /// </summary>
        [ConfigurationProperty("executePoint", IsRequired = false)]
        public string ExecutePoint
        {
            get { return (string)this["executePoint"]; }
            set { this["executePoint"] = value; }
        }

        /// <summary>
        /// 執行失敗時, 嘗試重新執行一次
        /// </summary>
        [ConfigurationProperty("reTryOnFailed", IsRequired = false, DefaultValue = false)]
        public bool ReTryOnFailed
        {
            get { return (bool)this["reTryOnFailed"]; }
            set { this["reTryOnFailed"] = value; }
        }

        public DateTime StartTime { get; set; }
        public DateTime LastExecuted { get; set; }
        public DateTime NextExecute { get; set; }
        public Exception Exception { get; set; }

        public override string ToString()
        {
            return string.Format("<module name=\"{0}\" type=\"{1}\" binary=\"{2}\" assembly=\"{3}\" interval=\"{4}\" />", this.Name, this.Type, this.Binary, this.Assembly, this.Interval);
        }

        public override int GetHashCode()
        {
            return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly).GetHashCode();
        }

        public override bool Equals(object compareTo)
        {
            if (compareTo == null || !(compareTo is Module)) return false;
            Module obj = compareTo as Module;
            return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly)
                == string.Format("{0}{1}{2}{3}", obj.Name, obj.Type, obj.Binary, obj.Assembly);
        }

        public ModuleData ToModuleData()
        {
            ModuleData data = new ModuleData();
            data.Assembly = this.Assembly;
            data.Binary = this.Binary;
            data.Exception = this.Exception;
            data.Interval = this.Interval;
            data.LastExecuted = this.LastExecuted;
            data.Name = this.Name;
            data.NextExecute = this.NextExecute;
            data.StartTime = this.StartTime;
            data.Type = this.Type;
            data.Duration = this.Duration;
            data.ExecutePoint = this.ExecutePoint;
            data.ReTryOnFailed = this.ReTryOnFailed;
            return data;
        }
    }
}
ModuleCollection.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Configuration;
using System.Configuration;

namespace ScheduleService.Configuration
{
    public sealed class ModuleCollection : ConfigurationElementCollection
    {
        public override ConfigurationElementCollectionType CollectionType
        {
            get
            {
                return ConfigurationElementCollectionType.AddRemoveClearMap;
            }
        }

        public Module this[int index]
        {
            get { return (Module)BaseGet(index); }
            set
            {
                if (BaseGet(index) != null)
                    BaseRemove(index);
                BaseAdd(index, value);
            }
        }

        protected override ConfigurationElement CreateNewElement()
        {
            return new Module();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return (element as Module).Name;
        }

        #region Operation
        public void Add(Module module)
        {
            BaseAdd(module);
        }

        public void Clear()
        {
            BaseClear();
        }

        public void Rmove(Module module)
        {
            BaseRemove(module.Name);
        }

        public void Remove(string name)
        {
            BaseRemove(name);
        }

        public void RemoveAt(int index)
        {
            BaseRemoveAt(index);
        }
        #endregion
    }
}
MonitorPort.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;


namespace ScheduleService.Configuration
{
    public sealed class MonitorPort : ConfigurationElement
    {
        [ConfigurationProperty("value", IsRequired = true)]
        public int Value
        {
            get { return (int)this["value"]; }
            set { this["value"] = value; }
        }
    }
}
Plugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;

namespace ScheduleService.Configuration
{
    public sealed class Plugin : ConfigurationSection
    {
        ///// <summary>
        ///// 重載間隔
        ///// </summary>
        //[ConfigurationProperty("reloadInterval")]
        //public ReloadInterval ReloadConfigInterval
        //{
        //    get { return (ReloadInterval)this["reloadInterval"]; }
        //    set { this["reloadInterval"] = value; }
        //}
        ///// <summary>
        ///// 監控記憶體使用情況
        ///// </summary>
        //[ConfigurationProperty("memoryUsage")]
        //public MemoryUsage MemoryUsage
        //{
        //    get { return (MemoryUsage)this["memoryUsage"]; }
        //    set { this["memoryUsage"] = value; }
        //}
        /// <summary>
        /// 監控通訊埠
        /// </summary>
        [ConfigurationProperty("monitorPort")]
        public MonitorPort MonitorPort
        {
            get { return (MonitorPort)this["monitorPort"]; }
            set { this["monitorPort"] = value; }
        }
        /// <summary>
        /// 外掛log
        /// </summary>
        [ConfigurationProperty("exceptionHandler")]
        public Logger ExceptionHandler
        {
            get { return (Logger)this["exceptionHandler"]; }
            set { this["exceptionHandler"] = value; }
        }
        /// <summary>
        /// 模組
        /// </summary>
        [ConfigurationProperty("plugins")]
        [ConfigurationCollection(typeof(ModuleCollection), AddItemName = "module", ClearItemsName = "clearModules", RemoveItemName = "removeModules")]
        public ModuleCollection Plugins
        {
            get { return (ModuleCollection)this["plugins"]; }
            set { this["plugins"] = value; }
        }
    }
}
ReloadInterval.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;

namespace ScheduleService.Configuration
{
    /// <summary>
    /// 已不使用
    /// </summary>
    public sealed class ReloadInterval : ConfigurationElement
    {
        [ConfigurationProperty("value", IsRequired = true)]
        public int Value
        {
            get { return (int)this["value"]; }
            set { this["value"] = value; }
        }
    }
}

Plugin

IAlarmPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Plugin
{
    public interface IAlarmPlugin
    {
        void ExecuteAt(object sender, System.Timers.ElapsedEventArgs e);
    }
}
IComboPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Plugin
{
    public interface IComboPlugin
    {
        bool OnExecuting();
        void OnExecuted();
    }
}
IComplexPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Plugin
{
    public interface IComplexPlugin : ISimplePlugin, IComboPlugin, IGeneralPlugin
    {

    }
}
IGeneralPlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Plugin
{
    public interface IGeneralPlugin
    {
        bool OnStart();
        void OnStop();
    }
}
ISimplePlugin.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Plugin
{
    public interface ISimplePlugin
    {
        void Execute(object sender, System.Timers.ElapsedEventArgs e);
    }
}
PluginLoader.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.XPath;
using System.Xml.Linq;
using System.Configuration;
using ScheduleService.Configuration;
using ScheduleService.Utilily;

namespace ScheduleService.Plugin
{
    public class PluginLoader
    {
        private List<Module> LoadedPlugins;
        /// <summary>
        /// 載入目前執行個體的設定檔
        /// </summary>
        public void LoadConfig()
        {
            LoadConfig(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath);
        }
        /// <summary>
        /// 載入指定的設定檔
        /// </summary>
        /// <param name="configPath"></param>
        public void LoadConfig(string configPath)
        {
            try
            {
                LoadedPlugins = new List<Module>();
                #region 重新載入設定檔
                ConfigurationManager.RefreshSection("scheduleService.plugin");
                ModuleCollection modules = ((Configuration.Plugin)ConfigurationManager.GetSection("scheduleService.plugin")).Plugins;
                foreach (Module m in modules)
                {
                    LoadedPlugins.Add(m);
                }
                #endregion
                #region 以讀檔方式重新載入設定檔
                //XElement root = XElement.Load(configPath);
                //IEnumerator<XElement> itor = root.XPathSelectElements("//scheduleService.plugin/plugins/module").GetEnumerator();
                //while (itor.MoveNext())
                //{
                //    try
                //    {
                //        Module m = new Module();
                //        m.Assembly = itor.Current.Attribute("assembly").Value;
                //        m.Binary = itor.Current.Attribute("binary").Value;
                //        m.Name = itor.Current.Attribute("name").Value;
                //        m.Type = itor.Current.Attribute("type").Value;
                //        m.Interval = int.Parse(itor.Current.Attribute("interval").Value);
                //        LoadedPlugins.Add(m);
                //    }
                //    catch (Exception ex)
                //    {
                //        ServiceLogger.Log(string.Format("載入模組{0}時發生錯誤", itor.Current.ToString()), ex);
                //    }
                //}
                #endregion
            }
            catch (Exception ex)
            {
                ServiceLogger.Log(string.Format("載入設定檔{0}時發生錯誤", configPath), ex);
            }
        }
        /// <summary>
        /// 取得指定模組資料
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Module GetModule(string name)
        {
            if (LoadedPlugins == null)
            {
                LoadConfig();
            }

            foreach (Module m in LoadedPlugins)
            {
                if (m.Name == name)
                    return m;
            }
            return null;
        }
        /// <summary>
        /// 取得所有模組
        /// </summary>
        /// <returns></returns>
        public Module[] GetModules()
        {
            if (LoadedPlugins == null)
            {
                LoadConfig();
            }

            return LoadedPlugins.ToArray();
        }
        /// <summary>
        /// 取得所有模組設定字串
        /// </summary>
        /// <returns></returns>
        public string[] GetModuleStrings()
        {
            if (LoadedPlugins == null)
            {
                LoadConfig();
            }

            List<string> list = new List<string>();
            foreach (Module m in LoadedPlugins)
            {
                list.Add(m.ToString());
            }
            return list.ToArray();
        }
    }
}
Timer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using ScheduleService.Configuration;
using ScheduleService.Utilily;
using System.Text.RegularExpressions;

namespace ScheduleService.Plugin
{
    public class Timer : System.Timers.Timer
    {
        private bool flag;
        /// <summary>
        /// 模組的實體物件
        /// </summary>
        public object Instance { get; private set; }
        /// <summary>
        /// 執行模組的資料
        /// </summary>
        public ScheduleService.Configuration.Module Source { get; private set; }

        public Timer(ScheduleService.Configuration.Module module)
            : base()
        {
            try
            {
                Source = module;
                base.Enabled = true;


                Assembly assembly = Assembly.LoadFrom(PathHelper.PluginPath + Source.Binary);
                switch (Source.Type)
                {
                    case "ScheduleService.Plugin.ISimplePlugin":
                    case "ScheduleService.Plugin.IComplexPlugin":
                        ISimplePlugin simplePlugin = (ISimplePlugin)assembly.CreateInstance(Source.Assembly);
                        base.Interval = module.Interval * 1000;
                        base.Elapsed += new System.Timers.ElapsedEventHandler(Simple_Elapsed);
                        this.Instance = simplePlugin;
                        this.Source.NextExecute = DateTime.Now.AddSeconds(this.Source.Interval);
                        break;
                    case "ScheduleService.Plugin.IAlarmPlugin":  // 定時類型
                        IAlarmPlugin alarmPlugin = (IAlarmPlugin)assembly.CreateInstance(Source.Assembly);
                        base.Interval = 30000;  // 每30秒執行檢查
                        base.Elapsed += new System.Timers.ElapsedEventHandler(Alarm_Elapsed);
                        this.Instance = alarmPlugin;
                        this.Source.NextExecute = this.NextExecute(DateTime.Now);
                        break;
                    case "ScheduleService.Plugin.IGeneralPlugin":  // 未測試
                        IGeneralPlugin generalPlugin = (IGeneralPlugin)assembly.CreateInstance(Source.Assembly);
                        base.Enabled = false;
                        this.Instance = generalPlugin;
                        break;
                }
            }
            catch (Exception ex)
            {
                ServiceLogger.Log(string.Format("建立'{0}'實體時發生錯誤", Source.Assembly), ex);
                throw ex;
            }
        }

        void Alarm_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (this.flag)
            {
                ServiceLogger.Log("前一個處理排程正在進行中,略過本次排程");
                return;
            }

            if (string.Format("{0:yyyyMMddHHmm}", this.NextExecute(DateTime.Now.AddMinutes(-1))) == string.Format("{0:yyyyMMddHHmm}", DateTime.Now))
            {
                this.flag = true;
                this.Enabled = false;
                this.Source.LastExecuted = e.SignalTime; // 執行時間
                this.Source.NextExecute = this.NextExecute(DateTime.Now.AddMinutes(5));  // 預計下次執行時間
                try
                {
                    this.ExecuteAlarm(sender, e);

                    this.Source.Exception = null;
                }
                catch (Exception ex)
                {
                    this.Source.Exception = ex;
                    ServiceLogger.Log(string.Format("排程工作[{0}]執行時發生錯誤", Source.Name), ex);
                    if (this.Source.ReTryOnFailed)
                    {
                        // 延遲30秒後重試
                        System.Threading.Thread.Sleep(30000);
                        ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]", Source.Name), ex);
                        try
                        {
                            this.ExecuteAlarm(sender, e);
                        }
                        catch (Exception exx)
                        {
                            ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]執行時發生錯誤", Source.Name), exx);
                        }
                    }
                }

                // 強迫執行時間大於60秒, 避免重覆執行
                System.Threading.Thread.Sleep(60000);

                this.Enabled = true;
                this.flag = false;

                // 回收記憶體
                GC.Collect();
            }
        }

        private void ExecuteAlarm(object sender, System.Timers.ElapsedEventArgs e)
        {
            IComboPlugin comboPlugin = this.Instance as IComboPlugin;
            IAlarmPlugin alarmPlugin = this.Instance as IAlarmPlugin;

            // OnExecuting
            bool isCanceled = false;
            if (this.Instance is IComboPlugin && comboPlugin != null)
            {
                isCanceled = !comboPlugin.OnExecuting();
            }

            // Execute
            if (isCanceled)
            {
                ServiceLogger.Log(string.Format("執行模組[{0}]時被前置作業取消", Source.Name));
            }
            else
            {
                ServiceLogger.Log(string.Format("開始執行模組[{0}]", Source.Name));
                alarmPlugin.ExecuteAt(sender, e);
                ServiceLogger.Log(string.Format("排程工作[{0}]執行完成", Source.Name));
            }

            // OnExecuted
            if (this.Instance is IComboPlugin && comboPlugin != null)
            {
                comboPlugin.OnExecuted();
            }
        }


        void Simple_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (this.flag)
            {
                ServiceLogger.Log("前一個處理排程正在進行中,略過本次排程");
                return;
            }

            this.flag = true;
            this.Enabled = false;
            this.Source.LastExecuted = e.SignalTime; // 執行時間
            this.Source.NextExecute = e.SignalTime.AddSeconds(this.Source.Interval);  // 預計下次執行時間
            try
            {
                this.ExecuteSimple(sender, e);

                this.Source.Exception = null;
            }
            catch (Exception ex)
            {
                this.Source.Exception = ex;
                ServiceLogger.Log(string.Format("排程工作[{0}]執行時發生錯誤", Source.Name), ex);

                if (this.Source.ReTryOnFailed)
                {
                    // 延遲30秒後重試
                    System.Threading.Thread.Sleep(30000);
                    ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]", Source.Name), ex);
                    try
                    {
                        this.ExecuteSimple(sender, e);
                    }
                    catch (Exception exx)
                    {
                        ServiceLogger.Log(string.Format("重新嘗試執行排程工作[{0}]執行時發生錯誤", Source.Name), exx);
                    }
                }
            }

            this.Enabled = true;
            this.flag = false;

            // 回收記憶體
            GC.Collect();
        }

        private void ExecuteSimple(object sender, System.Timers.ElapsedEventArgs e)
        {
            IComboPlugin comboPlugin = this.Instance as IComboPlugin;
            ISimplePlugin simplePlugin = this.Instance as ISimplePlugin;

            // OnExecuting
            bool isCanceled = false;
            if (this.Instance is IComboPlugin && comboPlugin != null)
            {
                isCanceled = !comboPlugin.OnExecuting();
            }

            // Execute
            if (isCanceled)
            {
                ServiceLogger.Log(string.Format("執行模組[{0}]時被前置作業取消", Source.Name));
            }
            else
            {
                ServiceLogger.Log(string.Format("開始執行模組[{0}]", Source.Name));
                simplePlugin.Execute(sender, e);
                ServiceLogger.Log(string.Format("排程工作[{0}]執行完成", Source.Name));
            }

            // OnExecuted
            if (this.Instance is IComboPlugin && comboPlugin != null)
            {
                comboPlugin.OnExecuted();
            }
        }

        /// <summary>
        /// 計算下次執行時間
        /// </summary>
        /// <param name="baseTime"></param>
        /// <returns></returns>
        private DateTime NextExecute(DateTime baseTime)
        {
            int duration = -1;
            int hour = -1;
            int minute = -1;
            string[] time = this.Source.ExecutePoint.Split(new char[] { ':' });
            if (Regex.IsMatch(this.Source.ExecutePoint, "^(\\d+:)?\\d+:\\d+$"))
            {
                switch (time.Length)
                {
                    case 3:
                        // 計算週期的值
                        duration = int.Parse(time[0]);
                        switch (this.Source.Duration)
                        {
                            case Duration.Week:
                                if (duration < 0 || duration > 6) throw new FormatException("執行時間點:星期數值錯誤");
                                break;
                            case Duration.Month:
                                if (duration < 0 || duration > 31) throw new FormatException("執行時間點:日期數值錯誤");
                                break;
                        }
                        // 小時值
                        hour = int.Parse(time[1]);
                        if (hour < 0 || hour > 23) throw new FormatException("執行時間點:小時數值錯誤");
                        // 分鐘值
                        minute = int.Parse(time[2]);
                        if (hour < 0 || hour > 59) throw new FormatException("執行時間點:分鐘數值錯誤");
                        break;
                    case 2:
                        // 小時值
                        hour = int.Parse(time[0]);
                        if (hour < 0 || hour > 23) throw new FormatException("執行時間點:小時數值錯誤");
                        // 分鐘值
                        minute = int.Parse(time[1]);
                        if (hour < 0 || hour > 59) throw new FormatException("執行時間點:分鐘數值錯誤");
                        break;
                    default:
                        throw new FormatException("執行時間點:時間格式錯誤");
                }


                DateTime nextExecute = baseTime;
                switch (this.Source.Duration)
                {
                    case Duration.Day:
                        nextExecute = new DateTime(baseTime.Year, baseTime.Month, baseTime.Day, hour, minute, 0);
                        if (nextExecute < baseTime) nextExecute = nextExecute.AddDays(1);
                        break;
                    case Duration.Week:
                        int todayOfWeek = (int)DateTime.Now.DayOfWeek; // 今天的星期
                        int leftDays = duration - todayOfWeek < 0 ? duration - todayOfWeek + 7 : duration - todayOfWeek;  // 距下次執行日期的天數
                        nextExecute = new DateTime(baseTime.Year, baseTime.Month, baseTime.Day, hour, minute, 0).AddDays(leftDays);
                        if (nextExecute < baseTime) nextExecute = nextExecute.AddDays(7);
                        break;
                    case Duration.Month:
                        duration = Math.Min(DateTime.DaysInMonth(baseTime.Year, baseTime.Month), duration);  // 數值大於當月天數時, 在最後一天執行
                        nextExecute = new DateTime(baseTime.Year, baseTime.Month, duration, hour, minute, 0);
                        if (nextExecute < baseTime) nextExecute = nextExecute.AddMonths(1);
                        break;
                }
                return nextExecute;
            }
            else
            {
                throw new FormatException("執行時間點:時間格式錯誤");
            }
        }
    }
}

Remoting

MemoryUsage.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ScheduleService.Remoting
{
    [Serializable]
    public class MemoryUsage
    {
        public int NonpagedSystemMemorySize { get; set; }
        public long NonpagedSystemMemorySize64 { get; set; }
        public int PagedMemorySize { get; set; }
        public long PagedMemorySize64 { get; set; }
        public int PagedSystemMemorySize { get; set; }
        public long PagedSystemMemorySize64 { get; set; }
        public int PeakPagedMemorySize { get; set; }
        public long PeakPagedMemorySize64 { get; set; }
        public int PeakVirtualMemorySize { get; set; }
        public long PeakVirtualMemorySize64 { get; set; }
        public int PeakWorkingSet { get; set; }
        public long PeakWorkingSet64 { get; set; }
        public int PrivateMemorySize { get; set; }
        public long PrivateMemorySize64 { get; set; }
        public int VirtualMemorySize { get; set; }
        public long VirtualMemorySize64 { get; set; }
        public int WorkingSet { get; set; }
        public long WorkingSet64 { get; set; }

        protected internal void Refresh()
        {
            Process p = Process.GetCurrentProcess();
            this.NonpagedSystemMemorySize = p.NonpagedSystemMemorySize;
            this.NonpagedSystemMemorySize64 = p.NonpagedSystemMemorySize64;
            this.PagedMemorySize = p.PagedMemorySize;
            this.PagedMemorySize64 = p.PagedMemorySize64;
            this.PagedSystemMemorySize = p.PagedSystemMemorySize;
            this.PagedSystemMemorySize64 = p.PagedSystemMemorySize64;
            this.PeakPagedMemorySize = p.PeakPagedMemorySize;
            this.PeakPagedMemorySize64 = p.PeakPagedMemorySize64;
            this.PeakVirtualMemorySize = p.PeakVirtualMemorySize;
            this.PeakVirtualMemorySize64 = p.PeakVirtualMemorySize64;
            this.PeakWorkingSet = p.PeakWorkingSet;
            this.PeakWorkingSet64 = p.PeakWorkingSet64;
            this.PrivateMemorySize = p.PrivateMemorySize;
            this.PrivateMemorySize64 = p.PrivateMemorySize64;
            this.VirtualMemorySize = p.VirtualMemorySize;
            this.VirtualMemorySize64 = p.VirtualMemorySize64;
            this.WorkingSet = p.WorkingSet;
            this.WorkingSet64 = p.WorkingSet64;
        }
    }
}
ModuleData.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Configuration;

namespace ScheduleService.Remoting
{
    [Serializable]
    public sealed class ModuleData
    {
        public string Name { get; set; }
        public string Type { get; set; }
        public string Binary { get; set; }
        public string Assembly { get; set; }
        public int Interval { get; set; }
        public Duration Duration { get; set; }
        public string ExecutePoint { get; set; }
        public bool ReTryOnFailed { get; set; }

        public DateTime StartTime { get; set; }
        public DateTime LastExecuted { get; set; }
        public DateTime NextExecute { get; set; }
        public Exception Exception { get; set; }


        public override int GetHashCode()
        {
            return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly).GetHashCode();
        }

        public override bool Equals(object compareTo)
        {
            if (compareTo == null || !(compareTo is ModuleData)) return false;
            ModuleData obj = compareTo as ModuleData;
            return string.Format("{0}{1}{2}{3}", this.Name, this.Type, this.Binary, this.Assembly)
                == string.Format("{0}{1}{2}{3}", obj.Name, obj.Type, obj.Binary, obj.Assembly);
        }

    }
}
Monitor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Configuration;

namespace ScheduleService.Remoting
{
    public interface Monitor
    {
        MemoryUsage GetUsage();

        ModuleData[] GetModules();
    }
}
ServiceClient.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Net;

namespace ScheduleService.Remoting
{
    public class ServiceClient : Monitor
    {
        private TcpChannel channel;
        private Monitor remotingObj;

        public void Connect(string host, int port)
        {
            if (channel != null)
            {
                this.Close();
            }

            IPAddress[] ip = Dns.GetHostAddresses(host);
            if (ip.Count() == 0) throw new Exception("無法解析主機名稱或ip");

            this.channel = new TcpChannel();
            ChannelServices.RegisterChannel(this.channel, false);

            remotingObj = Activator.GetObject(
                typeof(Monitor),
                string.Format("tcp://{0}:{1}/ScheduleService.Remoting", ip[0], port)
            ) as Monitor;
        }

        public void Close()
        {
            if (this.channel != null)
            {
                ChannelServices.UnregisterChannel(this.channel);
                this.channel.StopListening(null);
                this.channel = null;
            }

            GC.Collect();
        }

        #region Monitor 成員

        public MemoryUsage GetUsage()
        {
            try
            {
                if (remotingObj == null)
                    return null;
                else
                    return remotingObj.GetUsage();
            }
            catch //(Exception ex)
            {
                return null;
            }
        }

        public ModuleData[] GetModules()
        {
            try
            {

                if (remotingObj == null)
                    return null;
                else
                    return remotingObj.GetModules();
            }
            catch //(Exception ex)
            {
                return null;
            }
        }

        #endregion
    }
}
ServiceMonitor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;

namespace ScheduleService.Remoting
{
    public class ServiceMonitor : MarshalByRefObject, Monitor
    {
        private TcpChannel channel;

        public void StartListen(int port)
        {
            // 結束既有的監聽
            if (this.channel != null)
            {
                this.StopListen();
            }

            this.channel = new TcpChannel(port);
            ChannelServices.RegisterChannel(this.channel, false);
            RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServiceMonitor),
                "ScheduleService.Remoting",
                WellKnownObjectMode.SingleCall
            );
        }

        public void StopListen()
        {
            if (this.channel != null)
            {
                ChannelServices.UnregisterChannel(this.channel);
                this.channel.StopListening(null);
                this.channel = null;
            }

            GC.Collect();
        }

        #region Monitor 成員

        public MemoryUsage GetUsage()
        {
            MemoryUsage usage = new MemoryUsage();
            usage.Refresh();
            return usage;
        }

        public ModuleData[] GetModules()
        {
            if (ScheduleServiceBase.ModuleList == null)
            {
                return new ModuleData[0];
            }
            else
            {
                List<ModuleData> list = new List<ModuleData>();
                foreach (ScheduleService.Configuration.Module m in ScheduleServiceBase.ModuleList)
                {
                    list.Add(m.ToModuleData());
                }
                return list.ToArray();
            }
        }

        #endregion
    }
}

Utilily

Convertor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Utilily
{
    public class Convertor
    {
        public static string MatchSize(double size, SizeName sizeName)
        {
            if (size < 1024 || sizeName == SizeName.TB)
            {
                return string.Format("{0:####.##}{1}", size, sizeName);
            }
            else
            {
                return Convertor.MatchSize(size / 1024, (SizeName)(((int)sizeName) + 1));
            }
        }
    }
}
ExceptionHandler.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Utilily
{
    public interface ExceptionHandler
    {
        void Log(string description);
        void Log(string description, Exception ex);
        void Log(string description, Exception ex, object data);
    }
}
FileoutputHandler.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using ScheduleService.Configuration;

namespace ScheduleService.Utilily
{
    public class FileoutputHandler : ScheduleService.Utilily.ExceptionHandler
    {
        #region ExceptionHandler 成員

        public void Log(string description)
        {
            File.AppendAllText(string.Format("{0}ScheduleService{1:yyyyMMdd}.log", PathHelper.LogPath, DateTime.Now),
                string.Format(@"Time : {0}
Description : {1}

", DateTime.Now, description));
        }

        public void Log(string description, Exception ex)
        {
            File.AppendAllText(string.Format("{0}ScheduleService{1:yyyyMMdd}.log", PathHelper.LogPath, DateTime.Now),
                string.Format(@"Time : {0}
Description : {1}
Message : {2}
Source : {3}
Exception type : {4}
Stack : {5}

", DateTime.Now, description, ex.Message, ex.Source, ex.GetType().Name, ex.StackTrace));
        }

        public void Log(string description, Exception ex, object data)
        {
            this.Log(description, ex);
        }

        #endregion
    }
}
PathHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace ScheduleService.Utilily
{
    public class PathHelper
    {
        /// <summary>
        /// 程式目錄
        /// </summary>
        public static string ApplicationPath
        {
            get { return AppDomain.CurrentDomain.BaseDirectory; }
        }
        /// <summary>
        /// 模組目錄
        /// </summary>
        public static string PluginPath
        {
            get { return string.Format("{0}Plugin\\", PathHelper.ApplicationPath); }
        }
        /// <summary>
        /// Log目錄
        /// </summary>
        public static string LogPath
        {
            get
            {
                string logPath = string.Format("{0}Log\\", PathHelper.ApplicationPath);
                if (!Directory.Exists(logPath))
                    Directory.CreateDirectory(logPath);
                return logPath;
            }
        }

        public static string TempPath
        {
            get
            {
                string tempPath = string.Format("{0}Temp\\", PathHelper.ApplicationPath);
                if (!Directory.Exists(tempPath))
                    Directory.CreateDirectory(tempPath);
                return tempPath;
            }
        }
    }
}
ServiceLogger.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Utilily;

namespace ScheduleService.Utilily
{
    public class ServiceLogger
    {
        private static ExceptionHandler exHandler;

        public static void SetHandler(ExceptionHandler handler)
        {
            exHandler = handler;
        }

        public static void Log(string description)
        {
            if (exHandler != null)
                exHandler.Log(description);
        }
        public static void Log(string description, Exception ex)
        {
            if (exHandler != null)
                exHandler.Log(description, ex);
        }
        public static void Log(string description, Exception ex, object data)
        {
            if (exHandler != null)
                exHandler.Log(description, ex, data);
        }
    }
}
SizeName.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScheduleService.Utilily
{
    public enum SizeName : int
    {
        Bytes = 1,
        KB = 2,
        MB = 3,
        GB = 4,
        TB = 5
    }
}

ScheduleServiceBase.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScheduleService.Plugin;
using System.Reflection;
using ScheduleService.Configuration;
using System.Diagnostics;
using System.Configuration;
using ScheduleService.Utilily;
using System.IO;
using ScheduleService.Remoting;

namespace ScheduleService
{
    public class ScheduleServiceBase : System.ServiceProcess.ServiceBase
    {
        /// <summary>
        /// 排程的timer
        /// </summary>
        private List<Timer> tasks;
        /// <summary>
        /// 目前載入的模組
        /// </summary>
        private static List<ScheduleService.Configuration.Module> moduleList;
        protected internal static List<ScheduleService.Configuration.Module> ModuleList { get { return ScheduleServiceBase.moduleList; } }
        /// <summary>
        /// 設定檔監控
        /// </summary>
        private FileSystemWatcher configWatcher;
        /// <summary>
        /// 遠端監控
        /// </summary>
        private ServiceMonitor monitor;

        public ScheduleServiceBase()
        {
            this.tasks = new List<Timer>();
            ScheduleServiceBase.moduleList = new List<ScheduleService.Configuration.Module>();
            this.configWatcher = new FileSystemWatcher();
            this.monitor = new ServiceMonitor();

            // 指定Log記錄方式
            ServiceLogger.SetHandler(new FileoutputHandler());
            ServiceLogger.Log("狀態: 啟動服務");

            ScheduleService.Configuration.Plugin plugin = null;
            try
            {
                plugin = (ScheduleService.Configuration.Plugin)ConfigurationManager.GetSection("scheduleService.plugin");
            }
            catch (Exception ex)
            {
                ServiceLogger.Log("初始設定檔時發生錯誤", ex);
            }

            try
            {
                if (plugin != null && plugin.ExceptionHandler != null && !string.IsNullOrEmpty(plugin.ExceptionHandler.Binary))
                {
                    Assembly assembly = Assembly.LoadFrom(PathHelper.PluginPath + plugin.ExceptionHandler.Binary);
                    ExceptionHandler handler = (ExceptionHandler)assembly.CreateInstance(plugin.ExceptionHandler.Assembly);
                    if (handler == null)
                        throw new Exception("建立例外處理器實體失敗");
                    else
                        ServiceLogger.SetHandler(handler);
                }
            }
            catch (Exception ex)
            {
                ServiceLogger.Log("設定例外處理器時發生錯誤", ex);
            }

            try
            {
                // 起始remoting
                this.monitor.StartListen(plugin.MonitorPort.Value);
            }
            catch (Exception ex)
            {
                ServiceLogger.Log("設定遠端監控時發生錯誤", ex);
            }
        }


        /// <summary>
        /// 測試用
        /// </summary>
        /// <param name="args"></param>
        public void DoStart(string[] args)
        {
            this.OnStart(args);
        }
        /// <summary>
        /// 啟始設定檔載入程式
        /// </summary>
        /// <param name="args"></param>
        protected override void OnStart(string[] args)
        {
            try
            {
                #region 監控設定是否改變
                this.configWatcher.Path = PathHelper.ApplicationPath;
                this.configWatcher.Filter = "*.config";
                this.configWatcher.IncludeSubdirectories = false;
                this.configWatcher.EnableRaisingEvents = true;
                this.configWatcher.Changed += new FileSystemEventHandler(configWatcher_Changed);
                #endregion

                // 載入模組
                this.ChangeModule();
            }
            catch (Exception ex)
            {
                ServiceLogger.Log("起動程式時發生錯誤", ex);
            }
        }

        /// <summary>
        /// 異動時新增/移除模組
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void configWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            // 暫止監控
            this.configWatcher.EnableRaisingEvents = false;

            // 設定檔變更時
            if (e.Name == string.Format("{0}.config", AppDomain.CurrentDomain.FriendlyName))
            {
                this.ChangeModule();
            }

            // 恢復監控
            this.configWatcher.EnableRaisingEvents = true;
        }
        /// <summary>
        /// 新增/移除模組
        /// </summary>
        private void ChangeModule()
        {
            try
            {
                PluginLoader loader = new PluginLoader();
                ScheduleService.Configuration.Module[] plugins = loader.GetModules();

                #region 加入新增的模組
                foreach (ScheduleService.Configuration.Module m in plugins)
                {
                    if (!ScheduleServiceBase.moduleList.Contains(m))
                    {
                        try
                        {
                            Timer timer = new Timer(m);
                            // 初始化(OnInit)
                            if (timer.Instance is IGeneralPlugin)
                            {
                                IGeneralPlugin complexPlugin = (IGeneralPlugin)timer.Instance;
                                if (!complexPlugin.OnStart())
                                {
                                    ServiceLogger.Log(string.Format("模組[{0}]初始化時由前置作業終止,未載入模組", m.Name));
                                    continue;
                                }
                            }
                            timer.Start();
                            this.tasks.Add(timer);
                            ScheduleServiceBase.moduleList.Add(m);

                            // 記錄時間
                            m.StartTime = DateTime.Now;
                            ServiceLogger.Log(string.Format("加入新模組[{0}]", m.Name));
                        }
                        catch (Exception ex)
                        {
                            ServiceLogger.Log(string.Format("加入模組[{0}]時發生錯誤", m.Name), ex);
                        }
                    }
                }
                #endregion

                #region 移除模組
                ScheduleService.Configuration.Module[] currentModule = loader.GetModules();
                for (int i = this.tasks.Count - 1; i >= 0; i--)
                {
                    Timer timer = this.tasks[i];
                    try
                    {
                        if (!currentModule.Contains(timer.Source))
                        {
                            timer.Enabled = false;
                            timer.Stop();
                            this.tasks.Remove(timer);
                            ScheduleServiceBase.ModuleList.Remove(timer.Source);
                            // 回收(Dispose)
                            if (timer.Instance is IGeneralPlugin)
                            {
                                IGeneralPlugin generalPlugin = (IGeneralPlugin)timer.Instance;
                                generalPlugin.OnStop();
                            }
                            ServiceLogger.Log(string.Format("已移除模組[{0}]", timer.Source.Name));
                        }
                    }
                    catch (Exception ex)
                    {
                        ServiceLogger.Log(string.Format("移除模組[{0}]時發生錯誤", timer.Source.Name), ex);
                    }
                }
                #endregion

                // 回收記憶體
                GC.Collect();
            }
            catch (Exception ex)
            {
                ServiceLogger.Log("模組異動時發生錯誤", ex);
            }
        }
        /// <summary>
        /// 測試用
        /// </summary>
        public void DoStop()
        {
            this.OnStop();
        }
        protected override void OnStop()
        {
            // 停止監控設定檔
            this.configWatcher.EnableRaisingEvents = false;
            // 停止remoting channel
            this.monitor.StopListen();

            foreach (ScheduleService.Plugin.Timer timer in this.tasks)
            {
                try
                {
                    timer.Enabled = false;
                    timer.Stop();
                    if (timer.Instance is IGeneralPlugin)
                    {
                        IGeneralPlugin generalPlugin = (IGeneralPlugin)timer.Instance;
                        generalPlugin.OnStop();
                    }
                }
                catch (Exception ex)
                {
                    ServiceLogger.Log("停止服務時發生錯誤", ex);
                }
            }
            ServiceLogger.Log("狀態: 停止服務");
        }
    }
}
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="scheduleService.plugin" type="ScheduleService.Configuration.Plugin, ServicePlugin"/>
    </configSections>
    <scheduleService.plugin>
        <monitorPort value="9876" />
        <!--<exceptionHandler binary="MyLib.dll" assembly="MyLib.MyHandler" />-->
        <plugins>
            <!--<module name="PluginName" type="ScheduleService.Plugin.IGeneralPlugin" binary="MyLib.dll" assembly="MyLib.ClassName" />-->
            <!--<module name="PluginName" type="ScheduleService.Plugin.ISimplePlugin" binary="MyLib.dll" assembly="MyLib.ClassName" interval="10" reTryOnFailed="false" />-->
            <!--
                druation = Day | Week | Month
                executePoint = (tt:)HH:mm
            -->
            <!--<module name="PluginName" type="ScheduleService.Plugin.IAlarmPlugin" binary="MyLib.dll" assembly="MyLib.ClassName" duration="Month" executePoint="3:15:32" reTryOnFailed="false" />-->
        </plugins>
    </scheduleService.plugin>
</configuration>
Review of installation/uninstallation
%windir%\Microsoft.NET\Framework\v2.0.50727\installutil ServiceController.exe
%windir%\Microsoft.NET\Framework\v2.0.50727\installutil /u ServiceController.exe
Send mail tool
using System;
using System.Net.Mail;
using System.Net;

namespace Bruce.Mail
{
    public class MailFactory
    {
        private static SmtpClient client;
        public static void SetSmtp(string smtpHost, int smtpPort, string username, string password)
        {
            SetSmtp(smtpHost, smtpPort);
            client.UseDefaultCredentials = false;
            client.Credentials = new NetworkCredential(username, password);
        }
        public static void SetSmtp(string smtpHost, int smtpPort)
        {
            client = new SmtpClient(smtpHost, smtpPort);
        }
        public static void SetSmtp()
        {
            client = new SmtpClient();
        }

        private MailMessage message;
        public MailMessage Message
        {
            get { return this.message; }
        }

        public MailFactory()
        {
            this.message = new MailMessage();
        }

        public MailFactory(MailAddress from, MailAddress to)
        {
            this.message = new MailMessage(from, to);
        }

        public MailFactory(string fromName, string fromAddress, string toName, string toAddress)
        {
            MailAddress from = string.IsNullOrEmpty(fromName) ? new MailAddress(fromAddress) : new MailAddress(fromAddress, fromName);
            MailAddress to = string.IsNullOrEmpty(toName) ? new MailAddress(toAddress) : new MailAddress(toAddress, toName);
            this.message = new MailMessage(from, to);
        }

        public void Write(string subject, string body)
        {
            this.message.Subject = subject;
            this.message.IsBodyHtml = false;
            this.message.Body = body;
            this.Send();
        }

        public Exception Send()
        {
            try
            {
                if (client == null) SetSmtp();
                client.Send(this.message);
                return null;
            }
            catch (Exception ex)
            {
                return ex;
            }
        }
    }
}
using System;
using System.Net.Mail;
using System.Collections.Generic;
using System.Threading;

namespace Bruce.Mail
{
    public abstract class PostMan
    {
        private static Queue<MailAddress> Recivers = new Queue<MailAddress>();
        public static void AddReciver(MailAddress address)
        {
            Recivers.Enqueue(address);
        }
        public static void AddReciver(string address)
        {
            Recivers.Enqueue(new MailAddress(address));
        }

        private int checkInterval;
        public int CheckInterval
        {
            get { return this.checkInterval; }
            set { this.checkInterval = value; }
        }
        private bool flag = true;
        public bool Flag
        {
            get { return this.flag; }
            set { this.flag = value; }
        }

        public void Work()
        {
            while (Flag)
            {
                if (Recivers.Count == 0)
                {
                    Thread.Sleep(30000);
                }
                else
                {
                    MailAddress to = Recivers.Dequeue();
                    Deliver(to);
                }
            }
        }

        abstract void Deliver(MailAddress to);
    }
}
ServerControl : RequestProcesser v2
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Security.Permissions;
using System.ComponentModel;
using System.Text;


namespace Bruce.Web.Controls
{
    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [ToolboxData("<{0}:RequestProcesser ID=\"RequestProcesser\" runat=\"server\"></{0}:RequestProcesser>")]
    public class RequestProcesser : CompositeControl
    {
        /// <summary>
        /// JavaScript物件名稱
        /// </summary>
        private string ObjectName = "__RequestProcesser";

        [Browsable(false)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public ProcesserAction Action { get; set; }


        [Browsable(false)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public ITemplate ContentTemplate { get; set; }


        protected override void OnPreRender(EventArgs e)
        {
            ScriptManager sm = ScriptManager.GetCurrent(this.Page);
            if (sm == null)
            {
                #region OnBeginRequest
                string script = string.IsNullOrEmpty(this.Action.OnBeginRequest.Text.Trim())
                    ? "document.getElementById('" + this.ClientID + "').style.display = 'block';"
                    : string.Format(@"
if((function(){{
{0}
}})() == false){{
    return false;
}}else{{
    document.getElementById('{1}').style.display = 'block';
    return true;
}}", this.Action.OnBeginRequest.Text.Trim(), this.ClientID);
                this.Page.ClientScript.RegisterOnSubmitStatement(this.Page.GetType(), "OnBeginRequest", script);
                #endregion
            }
            else
            {
                StringBuilder script = new StringBuilder();
                script.AppendFormat("var {0} = {{}};{1}", this.ObjectName, Environment.NewLine);

                #region OnBeginRequest

                script.AppendFormat(@"{0}.OnBeginRequest = function() {{
    if((function(){{
{1}
}})() == false){{
        return false;
    }}else{{
        document.getElementById('{2}').style.display = 'block';
        return true;
    }}
}};
//Sys.WebForms.PageRequestManager.getInstance().add_beginRequest({0}.OnBeginRequest);
",
this.ObjectName,
this.Action == null || this.Action.OnBeginRequest == null ? string.Empty : this.Action.OnBeginRequest.Text.Trim(),
this.ClientID);
                this.Page.ClientScript.RegisterOnSubmitStatement(this.Page.GetType(), "OnBeginRequest", "return " + ObjectName + ".OnBeginRequest();");

                #endregion

                #region OnEndRequest
                script.AppendFormat(@"{0}.OnEndRequest = function() {{
    document.getElementById('{1}').style.display = 'none';
    (function(){{
{2}
}})();
}};
Sys.WebForms.PageRequestManager.getInstance().add_endRequest({0}.OnEndRequest);
",
 this.ObjectName,
 this.ClientID,
 this.Action == null || this.Action.OnEndRequest == null ? string.Empty : this.Action.OnEndRequest.Text.Trim());
                #endregion

                this.Page.ClientScript.RegisterStartupScript(this.Page.GetType(), "Processing", script.ToString(), true);
            }


            this.Style["display"] = "none";

            base.OnPreRender(e);
        }

        protected override void CreateChildControls()
        {
            Controls.Clear();
            if (ContentTemplate != null)
            {
                ContentTemplate.InstantiateIn(this);
            }
        }

        public override void DataBind()
        {
            CreateChildControls();
            ChildControlsCreated = true;
            base.DataBind();
        }
    }

    [DesignTimeVisible(false)]
    public class ProcesserAction
    {
        [PersistenceMode(PersistenceMode.InnerProperty),]
        public Literal OnBeginRequest { get; set; }
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public Literal OnEndRequest { get; set; }
    }
}
ServerControl : RequestProcesser
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.IO;

namespace Bruce.Web.Controls
{
    public class RequestProcesser : Control
    {
        /// <summary>
        /// 送出Request前的動作
        /// </summary>
        public string OnBeginRequest { get; set; }
        /// <summary>
        /// 收到Response後的動作
        /// </summary>
        public string OnEndRequest { get; set; }
        /// <summary>
        /// JavaScript物件名稱
        /// </summary>
        private string ObjectName = "__RequestProcesser";

        protected override void OnPreRender(EventArgs e)
        {
            ScriptManager sm = ScriptManager.GetCurrent(this.Page);
            if (sm == null)
            {
                string script = string.IsNullOrEmpty(this.OnBeginRequest)
                    ? "document.getElementById('" + this.ClientID + "').style.display = 'block';"
                    : string.Format(@"
if((function(){{{0}}})() == false){{
    return false;
}}else{{
    document.getElementById('{1}').style.display = 'block';
    return true;
}}", this.OnBeginRequest, this.ClientID);
                this.Page.ClientScript.RegisterOnSubmitStatement(this.Page.GetType(), "OnBeginRequest", script);
            }
            else
            {
                this.Page.ClientScript.RegisterStartupScript(this.Page.GetType(), "Processing", string.Format(@"
var {3} = {{
    OnBeginRequest: function() {{
        if((function(){{{1}}})() == false){{
            return false;
        }}else{{
            document.getElementById('{0}').style.display = 'block';
            return true;
        }}
    }},
    OnEndRequest: function() {{
        document.getElementById('{0}').style.display = 'none';
        (function(){{{2}}})();
    }}
}};
//Sys.WebForms.PageRequestManager.getInstance().add_beginRequest({3}.OnBeginRequest);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest({3}.OnEndRequest);
", this.ClientID, this.OnBeginRequest, this.OnEndRequest, ObjectName), true);
                this.Page.ClientScript.RegisterOnSubmitStatement(this.Page.GetType(), "OnBeginRequest", "return " + ObjectName + ".OnBeginRequest();");
            }
            base.OnPreRender(e);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            writer.WriteBeginTag(string.Format("div id=\"{0}\" style=\"display:none;\">", this.ClientID));
            base.Render(writer);
            writer.WriteEndTag("div");
        }
    }
}
Escape {} in string.format
string.Format(@"
if({0} == false){{
    return false;
}}else{{
    document.getElementById('{1}').style.display = 'block';
    return true;
}}", this.OnBeginRequest, this.ClientID);
ServerControl : CGridView
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;

namespace Bruce.Web.Controls
{
    public class CGridView : GridView
    {
        private string _HoverRowStyle = "background-color:#C5DEFF;";
        public string HoverRowStyle { get { return _HoverRowStyle; } set { this._HoverRowStyle = value; } }


        protected override void OnRowDataBound(System.Web.UI.WebControls.GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                //當滑鼠放上去的時候 先儲存目前行的背景顏色 並設定顏色
                e.Row.Attributes.Add("onmouseover", string.Format("this.setAttribute('currentStyle',this.style.cssText);this.style.cssText='{0}'", this.HoverRowStyle));
                //當滑鼠離開的時候 將背景顏色回存的以前的顏色
                e.Row.Attributes.Add("onmouseout", "this.style.cssText=this.getAttribute('currentStyle');");
            }
            base.OnRowDataBound(e);
        }
    }
}
ServerControl : GridView Pager
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;

namespace Bruce.Web.Controls
{
    /// <summary>
    /// GridViewPager 的摘要描述
    /// </summary>
    [ToolboxData("<{0}:GridViewPager ID=\"GridViewPager\" runat=\"server\"></{0}:GridViewPager>")]
    public class GridViewPager : Control
    {
        #region 按鈕文字
        private string _FirstPageText = "第一頁";
        public string FirstPageText
        {
            get { return _FirstPageText; }
            set { _FirstPageText = value; }
        }
        private string _PreviousPageText = "上一頁";
        public string PreviousPageText
        {
            get { return _PreviousPageText; }
            set { _PreviousPageText = value; }
        }
        private string _NextPageText = "下一頁";
        public string NextPageText
        {
            get { return _NextPageText; }
            set { _NextPageText = value; }
        }
        private string _LastPageText = "最後頁";
        public string LastPageText
        {
            get { return _LastPageText; }
            set { _LastPageText = value; }
        }
        #endregion

        /// <summary>
        /// 使用圖示按鈕
        /// </summary>
        private bool _IsImageButton;
        public bool IsImageButton
        {
            get { return _IsImageButton; }
            set { _IsImageButton = value; }
        }

        #region 按鈕圖示
        private string _FirstPageImageUrl;
        public string FirstPageImageUrl
        {
            get { return _FirstPageImageUrl; }
            set { _FirstPageImageUrl = value; }
        }
        private string _PreviousPageImageUrl;
        public string PreviousPageImageUrl
        {
            get { return _PreviousPageImageUrl; }
            set { _PreviousPageImageUrl = value; }
        }
        private string _NextPageImageUrl;
        public string NextPageImageUrl
        {
            get { return _NextPageImageUrl; }
            set { _NextPageImageUrl = value; }
        }
        private string _LastPageImageUrl;
        public string LastPageImageUrl
        {
            get { return _LastPageImageUrl; }
            set { _LastPageImageUrl = value; }
        }
        #endregion

        /// <summary>
        /// 使用下拉選單
        /// </summary>
        private bool _IsDropDownList;
        public bool IsDropDownList
        {
            get { return _IsDropDownList; }
            set { _IsDropDownList = value; }
        }

        /// <summary>
        /// 顯示頁碼個數
        /// </summary>
        private int _PagerRange = 3;
        public int PagerRange
        {
            get { return _PagerRange; }
            set
            {
                if (value < 1)
                {
                    _PagerRange = 1;
                }
                else if (value > 10)
                {
                    _PagerRange = 10;
                }
                else
                {
                    _PagerRange = value;
                }
            }
        }

        /// <summary>
        /// 頁碼間隔
        /// </summary>
        private string _PagerPadding = "3px";
        public string PagerPadding
        {
            get { return _PagerPadding; }
            set { _PagerPadding = value; }
        }

        /// <summary>
        /// 文字顏色
        /// </summary>
        private Color _ForeColor;
        public Color ForeColor
        {
            get { return _ForeColor; }
            set { _ForeColor = value; }
        }

        private Control indexer = new Control();
        private GridView container;

        protected override void OnLoad(EventArgs e)
        {
            if (this.CheckContainer())
            {
                this.Controls.Clear();
                #region 設定按鈕屬性
                if (IsImageButton)
                {
                    ImageButton btnFirstPage = CreateImageButton("btnFirstPage",
                        FirstPageText,
                        "0",
                        container.PageIndex > 0,
                        this.FirstPageImageUrl);
                    ImageButton btnPreviousPage = CreateImageButton("btnPreviousPage",
                        PreviousPageText,
                        (container.PageIndex - 1).ToString(),
                        container.PageIndex - 1 >= 0,
                        this.PreviousPageImageUrl);
                    ImageButton btnNextPage = CreateImageButton("btnNextPage",
                        NextPageText,
                        (container.PageIndex + 1).ToString(),
                        container.PageIndex + 1 < container.PageCount,
                        this.NextPageImageUrl);
                    ImageButton btnLastPage = CreateImageButton("btnLastPage",
                        LastPageText,
                        (container.PageCount - 1).ToString(),
                        container.PageIndex + 1 < container.PageCount,
                        this.LastPageImageUrl);

                    this.Controls.Add(btnFirstPage);
                    this.Controls.Add(btnPreviousPage);

                    this.Controls.Add(this.indexer);

                    this.Controls.Add(btnNextPage);
                    this.Controls.Add(btnLastPage);
                }
                else
                {
                    LinkButton btnFirstPage = CreatePageButton("btnFirstPage",
                        FirstPageText,
                        "0",
                        container.PageIndex > 0);
                    LinkButton btnPreviousPage = CreatePageButton("btnPreviousPage",
                        PreviousPageText,
                        (container.PageIndex - 1).ToString(),
                        container.PageIndex - 1 >= 0);
                    LinkButton btnNextPage = CreatePageButton("btnNextPage",
                        NextPageText,
                        (container.PageIndex + 1).ToString(),
                        container.PageIndex + 1 < container.PageCount);
                    LinkButton btnLastPage = CreatePageButton("btnLastPage",
                        LastPageText,
                        (container.PageCount - 1).ToString(),
                        container.PageIndex + 1 < container.PageCount);

                    this.Controls.Add(btnFirstPage);
                    this.Controls.Add(btnPreviousPage);

                    this.Controls.Add(this.indexer);

                    this.Controls.Add(btnNextPage);
                    this.Controls.Add(btnLastPage);
                }
                #endregion

                if (IsDropDownList)
                    this.RenderDrowDownListPager();
                else
                    this.RenderPageNumbers();
            }
            base.OnLoad(e);
        }

        /// <summary>
        /// 位置錯誤時顯示錯誤訊息
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {
            if (!this.CheckContainer())
            {
                this.Controls.Clear();
                Literal warming = new Literal();
                warming.Text = "<span style=\"color:red;\">Pager object must be in a GridView.<span>";
                this.Controls.Add(warming);
            }
            base.OnPreRender(e);
        }

        /// <summary>
        /// 檢查container
        /// </summary>
        /// <returns></returns>
        private bool CheckContainer()
        {
            if (container == null)
            {
                Control control = this;

                while (control.Parent != null && !(control.Parent is GridView))
                {
                    control = control.Parent;

                }

                if (control.Parent != null)
                {
                    container = control.Parent as GridView;
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// 建立圖形按鈕
        /// </summary>
        /// <param name="ID"></param>
        /// <param name="AlternateText"></param>
        /// <param name="CommandArgument"></param>
        /// <param name="Visible"></param>
        /// <param name="ImageUrl"></param>
        /// <returns></returns>
        private ImageButton CreateImageButton(string ID, string AlternateText, string CommandArgument, bool Visible, string ImageUrl)
        {
            ImageButton btn = new ImageButton();
            btn.ID = ID;
            btn.AlternateText = AlternateText;
            btn.CommandArgument = CommandArgument;
            btn.ImageUrl = ImageUrl;
            btn.Visible = Visible;
            btn.ImageAlign = ImageAlign.Middle;
            btn.Click += PageButton_Click;
            btn.Style["margin-left"] = this.PagerPadding;
            btn.Style["margin-right"] = this.PagerPadding;
            return btn;
        }

        /// <summary>
        /// 建立文字按鈕
        /// </summary>
        /// <param name="ID"></param>
        /// <param name="Text"></param>
        /// <param name="CommandArgument"></param>
        /// <param name="Visible"></param>
        /// <returns></returns>
        private LinkButton CreatePageButton(string ID, string Text, string CommandArgument, bool Visible)
        {
            LinkButton btn = new LinkButton();
            btn.ForeColor = this.ForeColor;
            btn.ID = ID;
            btn.Text = Text;
            btn.CommandArgument = CommandArgument;
            btn.Visible = Visible;
            btn.Click += PageButton_Click;
            btn.Style["margin-left"] = this.PagerPadding;
            btn.Style["margin-right"] = this.PagerPadding;
            return btn;
        }

        /// <summary>
        /// 處理頁碼
        /// </summary>
        private void RenderPageNumbers()
        {
            int start = Math.Max(0, this.container.PageIndex - this.PagerRange);
            int end = Math.Min(this.container.PageCount - 1, this.container.PageIndex + this.PagerRange);
            for (int i = start; i <= end; i++)
            {
                if (i == this.container.PageIndex)
                {
                    Literal currentPage = new Literal();
                    currentPage.Text = (i + 1).ToString();
                    this.indexer.Controls.Add(currentPage);
                }
                else
                {
                    this.indexer.Controls.Add(CreatePageButton("p" + i, (i + 1).ToString(), i.ToString(), true));
                }
            }
        }

        /// <summary>
        /// 處理下拉選單
        /// </summary>
        private void RenderDrowDownListPager()
        {
            DropDownList ddl = this.indexer.FindControl("ddlPageSelector") as DropDownList;
            if (ddl == null)
            {
                ddl = new DropDownList();
                ddl.ID = "ddlPageSelector";
                ddl.AutoPostBack = true;
                ddl.SelectedIndexChanged += PageList_Select;

                Literal txt = new Literal();
                txt.Text = "第";
                this.indexer.Controls.Add(txt);
                this.indexer.Controls.Add(ddl);
                txt = new Literal();
                txt.Text = "頁";
                this.indexer.Controls.Add(txt);
            }

            if (ddl.Items.Count == 0)
            {
                for (int i = 0; i < this.container.PageCount; i++)
                {
                    ddl.Items.Add(new ListItem((i + 1).ToString(), i.ToString()));
                }
            }
            ddl.SelectedValue = this.container.PageIndex.ToString();
        }

        /// <summary>
        /// 換頁動作
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void PageButton_Click(object sender, EventArgs e)
        {
            if (this.CheckContainer())
            {

                LinkButton btn = sender as LinkButton;
                if (btn != null)
                    this.container.PageIndex = int.Parse(btn.CommandArgument);

                ImageButton ibtn = sender as ImageButton;
                if (ibtn != null)
                    this.container.PageIndex = int.Parse(ibtn.CommandArgument);
            }
        }
        /// <summary>
        /// 換頁動作
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void PageList_Select(object sender, EventArgs e)
        {
            if (this.CheckContainer())
            {
                DropDownList ddl = sender as DropDownList;
                this.container.PageIndex = int.Parse(ddl.SelectedValue);
            }
        }
    }
}