dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.41k stars 10k forks source link

Unsure how to inject HttpClient for InteractiveAuto render mode (RC2) #51468

Open SpaceShot opened 1 year ago

SpaceShot commented 1 year ago

I'm not sure if this is a problem in RC2 or if I'm just too new to InteractiveAuto render mode.

I have a component in the client project which calls back to the server for its api calls since the actual call to the third party api needs to be on the server (auth tokens, secrets, and the like).

Per the documentation in Call a web API in Blazor on the WebAssembly pivot, it says I can register an HttpClient back to the base address with:

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

Then I wrote the component to make the call back to localhost... then the server project happens to have a minimal api route to make the "real" call.

This has worked fine as long as I don't put the component on the home page. What I observe happening is that if I place the component on any "secondary page" and let the home page load, the wasm assembly must be streamed down and a breakpoint on the above line fires.

If I put it on the home page, that breakpoint doesn't fire, but the component is rendered. In OnInitializeAsync() it tries to use the HttpClient to call home but BaseAddress wasn't set, so it fails.

Should components intended to be used with InteractiveAuto look to a different part of the lifecycle?

I think another option was to also register an HttpClient in the server Program.cs, but I can't figure out how to do this so that it is picked up in the initial render (perhaps it is using Blazor Server for that or prerendering... it's auto after all).

I'm not sure this is a bug or I'm ahead of the curve on the documentation for Auto modes. I can provide a sample if this behavior seems like a potential bug.

Note: All components and pages are in the Client project. I am experimenting with the experience for global InteractiveAuto. I only moved Error.razor to server side per known RC2 issue.

SpaceShot commented 1 year ago

I think this may be what I was looking for... will try out and update the issue: Register common services

CrahunGit commented 1 year ago

You can use an abstraction over httpclient o even a mediator that can use its dependencies depending on the running mode.

mahald commented 1 year ago

To utilize AutoMode, you could inject an interface and implement it using the HTTPClient for WASM and EntityFramework (or your preferred data retrieval method or an external API) for the server.

Example:

namespace Application.Common.Interfaces;
public interface IDataLoaderService<T>
{
    Task<HashSet<T>> LoadData();
}
using System.Net.Http.Json;
using Application.Common.Interfaces;

namespace Application.Features.YourTypeTable;

public class HttpYourTypeDataLoaderService(HttpClient _httpClient) : IDataLoaderService<YourType>
{
    public async Task<HashSet<YourType>> LoadData()
    {
        var items = await _httpClient.GetFromJsonAsync<HashSet<YourType>>("yourURL");
        return items ?? [];
    }
}
using Application.Common.Interfaces;
using Application.Db;
using Microsoft.EntityFrameworkCore;

namespace Application.Features.YourTypeTable;

public class DbYourTypeDataLoaderService(ApplicationDbContext _dbContext) : IDataLoaderService<YourType>
{
    public async Task<HashSet<YourType>> LoadData()
    {
        return new HashSet<YourType>(await _dbContext.YourTable.ToListAsync()) ?? [];
    }
}

For server-side registration:

builder.Services.AddDbContext<ApplicationDbContext>();
builder.Services.AddScoped<IDataLoaderService<YourType>, DbYourTypeDataLoaderService>();

For WASM:

builder.Services.AddScoped<IDataLoaderService<YourType>, HttpYourTypeDataLoaderService>();

In the razor page:

    [Inject]
    private IDataLoaderService<YourType> _dataLoaderSevice { get; set; } = null!;

     private HashSet<YourType>? _items;

     private async Task LoadData() => _items = await _dataLoaderService.LoadData();

    protected override async Task OnInitializedAsync()
    {
        await LoadData();
    }
SpaceShot commented 1 year ago

The biggest problem I have is that to obtain the baseAddress needed for the callback I need IWebAssemblyHostEnvironment, which is not available server-side. The client side is not being instantiated "in time" because in Auto mode, Blazor is streaming down WASM while trying to render the page without. So the control fails because it has no way to call back into the host.

Register Common Services isn't working for me because I can't pass the WebAssemblyHostBuilder down into a method that would be callable by both server side and client side to register the common service (an HttpClient service).

I feel like most of the documentation (understandably) is still derived from the world where you have chosen Blazor WebAssembly or Blazor Server and here at this intersection of InteractiveAuto, there are gaps in the docs that I can't figure out how to fill in at the moment.

SpaceShot commented 1 year ago

To be more clear, this is the service registration I am looking to register in a common way that will work for server-side and client-side and therefore be available for use with InteractiveAuto render mode:

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

SpaceShot commented 1 year ago

Added sample on GitHub: https://github.com/SpaceShot/InteractiveAutoSample

MarcosMusa commented 1 year ago

I'm having the same problem. If you register httpClient in both projects you will be successful, but I don't know if it is the best practice.

Exemple:

To Wasm use: builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

To Server use: builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("http://localhost:port") });

mahald commented 1 year ago

one way to do it: https://github.com/SpaceShot/InteractiveAutoSample/pull/1

MarcosMusa commented 1 year ago

one way to do it: SpaceShot/InteractiveAutoSample#1

Why is the API called twice when reloading the page? Put a console log in "WebScraperServiceServer" to reproduce.

mahald commented 1 year ago

because of prerendering that happens first on the server then wasm is loaded and it's called again.

mahald commented 1 year ago

if you want to change this you either inject a service to detect prerendering or use the OnAfterRenderAsync hook instead of the OnInitlializedAsync Hook. Or disable prerendering.

 protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            Text = await _webScraper.Get();
            StateHasChanged();
        }
    }
mahald commented 1 year ago

PS: The Template you are using is the "Global WASM" Template so everything is running in WASM and you can't use InteractiveServer. What you see is the prerendering that takes place on the Server.

SpaceShot commented 1 year ago

Thanks for the pull request @mahald. I'll check it out but I think I see what you are saying. You've created an implementation where if you're on Server Interactivity then it just calls the external service and if you're on WASM then it calls back into the local endpoint. I did use the Global InteractiveAuto template. Mostly I am playing with InteractiveAuto mode to try and understand the nuances like this.

bcheung4589 commented 1 year ago

Why not use the IHttpClientFactory with a named HttpClient to your third party api?

SpaceShot commented 1 year ago

