What is observer pattern?
The observer pattern defines one-to-many dependency between objects so that one object changes its state, all its dependent objects are notified and updated automatically.
Design and Explanation.
Let’s understand the classes and / or objects participating in this pattern are:
Subject:
- Knows its observers.Any number of observer objects may observe a subject
- Provides an interface for attaching and detaching Observer objects
ConcreteSubject
- Stores state of interest to ConcreteObserver
- Sends a notification to its observers when its state changes
Observer
- Defines a updating interface (update() method ) for objects that should be notified of changes in a subject.
ConcreteObserver
- Maintains a reference to a ConcreteSubject object
- Stores data that should stay consistent with the subject’s
- Implements the Observer updating interface to keep its state consistent with the subject’s
Understanding the Observer Pattern
The observer pattern changes the relationship between objects by dividing them into subjects and observers. The subject objects maintain collection of dependent objects , the observers ( you can see in the above diagram), and notify them about important changes or actions. In the example application we are going to create in the next section uses subject as the Logger and the observers would be the Log classes.
here is the context of the application i am going to create using ASP.NET.
Let’s dive into creating observer pattern in ASP.NET MVC using C#. This example i am going to create uses more modern, built-in .NET features. This example uses .NET multicast delegates which are comprised of multiple methods that are called serially in the order in which they were added using the C# += operator.
Step 1: Create an ASP.NET MVC application using Visual Studio 2017 , select MVC template
create a folder called “Logging” under the root directory, we are going to put all our classes in to this directory. Now right click on the Logging folder, add a new class name “LogEventArgs.cs”, this class contains log specific event data for log events.
// Contains log specific event data for log events.
public class LogEventArgs : EventArgs
{
// Constructor of LogEventArgs.
public LogEventArgs(string message, Exception exception, DateTime date)
{
Message = message;
Exception = exception;
Date = date;
}
public string Message { get; private set; }
public Exception Exception { get; private set; }
public DateTime Date { get; private set; }
public override String ToString()
{
return "" + Date + " - " + Message + " - " + Exception.ToString();
}
}
Step 2: Now we are going to create Observer and ConcreateObserver classes before coding any subject class, so right click on Logging folder again, and create a new interface called “ILog”, here is the code for ILog interface.
// This interface defines a single method to write requested log to an output device
public interface ILog
{
// Write log request to a given output device
void Log(object sender, LogEventArgs e);
}
Step 3: Create 3 classes in the same folder as below, all these three classes implement the ILog interface.
- ObserverLogToDatabase.cs
- ObserverLogToEmail.cs
- ObserverLogToFile.cs
ObserverLogToDatabase.cs
// Writes log events to a database
public class ObserverLogToDatabase : ILog
{
public void Log(object sender, LogEventArgs e)
{
string message = "[" + e.Date.ToString() + "] " + ": " + e.Message;
// implement your own database logic to insert into the your table.
}
}
ObserverLogToEmail.cs
// sends log events via email.
public class ObserverLogToEmail : ILog
{
private string from;
private string to;
private string subject;
private string body;
private SmtpClient smtpClient;
public ObserverLogToEmail(string from, string to, string subject, string body, SmtpClient smtpClient)
{
this.from = from;
this.to = to;
this.subject = subject;
this.body = body;
this.smtpClient = smtpClient;
}
#region ILog Members
public void Log(object sender, LogEventArgs e)
{
string message = "[" + e.Date.ToString() + "] " + ": " + e.Message;
// commented out for now. you need privileges to send email.
// _smtpClient.Send(from, to, subject, body);
}
#endregion
}
ObserverLogToFile.cs
// writes log events to a local file
public class ObserverLogToFile : ILog
{
private string fileName;
public ObserverLogToFile(string fileName)
{
this.fileName = fileName;
}
public void Log(object sender, LogEventArgs e)
{
string message = "[" + e.Date.ToString() + "] " + ": " + e.Message;
FileStream fileStream;
// create directory, if necessary
try
{
fileStream = new FileStream(fileName, FileMode.Append);
}
catch (DirectoryNotFoundException)
{
Directory.CreateDirectory((new FileInfo(fileName)).DirectoryName);
fileStream = new FileStream(fileName, FileMode.Append);
}
// NOTE: be sure you have write privileges to folder
var writer = new StreamWriter(fileStream);
try
{
writer.WriteLine(message);
}
catch {/* do nothing */}
finally
{
try
{
writer.Close();
}
catch { /* do nothing */}
}
}
}
Step 4: Now its time to create subject class (Logger.cs), i have modified the implementation of this class to support real time logging , so introduced singleton pattern to have one Logger instance to be exists, so we can use the same instance across the project to notify all the observers when exception occurs in the code.
Logger.cs
// Design patterns: Singleton, Observer.
public sealed class Logger
{
// delegate event handler that hooks up requests.
public delegate void LogEventHandler(object sender, LogEventArgs e);
// the log event
public event LogEventHandler Log;
#region The Singleton definition
// the one and only singleton logger instance
// the one and only Singleton Logger instance.
private static readonly Logger instance = new Logger();
private Logger()
{
}
// gets the instance of the singleton logger object
public static Logger Instance
{
get { return instance; }
}
#endregion
public void Error(string message)
{
Error(message, null);
}
// log a message when severity level is "Error" or higher.
public void Error(string message, Exception exception)
{
OnLog(new LogEventArgs(message, exception, DateTime.Now));
}
// invoke the log event
public void OnLog(LogEventArgs e)
{
if (Log != null)
{
Log(this, e);
}
}
// attach a listening observer logging device to logger
public void Attach(ILog observer)
{
Log += observer.Log;
}
// detach a listening observer logging device from logger
public void Detach(ILog observer)
{
Log += observer.Log;
}
}
now we have all the classes both subject and observers and event handlers are hooked and ready, below is the code to create global logger instance and attaching observers to the logger class. As i said in the beginning, we are implementing observer pattern using ASP.NET MVC, do to your Global.asax.cs file add below code. first create a private method named “InitializeLogger()” method.
private void InitializeLogger()
{
// send log messages to email (observer pattern)
string from = "notification@yourcompany.com";
string to = "webmaster@yourcompany.com";
string subject = "Webmaster: please review";
string body = "email text";
var smtpClient = new SmtpClient("mail.yourcompany.com");
var logEmail = new ObserverLogToEmail(from, to, subject, body, smtpClient);
Logger.Instance.Attach(logEmail);
// send log messages to a file
var logFile = new ObserverLogToFile(@"C:\Temp\Error.log");
Logger.Instance.Attach(logFile);
// send log messages to database (observer pattern)
var logDatabase = new ObserverLogToDatabase();
Logger.Instance.Attach(logDatabase);
}
In the above code, i have created instances of individual observers and pass them to the Attach() method of the Logger class.The Logger class deals only with the observers through the Log event and notify the observers it defines and has no knowledge of the individual classes and what they do when the Log() method is called.
Clobal.asax.cs/Application_start() , call the InitializeLogger() method on the application_start method.
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
InitializeLogger();
}
Consuming the Observer Pattern in the HomeController.cs file
public class HomeController : Controller
{
public ActionResult Index()
{
try
{
throw new ArgumentNullException(); // throw exception manually, to test the pattern.
}
catch (Exception ex)
{
Logger.Instance.Error(ex.Message); // this call sends notification to all the observers and log the message.
}
return View();
}
}
Running the application produces the following results.
You can use this Logging functionality in your projects to send error notifications to your Email or insert a record into the Database table. that’s all for now, thank you for reading the post, please leave a comment or let me me know if you have any questions.