04/21/2008

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 and eliminate cross cutting concerns. 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 { get; private set; }

//List of person the action will be on
public List<Person> PersonList { get; private set; }

//For canceling action
public bool Cancel { get; set; }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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.

1
2
3
4
5
6
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!


comment: