OrchardCMS/OrchardCore · error · Error

Detected a connection attempt to an ASP.NET SignalR Server…

Error message

Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.

What it means

During negotiation the client checks negotiateResponse.ProtocolVersion; a presence of this field indicates the endpoint is a legacy ASP.NET (non-Core) SignalR server, whose protocol is incompatible with the @microsoft/signalr client. The client throws this descriptive error instead of failing later with cryptic protocol errors.

Solutions

  1. Migrate the server to ASP.NET Core SignalR (Microsoft.AspNetCore.SignalR) and use its hub endpoint.
  2. If the server must stay on ASP.NET SignalR 2.x, use the matching legacy client (jquery.signalR / Microsoft.AspNet.SignalR JS client).
  3. Verify the negotiate response: a Core server returns connectionId/availableTransports without ProtocolVersion.
  4. Check for reverse-proxy rewrites routing /signalr to a legacy host unintentionally.

Example fix

// before (legacy server + Core client)
const connection = new signalR.HubConnectionBuilder().withUrl("http://legacy-server/signalr").build();
// after (upgrade server to ASP.NET Core)
// Server: app.MapHub<MyHub>("/hub") with Microsoft.AspNetCore.SignalR
const connection = new signalR.HubConnectionBuilder().withUrl("https://core-app/hub").build();
Defensive patterns

Strategy: validation

Validate before calling

// client-side preflight
async function isAspNetCoreHub(url) {
  const res = await fetch(`${url}/negotiate`, { method: "POST" });
  const body = await res.json();
  return body.ProtocolVersion === undefined; // Core servers omit ProtocolVersion
}

Type guard

function isCoreNegotiateResponse(body) {
  return body && body.connectionId !== undefined && body.ProtocolVersion === undefined;
}

Try / catch

try {
  await connection.start();
} catch (err) {
  if (err.message.includes("ASP.NET SignalR Server")) {
    console.error("Target is legacy ASP.NET SignalR — use matching client/server versions");
  } else { throw err; }
}

Prevention

When it happens

Trigger: Pointing the JS client at an ASP.NET (Framework) SignalR endpoint /signalr/negotiate that returns a ProtocolVersion in its negotiate payload; mixing Microsoft.AspNet.SignalR server code with the @microsoft/signalr (Core) client.

Common situations: Migrating an old ASP.NET app to .NET Core and keeping the legacy hub URL; proxying '/signalr' to a legacy backend; upgrading the client package (@aspnet/signalr -> @microsoft/signalr) while the server is still ASP.NET SignalR 2.x.

Related errors


AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/7e4e056612d1ec15. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore.Modules/OrchardCore.SignalR/wwwroot/Scripts/signalr.js:2850

                }
                else {
                    throw new Error("Negotiation can only be skipped when using the WebSocket transport directly.");
                }
            }
            else {
                let negotiateResponse = null;
                let redirects = 0;
                do {
                    negotiateResponse = await this._getNegotiationResponse(url);
                    // the user tries to stop the connection when it is being started
                    if (this._connectionState === "Disconnecting" /* ConnectionState.Disconnecting */ || this._connectionState === "Disconnected" /* ConnectionState.Disconnected */) {
                        throw new AbortError("The connection was stopped during negotiation.");
                    }
                    if (negotiateResponse.error) {
                        throw new Error(negotiateResponse.error);
                    }
                    if (negotiateResponse.ProtocolVersion) {
                        throw new Error("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.");
                    }
                    if (negotiateResponse.url) {
                        url = negotiateResponse.url;
                    }
                    if (negotiateResponse.accessToken) {
                        // Replace the current access token factory with one that uses
                        // the returned access token
                        const accessToken = negotiateResponse.accessToken;
                        this._accessTokenFactory = () => accessToken;
                        // set the factory to undefined so the AccessTokenHttpClient won't retry with the same token, since we know it won't change until a connection restart
                        this._httpClient._accessToken = accessToken;
                        this._httpClient._accessTokenFactory = undefined;
                    }
                    redirects++;
                } while (negotiateResponse.url && redirects < MAX_REDIRECTS);
                if (redirects === MAX_REDIRECTS && negotiateResponse.url) {
                    throw new Error("Negotiate redirection limit exceeded.");
                }

View on GitHub (pinned to 4306c0717f)