My fantastic Webdings Logo

Reamped.NET

XML Thingey... It's a Technical Term


Using The Event Model: Throwing and Handling Custom Events

If you haven't tried using the event model in .NET you don't know what you're missing. If this is the case, then I'm glad you're here. Events are a very nice way to add flexibility to your projects. You can use them for a variety of things including validation and logging. You can think of an event as sort of an All Points Bulletin (APB) throughout your application. Once you declare and throw an event, every place in your code that has an event handler for that event will be executed. Events can be both static or per instance. You use events every time you build an ASP.NET website. The Page_Load event, Button_Click event, etc... It is like an application wide notification that something is happening.

 

An important note is that all event handlers that have been moved to the call stack will be fired. So when you call out to your custom event, all objects that are in the call stack with an event handler for that event will be fired. Another thing to keep in mind is that there is no guaranteed order in which the events will be fired. If you are in need of doing something that requires a specific order, you should use more events or take another approach entirely.

 

In this project, we will create some events and handle them in a logging system. The first thing we will need to do is set up an event delegate to strongly type our event. Along with doing that, I like to setup some custom event arguments that will take in the object(s) that I will be dealing with during the event.

C#-Code: PersonEventArgs.cs
using System;
using System.Collections.Generic;
//Delegate for a PersonEvent
public delegate void PersonEvent(PersonEventArgs e);
public class PersonEventArgs : EventArgs
{
    public PersonEventArgs()
    {
        
    }
    public PersonEventArgs(Person person)
    {
        this.Person = person;
    }
    public PersonEventArgs(List<Person> personList)
    {
        this.PersonList = personList;
    }
    //Person the action will be on
    public Person Person { getprivate set; }
    //List of person the action will be on
    public List<Person> PersonList { getprivate set; }
    //For canceling action
    public bool Cancel { getset; }
}

Ok, Now all we have to do is declare and throw our events. When declaring our events, they will be of the type PersonEvent.

C#-Code: PersonBLL.cs
using System.Collections.Generic;
using System.ComponentModel;
[DataObject(true)]
public class PersonBLL
{
    //Declare events
    public static event PersonEvent Pre_Get;
    public static event PersonEvent Post_Get;
    public static event PersonEvent Pre_Insert;
    public static event PersonEvent Post_Insert;
    public static event PersonEvent Pre_Update;
    public static event PersonEvent Post_Update;
    public static event PersonEvent Pre_Delete;
    public static event PersonEvent Post_Delete;
    [DataObjectMethod(DataObjectMethodType.Select)]
    public List<PersonPresentationShell> GetPeople()
    {
        //Call out to event handler
        if (Pre_Get != null)
        {
            PersonEventArgs args = new PersonEventArgs();
            Pre_Get(args);
            if (args.Cancel) return null;
        }
        List<PersonPresentationShell> result = new List<PersonPresentationShell>();
        List<Person> personList = DataAccessFactory.GetPeople();
        foreach (Person item in personList)
        {
            result.Add(new PersonPresentationShell(item));
        }
        //Call out to event handler
        if (Post_Get != null)
        {
            PersonEventArgs args = new PersonEventArgs(personList);
            Post_Get(args);
            if (args.Cancel) return null;
        }
        return result;
    }
    [DataObjectMethod(DataObjectMethodType.Update)]
    public void UpdatePerson(PersonPresentationShell p)
    {
        //Call out to event handler
        if (Pre_Update != null)
        {
            PersonEventArgs args = new PersonEventArgs(p.Person);
            Pre_Update(args);
            if (args.Cancel) return;
        }
        DataAccessFactory.UpdatePerson(p.Person);
        //Call out to event handler
        if (Post_Update != null)
        {
            PersonEventArgs args = new PersonEventArgs(p.Person);
            Post_Update(args);
        }
    }
    [DataObjectMethod(DataObjectMethodType.Delete)]
    public void DeletePerson(PersonPresentationShell p)
    {
        //Call out to event handler
        if (Pre_Delete != null)
        {
            PersonEventArgs args = new PersonEventArgs(p.Person);
            Pre_Delete(args);
            if (args.Cancel) return;
        }
        DataAccessFactory.DeletePerson(p.Person);
        //Call out to event handler
        if (Post_Delete != null)
        {
            PersonEventArgs args = new PersonEventArgs(p.Person);
            Post_Delete(args);
        }
    }
    [DataObjectMethod(DataObjectMethodType.Insert)]
    public void InsertPerson(PersonPresentationShell p)
    {
        //Call out to event handler
        if (Pre_Insert != null)
        {
            PersonEventArgs args = new PersonEventArgs(p.Person);
            Pre_Insert(args);
            if (args.Cancel) return;
        }
        DataAccessFactory.InsertPerson(p.Person);
        //Call out to event handler
        if (Post_Insert != null)
        {
            PersonEventArgs args = new PersonEventArgs(p.Person);
            Post_Insert(args);
        }
    }
}

