1

I have a working Blazor Server application with a Chat component running locally correctly, using signal R hub connection.

When deploying to Azure, I receive error Invalid negotiation response received.---> System.Text.Json.JsonReaderException: '0x1F' is an invalid start of a value.

Here is a similar linked ticket, but it was never answered: Blazor Server SignalR hub fails on StartAsync due to Azure ADB2C

Goal: Create a private chat feature for Blazor server application. I am unable to use singleton service because all users cant share the same instance of the service.

I have yet to find a sample where there is a messaging feature between users/usergroups in blazor server.

Since I am using Azure B2C auth with OIDC default authentication scheme, I have to manually pass the cookies and the headers.

Like I mentioned, this sample is working perfectly on localhost, when I open up two browsers (one in incognito), i am able to send messages between logged in users. When I publish to Azure App Service, however, I am unable to connect to the hub.

Code:

private HubConnection _hubConnection;
private User user;
ObservableCollection<Message> messages = new ObservableCollection<Message>();
SfTextBox MessageBox;
SfTextBox SendTo;

public class Message
{
    public string Id { get; set; }
    public string UserName { get; set; }
    public string MessageText { get; set; }
    public string Chat { get; set; }
}

protected override async Task OnInitializedAsync()
{
    var state = await authenticationStateProvider.GetAuthenticationStateAsync();
    user = state.ToUser();
    _hubConnection = new HubConnectionBuilder()
        .WithUrl(navigationManager.ToAbsoluteUri("/chatHub"), options =>
        {
            options.UseDefaultCredentials = true;
            var httpContext = HttpContextAccessor.HttpContext;
            var cookieCount = httpContext.Request.Cookies.Count();
            var cookieContainer = new System.Net.CookieContainer(cookieCount);
            foreach (var cookie in httpContext.Request.Cookies)
                cookieContainer.Add(new System.Net.Cookie(
                cookie.Key,
                WebUtility.UrlEncode(cookie.Value),
                path: httpContext.Request.Path,
                domain: httpContext.Request.Host.Host));
            options.Cookies = cookieContainer;

            NameValueHeaderValue headers = null;
            foreach (var header in httpContext.Request.Headers)
            {
                if (header.Key != ":method")
                    options.Headers.Add(header.Key, header.Value);
            }
            options.HttpMessageHandlerFactory = (input) =>
            {
                var clientHandler = new HttpClientHandler
                    {
                        PreAuthenticate = true,
                        CookieContainer = cookieContainer,
                        UseCookies = true,
                        UseDefaultCredentials = true,
                    };

                return clientHandler;
            };
        })
 .WithAutomaticReconnect()
 .Build();

    _hubConnection.On<string, string, string, string>("ReceiveMessage", (userName, from, to, message) =>
    {
        if (user.Email == to || user.Id == from)
        {
            messages.Add(new Message()
                {
                    Id = Guid.NewGuid().ToString(),
                    MessageText = message,
                    Chat = user.Id == from ? "sender" : "receive",
                    UserName = user.Id == from ? "You" : userName
                });
            StateHasChanged();
        }
    });

    await _hubConnection.StartAsync();
}

public async void Send()
{
    if (MessageBox.Value != "" && SendTo.Value != "")
    {
        var userName = user.DisplayName;
        var to = SendTo.Value;
        var message = MessageBox.Value;
        var from = user.Id;
        _hubConnection.SendAsync("SendMessage", userName, from, to, message);
    }
}

public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;

}

Garrett London
  • 27
  • 1
  • 10

2 Answers2

0

The issue is with authentication on azure platform. As you used manually giving cookies it worked fine on local, and when it comes to Azure platform, we need to provide authentication.

Follow the below link for support.

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-invalid-json?pivots=dotnet-6-0

https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-tutorial-build-blazor-server-chat-app

Sairam Tadepalli
  • 1,563
  • 1
  • 3
  • 11
  • Hi Sairam, I appreciate the advice, but i'm unable to come to a resolution with the links you provided. Could you explain a bit further? – Garrett London Mar 31 '22 at 15:21
  • Any progress? I have the same problem and cant find a working solution, works fine with Azure AD activated local/debugging, but not when deployed. – W Tech May 17 '22 at 08:02
  • Here is the link where we can get the procedure of authentication and successful installation of app on azure platform https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-blazor-server Also check the below link for further support. https://codewithmukesh.com/blog/realtime-chat-application-with-blazor/ – Sairam Tadepalli May 18 '22 at 00:58
0

HttpContextAccessor.HttpContext is where the problem is. It works on your local (IIS Express), but it does not work when the application is deployed to Azure (or IIS).

I posted a solution here: Solution: Custom SignalR Endpoints (Hubs) in a Blazor Server Application using Azure B2C When Deployed to Azure

Basically:

Grab all the Cookies at this point and put them in a collection of Cookies so they can be passed to the app.razor control.

<body>
@{
    var CookieCollection = HttpContext.Request.Cookies;
    Dictionary<string, string> Cookies = new Dictionary<string, string>();
    foreach (var cookie in CookieCollection)
    {
        Cookies.Add(cookie.Key, cookie.Value);
    }        
}
<component type="typeof(App)" render-mode="ServerPrerendered" param-Cookies="Cookies" />

Set them as a CascadingValue that will be passed to all other Blazor controls. Retrieve the collection of Cookies as a CascadingParameter and use those Cookies to manually set the Header and Cookies when creating the SignalR client.

             hubConnection = new HubConnectionBuilder()
             .WithUrl(Navigation.ToAbsoluteUri("/chathub"), options =>
             {
                 options.UseDefaultCredentials = true;
                 var cookieCount = Cookies.Count();
                 var cookieContainer = new CookieContainer(cookieCount);
                 foreach (var cookie in Cookies)
                     cookieContainer.Add(new Cookie(
                         cookie.Key,
                         WebUtility.UrlEncode(cookie.Value),
                         path: "/",
                         domain: Navigation.ToAbsoluteUri("/").Host));
                 options.Cookies = cookieContainer;

                 foreach (var header in Cookies)
                     options.Headers.Add(header.Key, header.Value);

                 options.HttpMessageHandlerFactory = (input) =>
                 {
                     var clientHandler = new HttpClientHandler
                         {
                             PreAuthenticate = true,
                             CookieContainer = cookieContainer,
                             UseCookies = true,
                             UseDefaultCredentials = true,
                         };
                     return clientHandler;
                 };
             })
             .WithAutomaticReconnect()
             .Build();

            hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
            {
                var encodedMsg = $"{user}: {message}";
                messages.Add(encodedMsg);
                InvokeAsync(StateHasChanged);
            });

            await hubConnection.StartAsync();
Michael Washington
  • 2,744
  • 20
  • 29