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

[Http] Add support for Kiota #2510

Open
kazo0 opened this issue Aug 14, 2024 · 8 comments · May be fixed by #2509
Open

[Http] Add support for Kiota #2510

kazo0 opened this issue Aug 14, 2024 · 8 comments · May be fixed by #2509
Assignees
Labels
area/http kind/enhancement New feature or request.

Comments

@kazo0
Copy link
Contributor

kazo0 commented Aug 14, 2024

Description

Creating a new project for Kiota under Http and Serialization for Kiota. Issue : #2459, Draft : #2509

  1. ServiceCollectionExtensions for Kiota: will facilitate the registration of Kiota clients, similar to how Refit is handled in Uno.Extensions.
public static IServiceCollection AddKiotaClient<TClient>(
    this IServiceCollection services,
    HostBuilderContext context,
    EndpointOptions? options = null,
    string? name = null,
    Func<IHttpClientBuilder, EndpointOptions?, IHttpClientBuilder>? configure = null
)
    where TClient : class
{
    // Registration logic for Kiota client
}

Usage

protected override void OnLaunched(LaunchActivatedEventArgs args)
{
    var appBuilder = this.CreateBuilder(args)
        .Configure(hostBuilder =>
        {
            hostBuilder.UseHttp((context, services) =>
                services.AddKiotaClient<IMyApiClient>(context, options =>
                {
                    options.BaseUrl = "https://localhost:5002";
                })
            );
        });
    ...
}
  • Allowing the configuration of Kiota clients via appsettings.json , similar to existing Http extensions.
{
    "MyApiClient": {
        "Url": "https://localhost:5002",
        "UseNativeHandler": true
    }
}
  1. Integrate Kiota’s serialization with Uno.Extensions.Serialization similar to Refit

Implement a KiotaSerializerAdapter to manage the serialization and deserialization of Kiota-generated models

public class KiotaSerializerAdapter : ISerializer
{
    // Serialization and deserialization logic
}
  1. Middleware for error-handling and auth

Use HttpClientRequestAdapter :
It will include support for IRequestAdapter and IAuthenticationProvider to handle requests and authentication

services.AddSingleton<IRequestAdapter, HttpClientRequestAdapter>(sp =>
{
    var authProvider = sp.GetRequiredService<IAuthenticationProvider>();
    // Middleware and authentication integration logic
});
@kazo0 kazo0 added kind/enhancement New feature or request. triage/untriaged Indicates an issue requires triaging or verification. labels Aug 14, 2024
@kazo0 kazo0 removed the triage/untriaged Indicates an issue requires triaging or verification. label Aug 14, 2024
@kazo0
Copy link
Contributor Author

kazo0 commented Aug 14, 2024

Notes from @nickrandolph

The trick with integrating Kiota so that it can take advantage of auth is to make sure the HttpClient is generated by the service container

auth registers additional http handlers that are used to set cookie or http header with the current access token

auth also handles the token refresh in the case of authorization challenges

all of which is transparent if you get the HttpClient from the service container

@Kunal22shah we would need to register the HttpClient through the Http Extensions like we do in Refit with the AddClientWithEndpoint extension method here:

return services.AddClientWithEndpoint<TInterface, TEndpoint>(
context,
options,
name: name,
httpClientFactory: (s, c) => Refit.HttpClientFactoryExtensions.AddRefitClient<TInterface>(s, settingsAction: serviceProvider =>
{
var serializer = serviceProvider.GetService<IHttpContentSerializer>();
var settings = serializer is not null ? new RefitSettings() { ContentSerializer = serializer } : new RefitSettings();
var auth = serviceProvider.GetService<IAuthenticationTokenProvider>();
if (auth is not null)
{
settings.AuthorizationHeaderValueGetter = () => auth.GetAccessToken();
}
settingsBuilder?.Invoke(serviceProvider, settings);
return settings;
}),
configure: configure);

So we'd need to use the registered HttpClient from that extension method and pass that one along to the HttpClientRequestAdapter I believe.

Same for the IAuthenticationProvider stuff, this is a bit confusing as both the Auth extensions from Uno and the Kiota packages define a IAuthenticationProvider interface.

@kazo0
Copy link
Contributor Author

kazo0 commented Aug 14, 2024

We need to dig deeper and brainstorm exactly what will be the implementation of AddKiotaClient. It's within that extension method that we should be registering the client with the requestadapter and all of that.

@kazo0
Copy link
Contributor Author

kazo0 commented Aug 14, 2024

@kazo0 we could register the HttpClient through the Http Extensions similar to how it's done for Refit. This will allow us touse the existing AddClientWithEndpoint pattern something like:

public static IServiceCollection AddKiotaClient<TClient>(
    this IServiceCollection services,
    HostBuilderContext context,
    EndpointOptions? options = null,
    string? name = null,
    Func<IHttpClientBuilder, EndpointOptions?, IHttpClientBuilder>? configure = null
)
    where TClient : class
{
    return services.AddKiotaClientWithEndpoint<TClient, EndpointOptions>(context, options, name, configure);
}

and then do something like this for AddClientWithEndpoint : (not sure if we want a custom auth handler for kiota?)

