views:

242

answers:

7

Imagine the following scenario - we have Page1 which contains controls Control A and Control B.

Say Control A has a button, and on the click of this button we want Control B to react. But we want to do this in an abstract fashion, i.e. we can't have Control B knowing anything about Control A, and vice versa.

That way we can develop these controls in isolation, and drive them by unit-testing.

Now, I thought I had the solution, just want to know what you guys think of it.

On Control A's button click, I put a 'message' on the Session, i.e. Session["MESSAGES"] = "ControlA_Click".

In Page1, on the Page_LoadComplete(), I put a call to ProcessMessages, which looks like this:

      List<Message> messages = SessionMessages.GetMessageList(Page);
  foreach(Message m in messages)
  {
   //Get Controls
   ControlA controlA = FindControl("controlA") as ControlA;
   controlA .ProcessMessage(m);

   ControlB controlB = FindControl("controlB") as ControlB;
            controlB.ProcessMessage(m);
      }

in ControlB's ProcessMessage() method, we can react to the messages that ControlB is interested in, like so:

    if (m.MessageName == SessionMessages.C_MESSAGE_SEARCH)
{
 this.Visible = true;
}

To me, this seems to work. It allows us to develop these controls completely separately from eachother, while still allowing for inter-control-communication at an abstract level.

The only thing I can think of that might bring this crashing down is perhaps the ASP.NET life-cycle in relation to Pages and User Controls. The way I figure it though is that ALL events should have been processed on the controls before Page_LoadComplete() is called on the owning Page.

Thoughts?

+2  A: 

an interesting abuse of the session...

you could also have the message queue belong to the hosting page instead

i would recommend that instead you have the hosting page do something to the control in response to the message, rather than making the control be 'smart' - there is really no need for a button to be 'smart'

Steven A. Lowe
Yeah well it doesn't have to be Session, suggestions welcomed!
Duncan
@Duncan: you commented while i was editing ;-)
Steven A. Lowe
Sorry! Hmmm see what I wanted to do was encapsulate everything inside the Control, cos I would really like the hosting page to be as dumb as possible... think we're on opposing sides on this one, but opinion appreciated and noted :)
Duncan
@Duncan: the logic that tells the button what to do (the 'controller' pattern) can be as generic and decoupled as you like, leaving all user-interface objects (button and page) 'thin and stupid' ;-)
Steven A. Lowe
Yeah, I'm going for the Supervising-Presenter pattern, didn't want to put it here as it might complicate it further. But I still wanted that to sit behind the actual Controls themselves... but you're saying you would have a PageController rather than a ControlAController and ControlBController?
Duncan
@Duncan: yes, the controller is a bridge for the interaction (you may find you need the full model-view-controller pattern if your interactions are model-driven); it can use messages, direct logic, properties and interfaces, events, or whatever is appropriate for your situation
Steven A. Lowe
+6  A: 
  1. Control A should raise an event
  2. The page housing the controls subscribes to the event & then calls the appropriate method in the other control
  3. Control B should process the message()
Briggie Smalls
My initial thoughts behind this was to remove the need to have the Page act as a 'middle-man' and hook up events/calls directly you see. Your approach would definitely work, but it's a little more hard-wired and therefore more difficult to unit test. Taken on board tho!
Duncan
A: 

Check out the Managed Extensibility Framework Contrib project. They have just a sample website that is just what you want.

Bruno Shine
+1  A: 

Isn't this what databinding is for? Control A responds to an event that updates the model and then calls databind on its dependencies.

If you want to make a messaging system, design it to the publisher and subscriber do not need to know about each other, only the message itself. Create an interface something like:

public interface IHandle<T> where T:IMessage
{
     void Process(T message)
}

You will need a method of discovering which controls implement it and build a map of messagetype->handlers, have a look at the way the main DI frameworks handle property injection to ASP .NET controls to see how you can achieve this. You can then use a single SendMessage method which is responsible for dispatching the message to all controls that can handle that message. It's more common see this sort of pattern in forms UI.

A: 

There are some problems with this approach like:
- The 'events' are not verified at compile (you can easily mistype an event name and find out about this at runtime or worst)
- Filling session with communication stuff
- You need to put control names as strings - If there are more than one control that is a subscriber to these event can become difficult to control
- When parameters will need to be send between controls the solution will become more difficult to manage

A better approach is to use the build in event mechanism by declaring events on the controls:

public event EventHandler SpecialClick;

Each control that needs to do something will subscribe to this event

controlA.SpecialClick += new EventHandler(controlA_SpecialClick)

using the normal dot.net events.

Aleris
A: 

As Briggie alludes to - this is exactly what Model-View Presenter is all about. Here's an article around MVP in .NET, if you want to roll your own.

Ideally you want to look at the MVC framework as an example of what you can do when you separate out everything.

What I normally do is have the button click event raise a domain-specific event, something like:


private void ControlA_OnClick(..)
{
  if(LoginRequested != null)
    LoginRequested(this, loginObj);
}

That way it makes it clear why someone would click the button and drives home the separation.

Cory Foy
+1  A: 

What you have is pretty much an EventBroker. I don't think Session is the appropiate place for this, as it's not necessary to live across requests. HttpContext might work, but unless I wanted the message bus to be shared between IHttpModules and IHttpHandlers, I'd probably just either use a base Page class that custom controls can cast their Page instance to:

interface IEventBroker {
 void Send(Message m);
}

class ControlA {
  void MyButton_Click(object sender, EventArgs e) {
     var eb = this.Page as IEventBroker;
     if (eb != null) eb.Send(new Message());
  }
}

or give the controls a reference to the EventBroker - in which case I'd probably make the EventBroker itself a control and give the ID to each control so that they could use Page.FindControl.

Mark Brackett
Thanks - EventBroker - that's definitely it. Found a good example here - http://codegator.com/mcook/archive/2007/12/14/using-eventbroker-in-a-web-project.aspx
Duncan