Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
SpaceShot opened this issue Oct 18, 2023 · 61 comments
Open

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

SpaceShot opened this issue Oct 18, 2023 · 61 comments
Assignees
Labels
area-blazor Includes: Blazor, Razor Components Docs This issue tracks updating documentation

Comments

@SpaceShot
Copy link

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.

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Oct 18, 2023
@SpaceShot
Copy link
Author

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

@CrahunGit
Copy link

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

@mahald
Copy link

mahald commented Oct 19, 2023

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
Copy link
Author

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
Copy link
Author

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) });

@mkArtakMSFT mkArtakMSFT added the Docs This issue tracks updating documentation label Oct 19, 2023
@mkArtakMSFT mkArtakMSFT added this to the .NET 8: Documentation milestone Oct 19, 2023
@SpaceShot
Copy link
Author

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

@MarcosMusa
Copy link

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
Copy link

mahald commented Oct 19, 2023

one way to do it: SpaceShot/InteractiveAutoSample#1

@MarcosMusa
Copy link

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
Copy link

mahald commented Oct 19, 2023

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

@mahald
Copy link

mahald commented Oct 19, 2023

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
Copy link

mahald commented Oct 19, 2023

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
Copy link
Author

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
Copy link

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

@SpaceShot
Copy link
Author

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
Copy link

bcheung4589 commented Oct 27, 2023

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
Copy link

byte-projects commented Nov 16, 2023

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
Copy link
Author

SpaceShot commented Nov 17, 2023 via email

@lnaie
Copy link

lnaie commented Nov 20, 2023

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
Copy link

fdonnet commented Nov 24, 2023

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
Copy link

bcheung4589 commented Nov 24, 2023

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
Copy link

fdonnet commented Nov 24, 2023

@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

  • first render direct via your server facade
  • after wasm load, from client through a server endpoint that will call the external api with the retrived token from cache

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
Copy link

You can solve this with the proposed solution in: #52350

@fdonnet
Copy link

fdonnet commented Nov 24, 2023

Thx @bcheung4589 ,

For now, I m able to persist authentification/autorization state both side with this guy example
CrahunGit/Auth0BlazorWebAppSample#1

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

  • Direct call to the httpclient (server side)
  • Call to server side blazor endpoint that call the httpclient (wasm side)

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.

@JasminSAB
Copy link

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

@Edemilorhea
Copy link

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
Copy link

JasminSAB commented Feb 24, 2024

@danroth27 * 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

@Edemilorhea
Copy link

@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
Copy link

@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
Copy link
Member

danroth27 commented Feb 27, 2024

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
Copy link
Member

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
Copy link

Hi @danroth27

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

  • .NET 8
  • Authentication Type Individual Users
  • Configured for HTTPS
  • Interactive render mode WebAssembly
  • Interactivity location per page/component
  • Included sample pages

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:

  • if the user is in the role of "Admin" everything is ok
  • if the user is not in the role of "Admin" the status code in the response is always 200

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:

  • added roles
  • added controllers
  • mapped controllers

My changes on frontend are:

  • create CookieHandler implementation - please have a look into it
  • registered CookieHandler as Transient = perhaps it could be scoped
  • added HttpClient named WebAPI with MessageHandler CookieHandler
  • added IHttpClientFactory as Scoped and created WebAPI HttpClient

@guardrex
Copy link
Contributor

guardrex commented Mar 6, 2024

[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 #51468 (comment), 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
Copy link

fdonnet commented Mar 6, 2024

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.

  • Have all the call logic server side
  • Manage auth token refresh server side
  • Manage caching server side
  • etc

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
Copy link
Contributor

guardrex commented Mar 6, 2024

@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.

@fdonnet
Copy link

fdonnet commented Mar 6, 2024

@guardrex yes

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

that was exactly why I implemented this stuff like this. Because of my OIDC (Keycloak) auth. And I m happy that it works not to bad for a draft/test mini project.

So, it seems perfect if you cover that in OIDC section because I think people will have questions about how to manage automode Balzor components when you call Api with auth in a cool and secure way ;)

@guardrex
Copy link
Contributor

guardrex commented Mar 6, 2024

I agree.

Just for completeness, the BlazorWebAppOidc (the non-BFF version) has a similar pattern for the component calls. The sample is at ...

https://github.com/dotnet/blazor-samples/tree/main/8.0/BlazorWebAppOidc

The difference is that the server doesn't need to make an external call, as there is no backend server web API. The app handles generating the weather data for SSR directly ...

https://github.com/dotnet/blazor-samples/blob/main/8.0/BlazorWebAppOidc/BlazorWebAppOidc/Weather/ServerWeatherForecaster.cs

The client calls an endpoint (securely) ...

https://github.com/dotnet/blazor-samples/blob/main/8.0/BlazorWebAppOidc/BlazorWebAppOidc.Client/Weather/ClientWeatherForecaster.cs

The web API endpoint for that calls the server's method directly ...

https://github.com/dotnet/blazor-samples/blob/main/8.0/BlazorWebAppOidc/BlazorWebAppOidc/Program.cs#L182-L185

Can I toss in a double "Yikes! Yikes!" here? 😆 These patterns will need to be explained out further. Currently, the BWA+OIDC article focuses heavily on the OIDC aspects and doesn't get into the (web) API patterns. That was fine to get our coverage started, but it's time to extend the coverage into these aspects.

I'll be working on it over the next few days. Hopefully, we'll have good coverage by early next week.

@AmBplus
Copy link

AmBplus commented Aug 12, 2024

I have explored various methods to solve the issue of how to send requests automatically, but most of them were incomplete or required a lot of work. Recently, I discovered that the Bit platform has an interesting solution to this problem. On this platform, there is a shared layer that contains an interface, let's say a "IDashboardController," which returns the required data to the server. On the client side, you use this interface, and on the server side, you implement its methods on the desired controller. Lets Call it Now DashboardController ,So far, this is a common approach and nothing new.

However, what is interesting is that, as I discussed with the platform's development team, the actual controller is never injected into the client side! The implementation is merely a way to ensure that the methods are existed on the controller, and no server-side action is intended. The second fascinating part is that, due to the code generator they have created, when you inject and use this interface, it automatically starts building the interface's code and uses an HttpClient to send requests to the server on the user's behalf. This means that the requests are always made from the client-side, and there is no need to write additional code for the client, which is very appealing to me.

For instance, if you want the interface to send requests automatically, you can implement a separate service class on the server side and add it to the dependency injection (DI) container. It would be fantastic if .NET itself provided such capabilities on the client side.

@OrihuelaConde
Copy link

I have explored various methods to solve the issue of how to send requests automatically, but most of them were incomplete or required a lot of work. Recently, I discovered that the Bit platform has an interesting solution to this problem. On this platform, there is a shared layer that contains an interface, let's say a "IDashboardController," which returns the required data to the server. On the client side, you use this interface, and on the server side, you implement its methods on the desired controller. Lets Call it Now DashboardController ,So far, this is a common approach and nothing new.

However, what is interesting is that, as I discussed with the platform's development team, the actual controller is never injected into the client side! The implementation is merely a way to ensure that the methods are existed on the controller, and no server-side action is intended. The second fascinating part is that, due to the code generator they have created, when you inject and use this interface, it automatically starts building the interface's code and uses an HttpClient to send requests to the server on the user's behalf. This means that the requests are always made from the client-side, and there is no need to write additional code for the client, which is very appealing to me.

For instance, if you want the interface to send requests automatically, you can implement a separate service class on the server side and add it to the dependency injection (DI) container. It would be fantastic if .NET itself provided such capabilities on the client side.

The only novel aspect of this solution is the automatic generation of code for HttpClient; the rest is the same as what @danroth27 proposed in his example.

@AmBplus
Copy link

AmBplus commented Aug 16, 2024

