views:

184

answers:

5

In my domain layer all domain objects emit events (of type InvalidDomainObjectEventHandler) to indicate invalid state when the IsValid property is called.

On an aspx codebehind, I have to manually wire up the events for the domain object like this:

_purchaseOrder.AmountIsNull += new DomainObject.InvalidDomainObjectEventHandler(HandleDomainObjectEvent);
_purchaseOrder.NoReason += new DomainObject.InvalidDomainObjectEventHandler(HandleDomainObjectEvent);
_purchaseOrder.NoSupplier += new DomainObject.InvalidDomainObjectEventHandler(HandleDomainObjectEvent);
_purchaseOrder.BothNewAndExistingSupplier += new DomainObject.InvalidDomainObjectEventHandler(HandleDomainObjectEvent);

Note that the same method is called in each case since the InvalidDomainobjectEventArgs class contains the message to display.

Is there any way I can write a single statement to wire up all events of type InvalidDomainObjectEventHandler in one go?

Thanks

David

+5  A: 

I don't think you can do this in a single statement.. But you can make the code more readible like this:

_purchaseOrder.AmountIsNull += HandleDomainObjectEvent;
_purchaseOrder.NoReason += HandleDomainObjectEvent;
_purchaseOrder.NoSupplier += HandleDomainObjectEvent;
_purchaseOrder.BothNewAndExistingSupplier += HandleDomainObjectEvent;

Other than that - seems like the answer's no :(

Artiom Chilaru
'No can do' is still a good answer!Thanks
David
A: 

You could consider to put the event handlers into an interface. Then you attach the interface:

public interface IPurchaseOrderObserver
{
    void AmountIsNullEventHandler(WhateverArgs);
    void NoReasonEventHandler(WhateverArgs);
    void NoSupplierEventHandler(WhateverArgs);
    void BothNewAndExistingSupplierEventHandler(WhateverArgs);
}

_purchaseOrder.RegisterObserver(DomainObject);

You either put this four lines into the RegisterObeserver method, or you replace the events and directly call the interfaces.

Stefan Steinegger
Not sure I followed this, but it still seems that I have to directly mention all the events somewhere, in the interface if not in the web page... is that right?
David
Yes, you need to mention all the events somewhere, but only on one single place.
Stefan Steinegger
+1  A: 

You can use reflection to do this automatically. I think you want something like this:

public static void WireEvents(object subject)
{
    Type type = subject.GetType();

    var events = type.GetEvents()
        .Where(item => item.EventHandlerType == typeof(InvalidDomainObjectEventHandler));

    foreach (EventInfo info in events)
        info.AddEventHandler(subject, new InvalidDomainObjectEventHandler(HandleDomainObjectEvent));
}

Then, all you have to do when you create a new object is this:

PurchaseOrder _purchaseOrder = new PurchaseOrder();
HelperClass.WireEvents(_purchaseOrder);

Don't forget that there is a performance penalty with reflection that will be apparent if you create PurchaseOrders and other similar objects in any great numbers.

Edit - other notes: you will need a using System.Reflection directive. As it stands, this code needs C#3 for the var keyword and .net framework 3.5 for the Where() method (and - if it's not automatically generated - using System.Linq;).

As David has done in a later answer, it can be re-written without changing the basic functionality for earlier versions.

Bob Sammers
Wow! I can only say thanks!
David
I couldn't get the compiler to accept the .Where method, but I've rewritten your code slightly and reposted it.Thank you very much for your help!
David
Hmmm. I'm not doing very well today! Once again, this code is tested and I've currently got a working copy in my code editor. My guess is we're not on the same versions. You didn't say what you were targeting, but perhaps I should have said that this code will only work on VS2008 / .net 3.5 upwards.
Bob Sammers
3.5 / 2008I don't really use Linq, maybe there's something I need to reference?
David
Ah, yes, that could also be the problem. `using System.Linq;` should probably be in your using statements list. I get that automatically when I create new classes (from, I believe, the default VS2008 template), but there could well be circumstances when it is not included. I've edited the response.
Bob Sammers
A: 

I looked at Bob Sammers' suggestion. The compiler wasn't liking the .Where method of the EventInfo[] returned by GetEvents(), but I've changed the code slightly to the following:

private void HookUpEvents()
{
  Type purchaseOrderType = typeof (PurchaseOrder);
  var events = purchaseOrderType.GetEvents();
  foreach (EventInfo info in events)
  {
    if (info.EventHandlerType == typeof(Kctc.Data.Domain.DomainObject.InvalidDomainObjectEventHandler))
    {
      info.AddEventHandler(_purchaseOrder, new Kctc.Data.Domain.DomainObject.InvalidDomainObjectEventHandler(HandleDomainObjectEvent));
    }
  }
}

After I added this method to the page, it all worked absolutely hunky dory. And I can add events to the purchase order object without having to remember to hook them up individually, which is exactly what I wanted.

David
+3  A: 

You can create an aggregate event in some base class (or in some helper class, or in the PurchaseOrder class itself, if you have access to it):

abstract class BaseOrderPage : Page {

  PurchaseOrder _purchaseOrder = new PurchaseOrder();

  ...

  public event InvalidDomainObjectEventHandler InvalidDomainObjectEvent {
    add {
      _purchaseOrder.AmountIsNull += value;
      _purchaseOrder.NoReason += value;
      _purchaseOrder.NoSupplier += value;
      _purchaseOrder.BothNewAndExistingSupplier += value;
    }
    remove {
      _purchaseOrder.AmountIsNull -= value;
      _purchaseOrder.NoReason -= value;
      _purchaseOrder.NoSupplier -= value;
      _purchaseOrder.BothNewAndExistingSupplier -= value;
    }
  }

}

And then just use it in the derived classes:

    InvalidDomainObjectEvent += new DomainObject.InvalidDomainObjectEventHandler(HandleDomainObjectEvent);

C# 2.0 and above:

    InvalidDomainObjectEvent += HandleDomainObjectEvent;

I've used this technique successfully to aggregate events of the FileSystemWatcher class.

Jordão
I've not seen that before, thank you!
David