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

HttpClient injection #131

Merged
merged 9 commits into from
Aug 10, 2018
Merged

HttpClient injection #131

merged 9 commits into from
Aug 10, 2018

Conversation

martincostello
Copy link
Contributor

@martincostello martincostello commented Jul 18, 2018

Adds a test showing a proof-of-concept for injection of HttpClient into Connection so that IHttpClientFactory can be used for scenarios such as ASP.NET Core 2.1+ applications.

Relates to #125.

Depends on:

Copy link
Collaborator

@grokys grokys left a comment

Choose a reason for hiding this comment

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

Was the intention that this method could be used by the client to configure the HttpClient passed to the ctor? If so, should it be public?

@@ -61,12 +75,26 @@ public Connection(ProductHeaderValue productInformation, Uri uri, ICredentialSto
}
}

private HttpClient CreateHttpClient(ProductHeaderValue header)
private static void ConfigureHttpClient(HttpClient httpClient, ProductHeaderValue header)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Was the intention that this method could be used by the client to configure the HttpClient passed to the ctor? If so, should it be public?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, this was more sort-of so that if the user didn't configure the client fully, a default configuration would be applied, though turns out I forgot to plumb that into the new constructor.

I'm not entirely sure what the constructor options that supporting this should be yet, I just added a minimum to make the test work to show how you could use it.

@StanleyGoldman
Copy link
Collaborator

StanleyGoldman commented Jul 25, 2018

So I had the obvious realization the other day that a lot of this has already come from octokit/octokit.net for good reason and should continue to be based off of that.

The constructor of Connection should more closely match what is found here..

https://github.com/octokit/octokit.net/blob/8407369485541b1fce28f1c9c8d5bf5852579c57/Octokit/Http/Connection.cs#L134-L159

@@ -40,6 +40,20 @@ public Connection(ProductHeaderValue productInformation, Uri uri, ICredentialSto
HttpClient = CreateHttpClient(productInformation);
}

/// <inheritdoc />
public Connection(HttpClient httpClient, ICredentialStore credentialStore)
Copy link
Collaborator

Choose a reason for hiding this comment

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

All constructors should roll into this one, and the signature should be..

Connection(ProductHeaderValue productInformation, Uri uri, ICredentialStore credentialStore, HttpClient httpClient)

@@ -40,6 +40,20 @@ public Connection(ProductHeaderValue productInformation, Uri uri, ICredentialSto
HttpClient = CreateHttpClient(productInformation);
}

/// <inheritdoc />
Copy link
Collaborator

Choose a reason for hiding this comment

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

For some reason we have /// <inheritdoc/> on all these constructors.
Remove this line and merge the branch overeager-inheritdoc into this branch to take care of the others.

public Connection(HttpClient httpClient, ICredentialStore credentialStore)
{
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
CredentialStore = credentialStore ?? throw new ArgumentNullException(nameof(credentialStore));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Good catch, we should have been doing this in the previous constructor.
Be sure to add more for all the arguments when you add them.


if (httpClient.BaseAddress == null)
{
httpClient.BaseAddress = GithubApiUri;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Much like in octokit.net we shouldn't handle this here, just set a member variable and we will handle this later.

@StanleyGoldman
Copy link
Collaborator

StanleyGoldman commented Jul 25, 2018

The rest of the differences between us and octokit.net is how we execute things.

  1. They build the request, setting the Uri at that moment.
    https://github.com/octokit/octokit.net/blob/8407369485541b1fce28f1c9c8d5bf5852579c57/Octokit/Http/Connection.cs#L206-L211
  2. Everything else is applied when they run the request:
    https://github.com/octokit/octokit.net/blob/8407369485541b1fce28f1c9c8d5bf5852579c57/Octokit/Http/Connection.cs#L630-L643

}
}

private static HttpClient CreateHttpClient(ProductHeaderValue header)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This method isn't needed, we should just new HttpClient() in the constructors above.

