-
Notifications
You must be signed in to change notification settings - Fork 182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Client <-> Server return values and exceptions. #65
Conversation
I have added Server to Client return values and exceptions as well. It appears I broke some of the example projects in the process according to Travis CI because I changed the .On() method to use Func with a return value. (Edit: I have not tested the .On() methods as I have my own system, should work though) |
I fixed that all the JSON types got obliterated. No more longs and JArrays. |
Hi! Any chance I can get your changes? I'd love to try to work on this pull... |
Hi, @danielohirsch! |
Hi, @danielohirsch! |
Thanks Henry - I just ended up copying in all of your new and changed files... |
Ah, I haven't fixed the example projects yet. This will work with C# to C# communication. We may have to make a custom JavaScript include that can handle the more specific changes I proposed in this PR. You can't use return values there (which are optional anyway) but it's especially missing the $type variables in JSON that helps C# to keep track of bool/int/string[]/long/byte etc. so it's a lot more reflection friendly. |
Gotcha - I am digging through your changes... nice work! |
Yeah that works already! The .On now takes a Func. I haven't tested the .On method as I am using reflection identical to the server but you should be able to write something like: connection.On("Test", (args) => {
return 5 + 5;
}); I mean... I am breaking so much in this PR. I might as well publish my client-side non-.On() solution that is so much better. |
I'd love to take a look at it. |
On the server inherit from WebSocketHandler and override the: public override async Task<object> OnInvokeMethodReceived(WebSocket socket, InvocationDescriptor invocationDescriptor)
{
// there must be a separator in the method name.
if (!invocationDescriptor.MethodName.Contains('/')) throw new Exception("Invalid method name.");
// find the controller and the method name.
string[] names = invocationDescriptor.MethodName.Split('/');
string controller = names[0].ToLower();
string command = "ᐅ" + names[1];
// use reflection to find the method in the desired controller.
object self = null;
MethodInfo method = null;
switch (controller)
{
case "session": // add more to this list.
self = m_SessionController; // make some class containing your methods.
method = typeof(SessionController).GetMethod(command);
break;
default:
throw new Exception($"Received command '{command}' for unknown controller '{controller}'.");
}
// if the method could not be found:
if (method == null)
throw new Exception($"Received unknown command '{command}' for controller '{controller}'.");
// insert client as parameter. this is totally worth it but this won't compile for you.
// List<object> args = invocationDescriptor.Arguments.ToList();
// args.Insert(0, m_Clients[WebSocketConnectionManager.GetId(socket)]);
// call the method asynchronously.
return await Task.Run(() => method.Invoke(self, args.ToArray()));
} This will allow you to call methods in different classes "controllers": public int ᐅDoMath(/*WebSocketClient client,*/ int a, int b)
{
if (a == 0 || b == 0) throw new Exception("I am disappointed in you son.");
return a + b;
} Using a simple prefix in the name: int value = await connection.SendAsync<int, int, int>("session/DoMath", 5, 5); On the client inherit from Connection and override the: protected override async Task<object> Invoke(InvocationDescriptor invocationDescriptor)
{
// there must be a separator in the method name.
if (!invocationDescriptor.MethodName.Contains('/')) return null;
// find the controller and the method name.
string[] names = invocationDescriptor.MethodName.Split('/');
string controller = names[0].ToLower();
string command = "ᐅ" + names[1];
// use reflection to find the method in the desired controller.
object self = null;
MethodInfo method = null;
switch (controller)
{
case "session": // add more to this list.
self = m_SessionController; // make some class containing your methods.
method = typeof(SessionController).GetMethod(command);
break;
default:
//App.MainWindow.ViewModel.StatusBarMessage = $"Received command '{command}' for unknown controller '{controller}'.";
return null;
}
// if the method could not be found:
if (method == null)
{
//App.MainWindow.ViewModel.StatusBarMessage = $"Received unknown command '{command}' for controller '{controller}'.";
return null;
}
// call the method asynchronously.
return await Task.Run(() => method.Invoke(self, invocationDescriptor.Arguments.ToArray()));
} This will allow you to call methods in different classes "controllers": public void ᐅOnSessionIdentifiers(string[] identifiers)
{
System.Windows.MessageBox.Show("Received " + identifiers.Count() + " identifiers!");
} Using a simple prefix in the name: await WebSocketManager.InvokeClientMethodOnlyAsync(client.Identifier, "session/OnSessionIdentifiers", GetSessionIdentifiers().ToArray()); (edit: you can return values too. just my example uses void here.) I use the beautiful ᐅ character to prevent people calling methods they aren't supposed to as well as indicating that this method is called through reflection (as it has 0 references, don't want other programmers deleting them thinking it's unused code and git). |
I mean if everyone is okay with it I would do what @RobSchoenaker proposed here. And just remove .On() completely because it's a mess and no-one wants to bother with an object[] for the parameters. And replace it with some proper reflection logic just like the server and my post above. |
Also is anyone able to figure out why these lines: int value = await connection.SendAsync<int, int, int>("session/DoMath", 5, 5); Don't have type inference? Is it because it returns a Task that breaks it? It's sad that you have to specify all of the types now... |
It works for me... with a string in a static method - so calling Wait on the task instead of await |
…tionStrategy, ControllerMethodInvocationStrategy, DecoratedControllerMethodInvocationStrategy.
I have added several method invocation strategies for the client (work in progress). The prefix is totally optional but I like ᐅ hehe. ;) StringMethodInvocationStrategy var strategy = new StringMethodInvocationStrategy();
var connection = new Connection(strategy);
strategy.On("Session/OnSessionIdentifiers", (args) =>
{
System.Windows.MessageBox.Show("Received " + args.Length + " arguments!");
}); ControllerMethodInvocationStrategy var strategy = new ControllerMethodInvocationStrategy("ᐅ", new TestController());
var connection = new Connection(strategy);
public class TestController
{
// called as 'OnSessionIdentifiers'.
public void ᐅOnSessionIdentifiers(WebSocket socket, string[] identifiers)
{
System.Windows.MessageBox.Show("Received " + identifiers.Count() + " identifiers!");
}
} DecoratedControllerMethodInvocationStrategy var strategy = new DecoratedControllerMethodInvocationStrategy("ᐅ");
var connection = new Connection(strategy);
strategy.Register("Session", new TestController());
// can also set a custom separator instead of '/'.
// called as 'Session/OnSessionIdentifiers'. |
When you throw exceptions, because it's reflection that invokes methods, they will show up as user unhandled but they are handled. You can fix this behavior as described here. |
…lient-side methods).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some room for improvement. Looks good!
@Henry00IS can you please double check the failing build? |
Awesome! And yeah it's the example project(s) that have to be modified. |
…o it's not converted to a string (which causes an exception).
Good news, found a legitimate excuse to fix up JavaScript for this new system (as I only work on this due to work). So will be making a small JavaScript include library that takes care of some overhead and by doing so fix the example projects. |
WebSocketConnectionManager: Now preventing several exceptions on the disconnecting of a client. PrimitiveJsonConverter: Whitelisted System.String as a primitive type so Json.Net doesn't generate a simple array of strings when the JS Client expects $type+$value entries.
I think I covered all of it. The chat example uses all of the new features in one way or another. |
…ll all waiting callbacks with an error so the program execution can continue properly.
At this point we can merge it and hope that people will contribute to it to make it even better. |
As requested. Merged. I still need to merge my original changes too - too busy @ work 🙂 |
This is now officially my favorite networking library. 😁 |
Nice job Henry |
Suuuper excited about this merge! |
is there any sample in example project? |
ok. I found it. |
For anyone reading this in the future, have a look at the ChatApplication sample. |
from javascript I can use connection.methods. to return value but how to return value in c# client? |
When you create a new C# Connection, you pass a MethodInvocationStrategy in the constructor. It's identical to how you'd make a server method (depending on the invocation strategy you decide to use).
|
I'm sorry. It's something like this? _strategy.On("CallMethod", (arguments) => From server it will return value "test" when call InvokeClientMethodAsync. |
Sure, that's the Just make a class with a method like I posted above and pass it to the
|
I tried using StringMethodInvocationStrategy but can't get return value. But I can't get the ret value. I assume I should get value "test". |
I tried using ControllerMethodInvocationStrategy but I get error. ControllerMethodInvocationStrategy _ctrlStrategy = new ControllerMethodInvocationStrategy(InnerHandler); public class InnerHandler |
I can call method using ControllerMethodInvocationStrategy but can't get return value. |
I tried but it freeze. Than I get timeout exception. No problem if I invoke the method on the client without return value. From client sent to server I had no problem to get return value. |
Here is an example. Assume we have this method on the server:
(sorry, the first argument is only in my library, bad screenshot on my part)
We call it on the client with parameters 7 and 3 to get result 10:
When we call it with parameters 0 and 3 we get the exception as expected:
Here is a flowchart of how I associate invocations and return values:
There is a cancellation token in place so that if it has been waiting for a reply from the server for 60 seconds it will give up automatically and throws an exception. Please carefully review the code and feel free to come up with suggestions. This is likely going to be a breaking change. Server to Client is coming up soon...