You have implemented Recipe 7.5, but you want to allow an event listener to be able to cancel an action that raised a particular event. For example, if a class attempts to create a new directory, you want to be able to verify that the directory is being created in the correct location. If the directory is not being created in the correct location (perhaps an insecure location), you want to be able to prevent the directory’s creation.
Use a class derived from EventArgs
as the second
parameter to the event handler. In this example, we use
CancelEventArgs
, a class defined in the .NET
Framework Class Library. The Solution for Recipe 7.5 has been modified to include an event that is
raised before the Create
method of the
DirectoryInfoNotify
object actually creates a new
path. An object of type CancelEventArgs
is passed
to this new event to allow any listeners of this event to cancel the
Create
method action. The modified class is shown
here with the modifications
highlighted:
using System; using System.ComponentModel; using System.IO; public class DirectoryInfoNotify { public DirectoryInfoNotify(string path) { internalDirInfo = new DirectoryInfo(path); } private DirectoryInfo internalDirInfo = null; public event CancelEventHandler BeforeCreate; public event EventHandler AfterCreate; public event EventHandler AfterCreateSubDir; public event EventHandler AfterDelete; public event EventHandler AfterMoveTo; protected virtual void OnBeforeCreate(CancelEventArgs e) { if (BeforeCreate != null) { BeforeCreate(this, e); } } protected virtual void OnAfterCreate( ) { if (AfterCreate != null) { AfterCreate(this, new EventArgs( )); } } protected virtual void OnAfterCreateSubDir( ) { if (AfterCreateSubDir != null) { AfterCreateSubDir(this, new EventArgs( )); } } protected virtual void OnAfterDelete( ) { if (AfterDelete != null) { AfterDelete(this, new EventArgs( )); } } protected virtual void OnAfterMoveTo( ) { if (AfterMoveTo != null) { AfterMoveTo(this, new EventArgs( )); } } // Event firing members public void Create( ) { CancelEventArgs args = new CancelEventArgs(false); OnBeforeCreate(args); if (!args.Cancel) { internalDirInfo.Create( ); OnAfterCreate( ); } } public DirectoryInfoNotify CreateSubdirectory(string path) { DirectoryInfo subDirInfo = internalDirInfo.CreateSubdirectory(path); OnAfterCreateSubDir( ); return (new DirectoryInfoNotify(subDirInfo.FullName)); } public void Delete(bool recursive) { internalDirInfo.Delete(recursive); OnAfterDelete( ); } public void Delete( ) { internalDirInfo.Delete( ); OnAfterDelete( ); } public void MoveTo(string destDirName) { internalDirInfo.MoveTo(destDirName); OnAfterMoveTo( ); } // Non-Event firing members public virtual string FullName { get {return (internalDirInfo.FullName);} } public string Name { get {return (internalDirInfo.Name);} } public DirectoryInfoNotify Parent { get {return (new DirectoryInfoNotify(internalDirInfo.Parent.FullName));} } public DirectoryInfoNotify Root { get {return (new DirectoryInfoNotify(internalDirInfo.Root.FullName));} } public override string ToString( ) { return (internalDirInfo.ToString( )); } }
The DirectoryInfoObserver
class contains each of
the event listeners and is shown here with the modifications
highlighted:
public class DirectoryInfoObserver { public DirectoryInfoObserver( ) {} public void Register(DirectoryInfoNotify dirInfo) { dirInfo.BeforeCreate += new CancelEventHandler(BeforeCreateListener); dirInfo.AfterCreate += new EventHandler(AfterCreateListener); dirInfo.AfterCreateSubDir += new EventHandler(AfterCreateSubDirListener); dirInfo.AfterMoveTo += new EventHandler(AfterMoveToListener); dirInfo.AfterDelete += new EventHandler(AfterDeleteListener); } public void UnRegister(DirectoryInfoNotify dirInfo) { dirInfo.BeforeCreate -= new CancelEventHandler(BeforeCreateListener); dirInfo.AfterCreate -= new EventHandler(AfterCreateListener); dirInfo.AfterCreateSubDir -= new EventHandler(AfterCreateSubDirListener); dirInfo.AfterMoveTo -= new EventHandler(AfterMoveToListener); dirInfo.AfterDelete -= new EventHandler(AfterDeleteListener); } public void BeforeCreateListener(object sender, CancelEventArgs e) { if (!e.Cancel) { if (!((DirectoryInfoNotify)sender).Root.FullName.Equals(@"d:")) { e.Cancel = true; } else { Console.WriteLine( "Notified BEFORE creation of directory--sender: " + ((DirectoryInfoNotify)sender).FullName); } } } public void AfterCreateListener(object sender, EventArgs e) { Console.WriteLine("Notified after creation of directory--sender: " + ((DirectoryInfoNotify)sender).FullName); } public void AfterCreateSubDirListener(object sender, EventArgs e) { Console.WriteLine("Notified after creation of SUB-directory--sender: " + ((DirectoryInfoNotify)sender).FullName); } public void AfterMoveToListener(object sender, EventArgs e) { Console.WriteLine("Notified of directory move--sender: " + ((DirectoryInfoNotify)sender).FullName); } public void AfterDeleteListener(object sender, EventArgs e) { Console.WriteLine("Notified of directory deletion--sender: " + ((DirectoryInfoNotify)sender).FullName); } }
The code for the modified DirectoryInfoNotify
class contains a new event called BeforeCreate
,
which is raised from the OnBeforeCreate
method.
The OnBeforeCreate
method is initially called by
the Create
method immediately before calling the
Create
method of the wrapped
DirectoryInfo
object. This setup will allow the
event listener for the BeforeCreate
event to
decide whether the directory creation operation should be cancelled.
The DirectoryInfoObserver
class contains a new
method, BeforeCreateListener
, which listens for
the BeforeCreate
event. In addition, the
Register
and UnRegister
methods
of this class contain logic to add this event to the list of events
that will be listened for on any registered
DirectoryInfoNotify
objects.
The OnBeforeCreate
method of the
DirectoryinfoNotify
class is passed a parameter of
a type called CancelEventArgs
, which exists in the
.NET FCL. This type derives from EventArgs
and
contains one useful property, called Cancel
. This
property will be used by the AfterCreateListener
method of the DirectoryInfoObserver
class to
determine whether the Create
method should be
cancelled before it has a chance to create a new directory.
The CancelEventArgs
object will be created in a
DirectoryInfoNotify
object, and when the
BeforeCreate
event is raised, the
CancelEventArgs
object will be passed to the
BeforeCreateListener
method on the
DirectoryInfoObserver
object. This method will
then determine whether the creation of the directory should proceed
or be cancelled. The determination is made by comparing the root
drive of the directory to see if it is anything but the
D:
drive; if so, the operation is cancelled.
This prevents any registered DirectoryInfoNotify
objects from creating a directory on any drive other than the
D:
drive.
If multiple DirectoryInfoObserver
objects are
listening to the BeforeCreate
event and one of
those observer objects decides to cancel the operation, the entire
operation is cancelled. In other words, the final handler to be
called gets the power of veto.
The same CancelEventArgs
object is referenced by
each observer as well as each object that raised the event. This
allows us to read the value of the Cancel
property
on the returned CancelEventArgs
object in the
Create
method of the
DirectoryInfoNotify
object. If this property
returns true
, the operation cannot proceed;
otherwise, the operation is permitted.
You are not confined to merely passing EventArgs
objects or any of its subclasses found in the FCL; you can subclass
the EventArgs
class to create a specialized
EventArgs
type. This would be beneficial if the
object passed in to the sender
parameter
of the event does not include all of the information that the
Xxx
Listener methods
will need. For example, you could create the following specialized
EventArgs
class:
public class UserEventArgs : EventArgs { public UserEventArgs(string userName) { this.userName = userName; } private string userName = ""; public string UserName { get {return (userName);} } }
This class passes the name of the logged-on user to the
Xxx
Listener
methods to
allow them to determine whether the operation should continue based
on that user’s privileges. This is just one example
of creating a specialized EventArgs
class. You can
create others to pass in whatever information your listeners need.
See Recipe 7.5; see the “Event” keyword, “EventHandler Delegate,” and “Handling and Raising Events” topics in the MSDN documentation.