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" />
<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.