if (httpClient.DefaultRequestHeaders.UserAgent.Count == 0)
{
var userAgent = new ProductInfoHeaderValue(header.Name, header.Version);
httpClient.DefaultRequestHeaders.UserAgent.Add(userAgent);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Setting userAgent and headers should be performed when we are executing the request.

@StanleyGoldman
Copy link
Collaborator

The final major difference in octokit.net is they have an interface for HttpClient in case you need to replace it and their own HttpClientAdapter which implements their standard version.
But I think we can leave that much for another time.

@StanleyGoldman
Copy link
Collaborator

@martincostello thanks for getting this conversation started 🎖.
Let me know if you want to continue chasing this dragon.

@martincostello
Copy link
Contributor Author

@StanleyGoldman Sure, I'll take a look at incorporating the feedback at some point later this week or early next 👍

@martincostello martincostello changed the title [WIP] Proof-of-concept for HttpClient injection [WIP] HttpClient injection Jul 28, 2018
@martincostello
Copy link
Contributor Author

martincostello commented Jul 28, 2018

@StanleyGoldman I've done some refactoring based on the feedback and rebased onto master. The changes I've made are:

  1. Refactored Connection as suggested to add the new constructor that accepts HttpClient following the pattern in Octokit as suggested. I've also added an extra HttpClient-based constructor that makes DI easier in the case where the default URI is used (i.e. for GitHub.com).
  2. Added an optional parameter to Run() that accepts a CancellationToken which is passed to the SendAsync() method of HttpClient. I've not plumbed this in to the query runners as then that goes a bit viral, but happy to add this in this PR or a different one if you think it's worth doing.
  3. Add validation of arguments to constructors and methods.
  4. Changed GitHubApiUri to a get property instead of a read-only field, as that's more usual for C#.
  5. Added ConfigureAwait(false) to places where await is used to prevent potential deadlocks in apps with a synchronization context, like an application with a UI.
  6. I've added XML documentation mostly lifted as-is from Octokit and applied it to all the public members of the Connection class, rather than just delete <inherit-doc/>.
  7. I've updated the test projects to target netcoreapp2.1 as 1.1 is relatively old in .NET Core terms now, as well as update the .NET Core Test SDK and xunit. This then created some code analysis warnings from the xunit analysers that have shipped in-box since 2.3.0, so I fixed those too. I think as an unintended side-effect of the runtime upgrade, some of the tests in ExpressionRewriterTests are now failing because the ToString() representations now include the airity of the type (so IEnumerable`1 instead of IEnumerable). I wasn't sure the best way to fix these, so for now they're still broken. Update tests' target framework #140
  8. Fixed a NullReferenceException in tests that use ExpressionCompiler that can occur when the tests are run in parallel with each other and try to add items to the dictionary at the same time. I've resolved this by changing the code to use ConcurrentDictionary instead. Fix NullReferenceException in tests #139

@StanleyGoldman
Copy link
Collaborator

Hey @martincostello I 💟 all that you are doing, but we gotta be careful of not doing too much in a single pull request. I haven't gotten the chance to look at this pull request again in deatil, but based on what you said in your last comment. It sounds like we can move some of these changes out to a different pull request.

Would it be possible to rebase this branch and move items 7 and 8 to separate pull requests?

@martincostello
Copy link
Contributor Author

@StanleyGoldman No problem. I'll do those now, then rebase this once those are in. Any ideas roughly on how to go about fixing the issues with the type name formatting breaking those expression tests?

@martincostello
Copy link
Contributor Author

@StanleyGoldman Have raised the two PRs for items 7 and 8.

@StanleyGoldman
Copy link
Collaborator

Thanks @martincostello

Any ideas roughly on how to go about fixing the issues with the type name formatting breaking those expression tests?

LOL. No clue yet.

@StanleyGoldman
Copy link
Collaborator

Okay, thanks for entertaining me there and separating out those other issues. @grokys is AFK for a few more days and I'd like to keep on moving forward.

I pushed a temporary branch called custom_target. It is a merge of #139 and #140.

Would you mind rebasing this branch onto that and changing this pull request to target that branch?
That way we can finish working out the changes here, and @grokys can give us the final approval when he gets back.

@martincostello
Copy link
Contributor Author

Sure. I'll do that shortly.

@martincostello martincostello changed the base branch from master to custom_target August 1, 2018 17:37
@martincostello
Copy link
Contributor Author

Rebased and target branch updated.

@martincostello
Copy link
Contributor Author

Blargh, I messed up something slightly in #140. I'll fix that now, then rebase this again later if you could update custom_target once I have?

@StanleyGoldman
Copy link
Collaborator

Merged.

@martincostello
Copy link
Contributor Author

Updated with the latest custom_target changes.

@StanleyGoldman
Copy link
Collaborator

Wanna rebase this onto master one last time?
My bad..

@martincostello
Copy link
Contributor Author

Sure 😄

Add a test showing a proof-of-concept for injection of HttpClient into Connection so that IHttpClientFactory can be used for scenarios such as ASP.NET Core applications.
Relates to #125.
Route all constructors into new constructor that accepts HttpClient.
Add extra constructor to simplify dependency injection for GitHub.com.
Add XML documentation to class and members based on Octokit.
Add parameter validation.
Change GithubApiUri to a property.
Set HttpRequestMessage properties (User Agent etc.) per request.
Add ConfigureAwait(false) to awaits.
Add optional parameter for using a CancellationToken.
@martincostello martincostello changed the base branch from custom_target to master August 3, 2018 14:52
@martincostello
Copy link
Contributor Author

Rebased. Also, pending further code review, I should add a bunch more unit tests for the changes in Connection (like for parameter validation).

@StanleyGoldman
Copy link
Collaborator

You are never going to hear me argue against more tests... 😼

Add a TODO comment suggesting changing ICredentialStore to support CancellationToken.
Make the run method in Connection virtual to allow more useful derived classes.
Add unit tests to cover the basic behaviour of managing HTTP requests.
@martincostello
Copy link
Contributor Author

Have added some new unit tests for the Connection class.

I've also made Run() virtual so that if someone made a derived class it would have more utility given the protected members it has.

It also occurred to me that passing the CancellationToken through to ICredentialStore would be useful if an implementation needed to do a network call, given the method is async. As that would be another interface change, I figured I'd pose the question first rather than just change it.

@StanleyGoldman
Copy link
Collaborator

We haven't forgotten you, just tending to our other projects.

@StanleyGoldman
Copy link
Collaborator

The interface change makes sense, oddly enough though no one seems to have encountered this issue with octokit.net. Connection.Run

await _authenticator.Apply(request).ConfigureAwait(false);
var response = await _httpClient.Send(request, cancellationToken).ConfigureAwait(false);

@StanleyGoldman
Copy link
Collaborator

I made that change you were referring to and made some minor tweaks


namespace Octokit.GraphQL.IntegrationTests.Configuration
{
public static class HttpClientFactoryTests
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a reason why the test class is static?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

xunit supports classes/methods being static for tests if everything is self contained to the fact/theory - happy to make non-static if that’s your convention.

public static class HttpClientFactoryTests
{
[Fact]
public static async Task Can_Configure_With_HttpClient_Factory()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same with this method?

@StanleyGoldman StanleyGoldman changed the title [WIP] HttpClient injection HttpClient injection Aug 9, 2018
@StanleyGoldman
Copy link
Collaborator

Other than that, this is looking pretty good IMO.

Fix unit test broken by refactoring.
@martincostello
Copy link
Contributor Author

I've fixed up a test, but other than any other feedback, I think I'm done on this PR.

I've also done a commit on a separate branch (17c3cf4) to plumb in CancellationToken to the runners and extension methods that use IConnection that builds on this changes in this PR.

Copy link
Collaborator

@grokys grokys left a comment

Choose a reason for hiding this comment

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

LGTM!

@StanleyGoldman StanleyGoldman merged commit c4cb46e into octokit:master Aug 10, 2018
@martincostello martincostello deleted the HttpClient-Injection branch August 10, 2018 10:37
@StanleyGoldman StanleyGoldman added this to the v0.1.1-beta milestone Aug 10, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants