Hosting einer Blazor Web App in Azure Container Apps hinter Azure Front Door in einem Unterordner

Ein Beispiel

By Thimo Buchheister

Wenn man Blazor Web Apps in einer Produktionsumgebung hinter Reverse Proxies hostet, besteht eine häufige Herausforderung darin, die Anwendung korrekt in einem Unterordner betreiben zu können. In diesem Beitrag zeige ich, wie ich eine Blazor Web App (basierend auf dem Visual Studio 2022 .NET 9 Blazor-Web-App-Template oder dem Aspire-Template) in Azure Container Apps bereitstelle und Azure Front Door als Reverse Proxy davorschalte. Der knifflige Teil ist sicherzustellen, dass die App reibungslos in einem Unterordner (in diesem Fall /management/) funktioniert.

Wenn Ihre Anwendung nicht korrekt konfiguriert ist, können Probleme wie fehlerhafte Routen oder fehlende statische Dateien auftreten. In diesem Beitrag erfahren Sie, wie Sie die Konfiguration Ihrer Blazor-Anwendung anpassen, um den Unterordnerpfad hinter Azure Front Door zu berücksichtigen. Legen wir los.

Warum hinter Azure Front Door in einem Unterordner hosten?

Azure Front Door ist ein globales, skalierbares Eingangstor, das das Lastenausgleichen von Datenverkehr über Ihre Dienste hinweg erleichtert und die weltweite Bereitstellung Ihrer Anwendungen unterstützt. Indem Sie Ihre Blazor-Anwendung hinter Azure Front Door platzieren, profitieren Sie von Caching, Routing und Sicherheitsfunktionen.

Manchmal betreibt man mehrere Anwendungen (z. B. eine API, eine Standard-Website und ein Verwaltungsportal) unter derselben Domain. Anstatt dafür eigene Subdomains zu verwenden, können Sie die Blazor Web App in einem Unterordner wie https://example.com/management/ hosten. Diese Struktur ermöglicht es, eine einzige Domain zu behalten und gleichzeitig die verschiedenen Bereiche Ihrer Plattform logisch zu trennen.

Konfiguration von App.razor für das korrekte base href

Der erste Schritt, um Ihre Blazor-App aus einem Unterordner auszuliefern, ist die Anpassung des base-Tags. Wenn dies nicht korrekt gesetzt ist, können Client-seitiges Routing und statische Dateipfade fehlschlagen.

So können Sie App.razor anpassen, um den base href abhängig von der Umgebung dynamisch zu setzen:

@inject IWebHostEnvironment WebHostEnvironment
@{
    var baseHref = "/";
    if (!WebHostEnvironment.IsDevelopment())
    {
        baseHref = "/management/";
    }
}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="@baseHref" />
    <link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
    <link rel="stylesheet" href="@Assets["app.css"]" />
    <link rel="stylesheet" href="@Assets["Management.WebApp.styles.css"]" />
    <ImportMap />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet />
</head>

<body>

    <!-- Blazor routing -->
    <Routes />

    <!-- Core Blazor -->
    <script src="_framework/blazor.web.js"></script>

</body>

</html>

Was bewirkt das?

  • Entwicklung vs. Produktion: Im Entwicklungsmodus bleibt der Pfad bei /, damit lokal kein Unterordner notwendig ist. In der Produktion schalten wir auf /management/ um.
  • <base>-Tag: Dieses Tag sagt dem Browser, wie er relative URLs zusammenbauen soll. Wenn es auf /management/ zeigt, weiß der Browser, dass jeder relative Pfad mit /management/ beginnen soll.

Wenn Sie Ihre Navigationslinks mit einem führenden Slash (/) versehen, könnte der Browser bei einem Klick zurück zum Domain-Stamm anstatt in den Unterordner springen. Entfernen Sie daher den führenden Slash, sodass das Routing relativ zum aktuellen Pfad-Basisverzeichnis bleibt. Hier ein Beispiel:

<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">Management</a>
    </div>
</div>

<input type="checkbox" title="Navigation menu" class="navbar-toggler" />

<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
    <nav class="nav flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="bi bi-house-door-fill" aria-hidden="true"></span> Home
            </NavLink>
        </div>

        <div class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="bi bi-plus-square-fill" aria-hidden="true"></span> Counter
            </NavLink>
        </div>

        <div class="nav-item px-3">
            <NavLink class="nav-link" href="weather">
                <span class="bi bi-list-nested" aria-hidden="true"></span> Weather
            </NavLink>
        </div>
    </nav>
</div>

Was bewirkt das?

  • Relatives Routing: So wird sichergestellt, dass bei einem Klick auf counter in der Produktion /management/counter aufgerufen wird, anstatt https://example.com/counter (was den Unterordner ignorieren würde).

Anpassung von Program.cs für Path Base und Forwarded Headers

