I have a SignalR method used to send a request to a specific client and then get a return value. I know this isn't possible, but instead the client needs to send the response as a separate message to the hub.
I send the message to the client from outside the hub:
public String GetResponseFromUser(String userId, String request)
{
// Use requestId to be sure the response is on this request
var requestId = Guid.NewGuid().ToString();
var hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
hubContext.Clients.User(userId).Request(requestId, request);
// Wait for client to send response to the Hub's Response method
var response = ...?
return response;
}
Hub class
public class MyHub : Hub
{
public void Response(String requestId, String response)
{
// Somehow I want to get the response to the method above
}
}
How can I wait for the client response and use this response in my method outside the hub?
Once your hub is connected you have to wait and listen for the answer:
hubContext.On("Response", (requestId, response) =>
{
// do something
}
);
Of course you have to keep that connection alive.
Related
I have created a class that inherits the Signalr Hub class and it runs on startup. When a connection is made, there are some custom headers sent from the client that I use to generate a user object. I want to store these in memory on the server so that I can retrieve the list and display them in a UI. Someone can then use this UI to see the user info and perform interactions with this connection. I have setup a hub class in an ASP MVC project and i am using a console app for the client. I can connect fine and the server can communicate back, but the property that I use in the hub class to keep track of the connected users is reset to null every time a request is made to the hub class.
public class JobRunHandler : Hub
{
private List<JobRunClient> RunningJobs { get; set; }
public JobRunHandler()
{
if(this.RunningJobs == null) this.RunningJobs = new List<JobRunClient>();
}
public override Task OnConnected()
{
JobRunClient runclient = new JobRunClient()
{
ConnectionID = Context.ConnectionId,
Someotherstuff = this.GetHeaderInt(Context.Headers["Someotherstuff"])
};
this.RunningJobs.Add(runclient);
return base.OnConnected();
}
public override Task OnReconnected()
{
var existingClient = this.GetConnectingClient();
if (existingClient == null)
{
JobRunClient runclient = new JobRunClient()
{
ConnectionID = Context.ConnectionId,
Someotherstuff = this.GetHeaderInt(Context.Headers["Someotherstuff"])
};
this.RunningJobs.Add(runclient);
}
return base.OnReconnected();
}
public override Task OnDisconnected(bool stopCalled)
{
this.RemoveClient(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
public void TestMethod()
{
Clients.All.ping();
var client = this.GetConnectingClient();
}
}
I have put break points in every method so i know when it runs. The client never disconnects or triggers reconnect, so there is no issue with the connection being broken. The client connects and the OnConnected() method triggers and the value is added to this.RunningJobs. The client then calls the TestMethod() which works, but when i check this.RunningJobs it is empty.
When Clients.All.ping(); runs it does actually send a ping back to the client. So the connection is made successfully, the server maintains the connection and i can send a ping back to the client when a separate method is called, but for some reason the property is being reset and I dont know why. I can use redis for this if I have to, but I have seen others use this strategy and its not been an issue.
Here is the client code I have created to test this (the console app)
var hubConnection = new HubConnection("http://localhost:2497/");
hubConnection.Credentials = CredentialCache.DefaultNetworkCredentials;
IHubProxy myHubProxy = hubConnection.CreateHubProxy("JobRunHandler");
myHubProxy.On("ping", () => Console.Write("Recieved ping \n"));
hubConnection.Headers.Add("Someotherstuff", "1");
hubConnection.Start().Wait();
while(true)
{
myHubProxy.Invoke("BroadcastCompletion").ContinueWith(task =>
{
if (task.IsFaulted)
{
Console.WriteLine("!!! There was an error opening the connection:{0} \n", task.Exception.GetBaseException());
}
}).Wait();
Console.WriteLine("Broadcast sent to the server.\n");
Thread.Sleep(4000);
}
The hub is transient. SignalR creates a hub instance each time a hub method is invoked so you cannot store any state in an instance property between the calls.
I changed the property being used for this to a ConcurrentDictionary and this seems to be doing the trick. It allows me to store the client connections across all connections. I used the following code for this.
private static readonly ConcurrentDictionary<string, JobRunClient> RunningJobs = new ConcurrentDictionary<string, JobRunClient>();
I have a web api that I can access successfully through a browser :-
https://127.0.0.1:8443/ncrApi
I am trying to create a simple console program in C# using VS2015 to send data and receive a response using http POST.
Here is what I have so far:-
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace WebSample
{
class ApiSendData
{
public string ApiFunction { get; set; }
public string DppName { get; set; }
public string ClearData { get; set; }
public string DppVersion { get; set; }
}
class Program
{
static void Main(string[] args)
{
// The Main function calls an async method named RunAsync
// and then blocks until RunAsyncc completes.
RunAsync().Wait();
}
static async Task RunAsync()
{
using (var client = new HttpClient())
{
// This code sets the base URI for HTTP requests,
// and sets the Accept header to "application/json",
// which tells the server to send data in JSON format.
client.BaseAddress = new Uri("https://localhost:8443/ncrApi");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP POST
var datatobeSent = new ApiSendData()
{
ApiFunction ="NcrSecureData",
DppName ="CSampleCustomer",
DppVersion ="Latest",
ClearData ="1234567890"
};
HttpResponseMessage response = await client.PostAsJsonAsync("ncrApi", datatobeSent);
if (response.IsSuccessStatusCode)
{
// Get the URI of the created resource.
Uri ncrUrl = response.Headers.Location;
// do whatever you need to do here with the returned data //
}
}
}
}
}
However I am getting an error on the HttpResonseMessage response statement....{"An error occurred while sending the request."}
{"The request was aborted: Could not create SSL/TLS secure channel."}
I suspect it is because I am not correctly understanding the the client.BaseAddress and the HttpResponseMessage response statements.
Here is what I have been following:-
http://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client
You are probably getting an error because the final address is your baseAddress + post address, that is: http://localhost:8443/nrcApi/nrcApi , which doesn't exist
Try changing your client.BaseAddress to:
client.BaseAddress = new Uri("https://localhost:8443/");
For SSL connection errors, try generating a trusted certificate:
Make https call using httpclient
Your code looks ok. However, the issue seems to be the call you are making. Basically in the following call, the first parameter is the method/function to be invoked after your URI.
HttpResponseMessage response = await client.PostAsJsonAsync("ncrApi",
datatobeSent);
In this case ncrApi is your method. Calling it in the base URL will result in it being added to the final call making it give an address to an endpoint that does not exist.
I've been working on implementing signalr as part of a wcf service to talk to a .net client. Apart form a connection message all communication is one way passing a dynamic payload to the client side.
I've managed to set it up so that the client will connect to the service and pass a connection message but I can't get the pushing of a message from the service to the client.
Sorry if I've missed this answered else where but I've been unable to find a reason for this failing as it seems to follow the "how to's"
Any help would be much appreciated and thank you in advance
Server side:
WCF external call
public class MessageService : IMessageService
{
public string PushAlerts()
{
var payLoad = new PayLoad
{
MethodName = "alerts"
};
IHubContext connectionHub = GlobalHost.ConnectionManager.GetHubContext<PushConnection>();
connectionHub.Clients.All.Notify(payLoad);
}
}
My Hub
[HubName("PushHub")]
public class PushHub : Hub
{
public override Task OnConnected()
{
var connectionMessage = Context.QueryString["CONNECTION MESSAGE"];
if (connectionMessage != null)
{
Debug.WriteLine("connectionMessage");
}
return base.OnConnected();
}
}
ClientSide:
var querystringData = new Dictionary<string, string>{};
querystringData.Add("CONNECTION MESSAGE", "foo Connection");
var hubConnection = new HubConnection("http://localhost:60479/", querystringData); //Running local till working
hubConnection.TraceLevel = TraceLevels.All;
hubConnection.TraceWriter = Console.Out;
IHubProxy clientHubProxy = hubConnection.CreateHubProxy("PushHub");
clientHubProxy.On("Notify", payLoad =>
SynchronizationContext.Current.Post(delegate
{
ResponseMethod(payLoad);
}, null)
);
await hubConnection.Start();
I've missed out payload but that only holds a string value at present. I've also setup a pipemodule for logging perposes.
Thanks Again
Ok so I resolved this problem in two ways firstly I moved the call to the client inside the hub its self, which I then called from a method in my wcf service.
[HubName("PushHub")]
public class PushHub : Hub
{
IHubContext connectionHub = GlobalHost.ConnectionManager.GetHubContext<PushConnection>();
public void Send(Payload payload)
{
connectionHub.Clients.All.Notify(payLoad);
}
}
Secondly the client code for the method was all wrong. In the end this worked:
clientHubProxy.On("Notify", (payLoad) => { dostuff };
Took a lot of fiddling but hope my answer helps others.
I have a SignalR hub, which i successfully call from JQuery.
public class UpdateNotification : Hub
{
public void SendUpdate(DateTime timeStamp, string user, string entity, string message)
{
Clients.All.UpdateClients(timeStamp.ToString("yyyy-MM-dd HH:mm:ss"), user, entity, message);
}
}
update message sent successfully from JS like so
var updateNotification = $.connection.updateNotification;
$.connection.hub.start({ transport: ['webSockets', 'serverSentEvents', 'longPolling'] }).done(function () { });
updateNotification.server.sendUpdate(timeStamp, user, entity, message);
and received successfully like so
updateNotification.client.UpdateClients = function (timeStamp, user, entity, message) {
I can't work out how to call sendUpdate from within my controller.
From your controller, in the same application as the hub (rather than from elsewhere, as a .NET client), you make hub calls like this:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<UpdateNotification>();
hubContext.Clients.All.yourclientfunction(yourargs);
See Broadcasting over a Hub from outside of a Hub near the foot of https://github.com/SignalR/SignalR/wiki/Hubs
To call your custom method is a little different. Probably best to create a static method, which you can then use to call the hubContext, as the OP has here: Server to client messages not going through with SignalR in ASP.NET MVC 4
Here's an example from SignalR quickstart
You need to create a hub proxy.
public class Program
{
public static void Main(string[] args)
{
// Connect to the service
var hubConnection = new HubConnection("http://localhost/mysite");
// Create a proxy to the chat service
var chat = hubConnection.CreateHubProxy("chat");
// Print the message when it comes in
chat.On("addMessage", message => Console.WriteLine(message));
// Start the connection
hubConnection.Start().Wait();
string line = null;
while((line = Console.ReadLine()) != null)
{
// Send a message to the server
chat.Invoke("Send", line).Wait();
}
}
}
I am using HttpClient (aka Web API client) to consume RESTfull services.
Services require session to be established (via login) and then destroyed (via logout) upon each operation. So the call to consume service A looks something like this (pseudocode)
// setup
create auth dictionary authDict
create authenticationContent using FormUrlEndodeContent(authDict)
create cookieContainer
create HttpClientHandler...
create HttpClient
// login
await httpClient.PostAsync(LoginUrl, authenticationContent);
do error checking
// perform Operation A
await httpClient.....post...or...get...
extract data, process it, tranform it, get a cup of coffee, etc, etc
populate OperationAResult
// logout
await httpClient.GetAsync(LogoutUrl);
// return result
return OperationAResult
My question is, how can I easily reuse setup, login, and logout for different operations?
Should I be creating some method that will take in Action<> and if so how do I make sure that operations occur in order?
Probably the easiest way is to just write a wrapper class.
public class MyHttpClient
{
private HttpClient _client = new HttpClient();
private MyHttpClientSetup _setup;
public MyHttpClient(MyHttpClientSetup setup)
{
this._setup = setup;
}
private void HttpLogin()
{
// .. custom login stuff that uses this._setup
}
private void HttpLogout()
{
// .. custom logout stuff that uses this._setup
}
public void Reset()
{
this._client = new HttpClient();
}
// Wrapped Properties from the private HttpClient (1 example)
public Uri BaseAddress
{
get{ return this._client.BaseAddress;}
set{ this._client.BaseAddress = value;}
}
// Wrapped HttpMethods (1 example)
// Extremely poorly written, should be delegated properly
// This is just a bad example not using Task properly
public Task<HttpResponseMessage> DeleteAsync(string requestUri)
{
this.HttpLogin();
Task<HttpResponseMessage> result = this._client.DeleteAsync(requestUri);
this.HttpLogout();
return result;
}
public class MyHttpClientSetup
{
// Properties required for setup;
}
}
You may be able to create a new MessageHandler to handle this stuff for you transparently.
public class ConnectionHandler : DelegatingHandler {
public HttpClient HttpClient {get;set;}
public TestHandler(HttpMessageHandler handler) {
this.InnerHandler = handler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
// Do your login stuff here
return base.SendAsync(request, cancellationToken) // Make your actual request
.ContinueWith(t => {
// Do your logout stuff here
}
}
}
Then you can just use a single instance of a HttpClient to do all your requests. To add your handler to the request/response pipeline you just need to create a regular HttpClientHandler, assign it to the InnerHandler property of your DelegatingHandler and then pass your new handler into the constructor of the HttpClient. From that point on, all requests made via the HttpClient will be routed through your ConnnectionHandler.
var connectionHandler = new ConnectionHandler(new HttpClientHandler());
var client = new HttpClient(connectionHandler);
connectionHandler.HttpClient = client;
var response = client.GetAsync("http://example.org/request").Result;
The advantage of using a single HttpClient instance is that you don't have to keep re-specifying the DefaultRequestHeaders. Also, disposing the HttpClient will kill the TCP Connection so the next request will have to re-open it.