I have a Blazor web application. It running in a docker compose with 2 databases. It uses a SignalR Hub references as TacticsHub. It was working fine with Visual Studio and Visual Studio code. I could debug and work with the sites. Now I tried to Deploy the website to a Server and Domain. For this I am using Nginx on a Linux server. When I was visiting the page that used SignalR, I get this exception:
HttpRequestException: Response status code does not indicate success: 405 (Method Not Allowed).
System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.NegotiateAsync(Uri url, HttpClient httpClient, ILogger logger, CancellationToken cancellationToken)
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.GetNegotiationResponseAsync(Uri uri, CancellationToken cancellationToken)
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.SelectAndStartTransport(TransferFormat transferFormat, CancellationToken cancellationToken)
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsyncCore(TransferFormat transferFormat, CancellationToken cancellationToken)
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsync(TransferFormat transferFormat, CancellationToken cancellationToken)
Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken)
Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken)
System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable+ConfiguredValueTaskAwaiter.GetResult()
Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncCore(CancellationToken cancellationToken)
Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncInner(CancellationToken cancellationToken)
Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsync(CancellationToken cancellationToken)
Wildblood.Tactics.Services.HubConnectionService.Register(Func<HubConnection, IDisposable> method) in HubConnectionService.cs
Wildblood.Tactics.Services.TacticExplorerService..ctor(IMongoDatabase mongoDatabase, IHubConnectionService hubConnectionService, IUserService userService) in TacticExplorerService.cs
System.RuntimeMethodHandle.InvokeMethod(object target, Void** arguments, Signature sig, bool isConstructor)
System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(object obj, Span copyOfArgs, BindingFlags invokeAttr)
System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor<TArgument, TResult>.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor<TArgument, TResult>.VisitCallSite(ServiceCallSite callSite, TArgument argument)
Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine+<>c__DisplayClass2_0.b__0(ServiceProviderEngineScope scope)
Microsoft.AspNetCore.Components.ComponentFactory+<>c__DisplayClass9_0.g__Initialize|1(IServiceProvider serviceProvider, IComponent component)
Microsoft.AspNetCore.Components.ComponentFactory.InstantiateComponent(IServiceProvider serviceProvider, Type componentType, IComponentRenderMode callerSpecifiedRenderMode, Nullable parentComponentId) Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateChildComponentOnFrame(RenderTreeFrame[] frames, int frameIndex, int parentComponentId) Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(ref DiffContext diffContext, int frameIndex) Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(ref DiffContext diffContext, int newFrameIndex) Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(ref DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl) Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, out Exception renderFragmentException) Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue() Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue() Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged() Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync() Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync() Microsoft.AspNetCore.Components.Rendering.ComponentState.SupplyCombinedParameters(ParameterView directAndCascadingParameters) Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters) Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(int componentId, ParameterView initialParameters) Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.BeginRenderingComponent(IComponent component, ParameterView initialParameters) Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.RenderEndpointComponent(HttpContext httpContext, Type rootComponentType, ParameterView parameters, bool waitForQuiescence) System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult() Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore(HttpContext context) Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore(HttpContext context) Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext+<>c+<b__10_0>d.MoveNext() Microsoft.AspNetCore.Builder.ServerRazorComponentsEndpointConventionBuilderExtensions+<>c__DisplayClass1_1+<b__1>d.MoveNext() Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
I can't access this page. Everything else works fine (database / auth).
This is my Program.cs:
namespace BlazorApp.Tactics;
using Microsoft.AspNetCore.HttpOverrides;
using System.Net;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using MudBlazor.Services;
using BlazorApp.Tactics.Components;
using BlazorApp.Tactics.Components.Account;
using BlazorApp.Tactics.Data;
using BlazorApp.Tactics.Services;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();
// MongoDB stuff
var mongoConnectionString = Environment.GetEnvironmentVariable("MONGO_CONNECTION_STRING");
builder.Services.AddSingleton<IMongoClient, MongoClient>(sp => new MongoClient(mongoConnectionString));
builder.Services.Configure<MongoDbSettings>(options =>
{
options.ConnectionString = mongoConnectionString;
options.DatabaseName = "MongoDB"; // Setzen Sie hier den Namen Ihrer Datenbank
});
builder.Services.AddSingleton(sp =>
{
var settings = sp.GetRequiredService<IOptions<MongoDbSettings>>().Value;
var client = sp.GetRequiredService<IMongoClient>();
return client.GetDatabase(settings.DatabaseName);
});
builder.Services.AddSingleton<MongoDbInitializer>();
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.AddScoped<AuthenticationStateProvider, PersistingRevalidatingAuthenticationStateProvider>();
builder.Services.AddMudServices();
builder.Services.AddScoped<IHubConnectionService, HubConnectionService>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<ITacticToolService, TacticToolService>();
builder.Services.AddScoped<ITacticExplorerService, TacticExplorerService>();
builder.Services.AddScoped<ITacticMemberListService, TacticMemberListService>();
builder.Services.AddScoped<ITacticMapSelectorService, TacticMapSelectorService>();
builder.Services.AddScoped<ITacticCanvasService, TacticCanvasService>();
builder.Services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = builder.Configuration["Google:ClientId"] ?? throw new InvalidOperationException("Google ClientId not found.");
googleOptions.ClientSecret = builder.Configuration["Google:ClientSecret"] ?? throw new InvalidOperationException("Google ClientSecret not found.");
});
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies();
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
builder.Services.AddTransient<IEmailSender<ApplicationUser>, EmailSender>();
var app = builder.Build();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
using (var scope = app.Services.CreateScope())
{
var mongoDbInitializer = scope.ServiceProvider.GetRequiredService<MongoDbInitializer>();
mongoDbInitializer.Initialize();
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.MapStaticAssets();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(Client._Imports).Assembly);
app.MapHub<TacticsHub>("/tacticsHub");
// Add additional endpoints required by the Identity /Account Razor components.
app.MapAdditionalIdentityEndpoints();
app.Run();
}
}
This is my HubConnectionService creating the HubConnection:
namespace BlazorApp.Tactics.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using Wildblood.Tactics.Models.Messages;
public class HubConnectionService : IHubConnectionService, IAsyncDisposable
{
private HubConnection hubConnection;
public HubConnectionService(NavigationManager navigationManager)
{
hubConnection = new HubConnectionBuilder()
.WithUrl(navigationManager.ToAbsoluteUri("/tacticsHub"))
.WithAutomaticReconnect()
.Build();
}
public IDisposable Register(Func<HubConnection, IDisposable> method)
{
var connection = method(hubConnection);
if (hubConnection.State == HubConnectionState.Disconnected)
{
hubConnection.StartAsync().GetAwaiter().GetResult();
}
return connection;
}
public async Task UpdateTactic(
string tacticId, string folderId, string slideId, UpdateTacticMessage message)
{
await hubConnection.SendAsync("UpdateTactic", tacticId, folderId, slideId, message);
}
public async Task UpdateEntities(
string tacticId, string folderId, string slideId, UpdateEntitiesMessage message)
{
await hubConnection.SendAsync("UpdateEntities", tacticId, folderId, slideId, message);
}
public async ValueTask DisposeAsync()
{
if (hubConnection != null)
{
await hubConnection.DisposeAsync();
}
}
}
This is the Hub itself:
namespace BlazorApp.Tactics
{
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
public class TacticsHub : Hub
{
public async Task UpdateTactic(string tacticId, string folderId, string slideId, object message)
{
Console.WriteLine($"Received update for tactic {tacticId}: {message}");
await Clients.Others.SendAsync("UpdateTactic", tacticId, folderId, slideId, message);
}
public async Task UpdateEntities(string tacticId, string folderId, string slideId, object message)
{
Console.WriteLine(
$"Received update for entities {tacticId} {folderId} {slideId}: {message}");
await Clients.Others.SendAsync("UpdateEntities", tacticId, folderId, slideId, message);
}
}
}
This is my Nginx site in the sites-enabled directory:
server {
listen 80;
server_name example.com
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# HSTS & Sicherheit
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "keep-alive";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# für Blazor Server & SignalR wichtig
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
location /tacticsHub/ {
proxy_pass http://localhost:8080/tacticsHub/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
This is my docker-compose.yml:
services:
BlazorApp:
image: ${DOCKER_REGISTRY-}BlazorImage
build:
context: .
dockerfile: dockerfile reference
networks:
- site-network
environment:
- MONGO_CONNECTION_STRING=mongodb://username:password@mongodb:27017
- ASPNETCORE_URLS=http://+:8080;
depends_on:
- mongodb
mssql:
image: mcr.microsoft.com/mssql/server:2019-latest
container_name: mssql_container
ports:
- "1433:1433"
environment:
SA_PASSWORD: "password"
ACCEPT_EULA: "Y"
volumes:
- mssql_data:/var/opt/mssql
networks:
- site-network
mongodb:
image: mongo:6.0
container_name: mongodb
hostname: mongodb
restart: always
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: username
MONGO_INITDB_ROOT_PASSWORD: password
volumes:
- mongodb_data:/data/db
networks:
- site-network
volumes:
mssql_data:
mongodb_data:
networks:
site-network:
I cant think of a problem on why it does not work.
I expect my website to function the same way it does while developing. Its dockerized for this reason.
What actually happened: Lots of different behaiviours because of the server hosting.
What am I doing wrong and how can I configure the server or code that it works on my server.