Hi @bcheung4589 I have been experimenting with InteractiveAuto, although really it can all be applied to InteractiveWebAssembly as well. In this mode, I presented a sample app with a sample call to a third party API. Let's imagine that the API to be called isn't open to the public and I need to protect the keys or credentials or access to that API. I choose not to simply call it from the component, which resides in the Client project of the Blazor solution, because then it would be available to prying eyes of anyone who used the application and had the client DLLs streamed down to their browser.

Therefore, my goal was to implement a sort of backend-for-frontend pattern, where the call was safely protected on the server. In this case, I was calling back into a minimal API in the web server itself. I was then looking for how to make that callback from client and server for both scenarios in InteractiveAuto. Thanks to @mahald, I learned I was thinking about the solution too naively, and that when I am on the server, why not simply make the call. His solution reminded me implementing the call as a service makes that easier and then I get what I want. When using a component with server interactivity, the call is made safely. When using a component with webassembly interactivity, the call is essentially marshaled to the server to make for the component, keeping whatever secrets safe.

In this "new world" of InteractiveAuto, I had been exploring how to perform functions like this, since that world essentially says make your component "client-side ready" and then you're good for whatever happens.

My sample is designed for a small web app where maybe it would be fine to keep it all together as shown. In a larger enterprise app you might still use secure cookies with strict policy so that client components make calls safely to the server and then "forwarded" along to the target. The Duende BFF sample does this for you, but not every app needs enterprise level infrastructure. It depends.

I think the outcome of this issue might be some documentation with InteractiveAuto specific advice, but I don't speak for the team. I'm grateful to @mahald for the sample as it really showed me the way and I'm using the technique now in the real app.

bcheung4589 commented 1 year ago

I have been experimenting with InteractiveAuto, although really it can all be applied to InteractiveWebAssembly as well. In this mode, I presented a sample app with a sample call to a third party API. Let's imagine that the API to be called isn't open to the public and I need to protect the keys or credentials or access to that API. I choose not to simply call it from the component, which resides in the Client project of the Blazor solution, because then it would be available to prying eyes of anyone who used the application and had the client DLLs streamed down to their browser.

In that case I really do have to recommend using the IHttpClientFactory.

This should work for both modes and isnt Server specific. Keep the appsettings.json ofcourse on server and never client, just a disclaimer.

His solution reminded me implementing the call as a service makes that easier and then I get what I want. When using a component with server interactivity, the call is made safely. When using a component with webassembly interactivity, the call is essentially marshaled to the server to make for the component, keeping whatever secrets safe.

The con is now, that your component is coupled to your server call and not the third-party call. If you need manipulation before sending the request to the third-party, then yes, you need the server as intermediate.

The Duende BFF sample does this for you, but not every app needs enterprise level infrastructure. It depends.

I have removed Duende as dependency in my project, and only use .NET Identity Core (with EF Core ofcourse).

I think the outcome of this issue might be some documentation with InteractiveAuto specific advice, but I don't speak for the team.

There has not been a offered guide/advise by .NET team probably because its so new and they might want to see what we as community do. (just my thought :))

What is important to understand is the conceptuel design of the web app.

1 Component runs once in browser if mode is WebAssembly. 2 Auto Component runs once on Server if AutoMode is on, then as WASM component. (2 calls in total on first load) 2.1 WASM Component calls Server endpoint with HttpClients after pageload. 2.2 You can configure the HttpClient how you like, so you could have component that never calls your own server.

If you would like to visualize this; try this (name as you see fit yourself, this is my personal preference):

Shared project: /Communication/IRenderContext.cs

/// <summary>
/// Provide the render mode information in which the component is rendering.
/// </summary>
public interface IRenderContext
{
    /// <summary>
    /// Rendering from the Client project. Using HTTP request for connectivity.
    /// </summary>
    public bool IsClient { get; }

    /// <summary>
    /// Rendering from the Server project. Using WebSockets for connectivity.
    /// </summary>
    public bool IsServer { get; }

    /// <summary>
    /// Rendering from the Server project. Indicates if the response has started rendering.
    /// </summary>
    public bool IsPrerendering { get; }
}

Client project: /Communication/ClientRenderContext.cs

/// <inheritdoc/>
public sealed class ClientRenderContext : IRenderContext
{
    /// <inheritdoc/>
    public bool IsClient => true;

    /// <inheritdoc/>
    public bool IsServer => false;

    /// <inheritdoc/>
    public bool IsPrerendering => false;
}

/Program.cs

// Add Render Context for the Client.
builder.Services.AddSingleton<IRenderContext, ClientRenderContext>();

Server project: /Communication/ServerRenderContext.cs

/// <inheritdoc/>
public sealed class ServerRenderContext(IHttpContextAccessor contextAccessor) : IRenderContext
{
    /// <inheritdoc/>
    public bool IsClient => false;

    /// <inheritdoc/>
    public bool IsServer => true;

    /// <inheritdoc/>
    public bool IsPrerendering => !contextAccessor.HttpContext?.Response.HasStarted ?? false;
}

/Program.cs

// RenderContext communicates to components in which RenderMode the component is running.
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<IRenderContext, ServerRenderContext>();

So now you can inject the IRenderContext into your components.

@inject IRenderContext RenderContext
@if (RenderContext.IsClient)
{
    // Render me only on WASM. No server calls anymore.
    // Blazor loading module...
}
else 
{
    // Render me only on Server. No HTTP calls.
}

Now you have control in your components what to render in what mode.


FACADE pattern

// this is my own personal implementation, do whatever you like here // but for this layer, I personally think the Facade pattern is absolutely perfect for this.

Shared project: /Facades/IAppFeatureFacade.cs

/// <summary>
/// The Facade layer is an abstraction layer for communication
/// between the Components and Application depending on the Blazor RenderMode.
/// 
/// Server Facades use WebSockets for requests; Client Facades use HTTP for requests.
/// </summary>
public interface IAppFeatureFacade { }

/Facades/IClientContactFacade.cs

/// <summary>
/// The IClientContactFacade exposes all the features concerning client contacts entities.
/// </summary>
public interface IClientContactFacade : IAppFeatureFacade
{
    /// <summary>
    /// Get client contact by id.
    /// </summary>
    /// <param name="id"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    Task<GetClientContactByIdResponse?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
}

Client project: /Facades/HttpClientContactFacade.cs // stripped but you get the idea

/// <summary>
/// The HttpClientContactFacade exposes all the API features 
/// available concerning client contact entities.
/// </summary>
/// <param name="httpClientFactory"></param>
public class HttpClientContactFacade(IHttpClientFactory httpClientFactory) : IClientContactFacade
{
    private readonly HttpClient http = httpClientFactory.CreateServerClient();

    /// <inheritdoc/>
    public async Task<GetClientContactByIdResponse?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var response = await http.GetFromJsonAsync<GetClientContactByIdResponse>($"clientcontact/{id}", cancellationToken: cancellationToken);

        return response;
    }
}

/Program.cs

builder.Services.AddScoped<IClientContactFacade, HttpClientContactFacade>();

Server project: /Facades/ClientContactFacade.cs // stripped but you get the idea

/// <inheritdoc/>
public class ClientContactFacade(ISender sender) : IClientContactFacade
{
    /// <inheritdoc/>
    public async Task<GetClientContactByIdResponse?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
        => await sender.Send(new GetClientContactByIdRequest(id), cancellationToken);

}

/Program.cs

builder.Services.AddScoped<IClientContactFacade, ClientContactFacade>();

/Endpoints/ClientContacts.cs // Minimal API

public class GetClientContactByIdEndpoint : IFeatureEndpoint
{
    public void AddRoutes(IEndpointRouteBuilder app)
        => app.MapGet("clientcontact/{id:Guid}", async (Guid id, IClientContactFacade contacts, CancellationToken cancellationToken)
            => (await contacts.GetByIdAsync(id, cancellationToken))?.ToResponse()
        ).WithTags("Client Contacts");
}

As you probably noticed, the ClientContactFacade is just a layered HttpClient call between the component and server that just calls the endpoint which in turn calls the ServerFacade. Why? to make sure both client- and server calls, run the same code path.

@inject IRenderContext RenderContext
@inject IClientContactFacade ClientContacts

@if (RenderContext.IsClient)
{
    // Render me only on WASM. No server calls.
    // ClientContacts.GetByIdAsync() => HttpClientContactFacade
}
else 
{
    // Blazor loading module...
    // Render me only on Server. No HTTP calls.
    // ClientContacts.GetByIdAsync() => ClientContactFacade
}

// ClientContacts.GetByIdAsync() => Calls ClientContactFacade first, then HttpClientContactFacade once on pageload (so 2 calls in total). But every call after is on HttpClientContactFacade.
byte-projects commented 11 months ago

I was hit by this today whilst using InterativeWebAssembly render mode globally after upgrading from a .net7 project.

  1. Home page (framework downloads) > Pricing page (with http call) > OK.
  2. Hit F5 - page reloads instantly but the base url is null on the named HttpClient, presumably because the wasm hasn't downloaded yet.

Since I'm using the InterativeWebAssembly globally, I was confused to see this behaviour because I was expecting that mode to act just like the old wasm mode in net7.

I remember watching a recent video suggesting that you have 2 versions of every component which fetches data, a client version (using http) and a server version (using direct service calls).

However, why doesn't InterativeWebAssembly work like wasm did in .net7, with the little timer visible during page load? I wasn't expecting fancy magic unless I picked one of the other render modes such as auto...

Update: I just discovered <Routes @rendermode="new InteractiveWebAssemblyRenderMode(prerender: false)" /> re: my last sentence.

SpaceShot commented 11 months ago

Without seeing your app, try turning off prerender and see what happens. Not saying I'm sure, when I get home I'll try to replicate your scenario as best I can.