services.AddClientWithEndpoint<TClient, TEndpoint>(...)
			.ConfigurePrimaryHttpMessageHandler(() =>
			{
				var serviceProvider = s.BuildServiceProvider();
				var authProvider = serviceProvider.GetRequiredService<IAuthenticationProvider>();
				return new AuthenticationHandler(authProvider); // Custom Auth Handler?
			})
			.ConfigureHttpClient(client =>
			{
				if (options?.Url != null)
				{
					client.BaseAddress = new Uri(options.Url);
				}
			}),
		configure: configure
	)
	.AddSingleton<IRequestAdapter, HttpClientRequestAdapter>(sp =>
	{
		var authProvider = sp.GetRequiredService<IAuthenticationProvider>(); 
		var parseNodeFactory = new Microsoft.Kiota.Serialization.Json.JsonParseNodeFactory();
		var serializationWriterFactory = new Microsoft.Kiota.Serialization.Json.JsonSerializationWriterFactory();
		var httpClient = sp.GetRequiredService<HttpClient>();
		return new HttpClientRequestAdapter(authProvider, parseNodeFactory, serializationWriterFactory, httpClient);
	})
	.AddSingleton<TClient>(sp =>
	{
		var requestAdapter = sp.GetRequiredService<IRequestAdapter>();
		return (TClient)Activator.CreateInstance(typeof(TClient), requestAdapter)!;
	});
	
	```
	
	i had a similar approach in chefs : https://github.com/unoplatform/uno.chefs/blob/fe5971f955bbc92847655a688b3b2d6ccf3e21e5/src/Chefs/App.xaml.cs#L83-L100
	
	

@nickrandolph
Copy link
Contributor

@kazo0 we could register the HttpClient through the Http Extensions similar to how it's done for Refit. This will allow us touse the existing AddClientWithEndpoint pattern something like:

public static IServiceCollection AddKiotaClient<TClient>(
    this IServiceCollection services,
    HostBuilderContext context,
    EndpointOptions? options = null,
    string? name = null,
    Func<IHttpClientBuilder, EndpointOptions?, IHttpClientBuilder>? configure = null
)
    where TClient : class
{
    return services.AddKiotaClientWithEndpoint<TClient, EndpointOptions>(context, options, name, configure);
}

and then do something like this for AddClientWithEndpoint : (not sure if we want a custom auth handler for kiota?)

services.AddClientWithEndpoint<TClient, TEndpoint>(...)
			.ConfigurePrimaryHttpMessageHandler(() =>
			{
				var serviceProvider = s.BuildServiceProvider();
				var authProvider = serviceProvider.GetRequiredService<IAuthenticationProvider>();
				return new AuthenticationHandler(authProvider); // Custom Auth Handler?
			})
			.ConfigureHttpClient(client =>
			{
				if (options?.Url != null)
				{
					client.BaseAddress = new Uri(options.Url);
				}
			}),
		configure: configure
	)
	.AddSingleton<IRequestAdapter, HttpClientRequestAdapter>(sp =>
	{
		var authProvider = sp.GetRequiredService<IAuthenticationProvider>(); 
		var parseNodeFactory = new Microsoft.Kiota.Serialization.Json.JsonParseNodeFactory();
		var serializationWriterFactory = new Microsoft.Kiota.Serialization.Json.JsonSerializationWriterFactory();
		var httpClient = sp.GetRequiredService<HttpClient>();
		return new HttpClientRequestAdapter(authProvider, parseNodeFactory, serializationWriterFactory, httpClient);
	})
	.AddSingleton<TClient>(sp =>
	{
		var requestAdapter = sp.GetRequiredService<IRequestAdapter>();
		return (TClient)Activator.CreateInstance(typeof(TClient), requestAdapter)!;
	});
	

i had a similar approach in chefs : https://github.com/unoplatform/uno.chefs/blob/fe5971f955bbc92847655a688b3b2d6ccf3e21e5/src/Chefs/App.xaml.cs#L83-L100

I don't recall how much implementation there was in the refit support but it looks like there's quite a bit here that I would have assumed is not required (eg configurehttpclient) if we're picking up the HttpClient from extensions but perhaps this is required for Kiota, which I'm not that familiar with.

@kazo0
Copy link
Contributor Author

kazo0 commented Aug 14, 2024

We should be able to do something like this:

https://learn.microsoft.com/en-us/openapi/kiota/tutorials/dotnet-dependency-injection#create-extension-methods

Where we can add handlers to the IHttpClientBuilder

@Kunal22shah
Copy link
Contributor

@nickrandolph @kazo0 i made changes to add the kiota handler as a part of the same draft PR so its easier to follow
#2509

@kazo0
Copy link
Contributor Author

kazo0 commented Aug 14, 2024

From what I see as part of the Refit implementation, the Refit extensions are responsible for the HttpClient creation, or at least responsible for providing the IHttpClientBuilder here

Below is a working example of how we could provide the IHttpClientBuilder from the Kiota extensions, through the AddHttpClient call from the example below:
microsoft/kiota#3816 (comment)

@Kunal22shah
Copy link
Contributor

so i followed their approach mentioned here : #2509

  • AddKiotaClient and AddKiotaClientWithEndpoint : registering the Kiota client in the dependency injection container. Including setting up the base URL, attaching Kiota handlers, and configuring the HttpClientRequestAdapter with the necessary components (IAuthenticationProvider, JsonParseNodeFactory, and JsonSerializationWriterFactory).
  • AddKiotaHandlers - dynamically registers Kiota handlers in the DI container. following this https://learn.microsoft.com/en-us/openapi/kiota/tutorials/dotnet-dependency-injection#create-extension-methods
  • AttachKiotaHandlers - attaches the previously registered Kiota handlers to the IHttpClientBuilder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/http kind/enhancement New feature or request.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants