Introduction
This article will help you to build a Scheduler that you can easily attach with your .NET Application. By using this scheduler, you can configure your schedule time and also manage modules with minimum afford. Here, I will show you how Observer design pattern helps to make a manageable scheduler.
Background
Before reading this article, all of you must have basic knowledge about Observer Design Pattern. It will help a lot and also make this article easy and interesting to you. By the way, here is a typical description.
"Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically."
Fig: 1.1 Class diagram for observer pattern.
In short, if I want to explain:
Concrete Subject inherits Subject and Observer inherits Concrete Observer.
If the Concrete Subject changes, it will notify to others.
Who are the others?
Others means those who are attached with the Concrete Subject.
So finally, Concrete Subject changes notify the attached modules.
Using the Code
Guess I need to build a scheduler which will send email, text SMS and upload files to FTP server. So from my previous discussion, I can say that I need to make a schedule-task (Concrete Subject) whose responsibility (attached) is to send those mails. In .NET 4.0, we have two nice Interfaces.
- IObservable<T>
- IObserver<T>
If you look at those Interfaces, you will notice that it has almost the same methods like our subject and observer in fig 1.1.
IObservable<T> has only one method:
Disposable Subscribe(IObserver<T> observer)
If compared with our attached method, only the return type makes the difference. So we don't need any detach method here. Why? Because it returns a disposable object of subscribe object. If you are still confused, no problem, I will explain it later in my discussion.
IObserver<T> has three methods:
void OnCompleted()
void OnError(Exception error)
void OnNext(T value)
OnNext (T value)
is our main concern. It works like Update
method on Observer
fig 1.1.
One thing that really surprised me where is the Notify of the IObservable<T>? Ok, let's get back to our scheduler jobs.
So we need a class for implementing the IObservable<T>. Below is the class:
public class Observable<T> : IObservable<T>
{
List<IObserver<T>> observers = new List<IObserver<T>>();
public Observable()
{
observers = new List<IObserver<T>>();
}
protected void Notify(T obj)
{
foreach (IObserver<T> observer in observers)
{
observer.OnNext(obj);
}
}
public IDisposable Subscribe(IObserver<T> observer)
{
if (!observers.Contains(observer))
{
observers.Add(observer);
}
return new Unsubscriber(observers, observer);
}
private class Unsubscriber : IDisposable
{
private List<IObserver<T>> observers;
private IObserver<T> observer;
public Unsubscriber(List<IObserver<T>> observers, IObserver<T> observer)
{
this.observers = observers;
this.observer = observer;
}
public void Dispose()
{
if (observer != null && observers.Contains(observer))
{
observers.Remove(observer);
}
}
}
}
So we have the generics class with the Notify
method. Here, you can see how you can get the IDisposable
after subscribe. Now we can consider the Observable<T>
class as Subject
. Now we need our concrete subject.
public class SchedulerTask : Observable<SchedulerTask>
{
bool _switchOn;
public bool SwitchOn
{
get
{
return _switchOn;
}
set
{
_switchOn = value;
if (_switchOn)
{
Notify(this);
}
}
}
public SchedulerTask()
{
}
}
So our SchedulerTask
class is designed in a way that if property value SwitchOn
sets true
, it will call the base classes Notify
method and all the subscribe modules will be notified.
So for three tasks, we need the classes that will process or execute the task. And they will be notified by the SchedularTask
class when update happens. To receive the notification (OnNext(T)
), they need to implement IObserver<T>
.
This is for MailSend
:
public class SendNotificationMail : IObserver<SchedulerTask>
{
public SendNotificationMail()
{
}
public void OnCompleted()
{
throw new NotImplementedException();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnNext(SchedulerTask value)
{
}
}
This is for TextSMS
:
public class SendTextMessaage : IObserver<SchedulerTask>
{
public SendTextMessaage()
{
}
public void OnCompleted()
{
throw new NotImplementedException();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnNext(SchedulerTask value)
{
}
}
This is for Upload file:
public class UpLoadFileFromFtp :IObserver<SchedulerTask>
{
public UpLoadFileFromFtp()
{
}
public void OnCompleted()
{
throw new NotImplementedException();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnNext(SchedulerTask value)
{
}
}
Here, I am not showing the full implementation for the send mail SMS or fileupload. So we have all of the modules. But still one important thing is left. Did you notice that? No, the subscription or attachment of the ScheduerTask
with SendNotificationMail
, SendTextMessaage
and UpLoadFileFromFtp
.
To manage those classes, we need a manager class right which will integrate those with each other.
public class ManageScheduleTasks
{
SchedulerTask objScheduler;
Action<bool> TriggerScheduler;
public ManageScheduleTasks()
{
Register();
}
protected void Register()
{
objScheduler = new SchedulerTask();
GetSubsCribtionList().ForEach(p =>
{
objScheduler.Subscribe(p);
});
TriggerScheduler = InvokeScheduler;
}
protected List<IObserver<SchedulerTask>> GetSubsCribtionList()
{
return new List<IObserver<SchedulerTask>>()
{
new SendNotificationMail(),
new SendTextMessaage(),
new UpLoadFileFromFtp()
};
}
public void StartScheduler()
{
double dblmilisec = ConfigurationManager.AppSettings["scheduleTimeinMinute"] != null ?
Convert.ToDouble(ConfigurationManager.AppSettings["scheduleTimeinMinute"])
* 60000 : -1;
System.Timers.Timer t = new System.Timers.Timer();
t.Elapsed += new System.Timers.ElapsedEventHandler(IsTimeToStartScheduler);
t.Interval = dblmilisec;
t.Enabled = true;
t.AutoReset = true;
t.Start();
}
private void IsTimeToStartScheduler(object sender, System.Timers.ElapsedEventArgs e)
{
var start = ConfigurationManager.AppSettings["scheduleStartTime"] != null ?
Convert.ToString(ConfigurationManager.AppSettings["scheduleStartTime"]) : "";
var triger = start.Equals(DateTime.Now.ToString("hh:mm tt"));
TriggerScheduler(triger);
}
private void InvokeScheduler(bool isStartTime)
{
objScheduler.SwitchOn = isStartTime;
}
}
Don't worry looking at the manager class. I will explain each and every method step by step.
First of all, look at the constructor of the ManageScheduleTasks
class. what does it do? It calls the Register
method. The Register
method actually subscribes to all the modules (by calling the function GetSubsCribtionList
) that will be notified by the SchedulerTask.
TriggerScheduler
is just an Action<bool>
. I will explain it too. So far so good, right.
public ManageScheduleTasks()
{
Register();
}
protected void Register()
{
objScheduler = new SchedulerTask();
GetSubsCribtionList().ForEach(p =>
{
objScheduler.Subscribe(p);
});
TriggerScheduler = InvoveScheduler;
}
protected List<IObserver<SchedulerTask>> GetSubsCribtionList()
{
return new List<IObserver<SchedulerTask>>()
{
new SendNotificationMail(),
new SendTextMessaage(),
new UpLoadFileFromFtp()
};
}
Now, a question might comes on your mind that what is the use of other three methods StartScheduler
, IsTimeToStartScheduler
, InvoveScheduler
? Below is the answer:
StartScheduler
: This method introduces a timer that calls a method IsTimeToStartScheduler
after every 1 minute. The interval value comes from the web.config scheduleTimeinMinute
. Here, I set 1
.
<appSettings>
<add key="scheduleTimeinMinute" value="1"/>
<add key="scheduleStartTime" value="01:00 AM"/>
</appSettings>
public void StartScheduler()
{
double dblmilisec = ConfigurationManager.AppSettings["scheduleTimeinMinute"] != null ?
Convert.ToDouble(ConfigurationManager.AppSettings["scheduleTimeinMinute"])
* 60000 : -1;
System.Timers.Timer t = new System.Timers.Timer();
t.Elapsed += new System.Timers.ElapsedEventHandler(IsTimeToStartScheduler);
t.Interval = dblmilisec;
t.Enabled = true;
t.AutoReset = true;
t.Start();
}
IsTimeToStartScheduler
: This method will get the exact time to start the scheduler from the web.config scheduleStartTime
and check whether it is time to start the scheduler. At 01:00AM, it will set the value of var trigger
is true
. 01:00AM comes from my web.config schedulerStartTime
.
<appSettings>
<add key="scheduleTimeinMinute" value="1"/>
<add key="scheduleStartTime" value="01:00 AM"/>
</appSettings>
If the value of var trigger
is true
, then the scheduler starts. Confused how it will start? Below is the explanation.
private void IsTimeToStartScheduler(object sender, System.Timers.ElapsedEventArgs e)
{
var start = ConfigurationManager.AppSettings["scheduleStartTime"] != null ?
Convert.ToString(ConfigurationManager.AppSettings["scheduleStartTime"]) : "";
var triger = start.Equals(DateTime.Now.ToString("hh:mm tt"));
TriggerScheduler(triger);
}
So as I told you earlier, TriggerScheduler
is an action that points to the InvokeScheduler
and it is actually setting the SwitchOn
value. So you remember the property when it calls the notify
? When it sets value to true
.
private void InvoveScheduler(bool isStartTime)
{
objScheduler.SwitchOn = isStartTime;
}
public bool SwitchOn
{
get
{
return _switchOn;
}
set
{
_switchOn = value;
if (_switchOn)
{
Notify(this);
}
}
}
Points of Interest
So, how do we hookup my ManageScheduleTasks
class with Http
? Here is the trick - use your Global.asax.cs class and put the below code on Application_Start
:
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
var manageSchdule = new ManageScheduleTasks();
manageSchdule.StartScheduler();
}
}
That's it! Your scheduler is now attached with your application.
This Scheduler will call on a specific time of the day. But if you want to call after every (1,2.3...n)
minute, you just do the following and change the value of scheduleTimeinMinute
to any minute interval you need.
private void IsTimeToStartScheduler(object sender, System.Timers.ElapsedEventArgs e)
{
TriggerScheduler(true);
}
So we finally developed a scheduler.