詳解免費(fèi)開源的DotNet任務(wù)調(diào)度組件Quartz.NET(.NET組件介紹之五)
很多的軟件項(xiàng)目中都會(huì)使用到定時(shí)任務(wù)、定時(shí)輪詢數(shù)據(jù)庫(kù)同步,定時(shí)郵件通知等功能。.NET Framework具有“內(nèi)置”定時(shí)器功能,通過System.Timers.Timer類。在使用Timer類需要面對(duì)的問題:計(jì)時(shí)器沒有持久化機(jī)制;計(jì)時(shí)器具有不靈活的計(jì)劃(僅能設(shè)置開始時(shí)間和重復(fù)間隔,沒有基于日期,時(shí)間等);計(jì)時(shí)器不使用線程池(每個(gè)定時(shí)器一個(gè)線程);計(jì)時(shí)器沒有真正的管理方案 - 你必須編寫自己的機(jī)制,以便能夠記住,組織和檢索任務(wù)的名稱等。
如果需要在.NET實(shí)現(xiàn)定時(shí)器的功能,可以嘗試使用以下這款開源免費(fèi)的組件Quartz.Net組件。目前Quartz.NET版本為3.0,修改了原來的一些問題:修復(fù)由于線程本地存儲(chǔ)而不能與AdoJobStore協(xié)同工作的調(diào)度器信令;線程局部狀態(tài)完全刪除;quartz.serializer.type是必需的,即使非序列化RAMJobStore正在使用;JSON序列化錯(cuò)誤地稱為序列化回調(diào)。
一.Quart.NET概述:
Quartz是一個(gè)作業(yè)調(diào)度系統(tǒng),可以與任何其他軟件系統(tǒng)集成或一起使用。作業(yè)調(diào)度程序是一個(gè)系統(tǒng),負(fù)責(zé)在執(zhí)行預(yù)處理程序時(shí)執(zhí)行(或通知)其他軟件組件 - 確定(調(diào)度)時(shí)間到達(dá)。Quartz是非常靈活的,并且包含多個(gè)使用范例,可以單獨(dú)使用或一起使用,以實(shí)現(xiàn)您所需的行為,并使您能夠以您的項(xiàng)目看起來最“自然”的方式編寫代碼。組件的使用非常輕便,并且需要非常少的設(shè)置/配置 - 如果您的需求相對(duì)基礎(chǔ),它實(shí)際上可以使用“開箱即用”。Quartz是容錯(cuò)的,并且可以在系統(tǒng)重新啟動(dòng)之間保留(記?。┠念A(yù)定作業(yè)。盡管Quartz對(duì)于在給定的時(shí)間表上簡(jiǎn)單地運(yùn)行某些系統(tǒng)進(jìn)程非常有用,但當(dāng)您學(xué)習(xí)如何使用Quartz來驅(qū)動(dòng)應(yīng)用程序的業(yè)務(wù)流程時(shí),Quartz的全部潛能可以實(shí)現(xiàn)。
Quartz是作為一個(gè)小的動(dòng)態(tài)鏈接庫(kù)(.dll文件)分發(fā)的,它包含所有的核心Quartz功能。 此功能的主要接口(API)是調(diào)度程序接口。 它提供簡(jiǎn)單的操作,如調(diào)度/非調(diào)度作業(yè),啟動(dòng)/停止/暫停調(diào)度程序。如果你想安排你自己的軟件組件執(zhí)行,他們必須實(shí)現(xiàn)簡(jiǎn)單的Job接口,它包含方法execute()。 如果希望在計(jì)劃的觸發(fā)時(shí)間到達(dá)時(shí)通知組件,則組件應(yīng)實(shí)現(xiàn)TriggerListener或JobListener接口。主要的Quartz'進(jìn)程'可以在您自己的應(yīng)用程序或獨(dú)立應(yīng)用程序(使用遠(yuǎn)程接口)中啟動(dòng)和運(yùn)行。
二.Quartz.NET主體類和方法解析:
1.StdSchedulerFactory類:創(chuàng)建QuartzScheduler實(shí)例。
/// <summary>
/// 返回此工廠生成的調(diào)度程序的句柄。
/// </summary>
/// <remarks>
///如果<see cref =“Initialize()”/>方法之一沒有先前調(diào)用,然后是默認(rèn)(no-arg)<see cref =“Initialize()”/>方法將被這個(gè)方法調(diào)用。
/// </remarks>
public virtual IScheduler GetScheduler()
{
if (cfg == null)
{
Initialize();
}
SchedulerRepository schedRep = SchedulerRepository.Instance;
IScheduler sched = schedRep.Lookup(SchedulerName);
if (sched != null)
{
if (sched.IsShutdown)
{
schedRep.Remove(SchedulerName);
}
else
{
return sched;
}
}
sched = Instantiate();
return sched;
}
public interface ISchedulerFactory
{
/// <summary>
/// Returns handles to all known Schedulers (made by any SchedulerFactory
/// within this app domain.).
/// </summary>
ICollection<IScheduler> AllSchedulers { get; }
/// <summary>
/// Returns a client-usable handle to a <see cref="IScheduler" />.
/// </summary>
IScheduler GetScheduler();
/// <summary>
/// Returns a handle to the Scheduler with the given name, if it exists.
/// </summary>
IScheduler GetScheduler(string schedName);
}
2.JobDetailImpl:傳遞給定作業(yè)實(shí)例的詳細(xì)信息屬性。
/// <summary>
/// 獲取或設(shè)置與<see cref =“IJob”/>相關(guān)聯(lián)的<see cref =“JobDataMap”/>。
/// </summary>
public virtual JobDataMap JobDataMap
{
get
{
if (jobDataMap == null)
{
jobDataMap = new JobDataMap();
}
return jobDataMap;
}
set { jobDataMap = value; }
}
3.JobKey:鍵由名稱和組組成,名稱必須是唯一的,在組內(nèi)。 如果只指定一個(gè)組,則默認(rèn)組將使用名稱。
[Serializable]
public sealed class JobKey : Key<JobKey>
{
public JobKey(string name) : base(name, null)
{
}
public JobKey(string name, string group) : base(name, group)
{
}
public static JobKey Create(string name)
{
return new JobKey(name, null);
}
public static JobKey Create(string name, string group)
{
return new JobKey(name, group);
}
}
4.StdSchedulerFactory.Initialize():
/// <summary>
/// 使用初始化<see cref =“ISchedulerFactory”/>
///給定鍵值集合對(duì)象的內(nèi)容。
/// </summary>
public virtual void Initialize(NameValueCollection props)
{
cfg = new PropertiesParser(props);
ValidateConfiguration();
}
protected virtual void ValidateConfiguration()
{
if (!cfg.GetBooleanProperty(PropertyCheckConfiguration, true))
{
// should not validate
return;
}
// determine currently supported configuration keys via reflection
List<string> supportedKeys = new List<string>();
List<FieldInfo> fields = new List<FieldInfo>(GetType().GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy));
// choose constant string fields
fields = fields.FindAll(field => field.FieldType == typeof (string));
// read value from each field
foreach (FieldInfo field in fields)
{
string value = (string) field.GetValue(null);
if (value != null && value.StartsWith(ConfigurationKeyPrefix) && value != ConfigurationKeyPrefix)
{
supportedKeys.Add(value);
}
}
// now check against allowed
foreach (string configurationKey in cfg.UnderlyingProperties.AllKeys)
{
if (!configurationKey.StartsWith(ConfigurationKeyPrefix) || configurationKey.StartsWith(ConfigurationKeyPrefixServer))
{
// don't bother if truly unknown property
continue;
}
bool isMatch = false;
foreach (string supportedKey in supportedKeys)
{
if (configurationKey.StartsWith(supportedKey, StringComparison.InvariantCulture))
{
isMatch = true;
break;
}
}
if (!isMatch)
{
throw new SchedulerConfigException("Unknown configuration property '" + configurationKey + "'");
}
}
}
三.Quartz.NET的基本應(yīng)用:
下面提供一些較為通用的任務(wù)處理代碼:
1.任務(wù)處理幫助類:
/// <summary>
/// 任務(wù)處理幫助類
/// </summary>
public class QuartzHelper
{
public QuartzHelper() { }
public QuartzHelper(string quartzServer, string quartzPort)
{
Server = quartzServer;
Port = quartzPort;
}
/// <summary>
/// 鎖對(duì)象
/// </summary>
private static readonly object Obj = new object();
/// <summary>
/// 方案
/// </summary>
private const string Scheme = "tcp";
/// <summary>
/// 服務(wù)器的地址
/// </summary>
public static string Server { get; set; }
/// <summary>
/// 服務(wù)器的端口
/// </summary>
public static string Port { get; set; }
/// <summary>
/// 緩存任務(wù)所在程序集信息
/// </summary>
private static readonly Dictionary<string, Assembly> AssemblyDict = new Dictionary<string, Assembly>();
/// <summary>
/// 程序調(diào)度
/// </summary>
private static IScheduler _scheduler;
/// <summary>
/// 初始化任務(wù)調(diào)度對(duì)象
/// </summary>
public static void InitScheduler()
{
try
{
lock (Obj)
{
if (_scheduler != null) return;
//配置文件的方式,配置quartz實(shí)例
ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
_scheduler = schedulerFactory.GetScheduler();
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 啟用任務(wù)調(diào)度
/// 啟動(dòng)調(diào)度時(shí)會(huì)把任務(wù)表中狀態(tài)為“執(zhí)行中”的任務(wù)加入到任務(wù)調(diào)度隊(duì)列中
/// </summary>
public static void StartScheduler()
{
try
{
if (_scheduler.IsStarted) return;
//添加全局監(jiān)聽
_scheduler.ListenerManager.AddTriggerListener(new CustomTriggerListener(), GroupMatcher<TriggerKey>.AnyGroup());
_scheduler.Start();
//獲取所有執(zhí)行中的任務(wù)
List<TaskModel> listTask = TaskHelper.GetAllTaskList().ToList();
if (listTask.Count > 0)
{
foreach (TaskModel taskUtil in listTask)
{
try
{
ScheduleJob(taskUtil);
}
catch (Exception e)
{
throw new Exception(taskUtil.TaskName,e);
}
}
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 啟用任務(wù)
/// <param name="task">任務(wù)信息</param>
/// <param name="isDeleteOldTask">是否刪除原有任務(wù)</param>
/// <returns>返回任務(wù)trigger</returns>
/// </summary>
public static void ScheduleJob(TaskModel task, bool isDeleteOldTask = false)
{
if (isDeleteOldTask)
{
//先刪除現(xiàn)有已存在任務(wù)
DeleteJob(task.TaskID.ToString());
}
//驗(yàn)證是否正確的Cron表達(dá)式
if (ValidExpression(task.CronExpressionString))
{
IJobDetail job = new JobDetailImpl(task.TaskID.ToString(), GetClassInfo(task.AssemblyName, task.ClassName));
//添加任務(wù)執(zhí)行參數(shù)
job.JobDataMap.Add("TaskParam", task.TaskParam);
CronTriggerImpl trigger = new CronTriggerImpl
{
CronExpressionString = task.CronExpressionString,
Name = task.TaskID.ToString(),
Description = task.TaskName
};
_scheduler.ScheduleJob(job, trigger);
if (task.Status == TaskStatus.STOP)
{
JobKey jk = new JobKey(task.TaskID.ToString());
_scheduler.PauseJob(jk);
}
else
{
List<DateTime> list = GetNextFireTime(task.CronExpressionString, 5);
foreach (var time in list)
{
LogHelper.WriteLog(time.ToString(CultureInfo.InvariantCulture));
}
}
}
else
{
throw new Exception(task.CronExpressionString + "不是正確的Cron表達(dá)式,無法啟動(dòng)該任務(wù)!");
}
}
/// <summary>
/// 初始化 遠(yuǎn)程Quartz服務(wù)器中的,各個(gè)Scheduler實(shí)例。
/// 提供給遠(yuǎn)程管理端的后臺(tái),用戶獲取Scheduler實(shí)例的信息。
/// </summary>
public static void InitRemoteScheduler()
{
try
{
NameValueCollection properties = new NameValueCollection
{
["quartz.scheduler.instanceName"] = "ExampleQuartzScheduler",
["quartz.scheduler.proxy"] = "true",
["quartz.scheduler.proxy.address"] =string.Format("{0}://{1}:{2}/QuartzScheduler", Scheme, Server, Port)
};
ISchedulerFactory sf = new StdSchedulerFactory(properties);
_scheduler = sf.GetScheduler();
}
catch (Exception ex)
{
throw new Exception(ex.StackTrace);
}
}
/// <summary>
/// 刪除現(xiàn)有任務(wù)
/// </summary>
/// <param name="jobKey"></param>
public static void DeleteJob(string jobKey)
{
try
{
JobKey jk = new JobKey(jobKey);
if (_scheduler.CheckExists(jk))
{
//任務(wù)已經(jīng)存在則刪除
_scheduler.DeleteJob(jk);
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 暫停任務(wù)
/// </summary>
/// <param name="jobKey"></param>
public static void PauseJob(string jobKey)
{
try
{
JobKey jk = new JobKey(jobKey);
if (_scheduler.CheckExists(jk))
{
//任務(wù)已經(jīng)存在則暫停任務(wù)
_scheduler.PauseJob(jk);
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 恢復(fù)運(yùn)行暫停的任務(wù)
/// </summary>
/// <param name="jobKey">任務(wù)key</param>
public static void ResumeJob(string jobKey)
{
try
{
JobKey jk = new JobKey(jobKey);
if (_scheduler.CheckExists(jk))
{
//任務(wù)已經(jīng)存在則暫停任務(wù)
_scheduler.ResumeJob(jk);
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 獲取類的屬性、方法
/// </summary>
/// <param name="assemblyName">程序集</param>
/// <param name="className">類名</param>
private static Type GetClassInfo(string assemblyName, string className)
{
try
{
assemblyName = FileHelper.GetAbsolutePath(assemblyName + ".dll");
Assembly assembly = null;
if (!AssemblyDict.TryGetValue(assemblyName, out assembly))
{
assembly = Assembly.LoadFrom(assemblyName);
AssemblyDict[assemblyName] = assembly;
}
Type type = assembly.GetType(className, true, true);
return type;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 停止任務(wù)調(diào)度
/// </summary>
public static void StopSchedule()
{
try
{
//判斷調(diào)度是否已經(jīng)關(guān)閉
if (!_scheduler.IsShutdown)
{
//等待任務(wù)運(yùn)行完成
_scheduler.Shutdown(true);
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 校驗(yàn)字符串是否為正確的Cron表達(dá)式
/// </summary>
/// <param name="cronExpression">帶校驗(yàn)表達(dá)式</param>
/// <returns></returns>
public static bool ValidExpression(string cronExpression)
{
return CronExpression.IsValidExpression(cronExpression);
}
/// <summary>
/// 獲取任務(wù)在未來周期內(nèi)哪些時(shí)間會(huì)運(yùn)行
/// </summary>
/// <param name="CronExpressionString">Cron表達(dá)式</param>
/// <param name="numTimes">運(yùn)行次數(shù)</param>
/// <returns>運(yùn)行時(shí)間段</returns>
public static List<DateTime> GetNextFireTime(string CronExpressionString, int numTimes)
{
if (numTimes < 0)
{
throw new Exception("參數(shù)numTimes值大于等于0");
}
//時(shí)間表達(dá)式
ITrigger trigger = TriggerBuilder.Create().WithCronSchedule(CronExpressionString).Build();
IList<DateTimeOffset> dates = TriggerUtils.ComputeFireTimes(trigger as IOperableTrigger, null, numTimes);
List<DateTime> list = new List<DateTime>();
foreach (DateTimeOffset dtf in dates)
{
list.Add(TimeZoneInfo.ConvertTimeFromUtc(dtf.DateTime, TimeZoneInfo.Local));
}
return list;
}
public static object CurrentTaskList()
{
throw new NotImplementedException();
}
/// <summary>
/// 獲取當(dāng)前執(zhí)行的Task 對(duì)象
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static TaskModel GetTaskDetail(IJobExecutionContext context)
{
TaskModel task = new TaskModel();
if (context != null)
{
task.TaskID = Guid.Parse(context.Trigger.Key.Name);
task.TaskName = context.Trigger.Description;
task.RecentRunTime = DateTime.Now;
task.TaskParam = context.JobDetail.JobDataMap.Get("TaskParam") != null ? context.JobDetail.JobDataMap.Get("TaskParam").ToString() : "";
}
return task;
}
}
2.設(shè)置執(zhí)行中的任務(wù):
public class TaskBll
{
private readonly TaskDAL _dal = new TaskDAL();
/// <summary>
/// 獲取任務(wù)列表
/// </summary>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <returns></returns>
public PageOf<TaskModel> GetTaskList(int pageIndex, int pageSize)
{
return _dal.GetTaskList(pageIndex, pageSize);
}
/// <summary>
/// 讀取數(shù)據(jù)庫(kù)中全部的任務(wù)
/// </summary>
/// <returns></returns>
public List<TaskModel> GetAllTaskList()
{
return _dal.GetAllTaskList();
}
/// <summary>
///
/// </summary>
/// <param name="taskId"></param>
/// <returns></returns>
public TaskModel GetById(string taskId)
{
throw new NotImplementedException();
}
/// <summary>
/// 刪除任務(wù)
/// </summary>
/// <param name="taskId"></param>
/// <returns></returns>
public bool DeleteById(string taskId)
{
return _dal.UpdateTaskStatus(taskId, -1);
}
/// <summary>
/// 修改任務(wù)
/// </summary>
/// <param name="taskId"></param>
/// <param name="status"></param>
/// <returns></returns>
public bool UpdateTaskStatus(string taskId, int status)
{
return _dal.UpdateTaskStatus(taskId, status);
}
/// <summary>
/// 修改任務(wù)的下次啟動(dòng)時(shí)間
/// </summary>
/// <param name="taskId"></param>
/// <param name="nextFireTime"></param>
/// <returns></returns>
public bool UpdateNextFireTime(string taskId, DateTime nextFireTime)
{
return _dal.UpdateNextFireTime(taskId, nextFireTime);
}
/// <summary>
/// 根據(jù)任務(wù)Id 修改 上次運(yùn)行時(shí)間
/// </summary>
/// <param name="taskId"></param>
/// <param name="recentRunTime"></param>
/// <returns></returns>
public bool UpdateRecentRunTime(string taskId, DateTime recentRunTime)
{
return _dal.UpdateRecentRunTime(taskId, recentRunTime);
}
/// <summary>
/// 根據(jù)任務(wù)Id 獲取任務(wù)
/// </summary>
/// <param name="taskId"></param>
/// <returns></returns>
public TaskModel GetTaskById(string taskId)
{
return _dal.GetTaskById(taskId);
}
/// <summary>
/// 添加任務(wù)
/// </summary>
/// <param name="task"></param>
/// <returns></returns>
public bool Add(TaskModel task)
{
return _dal.Add(task);
}
/// <summary>
/// 修改任務(wù)
/// </summary>
/// <param name="task"></param>
/// <returns></returns>
public bool Edit(TaskModel task)
{
return _dal.Edit(task);
}
}
3.任務(wù)實(shí)體:
/// <summary>
/// 任務(wù)實(shí)體
/// </summary>
public class TaskModel
{
/// <summary>
/// 任務(wù)ID
/// </summary>
public Guid TaskID { get; set; }
/// <summary>
/// 任務(wù)名稱
/// </summary>
public string TaskName { get; set; }
/// <summary>
/// 任務(wù)執(zhí)行參數(shù)
/// </summary>
public string TaskParam { get; set; }
/// <summary>
/// 運(yùn)行頻率設(shè)置
/// </summary>
public string CronExpressionString { get; set; }
/// <summary>
/// 任務(wù)運(yùn)頻率中文說明
/// </summary>
public string CronRemark { get; set; }
/// <summary>
/// 任務(wù)所在DLL對(duì)應(yīng)的程序集名稱
/// </summary>
public string AssemblyName { get; set; }
/// <summary>
/// 任務(wù)所在類
/// </summary>
public string ClassName { get; set; }
public TaskStatus Status { get; set; }
/// <summary>
/// 任務(wù)創(chuàng)建時(shí)間
/// </summary>
public DateTime? CreatedTime { get; set; }
/// <summary>
/// 任務(wù)修改時(shí)間
/// </summary>
public DateTime? ModifyTime { get; set; }
/// <summary>
/// 任務(wù)最近運(yùn)行時(shí)間
/// </summary>
public DateTime? RecentRunTime { get; set; }
/// <summary>
/// 任務(wù)下次運(yùn)行時(shí)間
/// </summary>
public DateTime? NextFireTime { get; set; }
/// <summary>
/// 任務(wù)備注
/// </summary>
public string Remark { get; set; }
/// <summary>
/// 是否刪除
/// </summary>
public int IsDelete { get; set; }
}
4.配置文件:
# You can configure your scheduler in either <quartz> configuration section # or in quartz properties file # Configuration section has precedence quartz.scheduler.instanceName = ExampleQuartzScheduler # configure thread pool info quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz quartz.threadPool.threadCount = 10 quartz.threadPool.threadPriority = Normal # job initialization plugin handles our xml reading, without it defaults are used # quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz # quartz.plugin.xml.fileNames = ~/quartz_jobs.xml # export this server to remoting context quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz quartz.scheduler.exporter.port = 555 quartz.scheduler.exporter.bindName = QuartzScheduler quartz.scheduler.exporter.channelType = tcp quartz.scheduler.exporter.channelName = httpQuartz
四.總結(jié):
在項(xiàng)目中比較多的使用到定時(shí)任務(wù)的功能,今天的介紹的組件可以很好的完成一些定時(shí)任務(wù)的要求。這篇文章主要是作為引子,簡(jiǎn)單的介紹了組件的背景和組件的使用方式,如果項(xiàng)目中需要使用,可以進(jìn)行更加深入的了解。
相關(guān)文章
asp.net 站點(diǎn)URLRewrite使用小記
asp.net的底層運(yùn)作已經(jīng)也亂談過一番, 今天記一下URLRewrite的方法。2009-11-11
Asp.Net使用服務(wù)器控件Image/ImageButton顯示本地圖片的方法
Image/ImageButton服務(wù)器控件顯示本地的圖片,實(shí)現(xiàn)思路是數(shù)據(jù)庫(kù)中存放了圖片的相對(duì)地址,讀取數(shù)據(jù)庫(kù)中的地址,用控件加載顯示圖片。具體實(shí)現(xiàn)步驟大家參考下本文2017-08-08
如何為CheckBoxList和RadioButtonList添加滾動(dòng)條
這篇文章主要介紹了為CheckBoxList和RadioButtonList添加滾動(dòng)條的方法,感興趣的小伙伴們可以參考一下2016-07-07
Visual Studio 2017 community安裝配置方法圖文教程
這篇文章主要為大家詳細(xì)介紹了Visual Studio 2017 community安裝配置方法圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09
ajaxToolkit:ModalPopupExtender演示及實(shí)現(xiàn)代碼
ajaxToolkit:ModalPopupExtender可以讓用戶模擬新開一個(gè)窗口,就是在模擬新開窗口作多項(xiàng)選項(xiàng)的功能,感興趣的朋友可以了解下,希望此文對(duì)你有所幫助2013-01-01
詳解ASP.NET MVC 利用Razor引擎生成靜態(tài)頁(yè)
本篇文章主要介紹了ASP.NET MVC 利用Razor引擎生成靜態(tài)頁(yè),詳細(xì)的介紹了原理和步驟,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-03-03
ASP.NET AJAX 4.0的模版編程(Template Programming)介紹
不過當(dāng)我評(píng)估ASP.NET AJAX 4.0的時(shí)候,我確實(shí)被它的特征給震住了。新的特征完全專注于瀏覽器技術(shù),比如XHTML和javascript。 我非常欽佩ASP.NET AJAX小組。2009-07-07
Elasticsearch.Net使用教程 MVC4圖書管理系統(tǒng)(2)
這篇文章主要介紹了Elasticsearch.Net使用教程教會(huì)大家利用MVC4制作圖書管理系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
asp.net web頁(yè)面元素的多語言化(多國(guó)語化)實(shí)現(xiàn)分享
開發(fā)的一些系統(tǒng),經(jīng)常要求支持多語言(例如日文,英文等),接下來介紹如何實(shí)現(xiàn)asp.net開發(fā)中web頁(yè)面實(shí)現(xiàn)頁(yè)面元素的多語言化(多國(guó)語化)感興趣的朋友可以了解下,或許對(duì)你學(xué)習(xí)有所幫助2013-02-02

