3

I created default blazor server side app. Then added Microsoft.AspNetCore.SignalR.Client and ChatHub class. Then edited startup.cs file (add services.AddSignalR() and endpoints.MapHub<ChatHub>("/chatHub")) and index.razor page. Then run by IIS express. it is okey.

Then added docker support and run Docker host. it is not working. Because only don't work hub connection StartAsync method. How to run it? Help me? Thank you very much guys.

Error is:

An unhandled exception occurred while processing the request. SocketException: Cannot assign requested address System.Net.Http.ConnectHelper.ConnectAsync(string host, int port, CancellationToken cancellationToken)

HttpRequestException: Cannot assign requested address System.Net.Http.ConnectHelper.ConnectAsync(string host, int port, CancellationToken cancellationToken)

index.razor code:

@code {
    private HubConnection _hubConnection;

    protected override async Task OnInitializedAsync()
    {
        _hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/chatHub"))
            .Build();

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

        await _hubConnection.StartAsync(); // **DON'T WORK IN DOCKER HOST.**
    }
}

Docker file:

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["BlazorApp1/BlazorApp1.csproj", "BlazorApp1/"]
RUN dotnet restore "BlazorApp1/BlazorApp1.csproj"
COPY . .
WORKDIR "/src/BlazorApp1"
RUN dotnet build "BlazorApp1.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "BlazorApp1.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "BlazorApp1.dll"]
itminus
  • 23,772
  • 2
  • 53
  • 88
batbayar
  • 83
  • 8

2 Answers2

6

My best guess is your hub client is trying to connect to "the-public-url-out-of-docker/chatHub":

    _hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("/chatHub"))
        .Build();

NavigationManager.ToAbsoluteUri(...) will convert /chatHub to a public url which are exposed to the end user. For example, if you're using reverse proxy, it might be the domain name.

Note there're url at three different levels:

  • the domain name that are exposed to public
  • the host ip & port
  • the container ip & port
  +----------------------------------+
  | HOST  (5000)                     |
  |   +                              |
  |   |Port Mapping---------------+  |
  |   >-->-->|Container (80)      |  |
  |          |                    |  |
  |          +--------------------+  |
  +-----^----------------------------+
        |  reverse proxy
+-------+----------------------------+
| nginx                              |
|  https://www.myexample.com/chatHub
|                                    |
+-------^----------------------------+
        |
        |
        |
        |
+-------+-----------+
|                   |
|   Browser         | (Brazor sees only the public url via NavgiationManager  )
|                   |
+-------------------+

However, when running in docker, the host's network is not accessible from the container's network all the time.

If that's case, there're several approaches that should work:

  1. Avoid using the public url like .WithUrl(NavigationManager.ToAbsoluteUri("/chatHub")). Hard-code it to the container ip&port. For example, if you container listens on 80, it should be http://localhost/chatHub.
  2. Configure a network for docker, or add a --network for when running docker. For more details, see this thread
itminus
  • 23,772
  • 2
  • 53
  • 88
  • Thank you very much itminus. It is working my example. I will try my real project. You gave me a great opportunity and i know something new. Thank you again. – batbayar Feb 26 '20 at 14:31
  • I just updated my real project. And it is working. But i can't take token of http client middleware token. I added services.AddSignalR() in startup.cs file. Then can't take token. because http context is null. Then I removed services.AddSignalR() from startup.cs file. it is normal working. Then i can take token from http client. What do you think about this? Thank you. – batbayar Feb 27 '20 at 01:44
  • @batbayar SingalR uses WebSocket, it's impossible to custom the header. As a walkaround, you could send the token via querystring, for example, if you're using JwtBearer Token, custom an event handler to receive the token. By the way, if my above solution addresses the original issue, could you please accept it as the answer? – itminus Feb 27 '20 at 02:04
  • excuse me. My English is not good. it is question. how to solve it? – batbayar Feb 27 '20 at 02:15
  • @batbayar " it is question. how to solve it": did you mean how to solve the token issue or how to accept my reply as the answer? – itminus Feb 27 '20 at 02:18
  • @batbayar If you're using JwtBearer Token, see [official docs](https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.1#bearer-token-authentication): add an events handler to retrieve the token from querystring: `options.Events = new JwtBearerEvents { OnMessageReceived =...}` – itminus Feb 27 '20 at 02:25
  • I using OpenIdConnect and HttpClientAuthorizationDelegatingHandler class. – batbayar Feb 27 '20 at 02:34
  • @batbayar I thought you were trying to get JWT token when using SingalR. Could you please show us the related codes (or create a new question on StackOverflow)? – itminus Feb 27 '20 at 02:37
  • i just create new post. it is https://stackoverflow.com/questions/60425588/how-to-take-token-from-http-client-with-signalr-in-blazor-server-app – batbayar Feb 27 '20 at 02:51
  • love your drawing. Solved with approach#1 BTW. Thanks. – Joe Lau May 09 '21 at 16:53
0

I had the same issue wherein running through IIS the application worked as expected, but when running through docker SignalR failed to connect to the hub. I fixed this issue by defining my own navigation manager that inherits from NavigationManager and overrides the EnsureInitialized() method to set the base URI.

public class CustomNavigationManager : NavigationManager
{
    protected override void EnsureInitialized()
    {
        var baseUri = Environment.GetEnvironmentVariable("blazor-app-url");
        Initialize(baseUri, baseUri);
    }
}

It also requires adding a container name, and an environment variable to my docker-compose.override.yml file to be used in the CustomNavigationManager.

version: '3.4'

networks:
  mynetwork:
    external: true      

services:
  my-blazor-app:
    networks: 
      - mynetwork
    container_name: blazor-app    
    environment:
      blazor_app_url: http://blazor-app:8080/
    ports:
      - "49101:8080"

CustomNavigationManager can then be registered as a Singleton in startup, injected into relevant components (replacing NavigationManager), and continue to be used as NavigationManager was before, e.g.:

public class WeatherForecastService : IWeatherForecastService
{
    private readonly HttpClient _httpClient;
    private readonly CustomNavigationManager _navigationManager;

    public WeatherForecastService(HttpClient httpClient, CustomNavigationManager navigationManager)
    {
        _httpClient = httpClient;
        _navigationManager = navigationManager;
    }

    public async Task<IEnumerable<WeatherForecast>?> GetWeatherForecast(DateTime startDate)
    {
        var uri = _navigationManager.ToAbsoluteUri($"/weatherforecast/get?startDate={startDate:s}");
        return await _httpClient.GetFromJsonAsync<WeatherForecast[]>(uri);
    }
}

For the application to continue to work when running through IIS express, the environment variable will also need to be defined in launchSettings.json.

"IIS Express": {
  "commandName": "IISExpress",
  "launchBrowser": true,
  "environmentVariables": {
    "blazor_app_url": "http://localhost:5282/"
  }
}
TkrZ
  • 33
  • 1
  • 2
  • 5