Here's my code so far. I have the main form and wcf objects that are created dynamically when a client connects. Right now all wcf objects subscribe to events being fired from the main form.
Yet I want the user to be able to pick a name from the main form's comboBox and fire an event only to the wcf object, that submitted this name.
What do you think would be the best way of doing this?
Thanks!
namespace server2
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
public event EventHandler sendEvnt;
private ServiceHost duplex;
private void Form2_Load(object sender, EventArgs e) /// once the form loads, create and open a new ServiceEndpoint.
{
duplex = new ServiceHost(typeof(ServerClass));
duplex.AddServiceEndpoint(typeof(IfaceClient2Server), new NetTcpBinding(), "net.tcp://localhost:9080/service");
duplex.Open();
this.Text = "SERVER *on-line*";
}
private void button1_Click(object sender, EventArgs e)
{
sendEvnt(this, new EventArgs());
// this send an event to all WCF objects
// what should I do for it to send an event ONLY to the wcf object, that's name is selected from the comboBox ?
}
}
class ServerClass : IfaceClient2Server
{
IfaceServer2Client callback;
public ServerClass()
{
callback = OperationContext.Current.GetCallbackChannel<IfaceServer2Client>();
}
public void StartConnection(string name)
{
var myForm = Application.OpenForms["Form2"] as Form2;
myForm.comboBox1.Items.Add(name);
myForm.comboBox1.SelectedItem = name; // adds a name to form's comboBox.
myForm.sendEvnt += new EventHandler(eventFromServer); // somehow need to incorporate the 'name' here.
callback.Message_Server2Client("Welcome, " + name );
}
void eventFromServer(object sender, EventArgs e)
{
var myForm = Application.OpenForms["Form2"] as Form2;
string msg = myForm.tb_send.Text;
if (msg == "") { msg = "empty message"; }
callback.Message_Server2Client(msg);
}
public void Message_Cleint2Server(string msg)
{
}
public void Message2Client(string msg)
{
}
}
[ServiceContract(Namespace = "server", CallbackContract = typeof(IfaceServer2Client), SessionMode = SessionMode.Required)]
public interface IfaceClient2Server ///// what comes from the client to the server.
{
[OperationContract(IsOneWay = true)]
void StartConnection(string clientName);
[OperationContract(IsOneWay = true)]
void Message_Cleint2Server(string msg);
}
public interface IfaceServer2Client ///// what goes from the sertver, to the client.
{
[OperationContract(IsOneWay = true)]
void AcceptConnection();
[OperationContract(IsOneWay = true)]
void RejectConnection();
[OperationContract(IsOneWay = true)]
void Message_Server2Client(string msg);
}
}
How are the WCF objects stored? I assume you are storing them in some sort of collection. If that is the case, try changing your collection to a Dictionary<string, WcfObjectType>. From there you can lookup the object in the Dictionary based on the string the user has entered.
Related
I have a M2MQTT client subscribed to a topic in the DashboardViewModel class. On message receive, the UI gets updated by calling Writelog.
public class DashboardViewModel : Object, IDashboardViewModel
{
private IDashboardView View { get; }
public DashboardViewModel(IDashboardView view)
{
View = view;
mqttClient = new MqttClient("localhost");
mqttClientId = Guid.NewGuid().ToString();
mqttClient.MqttMsgPublishReceived += mqttClient_MsgPublishReceived;
mqttClient.Subscribe(new string[] { "Home/Temperature" }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
mqttClient.Connect(mqttClientId);
//...
}
private void mqttClient_MsgPublishReceived(object sender, MqttMsgPublishEventArgs eventArgs)
{
string message = Encoding.UTF8.GetString(eventArgs.Message);
View.Writelog(message);
}
}
The textbox on FrmMain does not update; tbxLogs.InvokeRequired always returns false, i.e. tbxLogs.AppendText always executes. Any suggestions please?
public partial class FrmMain : Form, IDashboardView
{
private IDashboardViewModel dashboardViewModel = null;
private delegate void WriteLogCallback(string text);
public FrmMain()
{
InitializeComponent();
}
public void Writelog(string text)
{
if (tbxLogs.InvokeRequired)
{
WriteLogCallback callback = new WriteLogCallback(Writelog);
tbxLogs.Invoke(callback, new object[] { text });
}
else
{
tbxLogs.AppendText(text + "\n");
}
}
}
I think you need to use dispatcher :)
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action<String>(Writelog), message);
inside of this method
private void mqttClient_MsgPublishReceived(object sender, MqttMsgPublishEventArgs eventArgs)
{
string message = Encoding.UTF8.GetString(eventArgs.Message);
//here instead of View.Writelog(message);
}
The end goal is to have a WCF server that calls a callback function, and each subscribed WCF client displays a new instance of a form on the callback.
To accomplish this, I am using using a pub/sub pattern where the clients connect to the server via a named pipe and call Subscribe(), then receive a callback, EventReceived() when an event is read from the event log. In the implementation for the callback, the clients instantiate a new instance of a form and show it.
The problem is that the forms that are shown by the client freeze immediately. Any suggestions would be helpful. A simplified version of the code which exhibits the same behavior is below.
Client Code (Form1 is just a blank, default form with no additional code/decorators. Form2's only purpose in life is to record the SynchronizationContext.Current for the callbacks to use later.)
static class Program
{
public static SynchronizationContext SynchronizationContext;
[STAThread]
static void Main()
{
ClientContract callbacks = new ClientContract();
DuplexChannelFactory<IEventService> pipeFactory = new DuplexChannelFactory<IEventService>(
callbacks, new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/EventPipe"));
IEventService pipeProxy = pipeFactory.CreateChannel();
pipeProxy.Subscribe();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form2());
}
}
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
class ClientContract : IClientContract
{
public void EventReceived(int eventId)
{
Form1 newForm = new Form1();
newForm.Show();
}
}
Client Code - Form2 Constructor
public Form2()
{
InitializeComponent();
Program.SynchronizationContext = SynchronizationContext.Current;
}
Server Code
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(EventService), new Uri[] { new Uri("net.pipe://localhost") }))
{
host.AddServiceEndpoint(typeof(IEventService), new NetNamedPipeBinding(), "EventPipe");
host.Open();
Console.WriteLine("Service started");
do
{
Console.WriteLine("Enter \"callback\" to trigger a callback, or enter to quit");
string input = Console.ReadLine();
if (input == "callback")
{
EventService.PublishEvent(3);
}
else if (input == "")
{
break;
}
} while (true);
host.Close();
}
}
}
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.PerCall)]
class EventService : IEventService
{
public static event NewEventHandler NewEventAvailableHandler;
public delegate void NewEventHandler(EventLogEventArgs e);
private NewEventHandler newEventHandler;
private IClientContract callback = null;
public static void PublishEvent(int eventId)
{
EventLogEventArgs e = new EventLogEventArgs();
e.eventId = eventId;
if (NewEventAvailableHandler != null)
{
NewEventAvailableHandler(e);
}
}
public void Subscribe()
{
callback = OperationContext.Current.GetCallbackChannel<IClientContract>();
newEventHandler = new NewEventHandler(MagazineService_NewIssueAvailableEvent);
NewEventAvailableHandler += newEventHandler;
}
public void Unsubscribe()
{
NewEventAvailableHandler -= newEventHandler;
}
public void MagazineService_NewIssueAvailableEvent(EventLogEventArgs e)
{
callback.EventReceived(e.eventId);
}
}
public class EventLogEventArgs : EventArgs
{
public int eventId;
}
Shared Code (implemented via a class library DLL file)
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IClientContract))]
public interface IEventService
{
[OperationContract(IsOneWay = true, IsInitiating = true)]
void Subscribe();
[OperationContract(IsOneWay = true, IsInitiating = true)]
void Unsubscribe();
}
public interface IClientContract
{
[OperationContract(IsOneWay = true)]
void EventReceived(int eventId);
}
** edit **
As pointed out in the comments, the callback was received on a worker thread, but UI events need to happen on the main thread. I was able to get it to work, but it feels like a hack because I have a second, unused form, simply to get the SynchronizationContext for the callback to use.
I created a dll contain this event handler:
public void tcp1_Data(object sender, Sockets.DataEventArgs e)
{
Tcp tcp = (Tcp)sender;
response = "Socket Connection" + tcp.Tag.ToString() + " replied : " + e.Data.ToString();
tcp.Close();
}
this will fire when server write some thing in socket connection. so by this, I can read the data on socket.
I used this dll in another project. I want to know in my project (that used dll) exactly when server is writing data on socket connection. as you see in tcp1_Data event, I set result into response variable and in main project (that used dll), I checked this variable polling (if response is not null, it means that this event fired). but Its not what I want. I dont want check this variable all the time.
is there any other way?
I tried this as #ThorstenDittmar said:
my dll project (its name is ClientSample) contain:
TheClassInDLL Class
public class TheClassInDLL
{
public event EventHandler<MyEventArgs> DataEventCalled;
public void tcp1_Data(object sender, Sockets.DataEventArgs e)
{
Tcp tcp = (Tcp)sender;
// Note: LOCAL variable
string myresponse = "Socket Connection" + tcp.Tag.ToString() + " replied : " + e.Data.ToString();
// Call the new event here
if (DataEventCalled != null)
DataEventCalled(this, new MyEventArgs(myresponse));
tcp.Close();
}
// We use this class to pass arguments to the event handler
public class MyEventArgs : EventArgs
{
public MyEventArgs(string response)
{
this.Response = response;
}
public string Response
{
get;
private set;
}
}
}
TCPSample class
public class TCPSample
{
Tcp tcp = new Tcp();
tcp.Data += new System.EventHandler
and in another project that I used above dll:
public partial class Form1 : Form
{
private TheClassInDLL myClass;
ClientSample.TCPSample t = new ClientSample.TCPSample();
public Form1()
{
InitializeComponent();
myClass = new TheClassInDLL();
myClass.DataEventCalled += DataEvent;
}
private void button1_Click(object sender, EventArgs e)
{
t.newTCP();
}
private void DataEvent(object sender, TheClassInDLL.MyEventArgs e)
{
MessageBox.Show(e.Response);
}
}
but It didnt work, DataEvent never happend.
Thanks for any helping...
What you wrote here is an event handler that is called when something happens. There must be a class containing this event handler. Instead of writing a global response variable, declare and invoke another event you can subscribe to from outside that class like this:
public class <TheClassInDLL>
{
public event EventHandler<MyEventArgs> DataEventCalled;
// SNIP: All the other code that leads to tcp1_Data being called
...
// The event handler that's called by some code in the class
public void tcp1_Data(object sender, Dart.Sockets.DataEventArgs e)
{
Tcp tcp = (Tcp)sender;
// Note: LOCAL variable
string myresponse = "Socket Connection" + tcp.Tag.ToString() + " replied : " + e.Data.ToString();
// Call the new event here
if (DataEventCalled != null)
DataEventCalled(this, new MyEventArgs(myresponse));
tcp.Close();
}
// We use this class to pass arguments to the event handler
public class MyEventArgs : EventArgs
{
public MyEventArgs(string response)
{
this.Response = response;
}
public string Response
{
get;
private set;
}
}
}
From the caller, you use it like this:
public class <TheCallingClassOutsideDLL>
{
private <TheClassInDLL> myClass;
public TheCallingClassOutsideDLL()
{
myClass = new TheClassInDLL();
myClass.DataEventCalled += DataEvent;
}
private void DataEvent(object sender, <TheClassInDLL>.MyEventArgs e)
{
Console.WriteLine(e.Response);
}
}
Of course you need to replace <TheClassInDLL> and <TheCallingClassOutsideDLL> with the real class names! Creating additional classes of course doesn't work!
For that you got to define your own event and raise it when needed...
for Example -> In the class where you set the "response" variable define an event
//your custom event
public event EventHandler<CustomEventArgs> MyCustomEvent;
//This will raise your event and notify all who registered
private void RaiseMyCustomEvent(CustomEventArgs e)
{
var handler = MyCustomEvent;
if (handler != null)
{
handler(this, e);
}
}
Maybe you will also need CustomEventArgs (used in the example above)
public class CustomEventArgs : EventArgs
{
public String Message {get;private set;}
public CustomEventArgs(String message){
this.Message = message;
}
}
The class which is using the dll and that wants to get notified needs to register for this event
YourDllClassInstance.MyCustomEvent += OnMyCustomEvent;
public void OnMyCustomEvent(object sender, CustomEventArgs e){
Console.WriteLine("Event received");
}
That means in your dll class you got to do something like the following when you want to raise the event
response = "blablabla";
RaiseMyCustomEvent(new CustomEventArgs(response);
Is that what you where asking for?
All, I have extended this tutorial to get and reverse string displayed in two seperate WinForm applications. However, the end goal is to get this working between to WinForm apps that pass SQL between eachother. To facilitate this I have extended this example and the following is what I have
A library .dll containing
public class WcfInterface
{
private static WcfInterface instance;
private ServiceHost host;
private const string serviceEnd = "Done";
protected WcfInterface()
{
}
public static WcfInterface Instance()
{
if (instance == null)
instance = new WcfInterface();
return instance;
}
public void OpenServiceHost<T, U>()
{
host = new ServiceHost(typeof(U), new Uri[] { new Uri("net.pipe://localhost") });
host.AddServiceEndpoint(typeof(T), new NetNamedPipeBinding(), serviceEnd);
host.Open();
}
public void CloseServiceHost<T>()
{
host.Close();
}
public T AddListnerToServiceHost<T>()
{
ChannelFactory<T> pipeFactory =
new ChannelFactory<T>(new NetNamedPipeBinding(),
new EndpointAddress(String.Format("net.pipe://localhost/{0}",
serviceEnd)));
T pipeProxy = pipeFactory.CreateChannel();
return pipeProxy;
}
}
So on the 'server' form, I do
private void Form1_Load(object sender, EventArgs e)
{
List<string> sqlList = new List<string>();
foreach (string line in this.richTextBoxSql.Lines)
sqlList.Add(line);
SqlInfo sqlInfo = new SqlInfo(sqlList);
WcfInterface wcfInterface = WcfInterface.Instance();
wcfInterface.OpenServiceHost<ISqlListing, SqlInfo>();
}
Where
public class SqlInfo : ISqlListing
{
private List<string> sqlList;
public SqlInfo(List<string> sqlList)
{
this.sqlList = sqlList;
}
public List<string> PullSql()
{
return sqlList;
}
}
[ServiceContract]
public interface ISqlListing
{
[OperationContract]
List<string> PullSql();
}
In the client WinForm app
private ISqlListing pipeProxy;
public Form1()
{
InitializeComponent();
WcfInterface wcfInterface = WcfInterface.Instance();
pipeProxy = wcfInterface.AddListnerToServiceHost<ISqlListing>();
}
and on the click event I attampt to get the List<string> from the server
private void button1_Click(object sender, EventArgs e)
{
this.richTextBoxSql.Text = pipeProxy.PullSql().ToString(); // Code hangs here.
}
My question is what is wrong with this?
Thanks for your time.
Edit. I have now also changed the client code according to comments as follows
private ISqlListing pipeProxy { get; set; }
private const string serviceEnd = "Done";
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
this.richTextBoxSql.Text = pipeProxy.PullSql().ToString();
}
private void Form1_Load(object sender, EventArgs e)
{
ChannelFactory<ISqlListing> pipeFactory =
new ChannelFactory<ISqlListing>(
new NetNamedPipeBinding(),
new EndpointAddress(
String.Format("net.pipe://localhost/{0}", serviceEnd)));
pipeProxy = pipeFactory.CreateChannel();
}
this also hangs on the click event.
The way you have the code set up, you are creating a WCF server on the client by referencing WcfInterface.Instance. You are then calling it from the same thread that it is being served on, causing your application to lock up.
There are a number of ways to get around this. Here are a few that come to mind:
Get the service running in your first WinForm app, then use the "Add Service Reference" functionality in visual studio to create your proxies. Note that you'll have to
You can still reference a common library for the WCF contracts, but rework your code so that you're not creating an instance of the service in your "client" WinForms app.
Sorry for posting the same question again, but kinds didn't get it right the first time. :)
Here's the code of the server application, that can receive connection and should send messages to the client.
As you can see I have the "class Form1 : Form" for the GUI stuff and "class ServerWCallbackImpl : IServerWithCallback" with the server functions.
The problem is that this classes can not communicate, i.e. I can not induce a server function from a form event, such as buttonClick, nor can I change something within the form, say TextBox1.Text += ... , from the server function.
Maybe someone could please explain, how should the code look like, so that it would work?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
namespace server
{
[ServiceContract(Namespace = "server", CallbackContract = typeof(IDataOutputCallback), SessionMode = SessionMode.Required)]
public interface IServerWithCallback ///// what comes from the client to the server.
{
[OperationContract(IsOneWay = true)]
void StartConnection(string clientName);
[OperationContract(IsOneWay = true)]
void Message_Cleint2Server(string msg);
}
public interface IDataOutputCallback ///// what goes from the sertver, to the client.
{
[OperationContract(IsOneWay = true)]
void AcceptConnection();
[OperationContract(IsOneWay = true)]
void Message_Server2Client(string msg);
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) /// once the form loads, create and open a new ServiceEndpoint.
{
ServiceHost duplex = new ServiceHost(typeof(ServerWCallbackImpl));
duplex.AddServiceEndpoint(typeof(IServerWithCallback), new NetTcpBinding(), "net.tcp://localhost:9080/service");
duplex.Open();
this.Text = "SERVER *on-line*";
}
private void buttonSendMsg_Click(object sender, EventArgs e)
{
/// callback.Message_Server2Client(textBox2.Text);
/// The name 'Message_Server2Client' does not exist in the current context :(
}
}
class ServerWCallbackImpl : IServerWithCallback /// NEED TO SOMEHOW MERGE THIS ONE WITH THE FORM1 CLASS
{
IDataOutputCallback callback = OperationContext.Current.GetCallbackChannel<IDataOutputCallback>();
public void StartConnection(string name)
{
/// TextBox1.text += name + " has connected!";
/// 'TextBox1' does not exist in the current context :(
/// can't reach form1 components. :/
MessageBox.Show( name + " has connected!");
Message2Client("Welcome, "+name+"!");
}
public void Message_Cleint2Server(string msg)
{
/// TextBox1.text += msg;
/// 'TextBox1' does not exist in the current context :(
/// can't reach form1 components. :/
}
public void Message2Client(string msg)
{
callback.Message_Server2Client(msg);
}
}
}
The lifetime of the duplex variable is limited to the Form1_Load method. That means your host will terminate when the method is finished. To keep the host running declare the duplex variable outside the method and just instantiate it in the method.
Like so:
public partial class Form1 : Form {
//Declare the variable in the class, not the method body
private ServiceHost duplex;
public Form1() {
InitializeComponent();
}
// once the form loads open a new ServiceEndpoint.
private void Form1_Load(object sender, EventArgs e) {
duplex = new ServiceHost(typeof(ServerWCallbackImpl));
duplex.AddServiceEndpoint(typeof(IServerWithCallback), new NetTcpBinding(), "net.tcp://localhost:9080/service");
duplex.Open();
this.Text = "SERVER *on-line*";
}
private void buttonSendMsg_Click(object sender, EventArgs e) {
/// callback.Message_Server2Client(textBox2.Text);
/// The name 'Message_Server2Client' does not exist in the current context :(
}
}
EDIT1:
If ServiceHost is instantiated with a type like in your example an instance of that type will be created for each connection. You can set the lifetime of the instance to session, call or connection I believe. I'm not sure how that object would access your form though. However, if you instantiate the ServerWCallbackImpl class yourself and pass a reference to the host that instance will be used.
public partial class Form1 : Form {
//Declare the variable in the class, not the method body
private ServiceHost duplex;
private ServerWithCallbackImpl localInstance;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
localInstance = new ServerWithCallbackImpl();
NetTcpBinding binding = new NetTcpBinding ();
duplex = new ServiceHost(localInstance, new Uri[] { new Uri("net.tcp://localhost:9080") });
duplex .AddServiceEndpoint(typeof(IServerWithCallback), binding, "service");
duplex .Open();
this.Text = "SERVER *on-line*";
}
}
The ServerWithCallbackImpl object will then have to keep track of it's client(s). To have the server update the GUI you might want to pass a reference to the Form as parameter into the ServerWithCallbackImpl constructor. Take a look at the Publish/Subscriber pattern to get a deeper understanding of callbacks.