There we have it! An event will now be thrown before and after every get, insert, update and delete. Now we have to set up some event handlers in our logging object.

C#-Code: Logger.cs
using System;
using System.IO;
using System.Web;
public class Logger
{
    //Set the log file location
    private static string _logFileLocation = HttpContext.Current.Server.MapPath("~/App_Data/logfile.log");
    static Logger()
    {
        //Attach event handlers
        PersonBLL.Pre_Delete += new PersonEvent(PersonBLL_Pre_Delete);
        PersonBLL.Post_Delete += new PersonEvent(PersonBLL_Post_Delete);
        PersonBLL.Pre_Get += new PersonEvent(PersonBLL_Pre_Get);
        PersonBLL.Post_Get += new PersonEvent(PersonBLL_Post_Get);
        PersonBLL.Pre_Insert += new PersonEvent(PersonBLL_Pre_Insert);
        PersonBLL.Post_Insert += new PersonEvent(PersonBLL_Post_Insert);
        PersonBLL.Pre_Update += new PersonEvent(PersonBLL_Pre_Update);
        PersonBLL.Post_Update += new PersonEvent(PersonBLL_Post_Update);
    }
    static void PersonBLL_Post_Update(PersonEventArgs e)
    {
        WriteToLogFile("PersonId: " + e.Person.PersonId.ToString() + " - " + e.Person.FirstName + " " + e.Person.LastName + " was updated");
    }
    static void PersonBLL_Pre_Update(PersonEventArgs e)
    {
        WriteToLogFile("PersonId: " + e.Person.PersonId.ToString() + " - " + e.Person.FirstName + " " + e.Person.LastName + " is about to be updated");
    }
    static void PersonBLL_Post_Insert(PersonEventArgs e)
    {
        WriteToLogFile("PersonId: " + e.Person.PersonId.ToString() + " - " + e.Person.FirstName + " " + e.Person.LastName + " was inserted");
    }
    static void PersonBLL_Pre_Insert(PersonEventArgs e)
    {
        WriteToLogFile("PersonId: " + e.Person.PersonId.ToString() + " - " + e.Person.FirstName + " " + e.Person.LastName + " is about to be inserted");
    }
    static void PersonBLL_Post_Get(PersonEventArgs e)
    {
        WriteToLogFile("The people were gotten");
    }
    static void PersonBLL_Pre_Get(PersonEventArgs e)
    {
        WriteToLogFile("The people are about to be gotten");
    }
    static void PersonBLL_Post_Delete(PersonEventArgs e)
    {
        WriteToLogFile("PersonId: " + e.Person.PersonId.ToString() + " - " + e.Person.FirstName + " " + e.Person.LastName + " was deleted");
    }
    static void PersonBLL_Pre_Delete(PersonEventArgs e)
    {
        WriteToLogFile("PersonId: " + e.Person.PersonId.ToString() + " - " + e.Person.FirstName + " " + e.Person.LastName + " is about to be deleted");
    }
    public static void WriteToLogFile(string message)
    {
        DateTime now = DateTime.Now;
        using(StreamWriter sw = File.AppendText(_logFileLocation))
        {
            sw.WriteLine(now.ToString() + " - " + message);
        }
    }
}

Finally, we have to create an instance of our logger class in the Global.asax file to attach our event handlers to our events when the application starts.

C#-Code: Global.asax
void Application_Start(object sender, EventArgs e) 
{
    //Create an instance of the logger
    //To attach event handlers
    Logger logger = new Logger();
}

There we have it. Now on every CRUD operation in our application, a log entry will be written with some data about that action. Once you get the hang of it events become extremely useful. One big instance I like to use it for is authorization of performing an action in a membership and roles scenario.

Hope this helps!

 

