|
|
Listing 11-10 shows the implementation of the CheckerSink. Calls from SyncProcessMessage() and AsyncProcessMessage() have been added to the private DoCheck() method, which iterates over the assigned attributes and forwards the business logic checks to CheckAttribute.DoCheck() for each parameter that is marked with this attribute.
Listing 11-10: The CheckerSink
using System; using System.Reflection; using System.Runtime.Remoting; using System.Runtime.Remoting.Activation; using System.Runtime.Remoting.Contexts; using System.Runtime.Remoting.Messaging; namespace ContextBound { public class CheckerSink: IMessageSink { IMessageSink _nextSink; String _mType; public CheckerSink(IMessageSink nextSink, String mType) { _nextSink = nextSink; _mType = mType; } public IMessage SyncProcessMessage(IMessage msg) { DoCheck(msg); return _nextSink.SyncProcessMessage(msg); } public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink) { DoCheck(msg); return _nextSink.AsyncProcessMessage(msg,replySink); } public IMessageSink NextSink { get { return _nextSink; } } private void DoCheck(IMessage imsg) { // not interested in IConstructionCallMessages if (imsg as IConstructionCallMessage != null) return; // but only interested in IMethodMessages IMethodMessage msg = imsg as IMethodMessage; if (msg == null) return; // Check for the Attribute MemberInfo methodbase = msg.MethodBase; object[] attrs = methodbase.GetCustomAttributes(false); foreach (Attribute attr in attrs) { CheckAttribute check = attr as CheckAttribute; // only interested in CheckAttributes if (check == null) continue; // if the method only has one parameter, place the check directly // on it (needed for property set methods) if (msg.ArgCount == 1) { check.DoCheck(msg.Args[0]); } } // check the Attribute for each parameter of this method ParameterInfo[] parms = msg.MethodBase.GetParameters(); for (int i = 0;i<parms.Length;i++) { attrs = parms[i].GetCustomAttributes(false); foreach (Attribute attr in attrs) { CheckAttribute check = attr as CheckAttribute; // only interested in CheckAttributes if (check == null) continue; // if the method only has one parameter, place the check directly // on it (needed for property set methods) check.DoCheck(msg.Args[i]); } } } } }
You can then change the sample client to demonstrate what happens when it performs an invalid operation, as shown in Listing 11-11.
Listing 11-11: This Client Does Not Honor the Business Logic Constraints
using System; using System.Runtime.Remoting.Contexts; namespace ContextBound { public class TestClient { public static void Main(String[] args) { Organization org = new Organization(); try { Console.WriteLine('Will set the name'); org.Name = 'Happy Hackers'; Console.WriteLine('Will donate'); org.Donate(99); Console.WriteLine('Will donate more'); org.Donate(102); } catch (Exception e) { Console.WriteLine('Exception: {0}',e.Message); } Console.WriteLine('Finished, press <return> to quit.'); Console.ReadLine(); } } }
When you start this application, you will get the output shown in Figure 11-2.
Figure 11-2: The client's illegal operation is prohibited by the CheckerSink.
Great! You are now checking your business logic constraints by using attributes that are assigned at the metadata level instead of checks that are hidden in your source code.
One interesting consideration that I have not yet mentioned is the following: what would happen if the first Organization object instantiates another Organization object and calls the Donate() method on the secondary object? Will this call also go through the message sink? In fact, in the current configuration it won't. This example just protects your class library from "outside" clients but doesn't affect any calls inside this context. This is because the CheckableAttribute's IsContextOK() only requests a new context when it's called from outside a checked context.
To make all calls to Organization (no matter what their origin) go through the CheckerSink, you'd have to change CheckableAttribute to return false from IsContextOK():
public override bool IsContextOK(Context ctx, IConstructionCallMessage ctor) { return false; }
This will request a new context for each and every instance of any class that is marked with [Checkable] and that inherits from ContextBoundObject.
Note | Something you should never forget when using these techniques: you are dealing with remote objects! This also means that they are lifetime-managed by leases and will time out the same way as "conventional" remote objects do.You might therefore want to add a sponsor to it as shown in Chapter 6. |
|
|