I have explored various methods to solve the issue of how to send requests automatically, but most of them were incomplete or required a lot of work. Recently, I discovered that the Bit platform has an interesting solution to this problem. On this platform, there is a shared layer that contains an interface, let's say a "IDashboardController," which returns the required data to the server. On the client side, you use this interface, and on the server side, you implement its methods on the desired controller. Lets Call it Now DashboardController ,So far, this is a common approach and nothing new.
However, what is interesting is that, as I discussed with the platform's development team, the actual controller is never injected into the client side! The implementation is merely a way to ensure that the methods are existed on the controller, and no server-side action is intended. The second fascinating part is that, due to the code generator they have created, when you inject and use this interface, it automatically starts building the interface's code and uses an HttpClient to send requests to the server on the user's behalf. This means that the requests are always made from the client-side, and there is no need to write additional code for the client, which is very appealing to me.
For instance, if you want the interface to send requests automatically, you can implement a separate service class on the server side and add it to the dependency injection (DI) container. It would be fantastic if .NET itself provided such capabilities on the client side.

The only novel aspect of this solution is the automatic generation of code for HttpClient; the rest is the same as what @danroth27 proposed in his example.

Yes, the core concept is the same, but one is a starting point and the other is a final solution. One requires a lot of additional components to function, while the other is ready to use out-of-the-box. I didn't mean to imply that this solution is bad, but my concern is for newcomers who might find it challenging to navigate the complexities of creating a simple app. We need a straightforward solution for complex tasks, and while the community can provide alternatives, in the .NET ecosystem, we often prefer core tools developed by Microsoft. This way, we can be assured of ongoing maintenance and freely use them without relying heavily on third-party tools.

@richardaubin
Copy link

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.

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.

Friendly reminder that docs need updating on this :)

@dgoldm
Copy link

dgoldm commented Sep 4, 2024

@danroth27 -

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.

Are you sure that "sort of dummy" is the correct definition?
I made a small experiment - in an interactive server component, where I use IHttpContextAccessor.HttpContext to read the cookies, if I try to add a cookie, I get an exception:
Headers are read-only, response has already started

Doesn't that imply that it's real, not a dummy one?

@guardrex
Copy link
Contributor

guardrex commented Sep 4, 2024

@richardaubin ... The new 9.0 API is covered at ...

https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-9.0#detect-rendering-location-interactivity-and-assigned-render-mode-at-runtime

One has to set the document version selector to "9.0" to see it. That link ☝️ will get you there, however, because it sets the "view" to the 9.0 version of the article via its query string.

WRT IHttpContextAccessor/HttpContext, the latest PU-approved remarks are located at ...

https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/signalr?view=aspnetcore-8.0#ihttpcontextaccessorhttpcontext-in-razor-components

They didn't approve "a sort of dummy HttpContext with partial information" language. What you see there is what was approved.

We have a spot that requires a bit more explanation for the ServerWeatherForecaster in the BWA+OIDC sample app. We haven't explained the IHttpContextAccessor usage in that scenario in the BWA+OIDC article yet (nor have we addressed the server-calling-the-server pattern in the text). The ServerWeatherForecaster is used by the Weather component when rendered on the server, which is an InteractiveAuto component via the global setting on the Routes component. I'm planning on improving that coverage in November after things have cooled down a bit on 9.0 GA work. I have an open issue to address it, so it won't get lost.

@dgoldm
Copy link

dgoldm commented Sep 4, 2024

@guardrex - could we possibly have a more thorough explanation of the difference between the IHttpContextAccessor.HttpContext and the one that's available as a CascadingParameter? e.g. are they functionally equivalent, and what are the reasons to prefer one over the other?

@guardrex
Copy link
Contributor

guardrex commented Sep 4, 2024

As far as I know, the existing text is what the product unit wants (or wanted at the time) to say about it. I'm 👂 for them to say if they want to add or change what's there.

@dgoldm
Copy link

dgoldm commented Sep 4, 2024

As far as I know, the existing text is what the product unit wants (or wanted at the time) to say about it. I'm 👂 for them to say if they want to add or change what's there.

Actually I won't mind if it doesn't make it into the docs, if there's anyone here that can give an unofficial explanation 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components Docs This issue tracks updating documentation
Projects
None yet
Development

No branches or pull requests