How to setup Blazor Server with Blazor WASM with gRPC for virtual IIS application?

Matěj Rada 40 Reputation points
2024-05-10T13:41:33.87+00:00

Hi,
I have simple Blazor Server without static files. It is only falling into Blazor WASM with static files using FallBackTo. There is gRPC an its Controller mapped on the main endpoint. Everythig is working on https://localhost. Once I try to deploy it on any IIS virtual application, I need to adjust base tag in index.html to this new route. From that, everything still working - even fallback. However Controller and gRPC communication is failing. What has to be done to support any IIS installation? (Seems to me, that I it somewhat expensive - to correct every endpoint - so I assume, that I am doing it wrong and there is simple setup with mapping or something like that.) URL is set in launch.json, so one would assume, that it can be corrected by this configuration.

Internet Information Services
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,227 questions
Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,410 questions
0 comments No comments
{count} votes

Accepted answer
  1. Rada Matěj 180 Reputation points
    2024-05-15T07:34:37.3566667+00:00

    Now I understand everything. So, gRPC does not support IIS subpaths and we need to configure (HTTP2) handler:

    public class GrpcSubdirectoryHandler : DelegatingHandler
        {
            private readonly Uri basePath;
    
            public GrpcSubdirectoryHandler(HttpMessageHandler innerHandler, Uri basePath)
                : base(innerHandler)
            {
                this.basePath = basePath;
            }
    
            protected override Task<HttpResponseMessage> SendAsync(
                HttpRequestMessage request, CancellationToken cancellationToken)
            {
                var old = request.RequestUri;
    
                if (old == null)
                {
                    return base.SendAsync(request, cancellationToken);
                }
    
                string url = $"{old.Scheme}://{old.Host}:{old.Port}{basePath.PathAndQuery}{old.AbsolutePath.TrimStart('/')}";
                request.RequestUri = new Uri(url, UriKind.Absolute);
    
                return base.SendAsync(request, cancellationToken);
            }
        }
    

    Now channel:

    var singleHttpHandler = new GrpcSubdirectoryHandler(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()), baseAddress);
    
    builder.Services.AddSingleton(s =>
    {
        string baseUri = s.GetRequiredService<NavigationManager>().BaseUri;
        var channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions { HttpHandler = singleHttpHandler });
        var client = new PersonContract.PersonContractClient(channel);
        return client;
    });
    

    Where GrpcWebHandler is absolutely important for HTTP2 usage. As for the rest of IIS subfolder routing, we can still enjoy full client-side WASM with all routing falling back, just that I need IIS subfolder input, it has to be solved on server-side CSHTML page. Since base tag is solved there, fallback just needs to be into that page:

    app.MapFallbackToPage("/MainWASM");
    
    __
    
    @page
    @model ANeT.WebGuard.Pages.MainWASMModel
     
    <!DOCTYPE html>
    <html lang="en">
     
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        <title>WebGuard</title>
        <base href="@(HttpContext.Request.PathBase + (HttpContext.Request.PathBase.Value?.EndsWith('/') == true ? string.Empty : "/"))" />
    

    So, I will map everything to specific virtual PATH and then just use URL rewritting to fit any IIS installation my customers do. This is working and tested solution.

    1 person found this answer helpful.
    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Bruce (SqlWork.com) 57,641 Reputation points
    2024-05-10T16:41:23.12+00:00

    when you set the base href="..", the browser uses this the base path for relative url even with fetch. but with the gRPC, you initial with the base path. you will need to add the virtual path to the gRPC setup.

    	var baseUri = services.GetRequiredService<NavigationManager>().BaseUri; // includes href
    	var channel = GrpcChannel.ForAddress(baseUri, new GrpcChannelOptions { HttpClient = httpClient }); 
    }
    

    note: the launch url is just used by IIS, and the debugger to start the browser. if using core directly without IIS, it just uses the protocol and port.