I recently started using Signal R library in ASP.Net MVC 3 application. I am able to use Signal R to send message to client. But I noticed that if I logged in to the application from another browser, I get following error -
Exception type: InvalidOperationException
Exception message: Unrecognized user identity. The user identity cannot change during an active SignalR connection.
Server stack trace:
at Microsoft.AspNet.SignalR.PersistentConnection.GetConnectionId(HostContext context, String connectionToken)
at Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequest(HostContext context)
at Microsoft.AspNet.SignalR.Owin.CallHandler.Invoke(IDictionary2 environment)
at Microsoft.AspNet.SignalR.Owin.Handlers.HubDispatcherHandler.Invoke(IDictionary2 environment)
at Microsoft.Owin.Host.SystemWeb.OwinCallContext.Execute()
at Microsoft.Owin.Host.SystemWeb.OwinHttpHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object extraData)
And in some instances, I started getting this error repeatedly. And event log get full in couple of minutes.
I override OnConnect, OnDisconnect functions in my hub class and initiated hub connection from the Java script.
SignalR does not allow a user's Identity to change while maintaining a connection. This is because SignalR's connectionToken acts as an anti-xsrf token.
It appears that your "other" browser must actually be just another browser window. When you log in to the application in another window, it changes your ASP.NET forms authentication cookie (and therefore your Identity) in both browser windows.
Any ongoing SignalR connections will run into the aforementioned InvalidOperationException when they attempt to reconnect after the browser's auth cookie has changed. SignalR may attempt to reconnect quite frequently if the long polling transport is being used. Once the reconnect fails due to the exception, SignalR will repeatedly try to reconnect for about 40 seconds after which SignalR will give up and disconnect.
Related
I am building a website that is managed over Microsoft IIS. I want my whole website to work like this:
The IIS gets a HTTP request over port 80 for the main website and then answers with the HTML and JS documents. These documents remain static and are simply saved as files on the server, which the IIS reads and send to the client without further modifying it.
In the JS document that the client gets, there are fetch()-commands that send additional requests during runtime over a different port when the user is interacting with the website. The browser should get back JSON data from the server. I have written a ASP.NET web application to do this.
This is what my current setup in IIS looks like:
enter image description here
I have set up a site for step one (front-end) and one for step two (back-end). I added a binding for the first site to port 80 and set the default document. I linked the second site to the directory in which the build binary of my ASP.NET application lies. Then, I added the binding with Port 5000 and the same IP-Address to the second site.
Step one works. When sending a request over Port 80, I get the website documents. But the second step doesn't. In debugging mode of Visual Studio, it works fine and it does it job. But I didn't manage to get step two working in IIS yet. The response I get when sending my requests is a 404. I created an additioonal controller to my back-end for testing that should just respond some hardcoded strings and do nothing more. Actually that works. I do get a 200 and the strings as a response. So there must be something wrong with the ASP.NET back-end communicating with my database.
I found out that everytime, when sending my request, an error log is written by my back-end. It says:
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware1
An unhandled exception has occurred while executing the request.
System.Text.Json.JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerCycleDetected(Int32 maxDepth)
at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type inputType, JsonSerializerOptions options, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|27_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
Again, I do not get the error when debugging the back-end ap
Can someone help?
The problem was basically wrong SQL Server permission setup. I managed to solve my problem. This may be helpful to people who are facing similar problems:
The "JsonException object cycle" error itself was not that helpful at all. So, I went into further debugging. I attached VisualStudio Debugging to the actual running IIS process like so: https://learn.microsoft.com/en-us/visualstudio/debugger/attach-to-running-processes-with-the-visual-studio-debugger?view=vs-2019
This was helpful for finding the reason for the error, as I could now experience log messages that were much more fitting to the actual error. Mind that you have to change the hostingModel to "outofprocess" in the web.config in order to attach to the standalone .Net process. https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/out-of-process-hosting?view=aspnetcore-5.0
In case you are using SQL Server Management Studio, create a login for "NT Authority" (Object Explorer > Security > Logins > NT-Authority), grant access to your database and make sure that you do not select a "deny" role in user mapping, because I did so, and that was causing my error.
I have a couple of questions regarding SignalR Core authorization on the server side;
My server is written in ASP.NET Core, and it uses SignalR CORE for sending notifications to users.
1) If a client has connected with an options object containing an AccessTokenProvider and the access token changes -- does the server re-authorize the user even when using a long-running connection like a socket? Ie does SignalR create a new User object on the server side when the client's access token changes, while a connection is alive? If not -- how should this be handled?
2) On the client side - if a connection is aborted either from server side or by the client requesting a stop, does DisposeAsync() have to be called and a new connection object created, or can the previous one be reused safely without disposing it?
Thanks.
1) When token expires, you will need to refresh the token. To do that you will need to abort the connection and make it again. This is stupid but server will drop the connection when token will be expired.
2) If it is manually aborted, the object is disposed and the new connection will be new. If connection will drop for like, internet connection issues, with automatic reconnect, it will have the same instance and maintain same connection (and same connectionId).
I have come up with a well known SignalR problem which gives multiple results when googled "The connection id is in the incorrect format." However I cannot find the solution that would fit my needs. All the solutions suggested are the same: stop signalR in javascript in client. However, what I need is to somehow stop or handle the error in server side.
Example: the user logs in, SignalR starts, all is good, he clicks logout (just before the logout I override javascript event and stop SignalR). No error.
Another example: the user logs in, SignalR starts, user waits for some time the session ends. He clicks something, he is thrown to login page. However, SignalR now notices the User's Identity has changed and throws this error The connection id is in the incorrect format. Add a few of these happening and I have my IIS Worker working at 100% CPU thus resulting in a crashed server. A bit modified example would include SignalR pinging the server and again seeing that the session has ended and thus throwing an exception.
How should I handle such issue? Is there a way to stop SignalR for certain client from server side? Or maybe something different? Maybe I can make SignalR somehow stop relying on Identity? I am sure people must have somehow solved this already. I am using Forms authentication and MVC4 in my project. The version of SignalR is 1.0.0. I know it isn't latest, but I doubt it would do any good if it was the latest. Or maybe someone has proof that I am wrong?
I am adding the information of the error from my IIS logs just in case:
Exception information:
Exception type: InvalidOperationException
Exception message:
Server stack trace:
at Microsoft.AspNet.SignalR.PersistentConnection.GetConnectionId(HostContext context, String connectionToken)
at Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequest(HostContext context)
at Microsoft.AspNet.SignalR.Owin.CallHandler.Invoke(IDictionary`2 environment)
at Microsoft.AspNet.SignalR.Owin.Handlers.HubDispatcherHandler.Invoke(IDictionary`2 environment)
at Microsoft.Owin.Host.SystemWeb.OwinCallContext.Execute()
at Microsoft.Owin.Host.SystemWeb.OwinHttpHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object extraData)
Exception rethrown at [0]:
at Microsoft.Owin.Host.SystemWeb.Utils.<>c__DisplayClass1.<GetRethrowWithNoStackLossDelegate>b__0(Exception ex)
at Microsoft.Owin.Host.SystemWeb.CallContextAsyncResult.End(IAsyncResult result)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
Request information:
Request URL: http://example.com:port/signalr/abort?transport=serverSentEvents&connectionToken=0D8V3TwV78z1cU7_kbQGaSjZH1r_w4eGkkFPRftDiZXPJjbEZmXEluSOkvzVBDRQOGWzVPGfeCKdsvEUwhzb07-Kph-pJ3oog_ydjvsRSnoXqDnYZkPXwJgPrFsFYtmpFDKe0hOvHS7ZxApQF_4fbw2
Request path: /signalr/abort
User host address: A.B.C.D
User:
Is authenticated: False
Authentication Type:
Thread account name: IIS APPPOOL\DefaultAppPool
Thread information:
Thread ID: 63
Thread account name: IIS APPPOOL\DefaultAppPool
Is impersonating: False
Stack trace: at Microsoft.Owin.Host.SystemWeb.Utils.<>c__DisplayClass1.<GetRethrowWithNoStackLossDelegate>b__0(Exception ex)
at Microsoft.Owin.Host.SystemWeb.CallContextAsyncResult.End(IAsyncResult result)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
Just "thinking out loud", if the problem is just because of the session expiration, you might want to try to intercept when the session ends on the server and broadcast a message to all impacted clients (logic to be added for this) asking them to disconnect explicitly and avoid them thrashing your server. I guess your session is tied to authentication so a login would have to happen anyway, so you would reconnect SignalR after that. Makes sense?
You could also have a client side timeout expiring after N-1 minutes, where N is the duration of the Session. and have that one force a disconnection. The timeout itself could be updated using SignalR itself if things happening on the server make the time window move.
Both are pragmatic, quick and dirty solutions, to keep both SignalR and Session together.
You can find a good explanation in this thread. It seems you can't somehow use Session with SignalR.
I hope it will help you.
This should help you for session management
https://syfuhs.net/2013/03/24/real-time-user-notification-and-session-management-with-signalr-part-2/
http://weblog.west-wind.com/posts/2013/May/22/A-first-look-at-SignalR
I have read many other questions and Github Issue about this. The solution seems to be either call stop on the client before logging off or add the ping so that client doesn't get logged off automatcally.
"The connection ID is in the incorrect format" or "The user identity
cannot change during an active SignalR connection" error
This error may be seen if authentication is being used, and the client
is logged out before the connection is stopped. The solution is to
stop the SignalR connection before logging the client out.
However, in another typical scenario where only one person is allowed to be logged in to the site at one time, how should this be taken care of? I am talking about token based auth via sso where if the user logs on from another machine or browser and the auth token is invalidated for the previous session. In this case if the user gets to the previous session by doing anything the page refresh causes the user to be sent to login page (because the server knows that the auth token is invalid). However, if the user doesn't do anything on the previous page (it is open in background), the SignalR hosting app continues to throw the above mentioned error. How should this be dealt with? The host app throws tons of exceptions when this happens. Here is the stack trace...
Server stack trace:
at Microsoft.AspNet.SignalR.PersistentConnection.GetConnectionId(HostContext context, String connectionToken)
at Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequest(HostContext context)
at Microsoft.AspNet.SignalR.Owin.CallHandler.Invoke(IDictionary`2 environment)
at Microsoft.AspNet.SignalR.Owin.Handlers.HubDispatcherHandler.Invoke(IDictionary`2 environment)
at Microsoft.Owin.Host.SystemWeb.OwinCallContext.Execute()
at Microsoft.Owin.Host.SystemWeb.OwinHttpHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object extraData)
Exception rethrown at [0]:
at Microsoft.Owin.Host.SystemWeb.Utils.<>c__DisplayClass1.<GetRethrowWithNoStackLossDelegate>b__0(Exception ex)
at Microsoft.Owin.Host.SystemWeb.CallContextAsyncResult.End(IAsyncResult result)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
I add your report to SignalR issue https://github.com/SignalR/SignalR/issues/2544
We are building an app which will send messages to the browser using SignalR. The user may have multiple browser instances open and we would like each message to be sent to the appropriate browser. Our understanding is that the ClientId ConnectionId would allow us to do this. The issue we're running into is accessing the ClientId ConnectionId, or SessionId, at the appropriate times in the codebase. Here's our scenario:
A MVC Action executes and, as part of that processing, a call to a Biztalk endpoint is made. The Biztalk execution is out of process (from the point of view of the MVC Action) and doesn't return when completed. This is by design. To notify the MVC application that it has completed, Biztalk sends a message to the MVC application's SignalR hub by calling the /myapp/signalr endpoint. The message is received by SignalR and then should be routed to the appropriate browser instance.
Since the message to SignalR is being sent by Biztalk, and not the MVC application, the ClientId of the connection to SignalR is not the one that identifies the browser instance that should receive the message. So what we are attempting to implement is somethign similar to the Return Address pattern by including the ClientId ConnectionId of the browser instance that initiates the Biztalk call in the message to Biztalk. When Biztalk sends its message to SignalR one of the contents is that original ClientId ConnectionId value. When SignalR processes the message from Biztalk it then can use the ClientId ConnectionId included in the message to route that message to the appropriate browser instance. (Yes we know that this won't work if the browser has been closed and re-opened and we're fine with that.)
The problem we face is that when initially sending the message to Biztalk from our MVC Action we cannot access the ClientId ConnectionId as it's only available in the Hub's Context. This is understandable since the MVC Action doesn't know which Hub context to look for.
What we have tried in it's place is to pass the SessionId through the Biztalk message and return it to SignalR. This solves the problem of including the browser instance identifier in the Biztalk message and returning it to SignalR. What it introduces is the fact that when a client connects to the Hub we cannot access the Session (and thus the SessionId) in the Hub's OnConnect method.
David Fowler posted a gist that reportedly shows how to make readonly SessionState accessible in a Hub but it doesn't work. (https://gist.github.com/davidfowl/4692934) As soon as we add this code into our application messages sent to SignalR cause a HTTP 500 error which is caused by SignalR throwing the following exception.
[ArgumentNullException: Value cannot be null.Parameter name: s]
System.IO.StringReader..ctor(String s) +10688601
Microsoft.AspNet.SignalR.Json.JsonNetSerializer.Parse(String json, Type targetType) +77
Microsoft.AspNet.SignalR.Json.JsonSerializerExtensions.Parse(IJsonSerializer serializer, String json) +184
Microsoft.AspNet.SignalR.Hubs.HubRequestParser.Parse(String data) +101
Microsoft.AspNet.SignalR.Hubs.HubDispatcher.OnReceived(IRequest request, String connectionId, String data) +143
Microsoft.AspNet.SignalR.<>c__DisplayClassc.<ProcessRequest>b__7() +96
Microsoft.AspNet.SignalR.<>c__DisplayClass3c.<FromMethod>b__3b() +41
Microsoft.AspNet.SignalR.TaskAsyncHelper.FromMethod(Func`1 func) +67
No matter the mode that we set SessionStateBehavior (as shown by David Fowler's gist) we either get this exception when sending a message to the Hub or SessionState is null when we are in the Hub's OnConnect.
So, after all that pre-amble, what we are asking is how do people update the appropriate client when working with this type of disconnected messaging in SignalR?
If you're looking to send data to clients outside of a normal request to a hub then I'd recommend having a static Concurrent Dictionary on your hub that manages your users and maps them to corresponding connection Id's.
With this approach you can send to any user at any point based on their mapped Connection Id. Therefore when sending your data to Biztalk all you need to do is send your user id (created by you) and then when the data flows back to SignalR you can lookup the ConnectionId (if one exists) for that given user id.
Lastly, you can manage your user mappings by adding users to your concurrent dictionary in OnConnected, adding only if they are not there in OnReconnected, and removing in OnDisconnected.