From: Byte Projects @.> Sent: Thursday, November 16, 2023 6:31:24 PM To: dotnet/aspnetcore @.> Cc: Chris Gomez @.>; Author @.> Subject: Re: [dotnet/aspnetcore] Unsure how to inject HttpClient for InteractiveAuto render mode (RC2) (Issue #51468)

I was hit by this today whilst using InterativeWebAssembly render mode globally after upgrading from a .net7 project.

  1. Home page (framework downloads) > Pricing page (with http call) > OK.
  2. Hit F5 - page reloads instantly but the base url is null on the named HttpClient, presumably because the wasm hasn't downloaded yet.

Since I'm using the InterativeWebAssembly globally, I was confused to see this behaviour because I was expecting that mode to act just like the old wasm mode in net7.

I remember watching a recent video suggesting that you have 2 versions of every component which fetches data, a client version (using http) and a server version (using direct service calls).

However, why doesn't InterativeWebAssembly work like wasm did in .net7, with the little timer visible during page load? I wasn't expecting fancy magic unless I picked one of the other render modes such as auto...

— Reply to this email directly, view it on GitHubhttps://github.com/dotnet/aspnetcore/issues/51468#issuecomment-1815494040, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAEVHCHSXQJKFH6NR7AH3ALYE2O4ZAVCNFSM6AAAAAA6F7SS5SVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMJVGQ4TIMBUGA. You are receiving this because you authored the thread.Message ID: @.***>

lnaie commented 11 months ago

Server and client are using the same lifecycle events. Therefore OnInitialized and OnInitializedAsync are called both on server prerendering and client when using interactive web assembly mode. I think we need a better self-explanatory programming model here. Is adding a parameter like bool preRender doable?

fdonnet commented 11 months ago

hello, your facade thing helped me a lot bcheung4589 Thx. Initial call is made server side with direct access and after with http access. Happy about that.

But the issue is that at the first render it calls OnInitializedAsync, it retieves my collection and shows it for 1or 2 sec and it disapears... very strange.

If I click on a button to re-fill it, it works form client side with httpclient but I cannot understand why the first call in OnInitalizedAsync is clearer (server-side) (seems to be related to the component being loaded client side)

bcheung4589 commented 11 months ago

But the issue is that at the first render it calls OnInitializedAsync, it retieves my collection and shows it for 1or 2 sec and it disapears... very strange.

If I click on a button to re-fill it, it works form client side with httpclient but I cannot understand why the first call in OnInitalizedAsync is clearer (server-side) (seems to be related to the component being loaded client side)

Ive noticed this as well, my solution for now is not rendering on first call (so on prerendering I show "Module loading") which prevents the page flicker => which I find absolutely unacceptable. So Im doing it this way for now, which kinda defeats it purpose, as Im using the WASM part mostly. But currently dont have time to deep dive for a real solution :(

On pages that I dont use Interactivity, I dont have that page-flicker issue.

Another issue I have is that on the server call (so after prerendering) it doesnt hit middleware, unless you always use WASM (so it goes through an endpoint => calls middleware). But just running it InteractiveServer wont call middleware on its Server run, and registered scoped services in the ServiceCollection wont help.

So if you are planning to get a certain Id from the Claims (in middleware) using Interactivity, it can become very tricky.

Your results disappears because the second run is not doing something that it should, in my case it was: middleware isnt run, so my scoped TenantAccessor didnt get updated through middleware.

fdonnet commented 11 months ago

@bcheung4589 ... ok you are facing exactly the same issues as me. So I m not crazy... private static IComponentRenderMode _rendermode = new InteractiveAutoRenderMode(prerender: false);

Like you, I find that unacceptable too. Because what's the purpose of auto mode if you have this flickering with prerender on or be forced to wait the wasm loading before doing someting without prerender ?

And like you, if i use some -autorizedview- between the call it's weird and I think it's due to my user middleware too. Not able to retrieve the user to get accesstoken in distibuted cache before a call to an external api.

The final goal was to protect all the external api call with the server layer for all cases

With all the marketing they made... I think we will be able to manage some amazing stuff but I m blocked on my inital tentative for weird simple things....

bcheung4589 commented 11 months ago

You can solve this with the proposed solution in: https://github.com/dotnet/aspnetcore/issues/52350

fdonnet commented 11 months ago

Thx @bcheung4589 ,

For now, I m able to persist authentification/autorization state both side with this guy example https://github.com/CrahunGit/Auth0BlazorWebAppSample/issues/1

I m able to develop component that can work both side because of your facade pattern. (example calling an external api

Authentification is in place with Keycloack (openid), the tokens to access external api are stored in distributed cache (server side). To keep my user injected correctly I followed the circuit accessor example from learn Microsoft and it seems to work.

Now what is missing, is the flickering with prerender... :-) I hope I will be able to find a solution... Why the rerender after the inital run when you have a perfect first version from the server... sad.

fdonnet commented 11 months ago

@bcheung4589 for info it works now for no flcikering with @inject PersistentComponentState ApplicationState as explained here at the bottom of this article https://learn.microsoft.com/en-us/aspnet/core/blazor/components/prerendering-and-integration?view=aspnetcore-7.0&viewFallbackFrom=aspnetcore-8.0&pivots=server

but it's not a very elegant solution...

Interactive auto mode can work with prerender only if you persist the data for the double call to OnInitializedAsync... I hope, on time, they will come with a more integrated solution..

bcheung4589 commented 11 months ago

@bcheung4589 for info it works now for no flcikering with @Inject PersistentComponentState ApplicationState as explained here at the bottom of this article https://learn.microsoft.com/en-us/aspnet/core/blazor/components/prerendering-and-integration?view=aspnetcore-7.0&viewFallbackFrom=aspnetcore-8.0&pivots=server

but it's not a very elegant solution...

Interactive auto mode can work with prerender only if you persist the data for the double call to OnInitializedAsync... I hope, on time, they will come with a more integrated solution..

In my humble opinion, I dont like the solution (beside sharing your opinion of not finding it elegant). But I do get how you arrived at it, you want to preserve the state it loses at the second render call.

What I would like is, not preserve the state, but just dont execute the main code on the first call (prerender). With main code I mean the code to get the data I want to show (in a table or whatever).

Below are some minimal excerpts from my code.

In your razor component you do your checks: is it loading? did I get any results after loading?

@inject IRenderContext RenderContext

@if (!isLoading)
{
    if (users.Any())
    {
        <p>@users.Count() items found.</p>
        <QuickGrid Items="users" Class="table table-striped">
            <TemplateColumn Title="Name" SortBy="GridSort<UserViewModel>.ByAscending(p => p.FirstName)">
                <span @onclick="@(() => NavigateTo(context))" role="button" title="View details of @context.UserName">@context.FirstName @context.LastName</span>
            </TemplateColumn>
        </QuickGrid>
    }
    else
    {
        <p class="text-muted">No users found.</p>
    }
}
else {
    <p class="text-muted">Loading data..</p>
}

@code {

    private IQueryable<UserViewModel> users = null!;
    private bool isLoading = true; // notice we start with loading immediately

    protected override async Task OnInitializedAsync()
    {  
        if (RenderContext.IsPrerendering)
        {
            return; // no need for prerendering
        }

        // Require User or move away.
        await UserService.RequireUserAsync(AuthenticationStateProvider, Navigation);

        await LoadDataAsync();
    }

    private async Task LoadDataAsync()
    {
        isLoading = true;

        // get your users

        isLoading = false;
    }

}

How does this work?

  1. we wrap the razor/html code with a check to see if we are loading (isLoading) and initialize it with true, so we start of with the loading state.
  2. if the page is prerendering (1e call) it wont load the data by calling LoadDataAsync (and setting isLoading on false); keeping it loading state.
  3. on the 2e call it will load the data and set isLoading to false, showing our data without the flicker.

With this setup you can decide what data to load in LoadDataAsync => which will only load after the prerender (without middleware execution). This doesnt solve the issue that middleware isnt called, therefore you need the UserService.RequireUserAsync in here. You could now create a blazor component or whatever name you like, and put the [@if (!isLoading)] check in there.

Now! Create a new component UserRequiredState and add the UserService.RequireUserAsync() call in there. Add the UserRequiredState-component to the razor components where you require users. I havent implemented UserRequiredState-component myself yet, but it should work and I find it much more of a blazor-minded solution (using components) :)

fdonnet commented 11 months ago

I will try your way for when I have a lot of data to load, for when it's a small payload, i think it's good to have the view quickly and it works with this persitence workarround, no flickering and data are available in the first round.

PierreSpaak commented 10 months ago

Check out this solution https://stackoverflow.com/a/63833663 Instead of injecting the httpClient, just inject the ApiService and use ApiService.HttpClient It's working for me :-)

danielgreen commented 10 months ago
public bool IsPrerendering => !contextAccessor.HttpContext?.Response.HasStarted ?? false;

I like your approach @bcheung4589 but I would just query the use of HttpContext above, in light of the docs.

IHttpContextAccessor must be avoided with interactive rendering because there isn't a valid HttpContext available.

IHttpContextAccessor can be used for components that are statically rendered on the server. However, we recommend avoiding it if possible.

HttpContext can be used as a cascading parameter only in statically-rendered root components for general tasks, such as inspecting and modifying headers or other properties in the App component (Components/App.razor). The value is always null for interactive rendering.

Your approach detects interactive server rendering when Response.HasStarted = true, and in my .NET 8 Blazor Web App dev environment that's exactly what I'm seeing.

So I'm unclear how to square that with the docs that state HttpContext is always null for interactive rendering.

bcheung4589 commented 10 months ago
public bool IsPrerendering => !contextAccessor.HttpContext?.Response.HasStarted ?? false;

I like your approach @bcheung4589 but I would just query the use of HttpContext above, in light of the docs.

IHttpContextAccessor must be avoided with interactive rendering because there isn't a valid HttpContext available. IHttpContextAccessor can be used for components that are statically rendered on the server. However, we recommend avoiding it if possible. HttpContext can be used as a cascading parameter only in statically-rendered root components for general tasks, such as inspecting and modifying headers or other properties in the App component (Components/App.razor). The value is always null for interactive rendering.

Your approach detects interactive server rendering when Response.HasStarted = true, and in my .NET 8 Blazor Web App dev environment that's exactly what I'm seeing.

So I'm unclear how to square that with the docs that state HttpContext is always null for interactive rendering.

You do have a point about the docs, Im also not clear about that.

I just always get the HttpContext with the IHttpContextAccessor and HttpClients with IHttpClientFactory as recommended practice.

As a sidenote: getting HttpClients with IHttpClientFactory gives even more benefits because of being configurable through appsettings. Those kind of perks are always available if you use recommended/best practices.

danroth27 commented 10 months ago

Hi folks. Thank you for all the discussion and helpful suggestions! I'm going to try to summarize what I think the conclusions are here and what actions we're expecting to take as a result:

When using the interactive WebAssembly and Auto render modes, components will be prerendered by default, which is different than how Blazor WebAssembly apps worked prior to .NET 8. Auto components will also initially be rendered interactively from the server. This means that components using these render modes should be designed so that they can run successfully from both the client and the server. If the component needs to call an API when running on the client, then the recommended approach is to abstract that API call behind a service interface and then implement a client and server version of the service: the client version calls the API and the server version can typically access the server-side resources directly. Injecting an HttpClient on the server that makes calls back to the server is not recommended as the network request is typically unnecessary. We should make sure this guidance is clearly documented.

When using the WebAssembly render mode it is also an option to disable prerendering so that the components only render from the client: @rendermode new InteractiveWebAssemblyRenderMode(prerender: false).

When using prerendering, the component will render twice: first statically, then interactively. Nothing automatically flows state from the prerendered component to the interactive one. If your component performs async initialization operations and renders different content for different states during initialization (like a "Loading..."), then you may see a flicker when the component renders twice. You can address this by flowing prerendered state using the PersistentComponentState API so that the when the component renders interactively it can render the same way using the same state. However, there is a known issue with this API that it doesn't currently play nice with enhanced navigation. This is something we're looking to get fixed. You can work around this issue by disabling enhanced navigation on links to the page (data-enhanced-nav=false).

I put together a sample that demonstrates these patterns by replacing the Weather page with a page that makes an API call: https://github.com/danroth27/BlazorWebAppApiCall.

Note that if all you're doing is calling an API to display some data, then you can avoid all this complexity by just using static server-side rendering with streaming rendering. That way the component only runs from the server and the page will load much faster.

Please let me know if I missed anything else discussed here.

danielgreen commented 10 months ago

@danroth27 Please can you comment on the PreRendering test that relies on HttpContext.Response.IsStarted?

HttpContext does not appear to be null during interactive server mode (in local debug at least), despite what the docs state.

danroth27 commented 10 months ago

can you comment on the PreRendering test that relies on HttpContext.Response.IsStarted?

I'd avoid a component dependency on HttpContext if possible. Why do you need it?

HttpContext does not appear to be null during interactive server mode (in local debug at least), despite what the docs state.

It will be available initially during prerendering, but not when the component is rendered interactively.

danielgreen commented 10 months ago

can you comment on the PreRendering test that relies on HttpContext.Response.IsStarted?

I'd avoid a component dependency on HttpContext if possible. Why do you need it?

HttpContext does not appear to be null during interactive server mode (in local debug at least), despite what the docs state.

It will be available initially during prerendering, but not when the component is rendered interactively.

The intention of using HttpContext as above is to detect when the component is prerendering versus when it's interactive, as we may want to vary the UI in those situations.

When you say HttpContext won't be available when the component is rendered interactively (in server mode), in practice it does appear to be non-null and has Response.IsStarted = true. Is that not expected behaviour?

danroth27 commented 10 months ago

The intention of using HttpContext as above is to detect when the component is prerendering versus when it's interactive, as we may want to vary the UI in those situations.

Right, I'm questioning whether that is really necessary. What's an example of how you'd want to vary the UI for prerendering?

When you say HttpContext won't be available when the component is rendered interactively (in server mode), in practice it does appear to be non-null and has Response.IsStarted = true. Is that not expected behaviour?

It's certainly not expected when you access the HttpContext as a cascading value:

[CascadingParameter]
public HttpContext HttpContext { get; set; }

But you're probably right if you're using the IHttpContextAccessor service.

danielgreen commented 10 months ago

Right, I'm questioning whether that is really necessary. What's an example of how you'd want to vary the UI for prerendering?

Steve Sanderson gives an example in the intro to #49401 - you could have some buttons that are disabled during prerendering then light up once interactivity is available.

When you say HttpContext won't be available when the component is rendered interactively (in server mode), in practice it does appear to be non-null and has Response.IsStarted = true. Is that not expected behaviour?

It's certainly not expected when you access the HttpContext as a cascading value:

[CascadingParameter]
public HttpContext HttpContext { get; set; }

But you're probably right if you're using the IHttpContextAccessor service.

I see, indeed the server-side IRenderContext implementation above does use IHttpContextAccessor from DI.

Perhaps the docs should be reviewed because they currently say "avoid IHttpContextAccessor with interactive rendering because there isn't a valid HttpContext available."

What is meant by a "valid" HttpContext isn't clear. In the interactive server scenario, the HttpContext obtained via DI is non-null as described earlier in this thread.

bcheung4589 commented 10 months ago

You cant always rely on HttpContext in components, because if the component is in WASM mode, HttpContext will not be available. Thats the tricky part.

But using the IHttpContextAccessor should handle it nicely in cases it can; and in cases it cant or is null, I default to false: https://github.com/bcheung4589/PortalForgeX/blob/master/src/Presentation/PortalForgeX/Communication/ServerRenderContext.cs. This lives in the Server project.

In the client project (ClientRenderContext), I just set the IsPrerendering to hardcoded false. So far, it worked pretty well and Im 95% in control of the code, as in, I understand what it does and how to make it work how I would like it. There are some edge cases where I dont 100% understand the workings, but Im still pretty excited about Blazor 8!!

danroth27 commented 10 months ago

Steve Sanderson gives an example in the intro to https://github.com/dotnet/aspnetcore/issues/49401 - you could have some buttons that are disabled during prerendering then light up once interactivity is available.

Cool, that makes sense.

What is meant by a "valid" HttpContext isn't clear.

Interactive server rendering is done over a real-time connection with the browser based on SignalR. In this mode, you aren't really operating in the context of a request, but SignalR will create a sort of dummy HttpContext with partial information. I believe that's the HttpContext you get when you use the IHttpContextAccessor approach. We can see about clarifying this in the docs.

fdonnet commented 10 months ago

This is something we're looking to get fixed. You can work around this issue by disabling enhanced navigation on links to the page (data-enhanced-nav=false).

Cannot reproduce the bug with the persitent component but I m interrested to know when it can cause any issue. For the moment, i didn't decorate my nav link to the page with the (data-enhanced-nav=false).

Automode with a call to an external api is working. (When client: with reverse proxy blazor server controller that call the typed httpclient / when server: direct call to the typed httpclient)

People need to know that when using automode + prerendering (+ maybe persitent compo) =>they need to desactivate their interactivity during the prerendering (ex: a button click).

On my side, i put something like that in place using the rendercontext from @bcheung4589 (disable state + not allowed cursor)

        if (RenderContext.IsPrerendering)
        {
            <button type="button" class="mb-2 text-white bg-blue-400 dark:bg-blue-500 cursor-not-allowed font-medium rounded-lg text-sm px-5 py-2.5 text-center" disabled>Add</button>
        }
        else
        {
            <UbikButton Type="button" Label="Add" AdditionalCssClass="mb-2" OnClick="AddAccountDialog"></UbikButton>
        }

If you don't desactivate your interactivity the quick guy will click on your thing and it will raise JS error like that:

Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Value cannot be null. (Parameter 'jsObjectReference')
System.ArgumentNullException: Value cannot be null. (Parameter 'jsObjectReference')
   at System.ArgumentNullException.Throw(String paramName)
   at System.ArgumentNullException.ThrowIfNull(Object argument, String paramName)
   at 

Edit: what I put above don't solve completely the issue... a "very" short time frame remains when you can click and something is not fully ready behind and it will raise JS error on button click. It's a small windows but it's present...

The important part, I think:

Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState) 

