Skip to content

Commit

Permalink
Merge branch 'main' into header_collection_post_without_body_bug
Browse files Browse the repository at this point in the history
  • Loading branch information
james-s-tayler authored Mar 20, 2021
2 parents c206f0b + 05a1e1e commit 284c36f
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 70 deletions.
2 changes: 1 addition & 1 deletion InterfaceStubGenerator.Core/InterfaceStubGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ void WriteMethodOpening(StringBuilder source, IMethodSymbol methodSymbol, bool i
source.Append(string.Join(", ", list));
}

source.Append(@$") {GenerateConstraints(methodSymbol.TypeParameters, true)}
source.Append(@$") {GenerateConstraints(methodSymbol.TypeParameters, isExplicitInterface)}
{{");
}

Expand Down
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; }
}
}
}
26 changes: 26 additions & 0 deletions Refit.Tests/InheritedGenericInterfacesApi.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;

using Refit; // InterfaceStubGenerator looks for this
using static System.Math; // This is here to verify https://github.com/reactiveui/refit/issues/283

Expand Down Expand Up @@ -47,5 +49,29 @@ public interface IDataCrudApi<T, TKey> where T : class

[Delete("/{key}")]
Task Delete(TKey key);

[Get("")]
Task ReadAllClasses<TFoo>()
where TFoo : class, new();
}


public class DatasetQueryItem<TResultRow>
where TResultRow : class, new()
{
[JsonProperty("global_id")]
public long GlobalId { get; set; }

public long Number { get; set; }

[JsonProperty("Cells")]
public TResultRow Value { get; set; }
}

public interface IDataMosApi
{
[Get("/datasets/{dataSet}/rows")]
Task<DatasetQueryItem<TResulRow>[]> GetDataSetItems<TResulRow>()
where TResulRow : class, new();
}
}
2 changes: 1 addition & 1 deletion Refit.Tests/RequestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ public interface IRestMethodInfoTests
#endregion

[Post("/foo/{id}")]
Task<bool> OhYeahValueTypes(int id, [Body] int whatever);
Task<bool> OhYeahValueTypes(int id, [Body(buffered: true)] int whatever);

[Post("/foo/{id}")]
Task<bool> OhYeahValueTypesUnbuffered(int id, [Body(buffered: false)] int whatever);
Expand Down
Loading

0 comments on commit 284c36f

Please sign in to comment.