microsoft / kiota-http-dotnet

Kiota http provider implementation for dotnet with HttpClient
https://aka.ms/kiota/docs
MIT License
35 stars 15 forks source link

Blazor WASM client unable to send web requests through HttpClient BaseAddress #109

Closed gridlocdev closed 1 year ago

gridlocdev commented 1 year ago

This issue occurs for Blazor WASM applications consuming an OpenAPI spec that does not supply a base URL. Blazor WASM does not support the System.Net.Http.HttpClientHandler class, so those apps must supply an HttpClient to the HttpClientRequestAdapter to work.

The issue is that the BaseAddress property on the supplied HttpClient object does not infer that as the HttpClientRequestAdapter's BaseUrl, and it must be set separately for requests to be sent appropriately.

How to reproduce:

  1. Create a Blazor WASM application with .NET 7 using the empty template (For the sake of example, named "KiotaTestApp.FrontEnd")

  2. Generate a client from an API spec that does not supply a Base URL. E.g. (kiota generate -d http://<url>/swagger/v1/swagger.json -n KiotaTestApp.FrontEnd.Client -o ./Client -l CSharp -c ApiClient

    Note: The dotnet-aspnet-codegenerator tool can be used to scaffold an API with an OpenAPI spec that reproduces the above scenario.

  3. Note the following warning that appears in the console output:

    warn: Kiota.Builder.KiotaBuilder[0]
         OpenAPI warning: #/ - A servers entry (v3) or host + basePath + schemes properties (v2) was not present in the OpenAPI description. The root URL will need to be set manually with the request adapter.
    warn: Kiota.Builder.KiotaBuilder[0]
         No server url found in the OpenAPI document. The base url will need to be set when using the client.
  4. Add the Kiota API client service setup in a Blazor WASM application's Program.cs file like so:

    // ...
    var authProvider = new AnonymousAuthenticationProvider();
    var client = new HttpClient { BaseAddress = new Uri("http://<url>") };
    var requestAdapter = new HttpClientRequestAdapter(authProvider, httpClient: client );
    builder.Services.AddScoped<ApiClient>(_ => new ApiClient(requestAdapter));
    // ...
  5. In the Index.razor (or any other page) inject the client and create a method that sends an HTTP request with it

  6. Launch the application, and view that requests are not sent to the HttpClient's BaseAddress

Workaround

Explicitly setting the BaseUrl after adding an HttpClient applies the parameter value, allowing the client to send requests.

// ...
var authProvider = new AnonymousAuthenticationProvider();
var client = new HttpClient { BaseAddress = new Uri("http://<url>") };
var requestAdapter = new HttpClientRequestAdapter(authProvider, httpClient: client );
requestAdapter.BaseUrl = "http://<url>";
builder.Services.AddScoped<ApiClient>(_ => new ApiClient(requestAdapter));
// ...
andrueastman commented 1 year ago

Thanks for raising this @gridlocdev,

This is working as intended as the generated client will use the BaseUrl from the RequestAdapter instance not from the HttpClient.

No server url found in the OpenAPI document. The base url will need to be set when using the client.

As the warning suggests, passing an input document that does not supply a server url will require you to set the BaseUrl for the request adapter. Related to https://github.com/microsoft/kiota/issues/2046

var requestAdapter = new HttpClientRequestAdapter(authProvider, httpClient: client );
requestAdapter.BaseUrl = "http://<url>";
gridlocdev commented 1 year ago

@andrueastman I understand that right now the BaseUrl is applied within the content of the message that the HTTP client sends and not on it itself, I guess a better question to ask is:

Would there be any issues regarding populating the RequestAdapter instance's BaseUrl with the HttpClient's BaseAddress in its constructor when an HttpClient is supplied as a constructor parameter?

More specifically, it could be implemented like this (snippet from the kiota dotnet http library repository):

Current Implementation:

public HttpClientRequestAdapter(IAuthenticationProvider authenticationProvider, IParseNodeFactory? parseNodeFactory = null, ISerializationWriterFactory? serializationWriterFactory = null, HttpClient? httpClient = null, ObservabilityOptions? observabilityOptions = null)
{
    authProvider = authenticationProvider ?? throw new ArgumentNullException(nameof(authenticationProvider));
    createdClient = httpClient == null;
    client = httpClient ?? KiotaClientFactory.Create();
    pNodeFactory = parseNodeFactory ?? ParseNodeFactoryRegistry.DefaultInstance;
    sWriterFactory = serializationWriterFactory ?? SerializationWriterFactoryRegistry.DefaultInstance;
    obsOptions = observabilityOptions ?? new ObservabilityOptions();
    activitySource = new(obsOptions.TracerInstrumentationName);
}

Modified Implementation:

public HttpClientRequestAdapter(IAuthenticationProvider authenticationProvider, IParseNodeFactory? parseNodeFactory = null, ISerializationWriterFactory? serializationWriterFactory = null, HttpClient? httpClient = null, ObservabilityOptions? observabilityOptions = null)
{
    authProvider = authenticationProvider ?? throw new ArgumentNullException(nameof(authenticationProvider));
    createdClient = httpClient == null;
    client = httpClient ?? KiotaClientFactory.Create();
    BaseUrl = client.BaseAddress; // <-- This line was added
    pNodeFactory = parseNodeFactory ?? ParseNodeFactoryRegistry.DefaultInstance;
    sWriterFactory = serializationWriterFactory ?? SerializationWriterFactoryRegistry.DefaultInstance;
    obsOptions = observabilityOptions ?? new ObservabilityOptions();
    activitySource = new(obsOptions.TracerInstrumentationName);
}
baywet commented 1 year ago

Thanks for the detailed information here. I don't see any issue with using the base URL from the http client so consumers don't have to set it up twice. We might want to check if the property is null/empty first. (I can never remember whether property initialization happens before or after the constructor execution). Would you be willing to submit a pull request for this?

gridlocdev commented 1 year ago

@baywet Sure thing! I've just submitted https://github.com/microsoft/kiota-http-dotnet/pull/108.