Fun with callbacks Part 3: Strongly-typed callbacks

I got a lot of feedback after the first two posts in this series pointing out the need for a more strongly-typed communication than just strings. Several comments also pointed me to the great AJAX.NET library. I like the AJAX.NET approach because it looks very much like Web Service proxying. It's a real accomplishment as it succeeds in reproducing a reasonable part of the .NET type system in JavaScript. On the downside, it's really oriented at client-side script writing, and it suffers from the same drawbacks as Web Service proxying, namely that it gives the illusion of a local object whereas a service-oriented approach should be taken, trying to mutualize the communications with the server as much as possible. At least, the asynchronousness of callbacks makes it more obvious that you're dealing with networked resources.

To address this feedback, Sam Spencer and I have been implementing a marshalling infrastructure that makes it easy to leverage the existing ASP.NET 2.0 callback infrastructure to communicate with the server in a strongly-typed manner.

Instead of trying to reproduce more or less successfully the .NET type system client-side, we've taken the reverse approach, which is to reproduce the javascript type system in .NET. Sam has been developing a server-side class, EcmaScriptObject, that reproduces any client-side object graph server-side, as well as the client-side and server-side methods needed to serialize and deserialize so that objects can travel on the string-typed callback wire.

I've added around that a declarative way to create callbacks without writing any client script in a lot of cases. Here's the code for a small four operation calculator that uses this new control:

<script runat=server>
protected object[] Add(string commandName, object
[] args) {
  double a = (double
)args[0];
  double b = (double
)args[1];
  object[] results = new object
[2];
  switch
(commandName) {
    case "add"
:
      results[0] = a + b;
      results[1] = "plus"
;
      break
;
    case "substract"
:
      results[0] = a - b;
      results[1] = "minus"
;
      break
;
    case "multiply"
:
      results[0] = a * b;
      results[1] = "times"
;
      break
;
    case "divide"
:
      results[0] = a / b;
      results[1] = "over"
;
      break
;
  }
  return
results;
}
</script
>

<
sample:CallbackProxy runat=server ID="AddProxy" OnCallback="Add">
 
<Triggers
>
   
<sample:CallbackTrigger TriggerID="AddButton" CommandName="add"
/>
   
<sample:CallbackTrigger TriggerID="SubstractButton" CommandName="substract"
/>
   
<sample:CallbackTrigger TriggerID="MultiplyButton" CommandName="multiply"
/>
   
<sample:CallbackTrigger TriggerID="DivideButton" CommandName="divide"
/>
 
</Triggers
>
 
<Parameters
>
   
<sample:CallbackParameter ControlID="Number1" ClientType="float"
/>
   
<sample:CallbackParameter ControlID="Number2" ClientType="float"
/>
 
</Parameters
>
 
<ReturnValues
>
   
<sample:CallbackParameter ControlID="Sum" ClientType="float"
/>
   
<sample:CallbackParameter ControlID="Operation" ClientType="string"
/>
 
</ReturnValues
>
</sample:CallbackProxy
>

<asp:TextBox runat=server ID="Number1" Columns=5 Text="1"
/>
<asp:Button runat=server ID="AddButton" Text="+"
/>
<asp:Button runat=server ID="SubstractButton" Text="-"
/>
<asp:Button runat=server ID="MultiplyButton" Text="x"
/>
<asp:Button runat=server ID="DivideButton" Text="/"
/>
<asp:Label ID="Operation" runat="server" />
&nbsp;
<asp:TextBox runat=server ID="Number2" Columns=5 Text="1"
/>
=
<asp:Label runat=server ID="Sum" />

We've declared two input parameters of type float that come from textboxes (Number1 and Number2), which will be transmitted to the server-side callback handler (Add) in the oject[] args array. We've also declared two return values which will automatically go to the Sum label and to the Operation label. These come from the object[] that the callback handler returns. Finally, we define commands (the four operations) that we declaratively bind to buttons.

What's really nice here is that we only wrote server-side code and declared what the client-side script should do, but we did not have to code it.

Next time, I'll show another example using the CallbackProxy in tandem with the RefreshPanel on more complex types than floats.

N.B. The GotDotNet workspace has been updated and contains the new controls and samples: http://www.gotdotnet.com/Workspaces/Workspace.aspx?id=cb2543cb-12ec-4ea1-883f-757ff2de19e8.

Click to get to "Fun with callbacks" Part 1, Part 2 and Part 4.

5 Comments

  • Very classy stuff, Bertrand! Especially the RefreshPanel at gotdotnet.

  • Callbacks look like great techology capable to improve web controls development significantly.

    Thank you Bertrand for introducing the technology and providing us with amazing demos.

    My question is: are request params (the form input values) available during callbacks?

  • Sergey: during a callback, the page is in exactly the same state it was during the last postback, so the Form and QueryString parameters are still available, in addition to the callback parameters. Does that answer your question?

  • Bertrand, I meant whether are available the actual input values (at the moment of a callback) which have been changed since the last postback, but from your post I see that they are not available and only old values from the last postback can be obtained (and this is exactly what I observed in my test project). In other words I need to pass manually through the callback args all the information I want to gather from the page. Am I correct?

  • Yes, you are correct. It looks a little counter-intuitive at first, but it ensures that the page is in a consistent state during a callback even if multiple callbacks are triggered at once.

    The data that you want to be up-to-date must be passed as a part of the callback parameter, and Form will have the values it had during the latest postback.

Comments have been disabled for this content.