EDIT2: I have good results now with the addition of _isLoading... _isLoading or IsPrerendering

        if (_isLoading || RenderContext.IsPrerendering)
        {
//Disactivate button click
        }

Seems to don't let the user raise JS err related to component state.

Confirmed: a time window for a user click remains... Maybe we will have something that allow us to track that a changing state is in progress...

EDIT3: So yeah, after a lot of testing roll back to the "no pre-rendering" mode for loading data, works well...:

        if (RenderContext.IsPrerendering)
        {
            return;
        }

The user cannot click on elements and trigger JS err when loading data (very short time window, but anyway). In term of UI, all the rest of the page is loaded (static) but when you need interactivity and you have data loading it's seems hard to manage a thing that works for everything with prerendering.

JasminSAB commented 8 months ago

Complicating a very simple thing with the latest release of .NET is not good.. definitely not good.

Edemilorhea commented 8 months ago

Hi folks. Thank you for all the discussion and helpful suggestions! I'm going to try to summarize what I think the conclusions are here and what actions we're expecting to take as a result:

When using the interactive WebAssembly and Auto render modes, components will be prerendered by default, which is different than how Blazor WebAssembly apps worked prior to .NET 8. Auto components will also initially be rendered interactively from the server. This means that components using these render modes should be designed so that they can run successfully from both the client and the server. If the component needs to call an API when running on the client, then the recommended approach is to abstract that API call behind a service interface and then implement a client and server version of the service: the client version calls the API and the server version can typically access the server-side resources directly. Injecting an HttpClient on the server that makes calls back to the server is not recommended as the network request is typically unnecessary. We should make sure this guidance is clearly documented.

