WCF Basic WinForm App Communication Issue - c#

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.

Related

Connecting to Websocket on shared hosting when port is blocked

I built a program in c# (asp.net) that connects to wss://stream.binance.com:9443/ws using WebSocket4Net, and gets ticker data which is public.
My application runs fine on localhost, but on my hosting provider I get an error "An attempt was made to access a socket in a way forbidden by its access permissions" which is an issue of blocked port if I understand correctly.
My hosting provider allows me to enable specific ports for specific ip addresses, but in this case I don't have the ip address for the remote host wss://stream.binance.com:9443/ws
Is there a way to find out the ip address of the remote host when connection is open or when a message is received?
My code:
using System.Collections.Generic;
using Newtonsoft.Json;
using WebSocket4Net;
public static class BinanceWShandler
{
static WebSocket ws;
internal static bool isOpen { get; private set; }
public static void Start()
{
ws = new WebSocket("wss://stream.binance.com:9443/ws");
ws.Opened += Ws_Opened;
ws.Closed += Ws_Closed;
ws.Error += Ws_Error;
ws.MessageReceived += Ws_MessageReceived;
ws.EnableAutoSendPing = true;
ws.Open();
}
private static void Ws_Error(object sender, SuperSocket.ClientEngine.ErrorEventArgs e)
{
}
private static void Ws_Closed(object sender, EventArgs e)
{
}
private static void Ws_Opened(object sender, EventArgs e)
{
Request_Individual_Ticker obj = new Request_Individual_Ticker();
obj.method = "SUBSCRIBE";
List<string> pars = new List<string>();
pars.Add("!bookTicker");
obj.#params = pars;
obj.id = 1;
string JSONstring = JsonConvert.SerializeObject(obj);
ws.Send(JSONstring);
isOpen = true;
}
private static void Ws_MessageReceived(object sender, MessageReceivedEventArgs e)
{
SignalRChat.Hubs.ChatHub.instance.SendBinanceWS(e.Message);
}
public class Request_Individual_Ticker
{
public string method { get; set; }
public List<string> #params { get; set; }
public int id { get; set; }
}
}

Is there a way to get an instance of a class from another project?

I have three projects:
- Client (Windows Forms)
- Domain (Business logic)
- Service (Server)
The Domain project have AsyncServer that is used by the Service project and AsyncClient that is used by the Client project.
I want that the client to get the same of instance of the service have, because I tried to add the Service as a reference and to make it static and it doesn't work, also I implemented the Singleton Pattern to it, and still doesn't work, because every time when I try to access the Service, it creates a new instance.
So is there a way to get the same instance of the Program class or another class from the Service project?
Thank you!
A part of code from Service project:
ChatService:
private static ChatService chatService;
private static readonly object syncObject = new object();
public AsyncServer AsyncServer { get; private set; }
private ChatService()
{
InitializeComponent();
AsyncServer = new AsyncServer();
}
public static ChatService GetInstance()
{
lock (syncObject)
{
if (chatService == null)
{
chatService = new ChatService();
}
}
return chatService;
}
Program: (the service is called with console parameter)
public static ChatService ChatServer { get; private set; }
public static void Main(string[] args)
{
if (args[0] == "console")
{
ChatServer = ChatService.GetInstance();
ChatServer.RunAsConsole(args);
}
else
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
ChatService.GetInstance()
};
ServiceBase.Run(ServicesToRun);
}
}
A part of code from Client project:
ChatForm:
private void OnConnectButtonClick(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(txtIP.Text) && !string.IsNullOrEmpty(txtName.Text))
{
asyncClient = new AsyncClient(txtIP.Text);
asyncClient.Connect();
asyncServer = Service.Program.ChatServer.AsyncServer;
asyncServer.ChatContentReceivedMethod += DisplayChatContent;
SetControlsForConnection();
}
}
If I debug the Client project, the ChatServer is null.

C# List.Add System.InvalidOperationException

