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...
Related
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;
}
I'm looking into using IHttpClientFactory for calling external APIs in my asp.net core app. I've seen a few examples where the client factory is created in the constructor of the service's class. Then the methods of that class, call that client factory to generate an instance of HttpClient to make Http requests. Like the following sample code:
public class MyTransientService: IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyTransientService(
IHttpClientFactory clientFactory
)
{
_clientFactory = clientFactory;
}
public async Task<MyData> GetData()
{
//construct the request
var httpClient = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
...
}
}
If the service is registered as transient in startup.cs, wouldn't a new instance of HttpClientFactory be generated each time that the service is called? A new HttpClientFactory per request? So wouldn't the following be a more efficient way to use the factory?
public class MyTransientService: IMyService
{
private readonly HttpClient _client;
public MyTransientService(
HtpClient client
)
{
_client = client;
}
public async Task<MyData> GetData()
{
Uri uri = new Uri(StaticUtils.AddQueryString(url, props));
var response = await _client.SendAsync(request);
...
}
}
I would consider creating the HttpClient yourself bad practice, on self you have control of how many is created. If MyTransientService is transient, you will end up creating a lot of socket connections (one for each instance/request) HttpClient is created to be reused.
Take a look at Typed clients: https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient<MyTransientService>();
services.AddControllers();
}
public class MyTransientService: IMyService
{
private readonly HttpClient _client;
public MyTransientService(
HtpClient client
)
{
_client = client;
}
public async Task<MyData> GetData()
{
Uri uri = new Uri(StaticUtils.AddQueryString(url, props));
var response = await _client.SendAsync(request);
...
}
}
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 have created this class for getting the Header value from requests.
public class AuthenticationHeader
{
private static IHttpContextAccessor _httpContextAccessor;
public AuthenticationHeader(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string AuthHeader => _httpContextAccessor.HttpContext?.Request.Headers["Authorization"];
}
and that I have registered that in my startup.cs like this
services.AddSingleton<AuthenticationHeader>();
And its been injected into my other classes like this.
public BaseClient(HttpClient client, ILogger<BaseClient> logger, AuthenticationHeader authHeader)
{
_client = client;
client.BaseAddress = new Uri("yrl");
client.DefaultRequestHeaders.Add("Accept", "application/json");
_logger = logger;
AuthHeader = authHeader;
}
Now as I have registered that as Singleton. So when call my Api for first time and provide the Authorization value in header the api is called successfully but the issue is when i pass empty Authorization header it still call's api successfully as it is storing old header value due to Singleton. How can I fix this? Is there any otherways to do what I am doing.
Try using HttpClientFactory, that was added Asp.Net Core 2.1, in conjunction with HttpMessageHandler to achieve what you are trying to do.
You can register the HttpClient in ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<BaseClient>(client =>
{
client.BaseAddress = new Uri("yrl");
client.DefaultRequestHeaders.Add("Accept", "application/json");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
}
With the above code in place, your BaseClient will receive the HttpClient instance via DI.
In order to validate/inspect the AuthHeader you can configure the HttpMessageHandler for the registered HttpClient. The code for the message handler is simple like below:
public class AuthHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("Authorization"))
{
return new HttpResponseMessage(HttpStatusCode.Forbidden)
{
Content = new StringContent("No Authorization header is present")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
In order to register the above handler, your code will look like below:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<AuthHeaderHandler>();
services.AddHttpClient<BaseClient>(client =>
{
//code omitted for brevity
...
})
.AddHttpMessageHandler<AuthHeaderHandler>();
}
You can inject whatever you need inside the message handler if needed. However, no need to inject the IHttpContextAccessor in the BaseClient. To read more about HttpClientFactory and HttpMessageHandlers please see this link and this. I hope this helps.
UPDATED ANSWER
Please have a look at the more concrete example of HttpMessageHandler that uses the IHttpContextAccessor and modifies the HttpRequestMessage i.e. adds the Authorization header before the call is made. You can modify the logic as per your need.
public class AuthHeaderHandler : DelegatingHandler
{
private readonly HttpContext _httpContext;
public AuthHeaderHandler(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (_httpContext != null)
{
var accessToken = await _httpContext.GetTokenAsync(TokenKeys.Access);
if (!string.IsNullOrEmpty(accessToken))
{
// modify the request header with the new Authorization token
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
}
return await base.SendAsync(request, cancellationToken);
}
}
UPDATED ANSWER 2
Please have a look at the simple solution that I have uploaded to GitHub. The solution is even simpler than I originally suggested. As you are not integrating any identity-based Authentication/Authorization, you can simply use a CustomActionFilter, I called it ValidateAuthHeader, to check if the AuthHeader is present or not and return the usual 403 if absent.
Within the ValidateAuthHeader, I have utilised the middleware code that you posted earlier. You can then simply add this attribute on the ActionMethods or Controllers which require this check.
Please have a look at the DataController and ValuesController. The DataController will receive the typed HttpClient that will be used to call the values endpoint. ValidateAuthHeader is present on the GetValues and will check for the AuthHeader. If it's absent it will generate the error.
[Route("api/[controller]")]
[ApiController]
public class DataController : ControllerBase
{
private readonly MyHttpClient _client;
public DataController(MyHttpClient client)
{
_client = client;
}
[ValidateAuthHeader]
public async Task<IActionResult> GetValues()
{
var response = await _client.GetAsync("api/values");
var contents = await response.Content.ReadAsStringAsync();
return new ContentResult
{
Content = contents,
ContentType = "application/json",
StatusCode = 200
};
}
}
The rest of the flow is the same as I originally suggested. The call will be passed through the AuthHeaderHandler which is an HttpMessageHandler for the registered MyHttpClient. Please have a look at the Startup.cs.
The handler will retrieve the HttpContext via HttpContextAccessor and will check for the AuthHeader. If present, it will add it to the RequestMessage parameter.
I hope this helps. Feel free to ask any questions that you may have.
Setting Auth Header without using HttpMessageHandler
Modify the MyHttpClient and add a public method called SetAuthHeader
public class MyHttpClient
{
private readonly HttpClient _httpClient;
public MyHttpClient(HttpClient client)
{
_httpClient = client;
}
public void SetAuthHeader(string value)
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", value);
}
}
Then call this method in your action method as you will have the AuthHeader in the HttpContext.Request at that point
[ValidateAuthHeader]
public async Task<IActionResult> GetValues()
{
var authHeader = Request.Headers["Authorization"];
_client.SetAuthHeader(authHeader.First());
var response = await _client.GetAsync("api/values");
var contents = await response.Content.ReadAsStringAsync();
return new ContentResult
{
Content = contents,
ContentType = "application/json",
StatusCode = 200
};
}
Remove the AuthHeaderHandler registration and delete the AuthHeaderHandler.
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");
}
}