8000 Added methods to support creating RefitSettings using the service provider by david-driscoll · Pull Request #1112 · reactiveui/refit · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Added methods to support creating RefitSettings using the service provider #1112

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -790,17 +790,17 @@ There may be times when you want to know what the target interface type is of th
have a derived interface that implements a common base like this:

```csharp
public interface IGetAPI<TEntity>
public interface IGetAPI<TEntity>
{
[Get("/{key}")]
Task<TEntity> Get(long key);
}

public interface IUsersAPI : IGetAPI<User>
public interface IUsersAPI : IGetAPI<User>
{
}

public interface IOrdersAPI : IGetAPI<Order>
public interface IOrdersAPI : IGetAPI<Order>
{
}
```
Expand Down Expand Up @@ -1099,6 +1099,14 @@ services.AddRefitClient<IWebApi>(settings)
// Add additional IHttpClientBuilder chained methods as required here:
// .AddHttpMessageHandler<MyHandler>()
// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

// or injected from the container
services.AddRefitClient<IWebApi>(provider => new RefitSettings() { /* configure settings */ })
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com"));
// Add additional IHttpClientBuilder chained methods as required here:
// .AddHttpMessageHandler<MyHandler>()
// .SetHandlerLifetime(TimeSpan.FromMinutes(2));