When using the WebAssembly render mode it is also an option to disable prerendering so that the components only render from the client: @rendermode new InteractiveWebAssemblyRenderMode(prerender: false).

When using prerendering, the component will render twice: first statically, then interactively. Nothing automatically flows state from the prerendered component to the interactive one. If your component performs async initialization operations and renders different content for different states during initialization (like a "Loading..."), then you may see a flicker when the component renders twice. You can address this by flowing prerendered state using the PersistentComponentState API so that the when the component renders interactively it can render the same way using the same state. However, there is a known issue with this API that it doesn't currently play nice with enhanced navigation. This is something we're looking to get fixed. You can work around this issue by disabling enhanced navigation on links to the page (data-enhanced-nav=false).

I put together a sample that demonstrates these patterns by replacing the Weather page with a page that makes an API call: https://github.com/danroth27/BlazorWebAppApiCall.

Note that if all you're doing is calling an API to display some data, then you can avoid all this complexity by just using static server-side rendering with streaming rendering. That way the component only runs from the server and the page will load much faster.

Please let me know if I missed anything else discussed here.

Sorry I wanna ask one question, I am using Blazorapp Global for development, and I also encountered that the server side requires httpClient to support API usage.

So I had to inject httpClient on the server side and encountered the problem of requiring BaseAddress.

