I'm creating a dotnet 6 blazor wasm website (core hosted) that uses B2C for auth, but having trouble with http client.
In program.cs I have the following for DI:
builder.Services.AddHttpClient<IBbtDataService, BbtDataService>(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
This Bbt service should be injected into the code behind for the FetchBbtData page shown below:
[Authorize]
public partial class FetchBbtData
{
[Inject]
public IBbtDataService BbtDataService { get; set; }
public IEnumerable<ClientOrg> ClientOrgs { get; set; }
protected async override Task OnInitializedAsync()
{
ClientOrgs = (await BbtDataService.GetClientOrgList()).ToList();
}
}
The code for the BbtDataService is as follows:
public class BbtDataService : IBbtDataService
{
private readonly HttpClient httpClient;
public BbtDataService(HttpClient httpClient)
{
httpClient = httpClient;
}
public async Task<IEnumerable<ClientOrg>> GetClientOrgList()
{
return await httpClient.GetFromJsonAsync<IEnumerable<ClientOrg>>($"api/clients");
}
}
If I put a breakpoint on the constructor of the BbtDataService I caan see that the httpClient parameter is valid and contains the correct base url. However, when execution then hits another breakpoint in the GetClientOrgList method, the value of the private readonly field httpClient is null - even though this was set in the constructor.
Anyone see where I went wrong?
You are assigning the parameter to itself. Add 2 _ :
private readonly HttpClient _httpClient;
public BbtDataService(HttpClient httpClient)
{
_httpClient = httpClient;
}
Related
I'm fairly new to Asp.Net core 6 and am working on an GraphQL API that receives a bearer token in the request. The API then invokes another Web API and passes the same bearer token in the header. Below is what my code looks like-
Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<HeaderHandler>();
builder.Services.AddScoped<TokenContainer>();
//builder.Services.AddScoped<IFooGateway, FooGateway>();
builder.Services.AddHttpClient<IFooGateway, FooGateway>((c) =>
{
c.BaseAddress = new Uri(builder.Configuration["FooApiUrl"]);
})
.AddHttpMessageHandler<HeaderHandler>();
builder.Services.AddTransient<GraphApiService>();
var app = builder.Build();
app.UseMiddleware<HeaderMiddleware>();
app.MapGraphQL();
app.Run();
HeaderMiddleware.cs
public class HeaderMiddleware
{
//TokenContainer _tokenContainer;
private readonly RequestDelegate _requestDelegate;
public HeaderMiddleware()
{
}
public HeaderMiddleware(RequestDelegate requestDelegate)
{
_requestDelegate = requestDelegate;
//_tokenContainer = tokenContainer;
}
public async Task InvokeAsync(HttpContext context, TokenContainer tokenContainer)
{
var header = context.Request.Headers.Authorization;
tokenContainer.SetToken(header);
await _requestDelegate(context);
}
TokenContainer.cs:
public class TokenContainer
{
public string BearerToken { get; private set; }
public void SetToken(string token) => BearerToken = token;
}
HeaderHandler.cs:
public class HeaderHandler : DelegatingHandler
{
TokenContainer _tokenContainer;
public HeaderHandler()
{
}
public HeaderHandler(TokenContainer tokenContainer)
{
_tokenContainer = tokenContainer;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// for every request sent via the http client, intercept & add the bearer token header.
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", _tokenContainer.BearerToken);
// continue with request pipeline
return await base.SendAsync(request, cancellationToken);
}
}
FooGateway.cs:
public class FooGateway : IFooGateway
{
private readonly HttpClient _httpClient;
private readonly IConfiguration _configuration;
private readonly string _context = String.Empty;
public FooGateway(HttpClient httpClient, IConfiguration configuration)
{
_configuration = configuration;
_context = configuration["FooContext"];
_httpClient = httpClient;
}
public void DoSomething()
{
_httpClient.PostAsync("/blabla");
}
}
So, the idea was that the bearer token for every incoming request will be stored in a class called TokenContainer and the HttpHandler will add it to all the outgoing requests.
However, what is happening is that the token is stored in the TokenContainer but the HeaderHandler gets a different instance of TokenContainer in its constructor with its BearerToken property set to null.
Can someone please explain why the same instance of TokenContainer from the middleware is not being passed into the HeaderHandler?
The issue you are seeing is because the lifetime of the HttpMessageHandler is not the same as the lifetime of the request: usually, the same handler will be reused across many requests and be controlled separately on expiration timers and such.
You should not expect that a service injected into your message handler will be the same object that is injected outside it when it is registered as scoped.
https://andrewlock.net/understanding-scopes-with-ihttpclientfactory-message-handlers/#scope-duration-in-ihttpclientfactory
As the article suggests, to use the same scoped instance as you do outside the handler, you have to rely on IHttpContextAccessor to access the current HttpContext and fetch the service from there. So your handler implementation would look something like this:
public class HeaderHandler : DelegatingHandler
{
IHttpContextAccessor _httpContextAccessor;
public HeaderHandler()
{
}
public HeaderHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var tokenContainer = _httpContextAccessor
.HttpContext
.RequestServices
.GetRequiredService<TokenContainer>();
// for every request sent via the http client, intercept & add the bearer token header.
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", tokenContainer.BearerToken);
// continue with request pipeline
return await base.SendAsync(request, cancellationToken);
}
}
This should make sure that the TokenContainer instance is the same across your current request and http calls.
Remember that to add this functionality you need to add the accessor like this:
services.AddHttpContextAccessor();
I have created an Blazor WebAssembly project and want to provide a WebAPI with one public available function.
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class SystemEvalApiController : ControllerBase
{
public SystemEvalApiController(AppDbContext context, IMapper mapper)
{...}
[Route("LatestEvals")]
[AllowAnonymous]
public ActionResult LatestEvals()
that is my Api controller and I should be able to call it with:
SystemEvalPublicViewModel = await Http
.GetFromJsonAsync<SystemEvalPublicViewModel>(
HttpService.BuildUrl("api/SystemEvalApi/LatestEvals"));
When i am not logged into any account. But instead I get this error:
info: System.Net.Http.HttpClient.JPB.BorannRemapping.ServerAPI.LogicalHandler[100]
Start processing HTTP request GET https://localhost:44330/api/SystemEvalApi/LatestEvals
blazor.webassembly.js:1 info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed.
It looks like the "DefaultAuthorizationService" does not recognize the Anonymous attribute but I cannot find the point where it fails directly.
How do I declare an WebAPI function to be accessable from the HttpClient without Login.
Microsoft.AspNetCore.Components.WebAssembly.Server 3.2.0.-rc1.20223.4
Edit:
Here is the declaration for ClientServices:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddHttpClient("JPB.BorannRemapping.ServerAPI", client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
})
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("JPB.BorannRemapping.ServerAPI"));
builder.Services.AddTransient(e => new HttpService(e.GetService<HttpClient>()));
builder.Services.AddApiAuthorization();
builder.Services.AddBlazoredLocalStorage();
await builder.Build().RunAsync();
So each time you acquire an HttpClient it use the BaseAddressAuthorizationMessageHandler which try to authentify the request. But it this case your request should not be authentified, so you can make something like :
Registration
builder.Services.AddHttpClient("JPB.BorannRemapping.ServerAPI.Anonymous", client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
});
Usage
#inject IHttpClientFactory _factory
#code {
...
var httpClient = _factory.CreateClient("JPB.BorannRemapping.ServerAPI.Anonymous");
var httpService = new HttpService(httpClient);
SystemEvalPublicViewModel = await httpClient
.GetFromJsonAsync<SystemEvalPublicViewModel>(
httpService.BuildUrl("api/SystemEvalApi/LatestEvals"));
}
Building on the answer from #agua from mars.
Registration in Program.cs
You could add 2 named HttpClient to the services collection (the first for authenticated calls the second for anonymous):
builder.Services.AddHttpClient("YourProject.ServerAPI",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddHttpClient("YourProject.ServerAPI.Anonymous",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("YourProject.ServerAPI"));
//Register a new service for getting an Anonymous HttpClient
builder.Services.AddScoped<IHttpAnonymousClientFactory, HttpAnonymousClientFactory>();
Add new Interface and Implementation for Dependency Injection:
public interface IHttpAnonymousClientFactory
{
HttpClient HttpClient { get; }
}
public class HttpAnonymousClientFactory : IHttpAnonymousClientFactory
{
private readonly IHttpClientFactory httpClientFactory;
public HttpAnonymousClientFactory(IHttpClientFactory httpClientFactory)
{
this.httpClientFactory = httpClientFactory;
}
public HttpClient HttpClient => httpClientFactory.CreateClient("YourProject.ServerAPI.Anonymous");
}
Usage in Razor Component (for Anonymous HttpClient)
[Inject]
private IHttpAnonymousClientFactory httpAnonymousClientFactory { get; set; }
private MyViewModel myModel;
protected override async Task OnInitializedAsync()
{
myModel = await httpAnonymousClientFactory.HttpClient.GetFromJsonAsync<MyViewModel>($"api/mycontroller/myendpoint");
}
Usage in Razor Component (for Authenticated HttpClient)
[Inject]
private HttpClient httpClient { get; set; }
private MyOtherViewModel myModel;
protected override async Task OnInitializedAsync()
{
myModel = await httpClient.GetFromJsonAsync<MyOtherViewModel>($"api/mycontroller/mysecureendpoint");
}
I am adding claims transformation into my Blazor (server-side) application. I am creating an HTTP Web API service using DI. Below is the start up code.
services.AddHttpClient<IAPIClient, APIClient>();
services.AddScoped<IClaimsTransformation, ClaimsLoader>();
I would like to then use claims transformation to call this Web API once I am authenticated. Which looks like:
public class ClaimsLoader : IClaimsTransformation
{
private readonly HttpClient _apiClient;
private readonly IHttpContextAccessor _httpAccessor;
public ClaimsLoader(IHttpContextAccessor httpAccessor, HttpClient apiClient)
{
_httpAccessor = httpAccessor;
_apiClient = apiClient;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (ClaimsIdentity)principal.Identity;
var claimsIdentity = new ClaimsIdentity(
identity.Claims,
identity.AuthenticationType,
identity.NameClaimType,
identity.RoleClaimType);
...claims Web API call
return new ClaimsPrincipal(claimsIdentity);
}
}
This is the Web API client setup:
public class APIClient : IAPIClient
{
private readonly HttpClient _httpClient;
public APIClient(IHttpContextAccessor httpAccessor, HttpClient client, IConfiguration configuration)
{
var accessToken = httpAccessor.HttpContext.GetTokenAsync("access_token").Result;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.BaseAddress = new Uri(configuration["Api_Location"]);
_httpClient = client;
}
....
}
The problem arises because I don't understand DI that well. I want to use the APIClient that I created before it but, I am not sure how to pass that into the ClaimsLoader. I assume that it would be something like:
private readonly IHttpContextAccessor _httpAccessor;
private readonly IAPIClient _apiClient;
public ClaimsLoader(IHttpContextAccessor httpAccessor, IAPIClient apiClient)
{
_httpAccessor = httpAccessor;
_apiClient = apiClient;
}
But trying this cause the app to hang when starting. What am I missing? Ihave the API call create and working so that it will return a list of claims.
UPDATE 10/2/2019
I think I have found the issue is related to an infinite loop caused by the following line:
var accessToken = _httpAccessor.HttpContext.GetTokenAsync("access_token").Result;
This causes a call to AuthenticateAsync which in turn then calls this line again. Is there a way to get the Bearer token differently so as not to cause this loop?
I have this code in Blazor 3.0.0-preview4-19216-03 targeting a client app:
namespace BlazorShared.Services
{
public interface ILogin
{
Task<string> Login();
}
public class LoginService : ILogin
{
private HttpClient _client;
public LoginService(HttpClient client)
{
_client = client;
}
public async Task<string> Login()
{
var myclient = new HttpClient();
var responseMessage = await myclient.GetAsync("http://www.google.es");
var content = await responseMessage.Content.ReadAsStringAsync();
Debug.WriteLine(content);
return content;
}
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ILogin, LoginService>();
}
public void Configure(IComponentsApplicationBuilder app)
{
app.AddComponent<App>("app");
}
}
and this HTML
#functions {
public async Task Submit()
{
var str = await LoginService.Login(null, null, null);
Console.WriteLine(str);
}
}
Full Razor file: https://pastebin.com/3LbQQvk0
I have tested it and the web request never gets done and I'm not able to show the service response in the client. I have tried debugging in chrome following the instructions And I see the service method is being called but the constructor of the service is not, and as I understood Blazor should inject the HttpClient. Any ideas what can be happening? Thanks.
The following is wrong even if it is not the culprit...
In the LoginService you define an HttpClient variable into which you assign an instance of HttpClient provided by DI. On the other hand, you define a new HttpClient object named myclient and use it in the Login method.
You should use the object provided by DI. You shouldn't yourself define HttpClient objects. Why ? Because Blazor configure for you the HttpClient it creates. For instance, setting the Document base URI, which enable your web app to be navigated as an SPA app.
Hope this helps...
I want to set a default header for every method in the UserHttpClient but I don`t want that every method is doing that, I want to do it in a general way.
The problem I see with the current implementation is, that when I call one method the _client gets disposed thus at the next call within a Http Request the _client is not initialized, as this happens within the constructor.
The UserHttpClient is registered via DI as per Http Request.
I also do not want to create a private/base method where I pass the _client and do the header addition there.
How would you solve that problem?
public class UserHttpClient : IUserRemoteRepository
{
private readonly string baseUrl = ConfigurationManager.AppSettings["baseUrl"];
private readonly string header = ConfigurationManager.AppSettings["userHeader"];
private readonly HttpClient _client;
public ServiceProductDataProvider(string toolSystemKeyHeader)
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Add(header, token);
}
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
using (_client)
{
// do stuff
var users = await _client.GetAsync("url here");
}
}
public async Task<UserDto> GetUser(Guid userId)
{
using (_client)
{
// do stuff
var users = await _client.GetAsync("url here");
}
}
}
The class UserHttpClient has a member that is IDisposable (private readonly HttpClient _client;). That means that the UserHttpClient should also implement IDisposable:
public void Dispose()
{
_client.Dispose();
}
Then, the class/code that is using UserHttpClient is responsible for Disposing it after it's done with it. If the instance is injected, then the DI framework you use probably handles disposing it automatically at the end of the request. What's left for you then is to simply remove the using blocks from the implementation:
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
// do stuff
var users = await _client.GetAsync("url here");
}
---- EDIT ----
You could also work around the issue by not reusing the HttpClient:
private string _toolSystemKeyHeader;
public ServiceProductDataProvider(string toolSystemKeyHeader)
{
_toolSystemKeyHeader = toolSystemKeyHeader
}
private HttpClient GetClientInstance()
{
HttpClient _client = new HttpClient();
_client.DefaultRequestHeaders.Add(header, _toolSystemKeyHeader); //?? in your original code, the toolSystemKeyHeader is not used, but I guess it is the token..?
return _client;
}
And:
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
using (var _client = GetClientInstance())
{
// do stuff
var users = await _client.GetAsync("url here");
}
}