Extensibility with Multiple Modules
It gets a little more complicated when you have more than one module tapping into this event. The code that executes the
Invoke method will fire all the modules wired to the
CheckDataSource events, one after the other. Remember, this happens because the initialization routine sends and receives the same ModuleEvents object to each module's Initialize method. Now think of the order of events here (no pun intended). If I have three modules that tap into this event, each checking the data source for something different, the first one that gets executed will receive the initial value of the
Cancel property, which is
false. If the code in any module changes that value, the next module that the code executes will contain that new value, because the
Invoke method gets called once with the one EventArgs object. This means that it is up to me to code the module event to check the value of
e.Cancel before executing any of the event logic. With that in mind, a properly written module that taps into the
CheckDataSource event should look like this:
' In VB:
Private Sub events_CheckDataSource( _
ByVal e As CheckDataSourceEventArgs)
If Not e.Cancel Then
If e.Source.ToUpper(). _
IndexOf("BAD") _> -1 Then
e.Cancel = True
End If
End If
End Sub
// In C#:
void events_CheckDataSource(
CheckDataSourceEventArgs e)
{
if (!e.Cancel)
{
if (e.Source.ToUpper().
IndexOf("BAD") > -1)
e.Cancel = true;
}
}
If you code all the modules this way, as soon as any module that intercepts this event sets the
e.Cancel property to
true, no other module's
CheckDataSource event will process any code.
Another choice for this kind of logic is probably a bit safer. The previous examples put the responsibility of check for cancellation in the module's code. But the same developer may not write all your modules, so it may not be reliable to do that. By writing a little more code on the client, you can iterate through the delegate invocation list (in this case,
CheckDataSource) and fire each one separately, checking the value of the
Cancel property after each one and deciding whether to continue.
' In VB:
Dim o_InvocationList() As _
[Delegate] = o_FilterEvents. _
CheckDataSource.GetInvocationList()
For Each o_Filter As
AcmeModuleDelegate( _
Of CheckDataSourceEventArgs) In
o_InvocationList
If o_Filter IsNot Nothing Then
Dim o_EventArgs As New _
CheckDataSourceEventArgs(
s_Source)
o_Filter.Invoke(o_EventArgs)
If o_EventArgs.Cancel Then
b_Cancel = True
Exit For
End If
End If
Next
// In C#:
Delegate[] o_InvocationList =
o_FilterEvents.CheckDataSource.
GetInvocationList();
foreach (
AcmeModuleDelegate
<CheckDataSourceEventArgs>
o_Filter in o_InvocationList)
{
if (o_Filter != null)
{
CheckDataSourceEventArgs o_EventArgs =
New CheckDataSourceEventArgs(
s_Source);
o_Filter.Invoke(o_EventArgs);
if (o_EventArgs.Cancel)
{
b_Cancel = true;
break;
}
}
}
Using this technique, as soon as one module sets its
e.Cancel property to true, the code breaks out of the loop and stops further processing. In both techniques, the
b_Cancel variable determines whether processing should continue, but the responsibility for making the check differs.
You can insert additional extensibility points in either of the two methods: the one that calls all module events in one
Invoke execution or the one that iterates through the invocation list of the corresponding delegate and invokes one event at a time.
Extensibility modules are a great way to put multiple points of extensibility in your client and, at the same time, centralize how you write the extensibility plug-ins. You can choose to develop a class for each event you want to subscribe to, or you can group several together. If you choose the latter, you should do so because the interception code you're placing into the various events is somehow logically related from one event to another. An example of this would be in writing a Profanity Filter. You might want such a filter to check for profanity at different points in your application. For example, in the scenario you've seen, you could check for profanity at both the
CheckDataSource point or at one called
PreProcessData by writing one module class that taps into both of these events. The events will still be called from their appropriate extensibility points within the client, but they will be housed in one class, grouping them together logically.
Listing 5 shows the entire code for such a module.
These patterns can help you design and code applications that are easier and more elegant to enhance or modify. They also introduce a style of programming that's based on the ability to interchange functionality easily. However, don't feel that you have to spend too much time on deciding where to insert points of extensibility in your application—just keep it all in mind. As you design and develop applications, more than likely you will find extensibility points that just snap out at you. Try to resist abstracting at the beginning of the design process, and don't be afraid to refactor into it later; but beware—after you get the hang of using these patterns, you will be addicted.
You can
download the code for this article from the User Group Downloads section of the link. You can find some basic references in the "
Additional References" sidebar. You can also watch a
DNR-TV episode on this entire subject.