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

Adding a way to initialize ServiceClient using provided HttpClient #2909

Closed
wants to merge 8 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace Microsoft.Rest.ClientRuntime.Tests
{
using Microsoft.Rest.ClientRuntime.Tests.CustomClients;
using System.Net.Http;
using Xunit;

public class CustomClientWithHttpClientTests
{
[Fact]
public void InitializeServiceClientWithHttpClient()
{
HttpClient hc = new HttpClient();
ContosoServiceClient contosoClient = new ContosoServiceClient(hc);
HttpResponseMessage response = contosoClient.DoSyncWork();
Assert.NotNull(response);
}

[Fact]
public void GetInitializedHttpClient()
{
ContosoServiceClient contosoClient = new ContosoServiceClient(null);
HttpResponseMessage response = contosoClient.DoSyncWork();
string cont = response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
Assert.NotNull(response);
}

[Fact]
public void InitializeMessageHandlerPostContructor()
{
HttpClient hc = new HttpClient(new ContosoMessageHandler());
ContosoServiceClient contosoClient = new ContosoServiceClient(hc);
HttpResponseMessage response = contosoClient.DoSyncWork("Hello");
string cont = response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
Assert.Equal("Contoso Rocks", cont);
}

[Fact]
public void ProvideHttpClientAfterInitialization()
{
DelegatingHandler[] handlers = new ContosoMessageHandler[] { new ContosoMessageHandler() };
ContosoServiceClient contosoClient = new ContosoServiceClient();
HttpResponseMessage response = contosoClient.DoSyncWork();
string cont = response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
Assert.Equal("Delayed User Provided HttpClient after initialization", cont);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace Microsoft.Rest.ClientRuntime.Tests.CustomClients
{
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// Customized client that emulates how partners will use Customized code to extend
/// generated client.
/// </summary>
public class ContosoServiceClient : ServiceClient<ContosoServiceClient>
{
public ContosoServiceClient():base()
{
HttpClient = new HttpClient(new DelayedHandler("Delayed User Provided HttpClient after initialization"));
}

/// <summary>
/// Constructor that accepts HttpClient
/// </summary>
/// <param name="httpClient"></param>
public ContosoServiceClient(HttpClient httpClient) : base (httpClient)
{

}

public ContosoServiceClient(HttpClientHandler rootHandler, DelegatingHandler[] handlers)
: base(rootHandler, handlers)
{ }

/// <summary>
/// Some task emulating getting response back
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
public HttpResponseMessage DoSyncWork(string content = null)
{
return Task.Factory.StartNew(() =>
{
return DoStuff(content);
}).Unwrap().GetAwaiter().GetResult();
}

/// <summary>
/// Creates request and sends
/// </summary>
/// <param name="content">string value</param>
/// <returns></returns>
private async Task<HttpResponseMessage> DoStuff(string content = null)
{
// Construct URL
string url = "http://www.microsoft.com";

// Create HTTP transport objects
HttpRequestMessage _httpRequest = null;

_httpRequest = new HttpRequestMessage();
_httpRequest.Method = HttpMethod.Get;
_httpRequest.RequestUri = new Uri(url);

// Set content
if (content != null)
{
_httpRequest.Content = new StringContent(content);
}

// Set Headers
_httpRequest.Headers.Add("x-ms-version", "2013-11-01");

return await this.HttpClient.SendAsync(_httpRequest, new CancellationToken()).ConfigureAwait(false);
}
}

/// <summary>
/// Custom message handler
/// </summary>
public class ContosoMessageHandler : DelegatingHandler
{
public ContosoMessageHandler() : base()
{
InnerHandler = new HttpClientHandler();
}

/// <summary>
/// Returns Contoso Rocks response
/// </summary>
/// <param name="request">HttpRequestMessage object</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns></returns>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
StringContent contosoContent = new StringContent("Contoso Rocks");
HttpResponseMessage response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
response.Content = contosoContent;
return await Task.Run(() => response);
}
}

/// <summary>
/// Yet another delegating handler for tests
/// </summary>
public class DelayedHandler : DelegatingHandler
{
string _handlerData;
private DelayedHandler() : base()
{
InnerHandler = new HttpClientHandler();
}

public DelayedHandler(string handlerData)
: this()
{
_handlerData = handlerData;
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
StringContent contosoContent = new StringContent(_handlerData);
HttpResponseMessage response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
response.Content = contosoContent;
return await Task.Run(() => response);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public FakeServiceClient()
// Prevent base constructor from executing
}

public FakeServiceClient(HttpClient httpClient)
: base(httpClient)
{ }

public FakeServiceClient(HttpClientHandler httpMessageHandler, params DelegatingHandler[] handlers)
: base(httpMessageHandler, handlers)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<ItemGroup>
<DnxInvisibleCompile Include="CustomClients\FabrikamServiceClient.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Rest.ClientRuntime.Tests.CustomClients;
using Microsoft.Rest.ClientRuntime.Tests.Fakes;
using Microsoft.Rest.TransientFaultHandling;
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using Microsoft.Rest.ClientRuntime.Tests.Fakes;
using Microsoft.Rest.TransientFaultHandling;
using Xunit;
using System.Net.Http.Headers;
using System.Diagnostics;
using Xunit;

namespace Microsoft.Rest.ClientRuntime.Tests
{
Expand Down Expand Up @@ -109,6 +109,16 @@ public void RetryHandlerRetriesWith500Errors()
Assert.Equal(2, attemptsFailed);
}

[Fact]
public void FakeSvcClientWithHttpClient()
{
HttpClient hc = new HttpClient(new ContosoMessageHandler());
var fakeClient = new FakeServiceClient(hc);
HttpResponseMessage response = fakeClient.DoStuffSync();
string responseContent = response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
Assert.Equal("Contoso Rocks", responseContent);
}

[Fact]
public void RetryHandlerRetriesWith500ErrorsAndSucceeds()
{
Expand Down Expand Up @@ -214,7 +224,7 @@ public void AddUserAgentInfoWithVersion()
Assert.True(testProduct.Product.Name.Equals(testProductName));
Assert.True(testProduct.Product.Version.Equals(testProductVersion));
}

#if NET451
[Fact]
public void VerifyOsInfoInUserAgent()
Expand Down
64 changes: 49 additions & 15 deletions src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClient.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using Microsoft.Rest.TransientFaultHandling;
#if NET45
using Microsoft.Win32;
#endif
namespace Microsoft.Rest
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using Microsoft.Rest.TransientFaultHandling;
#if NET45
using Microsoft.Win32;
#endif

/// <summary>
/// ServiceClient is the abstraction for accessing REST operations and their payload data types..
/// </summary>
Expand Down Expand Up @@ -42,6 +43,7 @@ public abstract class ServiceClient<T> : IDisposable
/// </summary>
private string _fxVersion;

#region NET45 specific code
#if NET45
/// <summary>
/// Indicates OS Name
Expand Down Expand Up @@ -114,6 +116,7 @@ private string ReadHKLMRegistry(string path, string key)
catch { return ""; }
}
#endif
#endregion

/// <summary>
/// Gets the AssemblyInformationalVersion if available
Expand Down Expand Up @@ -196,7 +199,7 @@ private string FrameworkVersion
/// pipeline).
/// </summary>
protected HttpClientHandler HttpClientHandler { get; set; }

/// <summary>
/// Initializes a new instance of the ServiceClient class.
/// </summary>
Expand All @@ -209,6 +212,19 @@ protected ServiceClient()
{
}

/// <summary>
/// Initializes a new instance of the ServiceClient class.
/// </summary>
/// <param name="httpClient">HttpClient</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Microsoft.Reliability",
"CA2000:Dispose objects before losing scope",
Justification = "The created objects should be disposed on caller's side")]
protected ServiceClient(HttpClient httpClient)
{
InitializeHttpClient(httpClient, CreateRootHandler());
}

/// <summary>
/// Initializes a new instance of the ServiceClient class.
/// </summary>
Expand Down Expand Up @@ -344,6 +360,17 @@ protected virtual void Dispose(bool disposing)
"CA2000:Dispose objects before losing scope",
Justification = "We let HttpClient instance dispose")]
protected void InitializeHttpClient(HttpClientHandler httpClientHandler, params DelegatingHandler[] handlers)
{
InitializeHttpClient(null, httpClientHandler, handlers);
}