I looked at the example you provided, and I don’t understand that your Service on the client side also calls httpClient, but you are using the minimum API.

And I use a traditional Controller. Is this the main difference?

JasminSAB commented 8 months ago

@danroth27 * Hi, few questions please:

Edemilorhea commented 8 months ago

@Edemilorhea Hi, few questions please:

  • in the auto approach is it possible that entire server app gets downloaded to the client, if yes in which case, I wonder about that very much due to the security
  • regarding the global or per component render mode, the approach that you have typed above won’t work in both scenarios
  • the way how we currently disable the prerender mode can be moved to the configuration level for all components or something like that
  • The HttpClient when it has to be registered to server is when you would like to preserve the interactivity on client side, this could be the lead on question you have asked
  • it would be very beneficial to have all different cases well documented
  • It would be very beneficial to have more samples with the template that is created, e.g the HttpClient with authentication etc - all cases can be improve
  • we had hosted mode which was very good, and people now tend to create it in .net 7 and upgrade the projects to .net 8 due to the complexity that was introduced KR, J

@JasminSAB Hi, thank you very much for your question and explanation. I would like to ask, so is it still not recommended to use .Net 8.0 for building commercial web applications?

Additionally, my English is not particularly good. I was wondering if you could provide some examples of your projects or some source codes for reference. I might understand better the development methods and architecture you are using.

JasminSAB commented 8 months ago

@Edemilorhea Hi, few questions please:

  • in the auto approach is it possible that entire server app gets downloaded to the client, if yes in which case, I wonder about that very much due to the security
  • regarding the global or per component render mode, the approach that you have typed above won’t work in both scenarios
  • the way how we currently disable the prerender mode can be moved to the configuration level for all components or something like that
  • The HttpClient when it has to be registered to server is when you would like to preserve the interactivity on client side, this could be the lead on question you have asked
  • it would be very beneficial to have all different cases well documented
  • It would be very beneficial to have more samples with the template that is created, e.g the HttpClient with authentication etc - all cases can be improve
  • we had hosted mode which was very good, and people now tend to create it in .net 7 and upgrade the projects to .net 8 due to the complexity that was introduced KR, J

@danroth27 -

Hi Daniel, may you please involve?

Many topics are mentioned here, there is a lot of improvements for next iterations.

KR, J

danroth27 commented 8 months ago

Hi @JasminSAB. I'll try to answer your questions below:

  • In the auto approach is it possible that entire server app gets downloaded to the client, if yes in which case, I wonder about that very much due to the security

With the Auto and WebAssembly render modes only the code in the referenced client project will be downloaded to the browser. Any components that you want to run client-side need to be in the dependency graph of the client project.

  • Regarding the global or per component render mode, the approach that you have typed above won’t work in both scenarios
  • the way how we currently disable the prerender mode can be moved to the configuration level for all components or something like that
  • The HttpClient when it has to be registered to server is when you would like to preserve the interactivity on client side, this could be the lead on question you have asked

Could you please clarify what exactly doesn't work? If you insulate a component from server and client specific concerns then you should be able to use that component with any render mode regardless of whether the render mode was configured globally on the router or per component.

  • it would be very beneficial to have all different cases well documented
  • It would be very beneficial to have more samples with the template that is created, e.g the HttpClient with authentication etc - all cases can be improve

Agreed, documenting these cases is what this issue is meant to track, and we need to move faster on getting it addressed. I'll follow up on that.

  • we had hosted mode which was very good, and people now tend to create it in .net 7 and upgrade the projects to .net 8 due to the complexity that was introduced

You should be able to replicate the experience of an ASP.NET Core hosted Blazor WebAssembly app by enabling global WebAssembly rendering and disabling prerendering: https://learn.microsoft.com/aspnet/core/migration/70-80#convert-a-hosted-blazor-webassembly-app-into-a-blazor-web-app

danroth27 commented 8 months ago

Sorry I wanna ask one question, I am using Blazorapp Global for development, and I also encountered that the server side requires httpClient to support API usage.

So I had to inject httpClient on the server side and encountered the problem of requiring BaseAddress.

I looked at the example you provided, and I don’t understand that your Service on the client side also calls httpClient, but you are using the minimum API.

And I use a traditional Controller. Is this the main difference?

@Edemilorhea Instead of injecting an HttpClient into your component you can instead inject a service based on some interface you define. You can then provide two implementations for that service: one for the client that uses an HttpClient to call an API, and one for the server that accesses the required server-based resource directly. Whether you implement your API endpoint using a minimal APi or a controller should not matter.

JasminSAB commented 8 months ago

Hi @danroth27

Thank you for your answers, I have found the template that fits my needs, It's:

This is the git repository: https://github.com/JasminSAB/WebInteractivity-per-component

My current problem is with the Test.razor component

I have an HttpRequest that is sent to the backend service to the API/test controller, and this controller is e.g. covered with "Admin!" role, and here I have two scenarios:

I have tried two approaches to execute API end point - directly via HttpClient and also with IHttpClientFactory

 // using httpclient factory
 protected override async Task OnInitializedAsync()
 {
     var _httpClient = factory.CreateClient("WebAPI");

     var request = new HttpRequestMessage(HttpMethod.Get, _httpClient.BaseAddress + "api/test");
     request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));

     using var response = await _httpClient.SendAsync(request, CancellationToken.None);

     // status code is always 200 even when the user is not in the role and it should be 403 in that case
     switch (response.StatusCode)
     {
         case HttpStatusCode.OK:
         {
             Console.WriteLine("All good!");
             stringList = await response.Content.ReadFromJsonAsync<List<string>>();
             break;
         }
         case HttpStatusCode.Forbidden:
         {
             Console.WriteLine("Forbiden!");
             break;
         }
         default:
         {
             Console.WriteLine("Some other scenario");
             break;
         }
     }
 }

You can have a look at both program.cs files I have left TODO comments with the changes that I made to the boilerplate code.

My changes on the backend are:

My changes on frontend are:

guardrex commented 7 months ago

[Sorry that I wrote a whole BOOK 📖 here! 🙈 ... but I want to call out a few subjects on this issue, bring everyone up to speed on the doc updates thus far, and mention what else I'm working for docs in this area. Please enjoy my novel! ... Let's call it Blazor War and Peaceful (web) APIs 😆]