Wenn Sie hinter einem Reverse Proxy (Azure Front Door, Azure Container Apps oder einen anderen Proxy) betrieben werden, müssen Sie Ihre Anwendung so konfigurieren, dass sie Forwarded Headers akzeptiert und den korrekten Path Base setzt. Die Reihenfolge der Middleware ist dabei entscheidend:

if (!app.Environment.IsDevelopment())
{
    // Pfadbasis in der Produktion setzen
    app.UsePathBase(new PathString("/management/"));

    // Routing manuell festlegen, um die richtige Reihenfolge sicherzustellen
    app.UseRouting();

    // Forwarded Headers verwenden
    var forwardHeaderOptions = new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.All
    };
    forwardHeaderOptions.KnownNetworks.Clear();
    forwardHeaderOptions.KnownProxies.Clear();
    app.UseForwardedHeaders(forwardHeaderOptions);
    
    app.Use((context, next) =>
    {
        // Die folgende Zeile fügt den Unterordner (/management) zurück zur URL hinzu,
        // sodass https://localhost:5000/mypage.html zu https://localhost:5000/management/mypage.html wird
        context.Request.PathBase = new PathString("/management");
        context.Request.Scheme = "https";

        // Sicherheitsheader setzen
        context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
        context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
        context.Response.Headers.Append("X-Frame-Options", "sameorigin");
        // Weitere Content-Security-Policy-Einstellungen können hier hinzugefügt werden, z.B. für PowerPoints, PDFs, etc.
        // context.Response.Headers.Add("Content-Security-Policy", "...");

        return next();
    });
}

Wichtige Punkte

  • UsePathBase: Teilt ASP.NET Core mit, dass die Anwendung unter /management/ gehostet wird. So werden automatisch Routen und statische Dateien korrekt mit dem Präfix versehen.
  • UseRouting: Muss nach dem Setzen der Path Base, aber vor anderen Routing-abhängigen Middleware-Komponenten wie UseEndpoints aufgerufen werden.
  • UseForwardedHeaders: Ist notwendig, um hinter Proxies Client-IP, Schema und andere Header richtig auszuwerten.
  • Sicherheitsheader: An dieser Stelle ist es sinnvoll, zusätzliche Sicherheit zu implementieren, etwa mit X-Content-Type-Options, Referrer-Policy, X-Frame-Options usw.

Deployment in Azure

1. Containerisieren Ihrer Blazor-Web-App

  • Stellen Sie sicher, dass Sie eine Dockerfile haben, die Ihre Anwendung auf dem gewünschten Port (z. B. 80) bereitstellt.
  • Pushen Sie Ihr Container-Image in ein Registry (z. B. Azure Container Registry).

2. Anlegen einer Azure Container App

  • Erstellen Sie eine Container App-Umgebung in Azure.
  • Deployen Sie Ihren Container, indem Sie das zuvor erstellte Container-Image aus Ihrem Registry referenzieren.
  • Konfigurieren Sie das Ingress (HTTP) der Container App, falls sie extern erreichbar sein soll.

3. Einrichtung von Azure Front Door

  • Erstellen Sie eine Front Door-Instanz und geben Sie Ihre Container App als Backend an.
  • Konfigurieren Sie eine Routing-Regel, die Anfragen an https://example.com/management/* zu Ihrem Container weiterleitet.
  • Je nach Wunsch können Sie den Pfad /management/ in der Weiterleitung entfernen oder beibehalten (bzw. weiterreichen), je nachdem, wie Ihr Container die Pfade erwartet.

4. Überprüfung der Konfiguration

  • Rufen Sie Ihre Produktions-URL auf, z. B. https://example.com/management/.
  • Stellen Sie sicher, dass Ihre Blazor-App ohne 404- oder SSL-Fehler geladen wird.
  • Testen Sie Routen wie https://example.com/management/counter, um zu prüfen, ob die Navigation korrekt funktioniert.

Fazit

Eine Blazor Web App in einem Unterordner hinter Azure Front Door und Azure Container Apps zu hosten, erfordert etwas Feintuning bei Routing und Path Base-Einstellungen. Durch die Anpassung des base href in App.razor, die Aktualisierung der Navigationslinks in NavMenu.razor und die richtige Reihenfolge der Middleware in Program.cs vermeiden Sie typische Probleme bei der Nutzung eines Unterordners.

Dieses Setup ermöglicht es Ihnen, mehrere Anwendungen hinter einer einzigen Domain zu betreiben und gleichzeitig die globalen Performance-Vorteile von Azure Front Door zu nutzen. Wenn alles korrekt konfiguriert ist, verarbeitet Ihre Blazor Web App die Routen unter /management/ korrekt und Sie profitieren von einem reibungslosen Deployment.

Teilen Sie gern Ihre Erfahrungen oder weitere Tipps in den Kommentaren, falls Sie ähnliche Konfigurationsherausforderungen hatten. Viel Spaß beim Codieren!

Share: X (Twitter) Facebook LinkedIn