Friday, 10 September 2010

C# WCF sample with Callback

  • Create new C# Library Project
  • Add Reference to System.ServiceModel
  • Define your service interface
namespace PubSubLibrary
{
[ServiceContract(
CallbackContract = typeof(IPubSubCallback),
SessionMode=SessionMode.Required)]
public interface IPubSubService
{
[OperationContract(IsOneWay = true)]
void Subscribe(string clientId);
[OperationContract(IsOneWay = true)]
void Publish(string clientId, string message);
}
}
  • Define your callback interface
namespace PubSubLibrary
{
public interface IPubSubCallback
{
[OperationContract(IsOneWay = true)]
void Broadcast(CallbackData callbackData);
}
[DataContract]
public class CallbackData
{
[DataMember]
public string ClientId { get; set; }
[DataMember]
public string Message { get; set; }
}
}
  • Create Server C# Console Project
  • Add Reference to Library project and System.ServiceModel
  • Add App.config item
  • Configure App.config (right click and select Edit WCF Configuration)
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MetadataBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="MetadataBehavior" name="PubSubServer.PubSubService">
<clear />
<endpoint address="mex" binding="ws2007HttpBinding" contract="IMetadataExchange"
listenUriMode="Explicit">
</endpoint>
<endpoint address="net.tcp://localhost:8086" binding="netTcpBinding"
contract="PubSubLibrary.IPubSubService" listenUriMode="Explicit">
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost:8085/pubsub" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
  • Implement your service class
namespace PubSubServer
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class PubSubService : IPubSubService
{
Dictionary<string, Subscription> subscriptions = new Dictionary<string, Subscription>();
public void Subscribe(string clientId)
{
Subscription subscription = new Subscription();
subscription.Callback = OperationContext.Current.GetCallbackChannel<IPubSubCallback>();
subscription.ClientId = clientId;
if(subscriptions.ContainsKey(clientId))
{
subscriptions.Remove(clientId); //remove old connection
}
subscriptions.Add(clientId, subscription);
Console.WriteLine("{0}: Connected", clientId);
}
public void Publish(string clientId, string message)
{
Console.WriteLine("{0}: {1}", clientId, message);
CallbackData callbackData = new CallbackData();            
callbackData.ClientId = clientId;
callbackData.Message = message;
Callback(callbackData);
}
public void Callback(CallbackData callbackData)
{            
for (int i = subscriptions.Count - 1; i >= 0; i--)
{
string key = subscriptions.Keys.ToArray()[i];
Subscription subscription = subscriptions[key];
try
{
subscription.Callback.Broadcast(callbackData);
}
catch (Exception)
{
subscriptions.Remove(subscription.ClientId);
Console.WriteLine("{0}: Disconnected", subscription.ClientId);                    
}
}
}
}
class Subscription
{
public string ClientId { get; set; }
public IPubSubCallback Callback { get; set; }
}
}
  • Host your service
namespace PubSubServer
{
class Program
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(PubSubService));
host.Open();
Console.WriteLine("Server running..");
Console.ReadLine();
}
}
}
  • Start your server
  • Create WPF client project
  • Add Reference to System.ServiceModel and System.Runtime.Serialization
  • Generate stubs
    • Right click on References and select Add Service Reference
    • Specify address http://localhost:8085/pubsub?wsdl
    • Alternatively generate stubs/configuration using command line svcutil tool in visual studio command prompt
svcutil /t:code http://localhost:8085/pubsub?wsdl /out:PubSubServiceProxy.cs /config:App.config /async
    • Add generated App.config and PubSubServiceProxy.cs to your client project
  • Implement Callback
namespace PubSubClient
{
internal class PubSubCallback : IPubSubServiceCallback
{
#region Delegates
public delegate void CallbackEventHandler(object source, CallbackEvent e);
#endregion
#region IPubSubServiceCallback Members
public void Broadcast(CallbackData callbackData)
{
fireCallbackEvent(callbackData);
}
#endregion
public event CallbackEventHandler callback;
private void fireCallbackEvent(CallbackData callbackData)
{
if (callback != null)
{
callback(this, new CallbackEvent(callbackData));
}
}
}
public class CallbackEvent : EventArgs
{
private readonly CallbackData callbackData;
public CallbackEvent(CallbackData callbackData)
{
this.callbackData = callbackData;
}
public CallbackData CallbackData
{
get { return callbackData; }
}
}
}
  • Implement your client WPF application
<Window x:Class="PubSubClient.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PubSub Client" Height="326" Width="579" ResizeMode="CanResizeWithGrip">
<DockPanel LastChildFill="True">
<StatusBar DockPanel.Dock="Bottom" Height="18"/>
<Grid>
<Grid.ColumnDefinitions>                
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox x:Name="clientId" Grid.Row="0" Grid.Column="0"  HorizontalAlignment="Stretch"  VerticalAlignment="Stretch"></TextBox>
<Button x:Name="subscribe" Grid.Row="0" Grid.Column="1"  HorizontalAlignment="Stretch"  VerticalAlignment="Stretch"  Content="Subscribe" Click="subscribe_Click"></Button>
<TextBox x:Name="message" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" HorizontalAlignment="Stretch"  VerticalAlignment="Stretch" PreviewKeyDown="message_PreviewKeyDown"></TextBox>
<Button x:Name="publish" Grid.Row="1" Grid.Column="3"  HorizontalAlignment="Stretch"  VerticalAlignment="Stretch"  Content="Send" Click="publish_Click"></Button>
<ListBox x:Name="messages" Grid.Row="2" Grid.ColumnSpan="4" />            
</Grid>
</DockPanel>
</Window>
namespace PubSubClient
{
public partial class Window1 : Window
{
private PubSubServiceClient client;
private bool isConnected;
public Window1()
{
InitializeComponent();
PubSubCallback callback = new PubSubCallback();
callback.callback += new PubSubCallback.CallbackEventHandler(callback_callback);
InstanceContext context = new InstanceContext(callback);
client = new PubSubServiceClient(context);
clientId.Text = Environment.UserName;
publish.IsEnabled = false;
}
void callback_callback(object source, CallbackEvent e)
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate
{
AddMessage(string.Format("{0}: {1}", e.CallbackData.ClientId, e.CallbackData.Message));
}));
}
private void subscribe_Click(object sender, RoutedEventArgs e)
{
Subscribe();
}
private void publish_Click(object sender, RoutedEventArgs e)
{
Publish(message.Text);
}
private void message_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
Publish(message.Text);
e.Handled = true;
}
}
private void Subscribe()
{
try
{                
client.Subscribe(clientId.Text);                
isConnected = true;
clientId.IsReadOnly = true;
subscribe.IsEnabled = false;
publish.IsEnabled = true;
}
catch (Exception ex)
{
MessageBox.Show("Failed to Connect.");
isConnected = false;
}
}
private void Publish(string text)
{
if (isConnected)
{
try
{
client.Publish(clientId.Text, text);
message.Clear();
}catch(Exception ex)
{
MessageBox.Show("Server is not responding");
}                
}
}
private void AddMessage(string message)
{
messages.Items.Add(message);
messages.ScrollIntoView(message);
}
}
}
  • Start your client app, specify clientId (your username by default) and click subscribe. To send a message type a message and click Send.
image
image

No comments:

Post a Comment