@SpaceShot ... I'm reading down this issue now for further work, particularly looking to address security aspects on #31973. I just merged a large update to address Dan's summary of critical points at https://github.com/dotnet/aspnetcore/issues/51468#issuecomment-1843881478, including new sample apps over in the Blazor samples repo. The coverage is live but not finalized. The MVP Summit is blocking review at the moment. I wanted to get better coverage in place quickly, so I went ahead and merged it. There will be further updates after I receive feedback from @danroth27 and possibly @MackinnonBuck (and/or other PU engineers).

WRT the remark that you made on IWebAssemblyHostEnvironment. I recognized a while back that that was going to be a problem, so I worked with Steve and Mackinnon to provide guidance at https://learn.microsoft.comaspnet/core/blazor/fundamentals/environments#read-the-environment-client-side-in-a-blazor-web-app, which deals exactly with when you have SSR for an Auto component and need to be able to inject IWebAssemblyHostEnvironment and get environment values on the server (e.g., base address as you mentioned). See if that guidance helps if you're still facing that challenge.

WRT HttpContext, I've touched our remarks a few times, but I don't have actionable changes to make at the moment. I'll have an 👂 open for further feedback.

WRT HttpClient, I feel it's important to distinguish between "external" web API calls to external backend apps (i.e., "external" meaning a completely separate app) and "internal" (web) API calls within BWAs [i.e., a component in the .Client project is calling a (web) API in the server project of the BWA, noting that "web" is in parenthesis for a good reason that I'll explain in a moment].

In the former case ... and as far as I know nothing has changed from Blazor Server days ... "external" web API calls are still made using HttpClient on the server, so an Auto component can take advantage of that when it adopts SSR. Later under CSR, the component uses a preconfigured HttpClient in the WASM project (.Client project) to call the "external" web API from the client. This is all pure, old skool web API ... over the network regardless of the render mode.

For BWAs and "internal" (web) APIs, the (web) API is in the server project of the BWA. As fully described here by @danroth27 and others, we'll pitch the service abstraction approach. With a server-side service implementation, the API is a garden variety API accessed directly by the server-side service. It's not a "web" API in the server context. It's just a normal API not accessed over a network, and that's why I've been putting "web" in parentheses. Under CSR, the client-side implementation of the service uses a preconfigured HttpClient to access the API as a true "web" API over the network.

Both of these scenarios are now covered ... LIVE but in draft form ... in the article. We might not stick with "internal" and "external" (web) API language. We'll sort that out soon enough. I need feedback from @danroth27, @MackinnonBuck, and community 🐱🐱🐱. The layout of the content (i.e., sections and section heading titles) may also change. We'll see.

I encourage those wanting an experience with both scenarios (and including Blazor WebAssembly apps that call a web API in a backend app) to check out the new guidance and run the sample apps. You can get a sense of the difference between these and the stability of the data during navigation across components with different render nodes because I included Interactive Server, Interactive WebAssembly, and Interactive Auto test components in the BWA sample app for both locations of APIs. The components also leverage the Persistent Component State API to avoid flickering when the components load (there's no streaming rendering, so it works well). The BWA app has two distinct APIs to demonstrate without confusion the "internal" (web) API (a movie list) and "external" web API (a todo list).

WRT typed HttpClients and named HttpClients with IHttpClientFactory, I do have a couple of actionable updates to make soon ... this week probably ... because the sections are very WASM-specific at the moment. This article used to pivot between Blazor Server and Blazor WASM coverage, and named/typed clients were only discussed in the WASM pivot. We've dropped the pivot, and I have typed/named clients out in the open now for all scenarios. I'll make a few changes for obvious problems in those sections. I think we'll sort out any further 🐉🐉🐉 and gotchas 😈 on PU review.

I'm focusing on security aspects to resolve #31973, but it's likely that I'll confer with PU peeps before merging any updates.

fdonnet commented 7 months ago

I feel it's important to distinguish between "external" web API calls to external backend apps

@guardrex I think it's important to note that a lot of people who call external apis would like to use the Balzor server side as some sort of proxy because it's very convenient to do it like this.

I use a facade/interface to do it like explained by another guy in this post and it's very elegant in automode (@bcheung4589 thx to him).

Serverside implementation of the facade : you call the external api

Clientside implementation of the facade : you call your blazor server endpoint that acts as a very simple reverse proxy => call you server side facade implementation and only forward the response back to the client.

Example of Balzor serverside controller Forward method that you can call for all your endpoints using httpcontext

        private async Task ForwardResponse(HttpResponseMessage? responseMsg)
        {
            if (responseMsg == null) return;

            HttpContext.Response.StatusCode = (int)responseMsg.StatusCode;

            if(HttpContext.Response.StatusCode != 204)
                await responseMsg.Content.CopyToAsync(HttpContext.Response.Body);
        }

It's just perfect to be able to manage external api calls (auth / token / caching) server side but have the possibility to put components on auto mode. It's some sort of magic trick I really like.

If you want an example. I have a public github that uses that for a simple project in Net 8.

EDIT : LINKS Shared interface: https://github.com/fdonnet/ubik_accounting/blob/main/src/Ubik.Accounting.Webapp.Shared/Facades/IAccountingApiClient.cs

Server side implementation: (The external api call) https://github.com/fdonnet/ubik_accounting/blob/main/src/Ubik.Accounting.WebApp/ApiClients/AccountingApiClient.cs

Client side implementation: (The Blazor Server side proxy call) https://github.com/fdonnet/ubik_accounting/blob/main/src/Ubik.Accounting.WebApp.Client/Facades/HttpApiAccountingFacade.cs

Blazor ServerSide Controller: (The reverse proxy called by the client side) https://github.com/fdonnet/ubik_accounting/blob/main/src/Ubik.Accounting.WebApp/Controllers/ReverseProxyWasm.cs

guardrex commented 7 months ago

@fdonnet ... That's the pattern that was adopted for the BWA with OIDC BFF sample app ...

I'm glad you said something for two reasons ...

Yikes! 😄 ... That's a lot to chew on for organizing coverage! This is why I felt it best to deal with the security aspects separately from @danroth27's initial coverage request that mainly focuses on the simplest case of the API in the server project without authn/z. I'll work on it this week.