Examples/documentation?
RedSpiderMkV opened this issue · 3 comments
Hi,
I was wondering if this is similar to the RMI API in Java?
If so, would it be possible to provide a short tutorial/example please?
Thanks a lot!
Hello,
It is a project I was building some time ago to explore possibilities of building client-server communication framework that would allow you to seamlessly replace a local interface implementations with remote calls.
Unfortunately, there is no documentation for this project anywhere, but it contains a set of examples presenting different features of the framework.
Please feel free to clone the sources and browse Example projects.
All examples bases on a simple structure:
- A contract assembly that defines interfaces and DTO objects used for communication,
- A server console program, implementing contract,
- A client console program, that communicates to server via those interfaces.
Below, I will briefly explain examples that are available in sources.
Stateless Services
This is the most basic and probably most common usage pattern where services are stateless so one instance of given contract interface implementation can serve all clients.
The StatelessServices.Contracts assembly defines a following contract:
public interface ICalculator
{
int Add(int x, int y);
int Divide(int x, int y);
int Multiply(int x, int y);
int Subtract(int x, int y);
}
public class Person
{
public string FirstName { get; private set; }
public string LastName { get; private set; }
public Person(string firstName, string lastName)
{
LastName = lastName;
FirstName = firstName;
}
}
public class Message
{
public string Text { get; private set; }
public string Title { get; private set; }
public Message(string title, string text)
{
Text = text;
Title = title;
}
/* ... */
}
public interface IGreeter
{
Message Greet(Person person);
}
As presented above, the contract is represented by pure POCO objects. No serialization attributes are needed. No public setters nor parameterless constructors are needed neither.
The StatelessServices.Server assembly contains contract implementation:
internal class CalculatorService : ICalculator
{
#region ICalculator Members
public int Add(int x, int y)
{
return x + y;
}
public int Subtract(int x, int y)
{
return x - y;
}
public int Multiply(int x, int y)
{
return x * y;
}
public int Divide(int x, int y)
{
return x / y;
}
#endregion
}
internal class GreeterService : IGreeter
{
#region IGreeter Members
public Message Greet(Person person)
{
return new Message(
string.Format("Personal greeting for Mr./Mrs. {0}", person.LastName),
string.Format("Hello {0}!", person.FirstName));
}
#endregion
}
...as well as hosting code:
class Program
{
static void Main(string[] args)
{
// Configures the framework
Configurator.Configure();
// Specifies implementations for the interfaces.
// For stateless services, a single implementation instance
// works for all client connections
var dispatcher = new OperationDispatcher()
.RegisterHandler<ICalculator>(new CalculatorService())
.RegisterHandler<IGreeter>(new GreeterService());
// Starts host at specified address and port and uses specified application identifier
using (var host = new StatelessServerEndpoint("net://127.0.0.1:3131/StatelessServices", dispatcher))
{
host.Start();
Console.WriteLine("Server started...\nPress enter to stop");
Console.ReadLine();
}
}
}
Finally, the StatelessServices.Client assembly contains client code:
class Program
{
static void Main(string[] args)
{
// Initializes framework
Configurator.Configure();
// Connects to the server
using (var client = new ClientConnection("net://localhost:3131/StatelessServices"))
{
client.Open();
// client.RemoteExecutor.Create<T>() creates a proxy for remote calls
GreetSomePeople(client.RemoteExecutor.Create<IGreeter>());
PerformSomeCalculations(client.RemoteExecutor.Create<ICalculator>());
Console.WriteLine("Done. Press enter to exit.");
Console.ReadLine();
}
}
// Example usage
private static void GreetSomePeople(IGreeter greeter)
{
Console.WriteLine(greeter.Greet(new Person("John", "Smith")));
Console.WriteLine(greeter.Greet(new Person("Kitty", "Johnson")));
}
private static void PerformSomeCalculations(ICalculator calculator)
{
Console.WriteLine("Lets add 3 and 5:");
Console.WriteLine(calculator.Add(3, 5));
Console.WriteLine("Lets subtract 5 and 7:");
Console.WriteLine(calculator.Subtract(5, 7));
Console.WriteLine("Lets multiply 3 by 2:");
Console.WriteLine(calculator.Multiply(3, 2));
Console.WriteLine("Lets divide 3 by 0:");
try
{
Console.WriteLine(calculator.Divide(3, 0));
}
catch (Exception e)
{
Console.WriteLine("{0}: {1}", e.GetType().Name, e.Message);
}
}
}
Stateful Services
This example presents how to configure a stateful services, where each client connection has associated state on server side (and which can be shared between services).
The contract and client assemblies are defined in the same way as in previous example.
The difference is on server side:
// A context class that is shared between all services
internal class SharedContext
{
private readonly IDictionary<IRemoteConnection, ClientContext> _clients = new ConcurrentDictionary<IRemoteConnection, ClientContext>();
public void AddClient(IRemoteConnection connection, ClientContext clientContext)
{
_clients.Add(connection, clientContext);
}
public IEnumerable<string> GetRegisteredClients()
{
return _clients.Values.Where(v => v.IsRegistered).Select(v => v.Name).ToArray();
}
public void RemoveClient(IRemoteConnection connection)
{
_clients.Remove(connection);
}
}
internal class ClientContext
{
public bool IsRegistered { get { return !string.IsNullOrEmpty(Name); } }
public string Name { get; set; }
}
// Service implementations
internal class RegistrationService : IRegistrationService
{
private readonly ClientContext _clientContext;
public RegistrationService(ClientContext clientContext)
{
_clientContext = clientContext;
}
#region IRegistrationService Members
public void Register(string name)
{
_clientContext.Name = name;
}
public string GetUserName()
{
return _clientContext.Name;
}
#endregion
}
internal class UserInfoService : IUserInfoService
{
private readonly ClientContext _clientContext;
private readonly SharedContext _sharedContext;
public UserInfoService(SharedContext sharedContext, ClientContext clientContext)
{
_sharedContext = sharedContext;
_clientContext = clientContext;
}
#region IUserInfoService Members
public IEnumerable<string> GetRegisteredUsers()
{
if(!_clientContext.IsRegistered)
throw new UnauthorizedAccessException("User is not registered");
return _sharedContext.GetRegisteredClients();
}
#endregion
}
internal class RegistrationService : IRegistrationService
{
private readonly ClientContext _clientContext;
public RegistrationService(ClientContext clientContext)
{
_clientContext = clientContext;
}
#region IRegistrationService Members
public void Register(string name)
{
_clientContext.Name = name;
}
public string GetUserName()
{
return _clientContext.Name;
}
#endregion
}
// Host definition
class Host : StatefulServerEndpoint
{
readonly SharedContext _sharedContext = new SharedContext();
public Host(string uri)
: base(uri, new ServerConfig())
{
ConnectionClosed += OnConnectionClose;
}
// Each client connection get associated service instances and context objects
protected override void InitializeConnection(IRemoteConnection connection)
{
var clientContext = new ClientContext();
_sharedContext.AddClient(connection, clientContext);
connection.OperationDispatcher
.RegisterHandler<IRegistrationService>(new RegistrationService(clientContext))
.RegisterHandler<IUserInfoService>(new UserInfoService(_sharedContext, clientContext));
}
private void OnConnectionClose(IRemoteConnection connection)
{
_sharedContext.RemoveClient(connection);
}
}
CallbackServices
This example shows that server can communicate back to client via callback interfaces.
In this case client has to define an implementation for callback:
class Program
{
static void Main(string[] args)
{
Configurator.Configure();
IOperationDispatcher callbackDispatcher = new OperationDispatcher();
callbackDispatcher.RegisterHandler<IClientCallback>(new ClientCallback());
using (var client = new ClientConnection("net://localhost:3133/CallbackServices", callbackDispatcher))
{
client.Open();
var userInfoService = client.RemoteExecutor.Create<ILongRunningOperation>();
userInfoService.Perform(5);
Console.WriteLine("Done. Press enter to exit.");
Console.ReadLine();
}
}
}
Server host implements StatefulServerEndpoint and provides callback proxy for service instance:
class Host : StatefulServerEndpoint
{
public Host(string uri)
: base(uri, new ServerConfig())
{
}
protected override void InitializeConnection(IRemoteConnection connection)
{
var clientCallback = connection.RemoteExecutor.Create<IClientCallback>();
connection.OperationDispatcher.RegisterHandler<ILongRunningOperation>(new LongRunningOperation(clientCallback));
}
}
OneWayMethodServices
In this example client uses a following proxy initialization method:
client.RemoteExecutor.Create<ILongRunningOperation>(NoResultMethodExecution.OneWay)
While all previous examples were basing on a synchronous calls, this one would be asynchronous, however it it applicable only to methods returning void
.
BroadcastServices
In this example, a server host implementation uses a broadcasting feature for sending callbacks.
The difference to a standard callback mechanism is that callback method would be executed on all currently connected clients.
BroadcastRemoteExecutor.Create<IBroadcastService>()
Only interfaces with void
methods could be used for broadcasting though.
I hope this description helps.
Thank you for this! I'll definitely check out these examples when I get back to my PC.
I feel this project is pretty interesting and deserves more publicity. Would you consider making a blog post? :)