/// <summary>
/// Initialize service client with provided HttpClient
/// </summary>
/// <param name="httpClient">HttpClient</param>
/// <param name="httpClientHandler">HttpClientHandler</param>
/// <param name="handlers">List of handlers from top to bottom (outer handler is the first in the list)</param>
protected void InitializeHttpClient(HttpClient httpClient, HttpClientHandler httpClientHandler, params DelegatingHandler[] handlers)
{
HttpClientHandler = httpClientHandler;
DelegatingHandler currentHandler = new RetryDelegatingHandler();
Expand All @@ -360,16 +387,23 @@ protected void InitializeHttpClient(HttpClientHandler httpClientHandler, params
{
handler = handler.InnerHandler as DelegatingHandler;
}

handler.InnerHandler = currentHandler;
currentHandler = handlers[i];
}
}

var newClient = new HttpClient(currentHandler, false);
if (httpClient == null)
{
HttpClient = new HttpClient(currentHandler, false);
}
else
{
HttpClient = httpClient;
}

FirstMessageHandler = currentHandler;
HttpClient = newClient;
Type type = this.GetType();
SetUserAgent(type.FullName, ClientVersion);
SetUserAgent(this.GetType().FullName, ClientVersion);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "2.3.5",
"version": "2.3.6",
"copyright": "Copyright (c) Microsoft Corporation",
"title": "Client Runtime Library for Microsoft AutoRest Generated Clients",
"description": "Infrastructure for error handling, tracing, and HttpClient pipeline configuration. Required by client libraries generated using AutoRest. \nSupported Platforms:\n - Portable Class Libraries\n - .NET Framework 4.5\n - Windows 8\n - Windows Phone 8.1\n - DotNet Core",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"netcoreapp1.0": {
"imports": ["dnxcore50", "portable-net45+win8"],
"dependencies": {
"System.Diagnostics.Tracing": "4.1.0"
"System.Diagnostics.Tracing": "4.3.0"
}
}
},
Expand All @@ -31,10 +31,13 @@
"type": "platform",
"version": "1.0.0"
},
"Microsoft.Rest.ClientRuntime.Azure.TestFramework": "[1.4.0-preview,2.0.0)",
"Microsoft.Azure.Management.OperationalInsights": {
"target": "project",
"type": "build"
},
"Microsoft.Rest.ClientRuntime.Azure.TestFramework": "[1.6.0,2.0.0)",
"Microsoft.Rest.ClientRuntime.Azure": "[3.3.3,4.0.0)",
"Microsoft.Azure.ResourceManager": "1.0.0-preview",
"Microsoft.Azure.Management.OperationalInsights": "[1.0.0-preview, 2.0.0)",
"xunit": "2.2.0-beta2-build3300",
"dotnet-test-xunit": "2.2.0-preview2-build1029"
}
Expand Down