```
Note that some of the properties of `RefitSettings` will be ignored because the `HttpClient` and `HttpClientHandlers` will be managed by the `HttpClientFactory` instead of Refit.

Expand Down
144 changes: 84 additions & 60 deletions Refit.HttpClientFactory/HttpClientFactoryExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Reflection;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;

namespace Refit
{
Expand All @@ -16,37 +19,7 @@ public static class HttpClientFactoryExtensions
/// <returns></returns>
public static IHttpClientBuilder AddRefitClient<T>(this IServiceCollection services, RefitSettings? settings = null) where T : class
{
services.AddSingleton(provider => RequestBuilder.ForType<T>(settings));

return services.AddHttpClient(UniqueName.ForType<T>())
.ConfigureHttpMessageHandlerBuilder(builder =>
{
// check to see if user provided custom auth token
HttpMessageHandler? innerHandler = null;
if (settings != null)
{
if (settings.HttpMessageHandlerFactory != null)
{
innerHandler = settings.HttpMessageHandlerFactory();
}

if (settings.AuthorizationHeaderValueGetter != null)
{
innerHandler = new AuthenticatedHttpClientHandler(settings.AuthorizationHeaderValueGetter, innerHandler);
}
else if (settings.AuthorizationHeaderValueWithParamGetter != null)
{
innerHandler = new AuthenticatedParameterizedHttpClientHandler(settings.AuthorizationHeaderValueWithParamGetter, innerHandler);
}
}

if(innerHandler != null)
{
builder.PrimaryHandler = innerHandler;
}

})
.AddTypedClient((client, serviceProvider) => RestService.For<T>(client, serviceProvider.GetService<IRequestBuilder<T>>()!));
return AddRefitClient<T>(services, _ => settings);
}

/// <summary>
Expand All @@ -58,35 +31,86 @@ public static IHttpClientBuilder AddRefitClient<T>(this IServiceCollection servi
/// <returns></returns>
public static IHttpClientBuilder AddRefitClient(this IServiceCollection services, Type refitInterfaceType, RefitSettings? settings = null)
{
return services.AddHttpClient(UniqueName.ForType(refitInterfaceType))
.ConfigureHttpMessageHandlerBuilder(builder =>
{
// check to see if user provided custom auth token
HttpMessageHandler? innerHandler = null;
if (settings != null)
{
if (settings.HttpMessageHandlerFactory != null)
{
innerHandler = settings.HttpMessageHandlerFactory();
}

if (settings.AuthorizationHeaderValueGetter != null)
{
innerHandler = new AuthenticatedHttpClientHandler(settings.AuthorizationHeaderValueGetter, innerHandler);
}
else if (settings.AuthorizationHeaderValueWithParamGetter != null)
{
innerHandler = new AuthenticatedParameterizedHttpClientHandler(settings.AuthorizationHeaderValueWithParamGetter, innerHandler);
}
}

if (innerHandler != null)
{
builder.PrimaryHandler = innerHandler;
}

})
.AddTypedClient(refitInterfaceType, (client, serviceProvider) => RestService.For(refitInterfaceType, client, settings));
return AddRefitClient(services, refitInterfaceType, _ => settings);
}

/// <summary>
/// Adds a Refit client to the DI container
/// </summary>
/// <typeparam name="T">Type of the Refit interface</typeparam>
/// <param name="services">container</param>
/// <param name="settingsAction">Optional. Action to configure refit settings. This method is called once and only once, avoid using any scoped dependencies that maybe be disposed automatically.</param>
/// <returns></returns>
public static IHttpClientBuilder AddRefitClient<T>(this IServiceCollection services, Func<IServiceProvider, RefitSettings?>? settingsAction) where T : class
{
services.AddSingleton(provider => new SettingsFor<T>(settingsAction?.Invoke(provider)));
services.AddSingleton(provider => RequestBuilder.ForType<T>(provider.GetRequiredService<SettingsFor<T>>().Settings));

return services
.AddHttpClient(UniqueName.ForType<T>())
.ConfigureHttpMessageHandlerBuilder(builder =>
{
// check to see if user provided custom auth token
if (CreateInnerHandlerIfProvided(builder.Services.GetRequiredService<SettingsFor<T>>().Settings) is {} innerHandler)
{
builder.PrimaryHandler = innerHandler;
}
})
.AddTypedClient((client, serviceProvider) => RestService.For<T>(client, serviceProvider.GetService<IRequestBuilder<T>>()!));
}

/// <summary>
/// Adds a Refit client to the DI container
/// </summary>
/// <param name="services">container</param>
/// <param name="refitInterfaceType">Type of the Refit interface</param>
/// <param name="settingsAction">Optional. Action to configure refit settings. This method is called once and only once, avoid using any scoped dependencies that maybe be disposed automatically.</param>
/// <returns></returns>
public static IHttpClientBuilder AddRefitClient(this IServiceCollection services, Type refitInterfaceType, Func<IServiceProvider, RefitSettings?>? settingsAction)
{
var settingsType = typeof(SettingsFor<>).MakeGenericType(refitInterfaceType);
var requestBuilderType = typeof(IRequestBuilder<>).MakeGenericType(refitInterfaceType);
services.AddSingleton(settingsType, provider => Activator.CreateInstance(typeof(SettingsFor<>).MakeGenericType(refitInterfaceType)!, settingsAction?.Invoke(provider))!);
services.AddSingleton(requestBuilderType, provider => RequestBuilderGenericForTypeMethod.MakeGenericMethod(refitInterfaceType).Invoke(null, new object?[] { ((ISettingsFor)provider.GetRequiredService(settingsType)).Settings })!);

return services
.AddHttpClient(UniqueName.ForType(refitInterfaceType))
.ConfigureHttpMessageHandlerBuilder(builder =>
{
// check to see if user provided custom auth token
if (CreateInnerHandlerIfProvided(((ISettingsFor)builder.Services.GetRequiredService(settingsType)).Settings) is { } innerHandler)
{
builder.PrimaryHandler = innerHandler;
}
})
.AddTypedClient(refitInterfaceType, (client, serviceProvider) => RestService.For(refitInterfaceType, client, (IRequestBuilder)serviceProvider.GetRequiredService(requestBuilderType)));
}

private static readonly MethodInfo RequestBuilderGenericForTypeMethod = typeof(RequestBuilder)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Single(z => z.IsGenericMethodDefinition && z.GetParameters().Length == 1);

static HttpMessageHandler? CreateInnerHandlerIfProvided(RefitSettings? settings)
{
HttpMessageHandler? innerHandler = null;
if (settings != null)
{
if (settings.HttpMessageHandlerFactory != null)
{
innerHandler = settings.HttpMessageHandlerFactory();
}

if (settings.AuthorizationHeaderValueGetter != null)
{
innerHandler = new AuthenticatedHttpClientHandler(settings.AuthorizationHeaderValueGetter, innerHandler);
}
else if (settings.AuthorizationHeaderValueWithParamGetter != null)
{
innerHandler = new AuthenticatedParameterizedHttpClientHandler(settings.AuthorizationHeaderValueWithParamGetter, innerHandler);
}
}

return innerHandler;
}

static IHttpClientBuilder AddTypedClient(this IHttpClientBuilder builder, Type type, Func<HttpClient, IServiceProvider, object> factory)
Expand Down
13 changes: 13 additions & 0 deletions Refit.HttpClientFactory/SettingsFor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Refit
{
public interface ISettingsFor
{
RefitSettings? Settings { get; }
}

public class SettingsFor<T> : ISettingsFor
{
public SettingsFor(RefitSettings? settings) => Settings = settings;
public RefitSettings? Settings { get; }
}
}
102 changes: 99 additions & 3 deletions Refit.Tests/HttpClientFactoryExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
namespace Refit.Tests

using Microsoft.Extensions.Options;

namespace Refit.Tests
{
using Microsoft.Extensions.DependencyInjection;

using System.Text.Json;
using Xunit;

public class HttpClientFactoryExtensionsTests
{
class User
{

}

class Role
{

}

[Fact]
Expand All @@ -25,5 +28,98 @@ public void GenericHttpClientsAreAssignedUniqueNames()

Assert.NotEqual(userClientName, roleClientName);
}

[Fact]
public void HttpClientServicesAreAddedCorrectlyGivenGenericArgument()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddRefitClient<IFooWithOtherAttribute>();
Assert.Contains(serviceCollection, z => z.ServiceType == typeof(SettingsFor<IFooWithOtherAttribute>));
Assert.Contains(serviceCollection, z => z.ServiceType == typeof(IRequestBuilder<IFooWithOtherAttribute>));
}

[Fact]
public void HttpClientServicesAreAddedCorrectlyGivenTypeArgument()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddRefitClient(typeof(IFooWithOtherAttribute));
Assert.Contains(serviceCollection, z => z.ServiceType == typeof(SettingsFor<IFooWithOtherAttribute>));
Assert.Contains(serviceCollection, z => z.ServiceType == typeof(IRequestBuilder<IFooWithOtherAttribute>));
}

[Fact]
public void HttpClientReturnsClientGivenGenericArgument()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddRefitClient<IFooWithOtherAttribute>();
var serviceProvider = serviceCollection.BuildServiceProvider();
Assert.NotNull(serviceProvider.GetService<IFooWithOtherAttribute>());
}

[Fact]
public void HttpClientReturnsClientGivenTypeArgument()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddRefitClient(typeof(IFooWithOtherAttribute));
var serviceProvider = serviceCollection.BuildServiceProvider();
Assert.NotNull(serviceProvider.GetService<IFooWithOtherAttribute>());
}

[Fact]
public void HttpClientSettingsAreInjectableGivenGenericArgument()
{
var serviceCollection = new ServiceCollection()
.Configure<ClientOptions>(o => o.Serializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions()));
serviceCollection.AddRefitClient<IFooWithOtherAttribute>(_ => new RefitSettings() {ContentSerializer = _.GetRequiredService<IOptions<ClientOptions>>().Value.Serializer});
var serviceProvider = serviceCollection.BuildServiceProvider();
Assert.Same(
serviceProvider.GetRequiredService<IOptions<ClientOptions>>().Value.Serializer,
serviceProvider.GetRequiredService<SettingsFor<IFooWithOtherAttribute>>().Settings!.ContentSerializer
);
}

[Fact]
public void HttpClientSettingsAreInjectableGivenTypeArgument()
{
var serviceCollection = new ServiceCollection()
.Configure<ClientOptions>(o => o.Serializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions()));
serviceCollection.AddRefitClient(typeof(IFooWithOtherAttribute), _ => new RefitSettings() {ContentSerializer = _.GetRequiredService<IOptions<ClientOptions>>().Value.Serializer});
var serviceProvider = serviceCollection.BuildServiceProvider();
Assert.Same(
serviceProvider.GetRequiredService<IOptions<ClientOptions>>().Value.Serializer,
serviceProvider.GetRequiredService<SettingsFor<IFooWithOtherAttribute>>().Settings!.ContentSerializer
);
}

[Fact]
public void HttpClientSettingsCanBeProvidedStaticallyGivenGenericArgument()
{
var contentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions());
var serviceCollection = new ServiceCollection();
serviceCollection.AddRefitClient<IFooWithOtherAttribute>(new RefitSettings() {ContentSerializer = contentSerializer });
var serviceProvider = serviceCollection.BuildServiceProvider();
Assert.Same(
contentSerializer,
serviceProvider.GetRequiredService<SettingsFor<IFooWithOtherAttribute>>().Settings!.ContentSerializer
);
}

[Fact]
public void HttpClientSettingsCanBeProvidedStaticallyGivenTypeArgument()
{
var contentSerializer = new SystemTextJsonContentSerializer(new JsonSerializerOptions());
var serviceCollection = new ServiceCollection();
serviceCollection.AddRefitClient<IFooWithOtherAttribute>(new RefitSettings() {ContentSerializer = contentSerializer });
var serviceProvider = serviceCollection.BuildServiceProvider();
Assert.Same(
contentSerializer,
serviceProvider.GetRequiredService<SettingsFor<IFooWithOtherAttribute>>().Settings!.ContentSerializer
);
}

class ClientOptions
{
public SystemTextJsonContentSerializer Serializer { get; set; }
}
}
}
0