Aspnetcore: Support returning values from client invocations

Created on 18 Sep 2017  路  28Comments  路  Source: dotnet/aspnetcore

Support server to clients acks so that reliable messaging can be implemented more easily. This would only be when using Clients.Client(). I think we should go back to the SendAsync (fire and forget) InvokeAsync (wait for ack) naming pattern. That's the one sticking point.

EDIT by @anurse: For clarity, this issue is tracking all work related to allowing values to be returned from client invocations. It also covers allowing the server to wait for a void-returning (or Task-returning) client side method to complete.

Design affected-medium area-signalr enhancement severity-minor triaged

Most helpful comment

I have the same need

All 28 comments

Vote +1 since I was trying to implement exactly this and I was confused in the .net client the difference between connection.InvokeAsync() and connection.SendAsync(). Old SignalR docs don't shine any light and my google foo not returning anything but this reference.

What exactly are you trying to do?

Whoops on second visit maybe I misunderstood the thought here.

In any case: I wanted to use the c# async/await construct to fire off a request message to the server from the .net client and "await' the server response. I was hoping I could use InvokeAsync to invoke a hub method which would have a return type once the server replies. But found that is not its use case and rolled my own version:

  • Dictionary of "PendingRequests" awaiting responses keyed by a GUID and paired to a TaskCompletionSource.
  • As SignalR.Client On<>() method is triggered by message from server I find the related GUID and return that response using the paired TaskCompletionSource return.

Allows for IMHO nicer .net client code when trying to use SignalR in a Request/Response style. (Which I understand is just one style of SignalR usage and you want to stay agnostic/light)

What the client code looks like:

LoginRequest request = new LoginRequest();
request.User = user;
LoginResponse response = await Server.InvokeAsync(request) as LoginResponse;

What the extension method looks like:

public async Task InvokeAsync(ClientRequest request)
{
var tcs = new TaskCompletionSource();
request.Id = Guid.NewGuid().ToString();
PendingResponse.Add(request.Id, tcs);
await Connection.InvokeAsync(request);
return await tcs.Task;
}

DOWN WITH REST :)

@brettclutch

I was hoping I could use InvokeAsync to invoke a hub method which would have a return type once the server replies. But found that is not its use case and rolled my own version:

Can you clarify? This is from client to server or server to client? If it's client to server it's exactly the case.

If you are talking about the server awaiting a client response, then that's what this issue is about and it would only be possible when addressing a single client.

This is client to server where client is awaiting a reply from the server where server is addressing a single client and the response from the server is controlled by my server code.

Today, the client supports waiting on a response from the server. What more is the server doing? Is it then calling back to some other client and wanting to wait on that response?

Nothing fancy, just looking for simple request/response where I did not see where/how client supports waiting on a response from the server.

To be clear, the response I await is not a simple AWK that server received message, but a return payload from the server as a result of the request.

Nothing fancy, just looking for simple request/response where I did not see where/how client supports waiting on a response from the server.

InvokeAsync waits on the server to complete and supports returning results from server to client.

To be clear, the response I await is not a simple AWK that server received message, but a return payload from the server as a result of the request.

Sure.

After reading what you were looking for, I'm curious what code you wrote that made you think it doesn't do exactly what you want.

On the client API

InvokeAsync - wait for the response
SendAsync - fire and forget

Guess I need some clarity or will need to wait for docs to come around sorry.

The crux of it was: How would await InvokeAsync know what the return payload is?

class MessageA
class MessageB

Assume, Sending MessageA via InvokeAsync from client will result in Server sending MessageB back to that single client.

Server:
return Clients.Client(Context.ConnectionId).InvokeAsync("MessageB", MessageB);

Client SignalR "Receive" code:
On("MessageB", () => { HandleMessageBCode })

Client "Calling" code:
await InvokeAsync("MethodWhichReturnsMessageB", MessageA)

Thats where I lost it. Theres no way for the client "calling" code to await the receive of MessageB in the "receive" code.

What am I missing?

I just wanted to you to be crystal clear with what you were doing. Code is usually the best way to do that so I appreciate it.

There's no way for the server to wait on the client callback and there's no way for a client callback to return results. This is about the client sending ACKS back to the server (which is what the original issue is about).

Theres no way for the client "calling" code to await the receive of MessageB in the "receive" code.

No that's not the correct way to put it (sorry to be pedantic here but it's important). There is a way for the calling client to wait on the server response. There's no built-in way for the server to wait on a client invocation. The Task returned from the server method represents the call going out to the client but doesn't wait until the client receives and processed that message.

@brettclutch
Wouldn't the following do exactly what you want?

```c#
Server:
public MessageB MethodWhichReturnsMessageB(MessageA arg)
{
// Do stuff
return MessageB;
}

Client:
MessageB messageB = await InvokeAsync("MethodWhichReturnsMessageB", MessageA);
```

@BrennanConroy, Thanks for posting, that gave me the little bit of info I needed for the lights to click on and understand how it works and yes thats exactly what I was trying to do. I now can clearly see the difference between InvokeAsync and SendAsync on the .net client.

Awesome, I will give that a try tonight. Sorry for the noise.

Thanks for this code implementing RPC (procedure is performed on remote server in this case).
May I ask, if there is some straightforward way to implement RPC on remote client?

What I am trying to achieve is:
1, Client A registers method on Server
2, Client B wants to call this method
2a, Client B calls server with name of the method and input arguments
2b, Server forwards these parameters to Client A
2c, Client A replies to server with result
2d, Server returns this result to Client B

I am able to implement this in asynchrous way only, where Client B requests server and receives only some unique RequestID. Then Client B has to waits until it receives asynchronous message with RequestID and final result of the RPC.

Is there a way to implement points 2a, 2b, 2c and 2d within one call, so ClientB directly receives result?

Something like this pseudocode:

//server code
public Response RPC(Request request)
{
    string whereToForward = methodThatFindWhoRegisteredMethod(request.methodName);
    Response response = Clients.Client(whereToForward ).InvokeAsync<Response>("Request", request);
    return response;
}

Is there a way to implement points 2a, 2b, 2c and 2d within one call, so ClientB directly receives result?

No. There's no first class way to do this _2c, Client A replies to server with result_. Even if they were though, it would still all be asynchronous. I think you mean that you just wanted a way to do it without storing request ids and managing that state yourself. If this feature existed, SignalR would have to do the exact same thing.

Thank you very much for your answer. I will pair request IDs myself.

We're not doing this for 2.1. We will revisit later.

hi @davidfowl , Are we going to release this anytime soon? I'm working on a micro-service orchestration design based on Azure SignalR Service, this feature would make my architecture simple.

No, there are no plans to do this in 3.0.

No, there are no plans to do this in 3.0.

Okay. Is there anyway I can achieve this with correct code base.

I have the same need

another +1 on this

Still no plan for this ? Thx.

@anurse just to understand will this be add and can u tell me when ?

We have no concrete plan to add this in an upcoming release at this time. We'll consider it during planning for 5.0 but have made no commitment on when we will ship this functionality.

another +1 on this

another +1 on this

I like how easy they simply say "no plan to do it" when that "it" is mandatory feature even for a software 20 years ago.

I like how easy they simply say "no plan to do it" when that "it" is mandatory feature even for a software 20 years ago.

I have to total agree with this statement... it will be a lot more useful to use when u support this feature

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rbanks54 picture rbanks54  路  3Comments

ipinak picture ipinak  路  3Comments

groogiam picture groogiam  路  3Comments

BrennanConroy picture BrennanConroy  路  3Comments

Kevenvz picture Kevenvz  路  3Comments