So I've been playing with Volta in my spare time this evening - just had a go at tier-splitting a winforms application to see how that works... so I did something pretty trivial, the form has a button, you type a name into a textbox, click the welcome button, it invokes a method on the welcome service and outputs the results to the textbox... I plan to tier split between the client and the underlying service, so that the service could be executed on a "Server".
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void welcomeButton_Click(object sender, EventArgs e)
{
SomeService service = new SomeService();
welcomeOutput.AppendText(service.WelcomeMessage(nameTextBox.Text));
}
}
The service itself looks like this:
[RunAt("Server")]
public class SomeService
{
public string WelcomeMessage(string name)
{
return string.Format("Welcome: {0}", name);
}
}
Unlike a web-app, you can't seem to use "RunAtOrigin" for indicating a tier-split between client/server - when you think about this makes sense (you could have more then server for a win-forms app, because the "origin" and the client are the same thing, unlike the client (javascript) and origin (web server) relationship that exists for a web app.
Now running the application spins up the Volta test server, if you click on volta icon (btw, cudos to whoever did the volta branding/logos - I love em) in the system tray, you can tick the "log requests" to see what traffic between client/server is occurring... clicking the welcome button shows two requests being made for each button click.

Now moving our eyes towards the tier splitter's output, we can see on the client side that our test service class is converted into a proxy (obviously)
[RunAt("Server")]
public class SomeService
{
// Fields
private Guid __id;
private static ClientMap __map;
private static string __uri;
// Methods
public SomeService()
{
object[] parameters = new object[0];
this.__id = Proxy.Call<Guid>(__Serializer.GetInstance(), __GetUri(), "SomeService", 1, null, null, parameters);
__GetMap().Add(this.__id, this);
}
private SomeService(__TypeSerializer __typeSerializer)
{
}
private static ClientMap __GetMap()
{
if (__map == null)
{
__map = new ClientMap();
}
return __map;
}
private static string __GetUri()
{
if (__uri == null)
{
string service = ConfigurationManager.AppSettings.Get("Server");
if (service == null)
{
throw new ArgumentException("Could not find application configuration entry for 'Server'.");
}
__uri = ServiceLocation.GetService(service, "__TierSplit.aspx");
}
return __uri;
}
public string WelcomeMessage(string name)
{
object[] parameters = new object[] { name };
return Proxy.Call<string>(__Serializer.GetInstance(), __GetUri(), "SomeService", 0, this, null, parameters);
}
// Nested Types
public sealed class __TypeSerializer : AbstractTypeSerializer<SomeService>
{
// Methods
public override SomeService Deserialize(ISerializer serializer, IObjectReader reader)
{
reader.ReadSeparator();
reader.ReadMemberName("id");
Guid id = new Guid(reader.ReadPrimitive());
ClientMap map = SomeService.__GetMap();
SomeService byID = (SomeService)map.GetByID(id);
if (byID == null)
{
byID = new SomeService(this);
byID.__id = id;
map.Add(id, byID);
}
return byID;
}
public override void Serialize(ISerializer serializer, IObjectWriter writer, SomeService value)
{
writer.WriteSeparator();
writer.WriteMemberName("id");
writer.WritePrimitive(value.__id.ToString());
}
}
}
And on the server-side, our service has also been altered slightly, note the inner __Service class:
[RunAt("Server")]
public class SomeService
{
// Methods
public string WelcomeMessage(string name)
{
return string.Format("Welcome: {0}", name);
}
// Nested Types
public sealed class __Service : Service<SomeService>
{
// Methods
public override string get_Name()
{
return "SomeService";
}
protected override void ProcessMethod(Service<SomeService>.Call call)
{
switch (call.GetMethod())
{
case 0:
call.Return<string>(call.GetInstance().WelcomeMessage(call.GetParameter<string>(0)));
break;
case 1:
call.Return<Guid>(call.AddInstance(new SomeService()));
break;
default:
throw new InvalidMethodException();
}
}
}
}
Now what about those two requests - looking at the code we can deduce that the two calls are occurring first for construction/registration of the client proxy, and second for handling the message - changing the client app to look like this:
public partial class Form1 : Form
{
SomeService service = new SomeService();
public Form1()
{
InitializeComponent();
}
private void welcomeButton_Click(object sender, EventArgs e)
{
welcomeOutput.AppendText(service.WelcomeMessage(nameTextBox.Text));
}
}
And clicking the welcome button fixes the behavior, so that for successive button clicks only one request is being generated.
Afraid that's all I have time for tonight... but I'll post more about my Volta explorations this week when I get spare moments.