How to report file progress to clients using SignalR? - c#

I have written a component that gets and excel file and stores its data into a database. Since I wanted this class to support different environments (for example using it inside a console app) So I decided to create some events:
public interface IDataSeeder
{
Task Seed(string fileName);
event EventHandler<UserSucceedEventArgs> UserAdded;
event EventHandler<UserErrorEventArgs> Error;
event EventHandler<UserUpdateEventArgs> UserUpdated;
event EventHandler<FinishDataSeederEventArgs> ProcessCompleted;
}
Each of this events will trigger in different places inside Seed method. It works like a charm in my console application.
Now I want to use this component inside my ASP.NET MVC app, for doing so I decided to use SignalR for pushing event's data to client, So I created a hub like this:
public class ProgressHub : Hub
{
public static void UserAdded(UserSucceedEventArgs e)
{
var ctx = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>();
ctx.Clients.All.userAdded(e);
}
public static void Error(UserErrorEventArgs e)
{
var ctx = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>();
ctx.Clients.All.error(e);
}
public static void UserUpdated(UserUpdateEventArgs e)
{
var ctx = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>();
ctx.Clients.All.userUpdated(e);
}
public static void ProcessCompleted(FinishDataSeederEventArgs e)
{
var ctx = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>();
ctx.Clients.All.processCompleted(e);
}
}
Then I created this action method for getting uploaded file and pass it to my component:
[HttpPost]
public async Task<ActionResult> AddFromExcel(HttpPostedFileBase excelFile)
{
if (excelFile != null)
{
var fileName = Utilities.UploadFile.UploadFile.UploadCommonFile(excelFile, "users");
_dataSeeder.UserAdded += DataSeeder_Succeed;
_dataSeeder.Error += DataSeeder_Error;
_dataSeeder.UserUpdated += DataSeeder_Update;
_dataSeeder.ProcessCompleted += DataSeeder_Finish;
await _dataSeeder.Seed(Server.MapPath($"~/Content/Files/users/{fileName}"));
return RedirectToAction("AddFromExcel");
}
return RedirectToAction("List");
}
private static void DataSeeder_Finish(object sender, FinishDataSeederEventArgs e)
{
ProgressHub.ProcessCompleted(e);
}
private static void DataSeeder_Update(object sender, UserUpdateEventArgs e)
{
ProgressHub.UserUpdated(e);
}
private static void DataSeeder_Error(object sender, UserErrorEventArgs e)
{
ProgressHub.Error(e);
}
private static void DataSeeder_Succeed(object sender, UserSucceedEventArgs e)
{
ProgressHub.UserAdded(e);
}
As you can see inside each event handler I notify the clients using my signalr hub.
All of this process is like a messaging system, but I have no idea how to implement it inside a web application. A flaw with my code is that after attaching the event handler I immediately redirect the user to another action method, I know it must be an asynchronous process but I have no idea how to make my events async.
Any idea?

What you have to do is match the SignalR server calls with JavaScript functions that will show the results to the connected clients:
<script type="text/javascript">
// the proxy to your hub
var hub = $.connection.ProgressHub;
// the function that will be called when you call
// ctx.Clients.All.userAdded(e);
hub.client.userAdded = function (data) {
// show the data
alert(data);
};
// follow suit with the rest of the functions
hub.client.error = function (data) {
alert(data);
};
// etc
</script>

Related

Any suggestion with my signalr codes?It is not working

