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

Modify interface to support HttpClientFactory users #494

Merged
merged 18 commits into from Jun 20, 2018

Conversation

thecontrarycat
Copy link
Contributor

PR for #491

…used in an HttpFactory approach while maintaining the advantages of the cache.
@@ -63,7 +62,7 @@ namespace {{Namespace}}
{
{{#IsRefitMethod}}
var arguments = new object[] { {{ArgumentList}} };
var func = methodImpls.GetOrAdd({{#MethodTypeParameterNames}}${{/MethodTypeParameterNames}}"{{Name}}{{#MethodTypeParameterNames}}<{{.}}>{{/MethodTypeParameterNames}}({{ArgumentListWithTypes}})", _ => requestBuilder.BuildRestResultFuncForMethod("{{Name}}", new Type[] { {{ArgumentTypesList}} }{{#MethodTypeParameterList}}, new Type[] { {{.}} }{{/MethodTypeParameterList}}));
var func = requestBuilder.BuildRestResultFuncForMethod("{{Name}}", new Type[] { {{ArgumentTypesList}} }{{#MethodTypeParameterList}}, new Type[] { {{.}} }{{/MethodTypeParameterList}});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't do this....this means we're running potentially expensive reflection code on every method invocation and not caching the result.

I'd need to see a benchmark comparing the two before removing this cache.

@clairernovotny
Copy link
Member

I may have spoken a little too soon in my code review comment as I was going top-down and didn't see the new cache.

Given the structural changes, can you please describe what you changed and how it's working & caching things?

@thecontrarycat
Copy link
Contributor Author

Basically, I've made the following changes:

  1. Remove the caching layer from the generated code.
  2. Introduced CachedRequestBuilderImplementation which is a decorator for IRequestBuilder and contains the caching layer that used to be in the generated code.
  3. Changed RequestBuilderFactory so that RequestBuilder.ForType<T>() returns a CachedRequestBuilderImplementation that wraps a RequestBuilderImplementation.
  4. Added an overload to RestService.For<T> that takes an HttpClient and an IRequestBuilder, allowing callers to hold the IRequestBuilder as a singleton and have transient HttpClient and Generated Code instances. This supports the HttpClientFactory approach.
  5. (Bonus) Cache the code in RestService.cs that resolves the interface type to generated code type, as in our scenario this code will be called multiple times and it's easy to cache it.

Copy link
Contributor

@bennor bennor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me other than a few minor readability things.

return "";
}

return parameterTypes.Select(t => t.Name).Aggregate((s1, s2) => s1 + ", " + s2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string.Join(", ", parameterTypes.Select(t => t.Name) would be a little clearer here. There's another one like this in the next method too.

@@ -27,7 +27,7 @@ partial class RequestBuilderImplementation : IRequestBuilder
readonly ConcurrentDictionary<CloseGenericMethodKey, RestMethodInfo> interfaceGenericHttpMethods;
readonly JsonSerializer serializer;
readonly RefitSettings settings;
readonly Type targetType;
public Type targetType { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a property, change to PascalCase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot. Fixing now

@clairernovotny
Copy link
Member

Haven't forgotten about this. Will try to get this reviewed and merged in the next few days.

@clairernovotny
Copy link
Member

@thecontrarycat This looks good, thanks! One thing that would be helpful though is a doc update that shows how to use this with IHttpClientFactory. Can you please add that and I'll complete the merge?

@thecontrarycat
Copy link
Contributor Author

@onovotny I've added a note to the documentation showing how you can use it with HttpClientFactory

@clairernovotny
Copy link
Member

@thecontrarycat Thanks, that makes sense.

In light of the pattern in the docs, what would we need to expose a single extension method (or two with overloads) to add Refit? Something like .AddRefit<T>(...) with the settings, or whatever?

@thecontrarycat
Copy link
Contributor Author

thecontrarycat commented Jun 18, 2018

I think it would be complicated to do a single IServiceCollection.AddRefit<T>() method because we'd need to implement the whole HttpClient configuration part (there are additional lines you can put between the AddHttpClient and AddTypedClient methods to customise the handlers used by the client).

I did start thinking that we could make IRequestHandler become IRequestHandler<T> and we could then replace the AddTypedClient call with an AddRefitClient<T> call where we could singleton register the appropriate IRequestHandler<T> in the same call.

Refactoring the IRequestHandler was a larger job than I wanted to do unless you think it makes sense.

Alternatively, we could static cache the IRequestHandler in RestService and not expose it publicly at all... (but I don't much care for static caches).

…alents, allowing us to provide a generic extension method for HttpClientFactory users.
@thecontrarycat
Copy link
Contributor Author

I did that refactor. IRequestBuilder and IRequestBuilderInternal have become IRequestBuilder<T> and IRequestBuilder, with the generic version being used during creation. This allows us to have a single extension method to configure Refit in the HttpClientFactory world (see updated docs).

{
public static class HttpClientFactoryExtensions
{
public static IHttpClientBuilder AddRefitClient<T>(this IServiceCollection services) where T : class
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need an overload that can specify the refit settings. Can either be as another parameter or as an Action<RefitSettings> where the method news it up and passes it to the user for setting?

@clairernovotny
Copy link
Member

This looks really great, I love having an easy one-liner to add in the startup that "does the right thing"

@thecontrarycat
Copy link
Contributor Author

Added the overload for RefitSettings. It's not an Action<RefitSettings> because the IRequestBuilder<T> will be a singleton and therefore there's no ability to change the value at runtime.

@clairernovotny clairernovotny merged commit d097564 into reactiveui:master Jun 20, 2018
@clairernovotny
Copy link
Member

Thank for this!

@clairernovotny
Copy link
Member

clairernovotny commented Jun 20, 2018

@thecontrarycat I know I already merged it, but any chance you could add a test or two that covers what the extension method is supposed to do and any tests that cover the new behavior? (new PR)

@lock lock bot locked and limited conversation to collaborators Jun 24, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants