Skip to content

Commit 7d1a213

Browse files
committed
Added strongly-typed message handler.
1 parent b4ba4e5 commit 7d1a213

File tree

4 files changed

+68
-22
lines changed

4 files changed

+68
-22
lines changed

Client/Default/FluentClient.cs

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
namespace Pathoschild.Http.Client.Default
66
{
77
/// <summary>Sends HTTP requests and receives responses from a resource identified by a URI.</summary>
8-
public class FluentClient : IClient
8+
/// <typeparam name="TMessageHandler">The HTTP message handler type.</typeparam>
9+
public class FluentClient<TMessageHandler> : IClient<TMessageHandler>
10+
where TMessageHandler : HttpMessageHandler
911
{
1012
/*********
1113
** Accessors
@@ -14,7 +16,7 @@ public class FluentClient : IClient
1416
public HttpClient BaseClient { get; protected set; }
1517

1618
/// <summary>The underlying HTTP message handler.</summary>
17-
public HttpClientHandler MessageHandler { get; protected set; }
19+
public TMessageHandler MessageHandler { get; protected set; }
1820

1921
/// <summary>The formatters used for serializing and deserializing message bodies.</summary>
2022
public MediaTypeFormatterCollection Formatters { get; protected set; }
@@ -26,20 +28,10 @@ public class FluentClient : IClient
2628
/// <summary>Construct an instance.</summary>
2729
/// <param name="client">The underlying HTTP client.</param>
2830
/// <param name="handler">The underlying HTTP message handler. This should be the same handler used by the <paramref name="client"/>.</param>
29-
public FluentClient(HttpClient client, HttpClientHandler handler)
30-
{
31-
this.MessageHandler = handler;
32-
this.BaseClient = client;
33-
this.Formatters = new MediaTypeFormatterCollection();
34-
}
35-
36-
/// <summary>Construct an instance.</summary>
3731
/// <param name="baseUri">The base URI prepended to relative request URIs.</param>
38-
public FluentClient(string baseUri)
32+
public FluentClient(HttpClient client, TMessageHandler handler, string baseUri = null)
3933
{
40-
this.MessageHandler = new HttpClientHandler();
41-
this.BaseClient = new HttpClient(this.MessageHandler, true) { BaseAddress = new Uri(baseUri) };
42-
this.Formatters = new MediaTypeFormatterCollection();
34+
this.Initialize(client, handler, baseUri);
4335
}
4436

4537
/// <summary>Create an asynchronous HTTP DELETE request message (but don't dispatch it yet).</summary>
@@ -113,5 +105,44 @@ public virtual IRequestBuilder Send(HttpRequestMessage message)
113105
{
114106
return new RequestBuilder(message, this.Formatters, request => this.BaseClient.SendAsync(request.Message));
115107
}
108+
109+
110+
/*********
111+
** Protected methods
112+
*********/
113+
/// <summary>Construct an uninitialized instance.</summary>
114+
protected FluentClient() {}
115+
116+
/// <summary>Initialize the client.</summary>
117+
/// <param name="client">The underlying HTTP client.</param>
118+
/// <param name="handler">The underlying HTTP message handler. This should be the same handler used by the <paramref name="client"/>.</param>
119+
/// <param name="baseUri">The base URI prepended to relative request URIs.</param>
120+
protected void Initialize(HttpClient client, TMessageHandler handler, string baseUri = null)
121+
{
122+
this.MessageHandler = handler;
123+
this.BaseClient = client;
124+
if (baseUri != null)
125+
this.BaseClient.BaseAddress = new Uri(baseUri);
126+
this.Formatters = new MediaTypeFormatterCollection();
127+
}
128+
}
129+
130+
/// <summary>Sends HTTP requests and receives responses from a resource identified by a URI.</summary>
131+
public class FluentClient : FluentClient<HttpClientHandler>, IClient
132+
{
133+
/// <summary>Construct an instance.</summary>
134+
/// <param name="client">The underlying HTTP client.</param>
135+
/// <param name="handler">The underlying HTTP message handler. This should be the same handler used by the <paramref name="client"/>.</param>
136+
/// <param name="baseUri">The base URI prepended to relative request URIs.</param>
137+
public FluentClient(HttpClient client, HttpClientHandler handler, string baseUri = null)
138+
: base(client, handler, baseUri) { }
139+
140+
/// <summary>Construct an instance.</summary>
141+
/// <param name="baseUri">The base URI prepended to relative request URIs.</param>
142+
public FluentClient(string baseUri)
143+
{
144+
var handler = new HttpClientHandler();
145+
this.Initialize(new HttpClient(handler), handler, baseUri);
146+
}
116147
}
117148
}

Client/IClient.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
namespace Pathoschild.Http.Client
55
{
66
/// <summary>Sends HTTP requests and receives responses from a resource identified by a URI.</summary>
7-
public interface IClient
7+
/// <typeparam name="TMessageHandler">The HTTP message handler type.</typeparam>
8+
public interface IClient<out TMessageHandler>
9+
where TMessageHandler : HttpMessageHandler
810
{
911
/*********
1012
** Accessors
@@ -13,7 +15,7 @@ public interface IClient
1315
HttpClient BaseClient { get; }
1416

1517
/// <summary>The underlying HTTP message handler.</summary>
16-
HttpClientHandler MessageHandler { get; }
18+
TMessageHandler MessageHandler { get; }
1719

1820
/// <summary>The formatters used for serializing and deserializing message bodies.</summary>
1921
MediaTypeFormatterCollection Formatters { get; }
@@ -67,4 +69,7 @@ public interface IClient
6769
/// <returns>Returns a request builder.</returns>
6870
IRequestBuilder Send(HttpRequestMessage message);
6971
}
72+
73+
/// <summary>Sends HTTP requests and receives responses from a resource identified by a URI.</summary>
74+
public interface IClient : IClient<HttpClientHandler> { }
7075
}

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ And you can do everything asynchronously:
5050
.AsAsync<Idea>();
5151
```
5252

53-
And you can access the [underlying HTTP message handler][HttpClientHandler], or inject your own to do pretty much whatever you want. The default handler lets you configure a range of features like authentication, cookies, and proxying:
53+
And you can configure a range of features like credentials and cookies using the default [HTTP message handler][HttpClientHandler]:
5454
```c#
5555
client.MessageHandler.Credentials = new NetworkCredential("username", "password");
5656
client.MessageHandler.CookieContainer.Add(new Cookie(...));
@@ -59,23 +59,32 @@ And you can access the [underlying HTTP message handler][HttpClientHandler], or
5959

6060
The code documentation provides more details on usage: see [IClient][], [IRequestBuilder][], and [IResponse][].
6161

62-
## Configurable formatting
63-
The client uses .NET's [MediaTypeFormatter][]s for serializing and deserializing HTTP messages. This is the same type used by the underlying HttpClient and the .NET Web API, and the client uses Microsoft's default formatters out of the box. When creating a client for an ASP.NET Web API, this lets you seamlessly use the same formatters on both sides.
62+
## Extension
63+
### Custom formats
64+
The client uses .NET's [MediaTypeFormatter][]s for serializing and deserializing HTTP messages — the same ones used by the HttpClient itself and the ASP.NET Web API. The client uses Microsoft's default formatters by default. When creating a client for an ASP.NET Web API, this lets you seamlessly use the same formatters on both sides.
6465

65-
You can use any of the many implementations already available, create your own ([MediaTypeFormatterBase][] might help), or use one of the formatters below. For example, to replace the default JSON formatter with the formatter below:
66+
You can use any of the many implementations already available (including the Json.NET formatters below), or create your own ([MediaTypeFormatterBase][] might help). For example, to replace the default JSON formatter with the formatter below:
6667
```c#
6768
IClient client = new FluentClient("http://example.org/api/");
6869
client.Formatters.Remove(client.Formatters.JsonFormatter);
6970
client.Formatters.Add(new JsonNetFormatter());
7071
```
7172

72-
### Json.NET
73+
#### Json.NET
7374
The [Pathoschild.Http.Formatters.JsonNet][] package provides three formats using [Json.NET][]: [BSON][] (`application/bson`), [JSON][] (`application/json`, `text/json`), and [JSONP][] (`application/javascript`, `application/ecmascript`, `text/javascript`, `text/ecmascript`). JSONP requests can include an optional `callback` query parameter that specifies the JavaScript method name to invoke.
7475
```c#
7576
client.Formatters.Add(new JsonNetBsonFormatter());
7677
client.Formatters.Add(new JsonNetFormatter());
7778
```
7879

80+
### Custom message handler
81+
You can inject your own [HTTP message handler][HttpClientHandler] to do pretty much anything you want. For example, you could easily create a custom handler for unit tests which talks directly to a mock without actual HTTP calls:
82+
```c#
83+
UnitTestHandler handler = new UnitTestHandler() { WasCalled = false };
84+
IClient<UnitTestHandler> client = new FluentClient(new HttpClient(handler), handler);
85+
bool wasCalled = client.MessageHandler.WasCalled; // strongly-typed access to the handler
86+
```
87+
7988
[HttpClient]: http://code.msdn.microsoft.com/Introduction-to-HttpClient-4a2d9cee
8089
[HttpClientHandler]: http://msdn.microsoft.com/en-us/library/system.net.http.httpclienthandler.aspx
8190
[HttpRequestMessage]: http://msdn.microsoft.com/en-us/library/system.net.http.httprequestmessage.aspx

Tests/Default/ClientTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Net.Http;
22
using NUnit.Framework;
33
using Pathoschild.Http.Client;
4+
using Pathoschild.Http.Client.Default;
45

56
namespace Pathoschild.Http.Tests.Default
67
{
@@ -149,7 +150,7 @@ protected IClient ConstructClient(string baseUri = "http://example.com/", bool i
149150
try
150151
{
151152
// execute
152-
IClient client = new Client.Default.FluentClient(baseUri);
153+
IClient client = new FluentClient(baseUri);
153154

154155
// verify
155156
Assert.NotNull(client.BaseClient, "The base client is null.");

0 commit comments

Comments
 (0)