unoplatform / uno.extensions

Libraries to ease common developer tasks associated with building multi-platform mobile, desktop and web applications using Uno Platform or WinAppSDK.
https://platform.uno/
Other
73 stars 45 forks source link

[Http] Add support for Kiota #2510

Open kazo0 opened 1 month ago

kazo0 commented 1 month ago

Description

Creating a new project for Kiota under Http and Serialization for Kiota. Issue : https://github.com/unoplatform/uno.extensions/issues/2459, Draft : https://github.com/unoplatform/uno.extensions/pull/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";
                })
            );
        });
    ...
}
  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 commented 1 month ago

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: https://github.com/unoplatform/uno.extensions/blob/f677bd295bc6d689e675b854f33a8ccee8e733ec/src/Uno.Extensions.Http.Refit/ServiceCollectionExtensions.cs#L59-L77

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 commented 1 month ago

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 commented 1 month ago

@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 commented 1 month ago

@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 commented 1 month ago

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 commented 1 month ago

@nickrandolph @kazo0 i made changes to add the kiota handler as a part of the same draft PR so its easier to follow
https://github.com/unoplatform/uno.extensions/pull/2509

kazo0 commented 1 month ago

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: https://github.com/microsoft/kiota/issues/3816#issuecomment-1962445644

Kunal22shah commented 1 month ago

so i followed their approach mentioned here : https://github.com/unoplatform/uno.extensions/pull/2509