I'm encountering now a serious problem about my signalr application.
What do you think that is lack to my code or any suggestion about signalr. I am new to signalr so can't really tell if it is correct.
I hope any suggestion or comment about my codes. Please anyone can review it.
All I want is when the database is changed my application will automatically change without refreshing the browser.
I also followed this limitation about Broker in SQL server
https://msdn.microsoft.com/en-us/library/ms181122.aspx
I also enable my database broker.
Hub
[HubName("iTestHub")]
public class iTestHub : Hub
{
public void Hello()
{
Clients.All.hello();
}
public static void Show()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<iTestHub>();
context.Clients.All.displayStatus();
}
}
AngularJS Controller
var iTest = $.connection.iTestHub;
// Declare a function on the job hub so the server can invoke it
iTest.client.displayStatus = function () {
getData();
};
// Start the connection
$.connection.hub.start();
getData();
function getData() {
StudentExamService.GetStudentExam(studId + '|' + yearCode).then(function(results) {
$scope.studentExams = results.data;
});
}
Web API starup
public void Configuration(IAppBuilder app)
{
ConfigureApp(app);
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
app.MapSignalR();
app.UseWebApi(config);
}
Globa.asax
protected void Application_Start(object sender, EventArgs e)
{
SqlDependency.Start(ConfigurationManager.ConnectionStrings["CentralizedStudentInformationConnectionString"].ConnectionString);
SqlDependency.Start(ConfigurationManager.ConnectionStrings["Aptus-TestConnection"].ConnectionString);
}
protected void Application_End(object sender, EventArgs e)
{
SqlDependency.Stop(ConfigurationManager.ConnectionStrings["CentralizedStudentInformationConnectionString"].ConnectionString);
SqlDependency.Stop(ConfigurationManager.ConnectionStrings["Aptus-TestConnection"].ConnectionString);
}
You should invoke getData() function, after start() method is done:
$.connection.hub.start().done(function(){
getData();
});
Now, you will invoke get data after connection is established.

C# callback on new thread

i am having trouble creating a callback on a newly started thread.
I have 2 classes, an API, and the Form.cs. I start a thread running a method in API, from Form.cs, i want to notify a method in Form.cs from inside the method in API.
I am familiar with delegation in Obj-C, but not in C#.
I only included the relevant code.
public partial class Main: Form
{
private Api Connect = new Api();
private void StartStopButton_Click(object sender, EventArgs e)
{
//new thread
Thread ThreadConnect = new Thread(Connect.startAttemptingWithUsername);
ThreadConnect.Start();
}
public void AttemptingWithPasswordMessage(string password)
{
// i want to notify this method from the API
}
}
class Api : UserAgent
{
public void startAttemptingWithUsername()
{
_shouldStop = false;
while (!_shouldStop)
{
Console.WriteLine(username);
// How would i notify AttemptingWithPasswordMessage from here?
System.Threading.Thread.Sleep(1000);
}
}
}
Provide an event to your other class, and fire that event whenever it is relevant based on the processing:
class Api : UserAgent
{
public event Action<string> SomeEvent;//TODO give better name
public void startAttemptingWithUsername()
{
_shouldStop = false;
while (!_shouldStop)
{
Console.WriteLine(username);
var handler = SomeEvent;
if (handler != null)
handler("asdf");
// How would i notify AttemptingWithPasswordMessage from here?
System.Threading.Thread.Sleep(1000);
}
}
}
Then add a handler for that event: (And marshal back to the UI thread)
private void StartStopButton_Click(object sender, EventArgs e)
{
//new thread
Thread ThreadConnect = new Thread(Connect.startAttemptingWithUsername);
ThreadConnect.Start();
Connect.SomeEvent += (data) => Invoke(
new Action(()=>AttemptingWithPasswordMessage(data)));
}

How to generate callback (event) from library to application in c#

I'm developing one library (DLL), in which I need to provide event (interrupt) to user as one method with data. Library's work is start listing on socket, receive data from socket and pass this data to user in one method.
Library:
public void start(string IP, int port)
{
// start logic...
// receives data from socket... send this data to user
}
Application:
Library. Class a = new Library. Class();
a.start(ip, port);
// I need this method called by library automatically when it receives data...
void receivedData(string data)
{
// data which received by library....
}
How to raise event to application with data from library?
Thanks in advance....
Add an event to your library like this:
public event Action<string> OnDataReceived = null;
Then, in Application:
Library.Class a = new Library.Class();
a.OnDataReceived += receivedData;
a.start(ip, port);
That's it.
But you may want to write events with the conventions and I suggest you'll start get use to it because .NET is using events that way so whenever you bump into that convention you'll know it's events.
So if I refactor your code a little bit it should be something like:
In your class library:
//...
public class YourEventArgs : EventArgs
{
public string Data { get; set; }
}
//...
public event EventHandler DataReceived = null;
...
protected override OnDataReceived(object sender, YourEventArgs e)
{
if(DataReceived != null)
{
DataReceived(this, new YourEventArgs { Data = "data to pass" });
}
}
When your class library wants to launch the event it should call the OnDataReceived which is responsible for checking that someone is listening and constructing the appropriate EventArgs for passing by your data to the listener.
In the Application you should change your method signature:
Library.Class a = new Library.Class();
a.DataReceived += ReceivedData;
a.start(ip, port);
//...
void ReceivedData(object sender, YourEventArgs e)
{
string data = e.Data;
//...
}
You should change signature of start method to pass there delegate:
public void start(string IP, int port, Action<string> callback)
{
// start logic...
// receives data from socket... send this data to user
callback(data);
}
Library. Class a = new Library. Class();
a.start(ip, port, receivedData);
// I need this method called by library automatically when it receives data...
void receivedData(string data)
{
// data which received by library....
}
Add event to your class
public event DataReceivedEventHandler DataReceived;
public delegate void DataReceivedEventHandler(object sender, SocketDataReceivedEventArgs e);
Create a class that contents your required parameters like Ex : SocketDataReceivedEventArgs here
Trigger event like
SocketDataReceivedEventArgs DataReceiveDetails = new SocketDataReceivedEventArgs();
DataReceiveDetails.data = "your data here";
DataReceived(this, DataReceiveDetails);
in application create method
void receivedData(object sender, SocketDataReceivedEventArgs e)
{
// e.data is data which received by library....
}
Now attach handler to it
Library. Class a = new Library. Class();
a.DataReceived += receivedData;
a.start(ip, port);
You need to write it in multiple threads as your requirement is
Here is how you can add thread safe support to above
Dispatcher.Invoke and accessing textbox from another thread

How to access initial parameters in the callback of an asynchronous WCF request?

I'm currently updating a client application that makes use of a WCF web service from synchronous to asynchronous calls. The main server and the client are on the same local network, but it is too unreliable for the application to hang while it waits for a response.
The application makes use of 4 identical endpoints across 2 servers (so if an instance has crashed or a server is offline, there should still be something to call).
The client has a layer responsible for making calls to the web service. My initial synchronous design was for the the active endpoint to be called and if an exception was thrown we would then move to the next endpoint and recursively call the same method. This would be done until all endpoints are exhausted.
I've now made the modifications to make this async but there is one issue. The parameters are lost once we are in the callback. So when it it time to call the Begin method again recursively, the parameters are not accessible to be passed in again.
What would be the best way to pass parameters from the Begin method to the callback method? Are they stored anywhere in the client object? Can it be done through the event or should I store them at the class level?
public delegate void GetUserInfoCompletedEventHandler(UserInfo e);
public static event GetUserInfoCompletedEventHandler GetUserInfoCompleted;
public delegate void GetUserInfoFaultedEventHandler(string errorMessage);
public static event GetUserInfoFaultedEventHandler GetUserInfoFaulted;
public static void BeginGetUserInfo(string fobID)
{
MyClient client = new MyClient(availableEndpoints[activeEndpointIndex].Name);
client.GetUserInfoCompleted += new EventHandler<GetUserInfoCompletedEventArgs>(client_GetUserInfoCompleted);
client.GetUserInfoAsync(fobID);
}
static void client_GetUserInfoCompleted(object sender, GetUserInfoCompletedEventArgs e)
{
// Get the instance of the client
MyClient client = (MyClient)sender;
if (null == e.Error)
{
// Close the client instance if there was no error
try { client.Close(); }
catch { }
if ((null != GetUserInfoCompleted) && (null != e.Result))
{
// Report as successful and raise the event
ServiceActionSuccessful();
GetUserInfoCompleted(e.Result);
}
}
else
{
// Abort the client as there was an error
try { client.Abort(); }
catch { }
if (e.Error is FaultException<WebServiceError>)
{
FaultException<WebServiceError> fault = (FaultException<WebServiceError>)e.Error;
if (null != GetUserInfoFaulted)
{
// A fault occurred in the web service
GetUserInfoFaulted(fault.Detail.ErrorMessage);
}
}
else
{
// Assume this was problem in connection so test if there any more endpoints to attempt
bool isNextEndpointAvaialble = ServiceActionFailure();
if (isNextEndpointAvaialble)
{
// If there are more endpoints to try, call the method to run again
BeginGetUserInfo(); // Need parameters here
}
else
{
if (null != GetUserInfoFaulted)
{
// No more endpoints to try
GetUserInfoFaulted(Errors.GetUserFriendlyMessage(e.Error));
}
}
}
}
}
If MyClient is a generated class, there should be a second function called
MyClient.GetUserInfoAsync(string fobID, object userState);
The content of the userState argument is passed dirctly to the GetUserInfoCompletedEventArgs.UserState property in the eventargs received by client_GetUserInfoCompleted.
So you could do something like this:
public static void BeginGetUserInfo(string fobID)
{
MyClient client = new MyClient(availableEndpoints[activeEndpointIndex].Name);
client.GetUserInfoCompleted += new EventHandler<GetUserInfoCompletedEventArgs>(client_GetUserInfoCompleted);
client.GetUserInfoAsync(fobID, fobID);
}
static void client_GetUserInfoCompleted(object sender, GetUserInfoCompletedEventArgs e)
{
string fobID = e.UserState as string;
// handle the event here...
}
Another alternative is to use a lambda for handling the event:
public static void BeginGetUserInfo(string fobID)
{
MyClient client = new MyClient(availableEndpoints[activeEndpointIndex].Name);
client.GetUserInfoCompleted += (sender, args) => client_GetUserInfoCompleted(sender, args, fobID);
client.GetUserInfoAsync(fobID);
}
static void client_GetUserInfoCompleted(object sender, GetUserInfoCompletedEventArgs e, string fobID)
{
// access the additional parameter fobID here
}

Handling Server-side RtmpConnection.Invoke() when connecting with NetConnection as a client using FluorineFx

I am trying to create a basic console app in order to stress test our FluorineFx based flash remoting server.
I can connect fine but the server method I am calling invokes this client-side function:
connection.Invoke("onServerDataPush", new string[] { "someParam", "anotherParam" });
I am struggling to find out how I can expose this method to the connection. The NetConnection.Call() method allow you to pass in a callback but the result of this is always null and the NetConnection call fails with the following error:
Could not find a suitable method with name onServerDataPush
Here is my client-side code:
class Program
{
private NetConnection _netConnection;
static void Main(string[] args)
{
var program = new Program();
program.Connect();
Console.ReadLine();
}
public void Connect()
{
_netConnection = new NetConnection();
_netConnection.ObjectEncoding = ObjectEncoding.AMF3;
_netConnection.OnConnect += netConnection_OnConnect;
_netConnection.NetStatus += netConnection_NetStatus;
_netConnection.Connect("rtmp://localhost:1935/MyApplication");
}
void netConnection_OnConnect(object sender, EventArgs e)
{
var responder = new Responder<object>(x =>
{
var test = x;
});
//The NetConnection object is connected now
_netConnection.Call("MyServerMethod", responder, "someParameter");
}
void netConnection_NetStatus(object sender, NetStatusEventArgs e)
{
string level = e.Info["level"] as string;
}
}
Debugging through RtmpClient line 308 finally allowed me to solve this.
You must set the NetConnection.Client property to the class that contains a method of the same signature as the one being invoked by the server (in my case this as the method is in the Program class).
public void onServerDataPush(string type, string json)
{
}
FluorineFx then calls the method using reflection.

Categories