I am handling an event from a child form in its parent form, and when I try adding items from the list contained within the event args of the handler (ScraperForm_SiteScraped in the code below), I am receiving the exception System.InvalidOperationException in my console.
Interestingly enough, it seems to succeed on the first add, but no subsequent attempts.
public partial class ProxyTesterView : UserControl
{
private BindingList<Proxy> proxies = new BindingList<Proxy>();
private BindingList<ProxyJudge> pudges = new BindingList<ProxyJudge>();
private BindingList<ProxyTest> tests = new BindingList<ProxyTest>();
private PauseOrCancelTokenSource pcts = new PauseOrCancelTokenSource();
private ProxyScraperForm scraperForm = new ProxyScraperForm();
public ProxyTesterView()
{
InitializeComponent();
proxies.ListChanged += Proxies_ListChanged;
scraperForm.SiteScraped += ScraperForm_SiteScraped;
}
private void Proxies_ListChanged(object sender, ListChangedEventArgs e)
{
ProxiesDataGridView.RowCount = proxies.Count;
}
private void AddFromScraperToolStripMenuItem_Click(object sender, EventArgs e)
{
scraperForm.Show();
}
private void ScraperForm_SiteScraped(object sender, SiteScrapedEventArgs e)
{
foreach (var proxy in e.ScrapedProxies)
{
proxies.Add(proxy);
}
}
}
Child Form
public partial class ProxyScraperForm : Form
{
private BindingList<IProxyScraperSite> sites = new BindingList<IProxyScraperSite>();
public int ScrapeInterval { get; set; } = 60000;
public event EventHandler<SiteScrapedEventArgs> SiteScraped;
public ProxyScraperForm()
{
InitializeComponent();
sites.Add(new ProxyScraperSiteUsProxyOrg());
sites.Add(new ProxyScraperSiteFreeProxyListNet());
sites.Add(new ProxyScraperSiteFreeProxyListsNet());
sites.Add(new ProxyScraperSiteHideMyName());
sites.Add(new ProxyScraperSiteHidester());
ScraperDataGridView.DataSource = sites;
}
private void ScrapeButton_Click(object sender, EventArgs e)
{
foreach (var site in sites)
{
Task.Run(async () =>
{
while (true)
{
var driver = SeleniumUtility.CreateDefaultFirefoxDriver();
var newProxies = await site.ScrapeAsync(driver);
driver.Quit();
OnSiteScraped(newProxies);
await Task.Delay(5000);
site.Status = $"Waiting {ScrapeInterval / 1000} seconds...";
await Task.Delay(ScrapeInterval);
}
});
}
}
private void OnSiteScraped(List<Proxy> scrapedProxies)
{
if (SiteScraped != null)
{
SiteScraped(this, new SiteScrapedEventArgs(scrapedProxies));
}
}
}
From our comments, turns out that this was a threading issue. As a good practice, always use a try/catch block when there's a chance that an exception can occur in a block of code. :)
Also, if you're using Visual Studio, you can make VS break on more exceptions by pressing CTRL+ALT+E and selecting the checkboxes. You can read more about exception breaking here.

Pulling Information from 'Server' via WCF

I have two WinForms applications: a 'server' and a 'client'. On the server
private ServiceHost host;
private const string serviceEnd = "Done";
public Form1()
{
InitializeComponent();
}
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);
host = new ServiceHost(
typeof(SqlInfo),
new Uri[] { new Uri("net.pipe://localhost") });
host.AddServiceEndpoint(typeof(ISqlListing),
new NetNamedPipeBinding(),
serviceEnd);
host.Open();
}
Where
public class SqlInfo : ISqlListing
{
public SqlInfo() {}
private List<string> sqlList;
public SqlInfo(List<string> sqlList) : this()
{
this.sqlList = sqlList;
}
public List<string> PullSql()
{
return sqlList;
}
}
[ServiceContract]
public interface ISqlListing
{
[OperationContract]
List<string> PullSql();
}
On the client I have
private ISqlListing pipeProxy { get; set; }
private const string serviceEnd = "Done";
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
List<string> l = pipeProxy.PullSql();
string s = String.Empty;
foreach (string str in l)
s += str + " ";
this.richTextBoxSql.AppendText(s.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();
}
The problem is, that when I 'pull' the List<string> from the server using pipeProxy.PullSql() it is calling the public SqlInfo() {} default constructor and setting the sqlList = null.
How do I get this code to return the text in the RichTextBox on the server app?
This is because you are using this kind of service host:
host = new ServiceHost(
typeof(SqlInfo),
new Uri[] { new Uri("net.pipe://localhost") });
you are passing a type and the WCF framework guess it have to create an instance of type SqlInfo to handle the request. Try to pass a reference to your constructed SqlInfo instance, ie sqlInfo in your case.
Use this overload of ServiceHost, it allow tyou to pass the instance directly:
host = new ServiceHost(
sqlInfo,
new Uri[] { new Uri("net.pipe://localhost") });

firing EVENTS only to a specific dynamic object?

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.

Categories