As a complete beginner in the field of instant messaging (using XMPP protocol), as well as windows phone 8.1 app development; I am trying to start off by using xmedianet library in order to connect to a server and communicate using XMPP protocol. After implementing the following example and tweaking it to my needs.
Here's the part of the code where I configure the connection parameters:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using WP8Xmpp.Resources;
using System.Net.XMPP;
using System.Net.Sockets;
using System.Threading;
namespace WP8Xmpp
{
public partial class MainPage : PhoneApplicationPage
{
private Boolean IsXmppSuccess { get; set; }
/// <summary>
/// Xmpp Client
/// </summary>
public XMPPClient ObjXmppClient { get; set; }
/// <summary>
/// XMPP Connection
/// </summary>
public XMPPConnection ObjXmppCon { get; set; }
// Constructor
public MainPage()
{
InitializeComponent();
}
private void btnLogin_Click(object sender, RoutedEventArgs e)
{
IsXmppValid();
}
private void IsXmppValid()
{
ObjXmppClient = new XMPPClient();
//initializing the xmpp client with credentials
ObjXmppClient.JID = "user#domain.com";
ObjXmppClient.JID.Resource = Guid.NewGuid().ToString();
ObjXmppClient.Password = "acc_password";
ObjXmppClient.Server = "server_uri";*/
ObjXmppClient.AutoReconnect = true;
ObjXmppClient.Port = 81; // I've already tried 5222 but 81 is the correct port in this server's case.
ObjXmppClient.RetrieveRoster = true;
ObjXmppClient.PresenceStatus = new PresenceStatus() { PresenceType = PresenceType.available, IsOnline = true };
ObjXmppClient.AutoAcceptPresenceSubscribe = true;
ObjXmppClient.AttemptReconnectOnBadPing = true;
ObjXmppCon = new XMPPConnection(ObjXmppClient);
ObjXmppCon.Connect();
ObjXmppClient.Connect();
//initializing the xmpp connection
ObjXmppCon.OnAsyncConnectFinished += ObjXmppCon_OnAsyncConnectFinished;
ObjXmppClient.OnStateChanged += new EventHandler(xMPPClient_OnStateChanged);
Thread.Sleep(2000);
} ...
When I launch this application using the WP 8.1 emulator and attempt a connection. Everything works fine until the Resource Binding step. I get the following output on the VS2013 console:
<--stream:features><ver xmlns="urn:xmpp:features:rosterver"/><compression xmlns="http://jabber.org/features/compress"><method>zlib</method></compression><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></stream:features>
Setting state to CanBind
Setting state to Binding
<--<
<--iq id="xxxxxxxx-xxxx-xxxx-xxxx-0f08b82b9f1f" to="user#domain/Resource" xmlns="jabber:client" type="result"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>user#domain/Resource</jid></bind></iq>
Followed by nothingness except for a few "thread exit" messages and the program terminating after a few minutes.
I've been at it for days now.. I've tried all possible scenarios for the connection parameters, even tried fiddling with the library's code to no avail. Could anyone try to replicate this configuration with the same library to see if it's a problem on my side?
Note: connection to the server with the same account using another xmpp client works just fine.
Turned out the problem was that, for some reason, the "Bound" state was not firing even though the binding attempt returned a successful result. I've managed to "fix" this the ugly way by using Thread.sleep() and then changing the state manually to "bound" (note that the same trick is used to get to the "session" state). Here's a sample of my "patchwork" code:
void xMPPClient_OnStateChanged(object sender, EventArgs e)
{
switch (ObjXmppClient.XMPPState)
{
case XMPPState.Binding:
this.Dispatcher.BeginInvoke(() =>
{
Thread.Sleep(2000);
ObjXmppClient.XMPPState = System.Net.XMPP.XMPPState.Bound;
}
);
break;
case XMPPState.Sessioning:
this.Dispatcher.BeginInvoke(() =>
{
Thread.Sleep(2000);
ObjXmppClient.XMPPState = System.Net.XMPP.XMPPState.Session;
}
);
break; ...
Related
In the following project, messages are sent to the server only through the client.
I need every time the client sends a message to the server, the server program responds to the client using the Console.ReadLine () function, without creating a new connection and using the same connection that was already created;
For example, when the client says hello, wait or the server will say answer!
Of course, after searching, I realized that I should probably use connection.SendObject(), but I could not use it properly :(
(Of course, if this answer is correct!)
Thank you for your help
server :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NetworkCommsDotNet;
using NetworkCommsDotNet.Connections;
using System.Reflection;
namespace server
{
class Program
{
static void Main(string[] args)
{
NetworkComms.AppendGlobalIncomingPacketHandler<string>("Message", PrintIncomingMessage);
Connection.StartListening(ConnectionType.TCP, new System.Net.IPEndPoint(System.Net.IPAddress.Any, 1000));
Console.WriteLine("Server listening on:\n");
foreach (System.Net.IPEndPoint localEndPoint in Connection.ExistingLocalListenEndPoints(ConnectionType.TCP))
Console.WriteLine("{0}:{1}", localEndPoint.Address, localEndPoint.Port);
Console.WriteLine("\nPress any key to close server.");
Console.ReadKey(true);
NetworkComms.Shutdown();
}
/// <summary>
/// Writes the provided message to the console window
/// </summary>
/// <param name="header">The packet header associated with the incoming message</param>
/// <param name="connection">The connection used by the incoming message</param>
/// <param name="message">The message to be printed to the console</param>
private static void PrintIncomingMessage(PacketHeader header, Connection connection, string message)
{
Console.WriteLine(message);
}
}
}
client :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NetworkCommsDotNet;
namespace client
{
class Program
{
static void Main(string[] args)
{
int loopCounter = 1;
while (true)
{
string messageToSend = Console.ReadLine();
NetworkComms.SendObject("Message", "192.168.1.1", 1000, messageToSend);
if (Console.ReadKey(true).Key == ConsoleKey.Q)
break;
else loopCounter++;
}
NetworkComms.Shutdown();
}
}
}
there are several ways to implement Real-time Application in .Net Such as:
1.SignalR (It uses WebSockets whenever possible)
2.Grpc (it uses http/2 and Streaming )
3.Also You can build An Application Using MeesageBrokers that implement pub-sub (publish-subscribe ) pattern like (Redis or kafka or ..)
I put a lot of information in this issue because I have no idea what will be relavent
Issue:
I am having an issue with a program I am working on where when running it, it will freeze my whole computer and return no error (I am completely incapable of doing anything CTRL+ALT+DEL doesn't even work). This program accepts a connection from a android client and atm the android client is not configured correctly so the connection is being rejected.
Question:
How can I stop my program from freezing my entire machine?
Conjecture:
I have a few theories as to what is going on but no idea how to fix them. I have read that this may have something to do with me running a single threaded process inside my async worker but I am not sure that the socket is a single threaded process. Also I am not entirely sure how I am supposed to deal with exceptions in a backgroundworker so I just let it fall back to the RunWorkerCompletedEventArgs then retrieve the error message from there.
What I have tried:
- I tried putting try catches every where then removing try catches nothing seems to be able to capture this error
- I checked my systems event log and nothing is showing up except my restarts after my computer freezes
- I have attempted to isolate the issue but it can literally happen at any point from the program starting till when I attempt to connect
Setup:
I am running the program out of visual studio 2012 professional on a windows 8 pro machine. The computer I am on has a i7-3770K 3.50GHz and 32GB of ram. The application that is attempting to make a connection to mine is a Android application and the credentials are incorrect when it is attempting to connect. Visual Studio is running off my main hard drive and building the project on another drive.
Closing:
With all that said would some one please be willing to help me? If you need any more information I will be happy to provide it, please ask.
Main Method:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace Server
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class SourceServer : Window
{
private BackgroundWorker worker = new BackgroundWorker();
public SourceServer()
{
InitializeComponent();
StartListeningForConnections();
}
private void StartListeningForConnections()
{
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.WorkerReportsProgress = true;
if (worker.IsBusy != true)
{
worker.RunWorkerAsync();
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
worker.ReportProgress(0, "Source server version 0.0.0.1ib started");
LoginServer oLoginServer = new LoginServer();
oLoginServer.StartListening(worker);
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
try
{
lvOutput.Items.Add(e.UserState.ToString());
}
catch (Exception exception)
{
lvOutput.Items.Add(exception.StackTrace);
}
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
System.IO.File.WriteAllText(Environment.CurrentDirectory + #"\log.txt", e.Error.StackTrace + " /n " + e.Error.Message);
}
else
{
MessageBox.Show("Error was null");
}
worker.Dispose();
}
}
}
SSL Socket Connection:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using MySql.Data.MySqlClient;
using System.IO;
namespace Server
{
public class LoginServer
{
// Incoming data from the client.
public static string data = null;
public static X509Certificate serverCertificate = null;
public delegate void UpdateListView(ListView oOutput);
public void StartListening(BackgroundWorker worker)
{
// Data buffer for incoming data.
byte[] bytes = new Byte[1024];
// Establish the local endpoint for the socket.
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[1];
serverCertificate = X509Certificate.CreateFromCertFile(#"server.crt");
TcpListener oServer = new TcpListener(ipAddress, 12345);
// Bind the socket to the local endpoint and
// listen for incoming connections.
// Start listening for connections.
while (true)
{
Thread.Sleep(100);
worker.ReportProgress(0, "Waiting for connection....");
// Program is suspended while waiting for an incoming connection.
//Socket handler = listener.Accept();
oServer.Start();
TcpClient oClient = oServer.AcceptTcpClient();
Stream oStream = oClient.GetStream();
SslStream oSSLStream = new SslStream(oStream);
data = null;
// An incoming connection needs to be processed.
string sUsername = "place holder";
string sPassword = "place holder";
while (true)
{
bytes = new byte[1024];
int bytesRec = oSSLStream.Read(bytes, 0, bytes.Length);
data += Encoding.ASCII.GetString(bytes, 0, bytesRec);
string[] sCredentials = data.Split("|".ToCharArray()[0]);
sUsername = sCredentials[0];
sPassword = sCredentials[1];
if (data.IndexOf("<EOF>") > -1)
{
break;
}
}
// Show the data on the console.
worker.ReportProgress(0, "Connection Recieved : ");
worker.ReportProgress(0, "Username: " + sUsername);
worker.ReportProgress(0, "Password: " + sPassword);
worker.ReportProgress(0, "");
// Echo the data back to the client.
byte[] msg;
if (sUsername.Equals("test") && sPassword.Equals("test"))
{
msg = Encoding.ASCII.GetBytes("approved<EOF>\n");
worker.ReportProgress(0, "approved");
oSSLStream.Write(msg, 0, msg.Length);
}
else
{
msg = Encoding.ASCII.GetBytes("rejected<EOF>\n");
worker.ReportProgress(0, "rejected");
oSSLStream.Write(msg, 0, msg.Length);
}
}
}
public void VerifyUser()
{
}
}
}
While I don't see any reason for this to lock up your entire computer, I do see a couple of reasons for the application to potentially hang...
Your while loop inside of your SSL server will never break unless your client writes '<EOF>' to the stream; which you would have to force it to do. I would likely do something similar to this:
while(( bytesRec = oSSLStream.Read(bytes,0,bytes.Length)) > 0 )
{
// Compare input & break
}
-- The while loop you have now ( without a thread sleep ) will consume all of your systems resources waiting for ... something that may never occur.
In a related issue - I note that your 'DoWork' method launches the listener - but does not start a new thread for this listener. This means that the listener is running inside of your interface thread - which will cause the interface ( and potentially more... ) to hang until the process is completed - which as stated, may never happen.
... Ahem... This last paragraph may be incorrect - you are running an async worker, so I may be incorrect in my second assessment.
Cheers, hope this is helpful.
I've had some hanging problems on Windows 8 that I never saw on Windows 7 (with VS2012). As you experienced it worked fine the first time but only locked up Visual Studio (and not my whole machine) and I had to force quit.
The Visual Studio 2012 Update 4 (which focuses on bug fixes and compatibility) seemed to fix it, although I didn't scientifically test this.
Note: As of 9/1/13 this is only the RC2 version so please check for newer versions, and edit this answer when RTM happens.
Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 9 years ago.
Improve this question
This is not actually a question. I'm trying to organize what I've learned about this subject this year. Since I am a beginner in C#, I had lots of difficulties doing this. But thanks to Stack Overflow and a lecture about packets in class, I was able to get enough information to write a program using multiple types of packets, and connections. This script is for beginners who do not know what to do about sockets.
The concept of sending a packet seems to be sending a whole class over the connection. Not writing the data directly to the stream. So, I have to make a DLL(Class Library) file which defines the packet class, and the derived classes of the packet class.
I'll write a simple code for the Packet.dll, which will be having one type of packet, the Login class.
*To make a DLL file, simply make a C# Class Library File on the VS. To compile it, press F7.
"Project Packet, Packet.cs"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace Packet
{
public enum PacketType
{
initType = 0, //it's nothing on this code.
login
}
[Serializable]
public class Packet
{
public int Length;
public int Type;
public Packet() {
this.Length = 0;
this.Type = 0;
}
public static byte[] Serialize(Object o) {
MemoryStream ms = new MemoryStream(1024 * 4); //packet size will be maximum of 4KB.
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, o);
return ms.ToArray();
}
public static Object Desirialize(byte[] bt) {
MemoryStream ms = new MemoryStream(1024 * 4);//packet size will be maximum of 4KB.
foreach( byte b in bt){
ms.WriteByte(b);
}
ms.Position = 0;
BinaryFormatter bf = new BinaryFormatter();
object obj = bf.Deserialize(ms);
ms.Close();
return obj;
}
}
}//end of Packet.cs
add a new class for this Project Packet, "Login.cs".
"Project Packet, Login.cs"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Packet
{
[Serializable] //to serialize this class, this statement is essential.
public class Login :Packet //derived class of Packet.cs. Must be public.
{
public string id_str; //id
public string pw_str; //pw
public Login(string id, string pw) { //constructer to make the code more shorter.
this.id_str = id;
this.pw_str = pw;
}
}
}//end of Login.cs
After you are finished with this, press F7 to compile, and you will get Packet.dll on your Debug folder of the Packet project file.
This is all about the Packet class. If you want to add more classes to serialize, just add a new class, and add a enum value on the PacketType.
Next, I'll write a short example source for using the Packet class.
Although it's a simple source using only one connection, and using only one type of packet, it will be written in multiple threads.
The original of this source which I wrote has many types of packets, and is expected to get multiple connections from multiple users, so I made the class "UserSocket" to make an instance of a connected user. And also, it will have the receiving thread function(A thread function for receiving packets from the client.) in another class "MessageThread.cs".
"Project Server, Form1.cs" //a Windows Form Project, which only has a textBox named textBox1.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Packet; //to add this, right click your project viewer and reference add Packet.dll.
using System.IO;
namespace Server
{
public partial class Form1 : Form
{
private TcpListener server_Listener;
public List<UserSocket> user_list = new List<UserSocket>(); //list to store user instances
private Thread server_Thread; //thread for getting connections.
public void setLog(string msg) //a function to write string on the Form1.textBox1.
{
this.BeginInvoke((MethodInvoker)(delegate()
{
textBox1.AppendText(msg + "\n");
}));
}
private void Form1_Load(object sender, EventArgs e)
{
server_Thread = new Thread(new ThreadStart(RUN)); //starts to wait for connections.
server_Thread.Start();
}
public void RUN() // Thread function to get connection from client.
{
server_Listener = new TcpListener(7778);
server_Listener.Start();
while (true)
{
this.BeginInvoke((MethodInvoker)(delegate()
{
textBox1.AppendText("Waiting for connection\n");
}));
UserSocket user = new UserSocket(); Make an instance of UserSocket
user.UserName = " ";
try
{
user.client = server_Listener.AcceptSocket();
}
catch
{
break;
}
if (user.client.Connected)
{
user.server_isClientOnline = true;
this.BeginInvoke((MethodInvoker)(delegate()
{
textBox1.AppendText("Client Online\n");
}));
user.server_netStream = new NetworkStream(user.client); //connect stream.
user_list.Add(user);
MessageThread mThread = new MessageThread(user, this, user_list); //make an instance of the MessageThread.
user.receiveP = new Thread(new ThreadStart(mThread.RPACKET)); //run the receiving thread for user.
user.receiveP.Start();
}
}
} //end of Form1.cs
"Project Server, UserSocket.cs"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using Packet;
namespace Server
{
public class UserSocket //Just a Class to make an instance of the connected user.
{
public NetworkStream server_netStream;
public bool server_isClientOnline = false;
public byte[] sendBuffer = new byte[1024 * 4];
public byte[] readBuffer = new byte[1024 * 4];
public string UserName = null; //Not in this code, but on the original, used to identify user.
public Login server_LoginClass;
public Socket client = null;
}
}//end of UserSocket.cs
"Project Server, MessageThread.cs"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Packet;
using System.IO;
namespace Server
{
public class MessageThread //A Class for threads for each users.
{
UserSocket uzr;
Form1 f;
List<UserSocket> user_list = new List<UserSocket>();
public MessageThread(UserSocket u, Form1 formget, List<UserSocket> u_l) //Constructer.
{
uzr = u;
f = formget;
this.user_list = u_l;
}
public void RPACKET() //Thread function for receiving packets.
{
f.setLog("rpacket online");
int read = 0;
while (uzr.server_isClientOnline)
{
try
{
read = 0;
read = uzr.server_netStream.Read(uzr.readBuffer, 0, 1024 * 4);
if (read == 0)
{
uzr.server_isClientOnline = false;
break;
}
}
catch
{
uzr.server_isClientOnline = false;
uzr.server_netStream = null;
}
Packet.Packet packet = (Packet.Packet)Packet.Packet.Desirialize(uzr.readBuffer);
//Deserialize the packet to a Packet.cs Type. It's because the packet.Type is in the super class.
switch ((int)packet.Type)
{
case (int)PacketType.login: //If the PacketType is "login"
{
uzr.server_LoginClass = (Login)Packet.Packet.Desirialize(uzr.readBuffer);
f.setLog("ID : " + uzr.server_LoginClass.id_str + " PW : " + uzr.server_LoginClass.pw_str);
uzr.UserName=uzr.server_LoginClass.id_str;
}
}
}
}
}
}
This will be all for the Server part. It starts a listening thread on the form_load to get connections, and if it's connected to a client, it will make an instance of UserSocket, and the connection will be made by the UserSocket.client(Socket client). And it will bind the socket with the NetworkStream of the UserSocket, and start a listening thread.
The listening thread will deserialize packets received by the client, and assign the received class to a member class of UserSocket.
Next, it will be the sending part of this script. The client part.(On the original source, it can send, and also receive packets from the server, but on this script, I'll just make it send a packet. To receive a packet, just make a thread, and a thread function simular to the server on the main page.
"Project Client, Form1.cs"
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Packet;
using System.IO;
namespace Client
{
public partial class Form1 : Form //A Simple Windows Form Project with Two TextBoxes, and a Button
{
private string myid;
private NetworkStream client_Netstream;
private byte[] sendBuffer = new byte[1024 * 4];
private byte[] receiveBuffer = new byte[1024 * 4];
private TcpClient client_tcpClient;
private bool client_isOnline = false;
public Login login;
private void Form1_Load(object sender, EventArgs e)
{
//On Form1 Load, It will connect to the server directly. So, the Server must be active before executing the client.
this.client_tcpClient = new TcpClient();
try
{
this.client_tcpClient.Connect("localhost", 7778);
}
catch
{
MessageBox.Show("Connection Failure\n");
return;
}
this.client_isOnline = true;
this.client_Netstream = this.client_tcpClient.GetStream();
}
private void button1_Click(object sender, EventArgs e)
{
if (!this.client_isOnline)
return;
login = new Login();
login.Type = (int)PacketType.login; //Very essential. must be defined for the server to identify the packet.
login.id_str = this.textBox1.Text;
login.pw_str = this.textBox2.Text;
Packet.Packet.Serialize(login).CopyTo(this.sendBuffer, 0);
this.client_Netstream.Write(this.sendBuffer, 0, this.sendBuffer.Length);
this.client_Netstream.Flush();
for (int i = 0; i < 1024 * 4; i++)
this.sendBuffer[i] = 0;
}
}
}//End of Form1.cs
As I said above, this client won't have a receiving thread. So this is all for the client.
It connects to the server on form load, and if you press the button1, the value of textbox1, and textbox2 will be sent to the server as a serialized packet, PacketType of 'login'. On this example the client just sends two variables, but it can send bigger classes such as classes with Lists.
This is all I can explain about socket programming on C# using Packets. I tried to make it simple, but I couldn't make it shorter. For begginers like me, if you have a question, please leave a comment, and for more skilled experts, if this code needs modification for more efficient coding, please tell me by answering this script.
This is a very long question and I am unclear what the key point is, but:
The concept of sending a packet seems to be sending a whole class over the connection. Not writing the data directly to the stream. So, I have to make a DLL(Class Library) file which defines the packet class, and the derived classes of the packet class.
No. In TCP packets are largely an implementation detail. The socket exposes a stream of data, without any further definitions of logical or physical splits. You can invent your own partitioning / framing any way you like within that, and it doesn't need to map to any "whole class" - it can entirely be "the data" if you like. The point is, however, that "writing data to a stream" is pretty convenient to handle via serializers, and serializers work nicely with a "whole class". However, in most of my socket work the data is more subtle than that and is processed manually and explicitly.
For more general socket guidance, consider http://marcgravell.blogspot.com/2013/02/how-many-ways-can-you-mess-up-io.html
I'm having a problem getting this chunk of code to work (sorry for the length). The code is sterilized to just show the relevant portions in diagnosing the problem.
It works fine when run from a console app.
But when Utlities.SendBulkEmail is called from an ASP.NET app, the BulkEmailCompleted routine never fires, and it is this routine that increments the m_CompletedWorkers counter.
How do I refactor the SendBulkEmail routine to use AsyncOperationManager instead of BackgroundWorker, so I can guarantee the thread that the results are returned on.
The SendBulkEmail routine itself is not multi-threaded. The multi-threading happens inside its foreach loop.
I think the basis of the original code was gotten from this website:
http://www.dotnetfunda.com/articles/article613-background-processes-in-asp-net-web-applications.aspx
The Utilities project is shared among various solutions, and is pretty much stand alone.
I'm hoping that I'm making this clear.
Any help will be appreciated.
The code follows:
IN THE WEBSITE PROJECT (Control.ascx.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Configuration;
using <company>.BusinessObjects.Emails;
using <company>.DataAccessors;
using <company>.DataObjects;
using <company>.Enumerations;
using <company>.Interfaces;
using <company>.Utilities;
...
protected void sendButton_OnClick(object sender, EventArgs e)
{
...
if (HasBenefits)
{
ReportingEmails emailer = new ReportingEmails();
...
//Prevent send if nothing selected
if (... > 0)
{
List<EmailOutcome> results = emailer.GenerateNotificationEmailsForEmployer(<some int>, <some list>);
...
}
}
...
}
IN THE BUSINESS OBJECT PROJECT
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net.Mail;
using System.Resources;
using System.Text;
using System.IO;
using <company>.BusinessObjects.Emails.Helpers;
using <company>.DataAccessors;
using <company>.DataObjects;
using <company>.Enumerations;
using <company>.Utilities;
namespace <company>.BusinessObjects.Emails
{
public class ReportingEmails
{
...
public List<EmailOutcome> GenerateNotificationEmailsForEmployer(int employerID, List<int> benefits = null)
{
...
SendNotificationEmails(List<ReportData>, ref emailSuccessByBenefit, true, benefitsToExclude);
return emailSuccessByBenefit;
}
private void SendNotificationEmails(List<ReporterCommsData> reportData, ref List<EmailOutcome> emailSuccessByBenefit, bool isFleet, List<int> benefitsToExclude)
{
Dictionary<int, MailMessage> bulkEmails = new Dictionary<int, MailMessage>();
//build up the set of emails to send
foreach (ReporterCommsData report in reportData)
{
...
if (String.IsNullOrEmpty(report.Email))
{
...
}
else
{
try
{
MailMessage email = null;
...
email = ConstructEmail(<param>, out <param>, <param>);
...
bulkEmails.Add(report.BenefitID, email); //add each email to the bulk email dictionary
...
}
catch (Exception ex)
{
...
}
}
} //end foreach
//do the bulk email send and get the outcomes
try
{
...
emailSuccessByBenefit.AddRange(Utilities.Mail.SendBulkEmail(bulkEmails, credentials));
}
catch (Exception ex)
{
...
}
}
...
}
...
}
IN THE UTILITIES PROJECT
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Net.Mail;
using System.Threading;
namespace <company>.Utilities
{
...
public class Mail
{
private static List<EmailOutcome> m_MultithreadedEmailSendResults = new List<EmailOutcome>();
private static int m_CompletedWorkers = 0;
...
/// <summary>
/// Sends a large number of emails asynchronously and then reports success of the individual items collectively
/// </summary>
/// <param name="emails">A dictionary of completed MailMessage objects to send out, keyed on an ID</param>
/// <param name="credentials">Network credentials which may be required to send the email</param>
/// <returns>List of EmailOutcome objects signifying the success or failure of sending each individual email</returns>
public static List<EmailOutcome> SendBulkEmail(Dictionary<int, MailMessage> emails, System.Net.NetworkCredential credentials = null)
{
const int NUMBER_OF_THREADS_PER_PROCESSOR = 1;
m_CompletedWorkers = 0;
List<EmailOutcome> results = new List<EmailOutcome>();
List<Dictionary<int, MailMessage>> splitEmailList = new List<Dictionary<int, MailMessage>>();
...
List<BackgroundWorker> workerThreads = new List<BackgroundWorker>();
foreach (Dictionary<int, MailMessage> splitEmails in splitEmailList)
{
// Initialise the parameter array
Object[] parameterArray = new Object[2];
parameterArray[0] = splitEmails;
parameterArray[1] = credentials;
// Runs on function startup
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(BulkEmailWorker);
worker.WorkerReportsProgress = false;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BulkEmailCompleted);
//Add worker to collection
workerThreads.Add(worker);
//Calling the BulkEmailWorker asynchronously
worker.RunWorkerAsync(parameterArray);
}
//Hold until all background workers complete
while (workerThreads.Count > m_CompletedWorkers)
{
Thread.Sleep(500); //Wait a half second
}
//Dispose of BackgroundWorkers
foreach (BackgroundWorker worker in workerThreads)
{
worker.Dispose();
}
//Get results
results.AddRange(m_MultithreadedEmailSendResults);
//Clear the static variable
m_MultithreadedEmailSendResults.Clear();
m_MultithreadedEmailSendResults = new List<EmailOutcome>();
return results;
}
...
/// <summary>
/// Event handler for the RunWorkerCompleted event. Adds the EmailOutcome results to the static
/// </summary>
private static void BulkEmailCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//Add the EmailOutcome objects to the static list of results if completed
if (worker != null)
{
Thread.Sleep(200);
worker.RunWorkerAsync();
}
else
{
m_MultithreadedEmailSendResults.AddRange(e.Result as List<EmailOutcome>);
m_CompletedWorkers++;
}
}
...
}
...
}
The way asp.net works is that it creates a Page object for each request. The same goes for controls. Your notifications of the emails being sent can only reach this object.
The threads you create will run take their time to execute, and your response will not wait for the email sending to complete. This means that you can't send the status using the same request.
However if you make another request, ajax or otherwise from the page, to get the updated status, a new Page and corresponding control objects will be created. You will have to get the status from you static objects and use that to show the status to a user.
You may find the UpdatePanel control handy to implement ajax.
I'm trying to write some code that monitors the TFS workspace(s) on my local workstation but at the moment I'm having problems getting the events to fire.
For example if I map a new folder in my workspace I want to subscribe to the versionControl.UpdatedWorkspace event, and if I do a “get” I want to map to the versionControl.Getting event. The code below is a console application that I think should work, but when I do a get nothing happens. Does anyone know how to successfully subscribe to these events?
VS2010, TFS 2010, WinXP SP3
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Text;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.VersionControl.Client;
namespace TestEventHanling
{
class Program
{
static void Main(string[] args)
{
Uri serverUri = new Uri(#"http://TfsServer:8080/tfs/collection");
using (TfsTeamProjectCollection collection = new TfsTeamProjectCollection(serverUri, CredentialCache.DefaultCredentials))
{
VersionControlServer versionControl = (VersionControlServer)collection.GetService(typeof(VersionControlServer));
versionControl.UpdatedWorkspace += new WorkspaceEventHandler(OnUpdatedWorkspace);
versionControl.Getting += new GettingEventHandler(OnGetting);
Console.WriteLine("Press \'q\' to quit.");
while (Console.Read() != 'q') ;
}
}
internal static void OnUpdatedWorkspace(object sender, WorkspaceEventArgs e)
{
foreach (WorkingFolder wf in e.Workspace.Folders)
{
Console.WriteLine("Workspace updated {0}", wf.ServerItem);
}
}
internal static void OnGetting(Object sender, GettingEventArgs e)
{
Console.WriteLine("Getting: {0}, status: {1}", e.TargetLocalItem, e.Status);
}
}
}
My understanding are that these are events that are on your local instance of VersionControlServer. That is to say, they will fire when you act on that instance in your code.
For example, if, somewhere else in your code, you updated a workspace, then the UpdatedWorkspace handler would fire.
There's a smaller set of events that you can subscribe to server-side (check-in, builds, etc.), but I'm not sure that you can monitor what's happening on the server through the VersionControlServer class.