Client C Sharp - XSockets/XSockets.NET-4.0 GitHub Wiki
The C# Client API has support for:
- .NET 3.5, 4.0+ (this section)
- iOS (MonoTouch) (this section)
- Android (MonoDroid) (this section)
- .NET MicroFramework 4.2, 4.3 (see specific section for NETMF)
- Dart (see specific section)
- Arduino (see specific section)
To get the client just get the latest package from http://nuget.org/packages/xsockets.client
The C# clients ALWAYS talk full-duplex/bi-directional communication, and just like the XSockets server this behavior has nothing to do with what OS or WebServer you are running.
Just like in the JavaScript client you can multiplex over several Controller
on one connection. To get a connections just create an instance of the XSocketClient.
var conn = new XSocketClient("ws://localhost:4502", "http://localhost", "chat");
conn.Open();
- The first parameter is the endpoint of the server
- The second parameter is the origin of the client
- The third parameter is params string[] for setting your controllers
Do note that the call to Open
is synchron and will wait until the connection is open and the handshake is completed.
By default the client will not reconnect if the connection is closed, but by just calling the SetAutoReconnect()
method, you can pass in a timeout for the reconnect to occur. By default the timeout is 5 seconds. If the connection is closed and autoreconnect is enabled the client will try to reconnect, if that reconnect fails the event OnAutoReconnectFailed
will be invoked. This way you can implement some custom logic between attempts to reconnect.
A simple sample where out IXSocketClient
is _client
, we also have a fail
counter and after 4 attempts we stop trying to reconnect.
//Using the default timeout
_client.SetAutoReconnect();
//If the reconnect fails...
_client.OnAutoReconnectFailed += (sender, eventArgs) =>
{
Console.WriteLine("AutoReconnect Failed");
fail++;
if (fail > 4)
{
_client.AutoReconnect = false;
}
}
To multiplex over several controllers on one connection you just pass in the controllers to use.
Below we connect to Controllers
one
and two
var conn = new XSocketClient("ws://localhost:4502", "http://localhost", "one","two");
Do note that you do not have to specify all controllers in the creation of the client. As soon as you start using a controller in the client API you will get an instance of it.
So if there is no controller three
defined in the connection you can still do:
conn.Controller("three").Invoke("somemethod");
You can add querystrings to the NameValueCollection
named QueryString
on the IXSocketClient
var conn = new XSocketClient("ws://localhost:4502","http://xsockets.net", "chat");
conn.QueryString.Add("color","blue");
conn.Open();
var conn = new XSocketClient("ws://localhost:4502","http://xsockets.net", "chat");
conn.Headers.Add("headername", "headervalue");
conn.Open();
var conn = new XSocketClient("ws://localhost:4502","http://xsockets.net", "chat");
conn.Headers.Add("myauthtoken", /* the token data */);
conn.Open();
Then verify the token in a custom AuthenticationPipeline
or in the OnOpen
even of the specific controller
var authTicket = new FormsAuthenticationTicket(1, "Uffe",DateTime.Now,DateTime.Now.AddMinutes(15),false, "batman|hulk");
string encTicket = FormsAuthentication.Encrypt(authTicket);
var authcookie = new Cookie(FormsAuthentication.FormsCookieName, encTicket);
conn.Cookies.Add(authcookie);
var conn = new XSocketClient("ws://localhost:4502", "http://xsockets.net", "chat");
conn.AddClientCertificate(new X509Certificate2("mycert.pfx"));
conn.Open();
TODO
You define the methods per Controller
since one connection can communicate over several Controllers
Client
The example below creates a listener for ChatMessage
and it expects the data to be a string.
conn.Controller("chat").On<string>("chatmessage", data => Console.WriteLine(data));
Server
The server code for the examples with the ChatModel
can look like
//using XSockets.Core.XSocket;
//using XSockets.Core.XSocket.Helpers;
this.InvokeToAll("I am Yogi the gummi bear","chatmessage");
If the method you're handling does not have parameters just use the non generic On
method.
Server
//using XSockets.Core.XSocket;
//using XSockets.Core.XSocket.Helpers;
public class Chat : XSocketController
{
public void CallAllClients()
{
this.InvokeToAll("test");
}
}
Note: You can of course use InvokeTo<T>
or Invoke
as well
Client
conn.Controller("chat").On("test", () => Console.WriteLine("Test Was Called"));
If we have a complex object being sent from the server, like the ChatModel
we can of course just do like this
conn.Controller("chat").On<ChatModel>("chatmessage", data => Console.WriteLine(data.UserName + " - " + data.Text));
Server
The server code for the examples with the ChatModel
can look like
//using XSockets.Core.XSocket;
//using XSockets.Core.XSocket.Helpers;
this.InvokeToAll(new ChatModel{Name="Yogi", Text="I am a gummi bear"},"chatmessage");
As an alternative to specifying specific types to the On method, you can specify parameters as dynamic objects.
conn.Controller("chat").On<dynamic>("chatmessage", data => Console.WriteLine(data.Text));
Note: Dynamic keyword will (currently) not exist in clients for Android/iOS. Note: Dynamic keyword does not exist in .NET 3.5 and earlier.
Server
The server code for the examples with the ChatModel
can look like
//using XSockets.Core.XSocket;
//using XSockets.Core.XSocket.Helpers;
this.InvokeToAll(new ChatModel{Name="Yogi", Text="I am a gummi bear"},"chatmessage");
If you for some reason want the actual IMessage sent from the server to the client you can specify IMessage as the type. You will then get the complete object that the client received!
conn.Controller("chat").On<IMessage>("chatmessage", o => Console.WriteLine("{0}, {1} {2}, {3},",o.Controller, o.Topic, o.Data, o.MessageType);
If you know the type contained in the Data part of the IMessage you can use Extract to get the value.
//example extracting the JSON into a specific type
var chatModel = o.Extract<ChatModel>();
Server
The server code for the examples with the ChatModel
can look like
//using XSockets.Core.XSocket;
//using XSockets.Core.XSocket.Helpers;
this.InvokeToAll(new ChatModel{Name="Yogi", Text="I am a gummi bear"},"chatmessage");
When you want to dispose of a handler you have to remove it from the Controller
where it was used.
var listener = conn.Controller("chat").On<string>("chatmessage", data => Console.WriteLine(data));
conn.Controller("chat").DisposeListener(listener);
To call a method on the server, use the Invoke method on the Controller
.
If the server method has no return value, use the non-generic overload of the Invoke method.
Server - method without return value
//using XSockets.Core.XSocket;
public class Chat : XSocketController
{
protected string UserName {get;set;}
public void SetUserName(string userName)
{
this.UserName = userName;
}
}
Client - calling a method that has no return value
conn.Controller("chat").Invoke("setusername", new {userName = "Steve"});
If the server method has a return value, specify the return type as the generic type of the Invoke method.
Server - for a method that has a return value
public IEnumerable<Stock> GetStocks()
{
return _stockTicker.GetAllStocks();
}
The Stock class used for return value
public class Stock
{
public string Symbol { get; set; }
public decimal Price { get; set; }
}
Client - calling a method that has a return value in a synchronous method
var stocks = conn.Controller("stockticker").Invoke<IEnumerable<Stock>>("GetStocks");
foreach (Stock stock in stocks.Result)
{
Console.WriteLine("Symbol: {0} price: {1}\n", stock.Symbol, stock.Price);
}
If there is no respons for 30000 ms there will be a TimeoutException
so you should wrap the synchronous call like:
try
{
var stocks = conn.Controller("stockticker").Invoke<IEnumerable<Stock>>("GetStocks");
foreach (Stock stock in stocks.Result)
{
Console.WriteLine("Symbol: {0} price: {1}\n", stock.Symbol, stock.Price);
}
}
catch (AggregateException ae)
{
ae.Handle((x) =>
{
if (x is TimeoutException)
{
//The communication did not respond within given time frame
//Handle it...
return true;
}
//Another exception handle it... or return false to stop app
return false;
});
}
Of course you can set the default 30000 ms to be longer or shorter if needed. Just pass in your timeout as a parameter in the call like
//Timeout will now be 5 seconds
var stocks = conn.Controller("stockticker").Invoke<IEnumerable<Stock>>("GetStocks",5000);
conn.Controller("stockticker").Subscribe<Stock>("newStock", stock => Console.WriteLine("Symbol: {0} price: {1}\n", stock.Symbol, stock.Price));
If you only want to get a message once and then unsubscribe automatically you can use one
conn.Controller("stockticker").One<Stock>("newStock", stock => Console.WriteLine("Symbol: {0} price: {1}\n", stock.Symbol, stock.Price));
This will make sure that the client unsubscribe to the topic after getting the first message.
If you want to get a message 1 to n times and then unsubscribe automatically you can use many
conn.Controller("stockticker").Many<Stock>("change", 3, data => Console.WriteLine(data));
This will unsubscribe the change
topic after getting 3 messages.
When you compare PUB/SUB with RPC an obvious disadvantage with PUB/SUB is that you do not know if the server has bound the subscription when you do a publish. Therefor you can pass in an additional function to get a callback from the server when the subscription is completed.
This works similar for all subscribe methods.
Subscribe<T>(string topic, Action<T> callback, Action<IMessage> confirmCallback);
One<T>(string topic, Action<T> callback, Action<IMessage> confirmCallback);
Many<T>(string topic, uint limit, Action<T> callback, Action<IMessage> confirmCallback);
where callback
is called when a publish occurs and confirmCallback
is the callback that confirms the subscription.
When you no longer want to subscribe to a topic
you just use the unsubscribe
method to tell the server to remove the subscription.
conn.Controller("chat").Unsubscribe("chatmessage");
Client
conn.Controller("chat").Publish("chatmessage",new {Text= "Hello people!"});
Server
//using XSockets.Core.XSocket;
//using XSockets.Core.XSocket.Helpers;
//The server migth publish the message back to all clients subscribing
public void ChatMessage(ChatModel chatModel)
{
this.PublishToAll(chatModel, "chatmessage");
}
The events on connection level provide information about the socket being opened/closed. The controllers has their own lifetime events.
conn.OnConnected += (sender, eventArgs) => Console.WriteLine("Connected");
conn.Open();
conn.OnDisconnected += (sender, eventArgs) => Console.WriteLine("Disconnected");
conn.Open();
The controller has nothing to do with the actual socket. These events tell you about the controllers you are using over your connection.
conn.Controller("chat").OnOpen += (sender, connectArgs) => {
Console.WriteLine("Opened");
};
conn.Controller("chat").OnClose += (sender, disconnectArgs) =>{
Console.WriteLine("Closed");
};
As soon as you start to communicate over a new controller the OnOpen
event will fire. You actually do not need to specify the controller in the connection. As long as the controller exists on the server the OnOpen
event will fire.
To close as controller (not the actual connection/socket) you just call the Close
method on the controller instance. This will fire the OnClose
event on the controller.
conn.Controller("chat").Close();
conn.Controller("chat").OnOpen += (sender, connectArgs) => {
Console.WriteLine("Open {0}", connectArgs.ClientInfo.Controller);
};
conn.Controller("chat").OnClose += (sender, disconnectArgs) => {
Console.WriteLine("Closed {0}", disconnectArgs.ClientInfo.Controller);
};
XSockets has supported binary messages for a long time, but in 4.0 we have made it even easier than before.
Lets say that we have a file c:\temp\xfile.txt
with the text This file was sent with XSockets.NET
and we want to send that file to the server.
Client
Client - C#
var blob = File.ReadAllBytes(@"c:\temp\xfile.txt");
conn.Controller("chat").Invoke("myfile", blob);
Server
//using XSockets.Core.Common.Socket.Event.Interface;
//using XSockets.Core.XSocket;
public void MyFile(IMessage message)
{
var filecontent = Encoding.UTF8.GetString(message.Blob.ToArray());
}
If we want to attach metadata about the binary data that is easy to do. Just pass along the object representing the metadata and XSockets will let you extract that data on the server.
Client
Client - C#
var blob = File.ReadAllBytes(@"c:\temp\xfile.txt");
conn.Controller("chat").Invoke("myfile", blob, new {Name="xfile.txt"});
Server
//using XSockets.Core.Common.Socket.Event.Interface;
//using XSockets.Core.XSocket;
//simple class for holding metadata about a file
public class FileInfo
{
public string Name {get;set;}
}
public void MyFile(IMessage message)
{
var filecontent = Encoding.UTF8.GetString(message.Blob.ToArray());
var metadata = message.Extract<FileInfo>();
}
Just use Extract<T>
to get back to metadata attached to the binary data.
To handle errors that the client raises, you can add a handler for the Error event on the connection object.
conn.OnError += (sender, errorArgs) => Console.WriteLine(errorArgs.Exception.Message);
You may also handle errors on individual controller using the OnError event on a specific controller.
conn.Controller("chat").OnError += (sender, errorArgs) => Console.WriteLine(errorArgs.Exception.Message);
To handle errors from method invocations, wrap the code in a try-catch block.
//using XSockets.Core.XSocket;
//using XSockets.Core.XSocket.Helpers;
try
{
var stocks = conn.Controller("stockticker").Invoke<IEnumerable<Stock>>("GetStocks");
foreach (Stock stock in stocks.Result)
{
Console.WriteLine("Symbol: {0} price: {1}", stock.Symbol, stock.Price);
}
}
catch (Exception ex)
{
Console.WriteLine("Error invoking GetStocks: {0}", ex.Message);
}
The client pool lets you call methods on the server. The client pool is made for sending only and is often used in legacy applications to boost them to real-time with 2 lines of code.
For example, if you have a half-duplex service such as a WCF you can boost it to real-time by just doing this.
var conn = ClientPool.GetInstance("ws://localhost:4502","http://localhost");
conn.Send("Hello from client pool","chatmessage","chat");
Above we send the text Hello from client pool
to the method ChatMessage
on the controller Chat
. As always you can send any object that can be serialized.
This feature lets you store any serializable object on the server by using simple methods on the client API's. This is useful when you need to store data between connections/pages for the client. You are responsible for cleaning up the memory your self since data written to the storage will remain there until you remove it or the server stops.
When you use the storage you always work on a controller instance since each controller can have its own storage
Note: You can set any serializable object as value in the storage
Set the generic type that you want to store (in this case string)
conn.Controller("chat").StorageSet("color","blue");
Since the storage is per client you will only get notifications for changes in the current client.
conn.Controller("chat").StorageOnSet<string>("color", s => Console.WriteLine("Color {0} was set in storage", s));
var color = conn.Controller("chat").StorageGet<string>("color");
Console.WriteLine("Got color {0} from storage", color);
conn.Controller("chat").StorageRemove("color");
conn.Controller("chat").StorageOnRemove<string>("color", s => Console.WriteLine("Color {0} was removed from storage", s));
conn.Controller("chat").StorageClear();