Download the code:
ThrowingAndHandlingEvents.zip (14.44 kb)

 

 

kick it on DotNetKicks.com


Comments

Jeff Garoutte

Great blog as always Ira.

But being an old crank....
The issues of event order are diffcult to track by Application design.

The event order is FIFO; First In First Out.  
Consider the following code.

[code]using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication3
{
    class Program
    {
        public static event EventHandler AnEvent;

        static void Main(string[] args)
        {
            AnEvent += new EventHandler(Program_AnEvent1);
            AnEvent += new EventHandler(Program_AnEvent2);
            AnEvent += new EventHandler(Program_AnEvent3);
            AnEvent += new EventHandler(Program_AnEvent4);
            if (AnEvent != null)
                AnEvent(null, EventArgs.Empty);
            Console.ReadKey();
        }

        public static void Program_AnEvent4(object sender, EventArgs e)
        {
            Console.WriteLine("4");
        }

        public static void Program_AnEvent3(object sender, EventArgs e)
        {
            Console.WriteLine("3");
        }

        public static void Program_AnEvent2(object sender, EventArgs e)
        {
            Console.WriteLine("2");
        }

        public static void Program_AnEvent1(object sender, EventArgs e)
        {
            Console.WriteLine("1");
        }
    }
}
[/code]
will always return 1,2,3,4
Compared to
[code]
static void Main(string[] args)
        {
            AnEvent += new EventHandler(Program_AnEvent3);
            AnEvent += new EventHandler(Program_AnEvent1);
            AnEvent += new EventHandler(Program_AnEvent2);
            AnEvent += new EventHandler(Program_AnEvent4);
            if (AnEvent != null)
                AnEvent(null, EventArgs.Empty);
            Console.ReadKey();
        }
[/code]
which returns 3,1,2,4

The problem, and the reason the order is diffcult to guarantee is becuase controling the order the events are added can be diffcult.  In the simplistic console application above it is fairly easy to do.  But in a larger more complex object model where events are setup in static constructors the order of the events becomes related to the order the objects are first accessed at runtime.

huh?

To expand on your example Ira, if we added a class called "RuleVerification" the wired into the "Pre" events to validate some rules...
[code]
public class RuleVerification
{
    static RuleVerification()
    {
        //Attach event handlers
        PersonBLL.Pre_Delete += new PersonEvent(PersonBLL_Pre_Delete);
        PersonBLL.Pre_Get += new PersonEvent(PersonBLL_Pre_Get);
        PersonBLL.Pre_Insert += new PersonEvent(PersonBLL_Pre_Insert);
        PersonBLL.Pre_Update += new PersonEvent(PersonBLL_Pre_Update);
    }

    static void PersonBLL_Pre_Delete(PersonEventArgs e)
    {
        e.Cancel = (0==1);
    }

    static void PersonBLL_Pre_Get(PersonEventArgs e)
    {
        e.Cancel = (0==1);
    }

    static void PersonBLL_Pre_Insert(PersonEventArgs e)
    {
        e.Cancel = (0==1);
    }

    static void PersonBLL_Pre_Update(PersonEventArgs e)
    {
        e.Cancel = (0==1);
    }
}
[/code]

Which fires first?  The Logger event or the RuleVerification event?  

It depends on which class, Logger or RuleVerification, had it's static constructor accessed first.  (In your sample the Logger would because we didnt add the RuleVerification to the Application Start event.)  Because in most applications the order they are accessed depends on what the user is doing it is often impossible to tell what the order will be or what events will be loaded.

In you example you use Application_Start to setup the logger, unless we added RuleVerification there as well the RuleVerification events would not be hooked in until the RuleVerification object was accessed (causing the static constructor to fire).  So the question wouldnt be the order, but will the events fire.

However, if the RuleVerification was added to the Application_Start method (which is really an event) the order and if they exist would be known as the order of the static constructor calls is known.

The order of events becomes a trick when events are added during runtime instead of at a fixed point like Application_Start.

There are other places one could wire events besides Application_Start, for exmaple the constructor, static or not, of an IHttpModule.  In the case of a security Module this would tie the order of events to the order the modules are loaded/entered in the webconfig and after the Application_Start.

In many applications however, the design is such that the order, or if they are hooked up, is not always known or possible to know.

Wow...that was longer than I expected.....



Comments are closed