Skip to content

Commit f48f301

Browse files
committed
Merged IRequestBuilder and IResponse into IRequest; updated readme.
1 parent df8d18e commit f48f301

26 files changed

+849
-1070
lines changed

Client/Client.csproj

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,13 @@
4747
<Compile Include="ApiException.cs" />
4848
<Compile Include="Default\Factory.cs" />
4949
<Compile Include="Default\FluentClient.cs" />
50-
<Compile Include="Default\RequestBuilder.cs" />
51-
<Compile Include="Default\Response.cs" />
50+
<Compile Include="Default\Request.cs" />
5251
<Compile Include="Delegating\DelegatingFluentClient.cs" />
53-
<Compile Include="Delegating\DelegatingRequestBuilder.cs" />
54-
<Compile Include="Delegating\DelegatingResponse.cs" />
52+
<Compile Include="Delegating\DelegatingRequest.cs" />
5553
<Compile Include="FluentClientExtensions.cs" />
5654
<Compile Include="IClient.cs" />
5755
<Compile Include="IFactory.cs" />
58-
<Compile Include="IRequestBuilder.cs" />
59-
<Compile Include="IResponse.cs" />
56+
<Compile Include="IRequest.cs" />
6057
<Compile Include="Properties\AssemblyInfo.cs" />
6158
</ItemGroup>
6259
<ItemGroup />

Client/Default/Factory.cs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,13 @@ public class Factory : IFactory
1616
/***
1717
** Fluent objects
1818
***/
19-
/// <summary>Construct an asynchronous HTTP request builder.</summary>
19+
/// <summary>Construct an asynchronous HTTP request.</summary>
2020
/// <param name="message">The underlying HTTP request message.</param>
2121
/// <param name="formatters">The formatters used for serializing and deserializing message bodies.</param>
22-
/// <param name="responseBuilder">Executes an HTTP request.</param>
23-
public virtual IRequestBuilder GetRequestBuilder(HttpRequestMessage message, MediaTypeFormatterCollection formatters, Func<IRequestBuilder, Task<HttpResponseMessage>> responseBuilder)
22+
/// <param name="dispatcher">Executes an HTTP request.</param>
23+
public virtual IRequest GetRequest(HttpRequestMessage message, MediaTypeFormatterCollection formatters, Func<IRequest, Task<HttpResponseMessage>> dispatcher)
2424
{
25-
return new RequestBuilder(message, formatters, responseBuilder, this);
26-
}
27-
28-
/// <summary>Construct an asynchronous HTTP response.</summary>
29-
/// <param name="request">The underlying HTTP request message.</param>
30-
/// <param name="task">The request task.</param>
31-
/// <param name="formatters">The formatters used for serializing and deserializing message bodies.</param>
32-
/// <param name="throwError">Whether to handle errors from the upstream server by throwing an exception.</param>
33-
public virtual IResponse GetResponse(HttpRequestMessage request, Task<HttpResponseMessage> task, MediaTypeFormatterCollection formatters, bool throwError = true)
34-
{
35-
return new Response(request, task, formatters, throwError);
25+
return new Request(message, formatters, dispatcher, this);
3626
}
3727

3828
/***

Client/Default/FluentClient.cs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@
44

55
namespace Pathoschild.Http.Client.Default
66
{
7-
/// <summary>Sends HTTP requests and receives responses from a resource identified by a URI.</summary>
7+
/// <summary>Sends HTTP requests and receives responses from REST URIs.</summary>
88
/// <typeparam name="TMessageHandler">The HTTP message handler type.</typeparam>
99
public class FluentClient<TMessageHandler> : IClient<TMessageHandler>
1010
where TMessageHandler : HttpMessageHandler
1111
{
12+
/*********
13+
** Properties
14+
*********/
15+
/// <summary>Constructs implementations for the fluent client.</summary>
16+
public IFactory Factory { get; protected set; }
17+
18+
1219
/*********
1320
** Accessors
1421
*********/
@@ -20,11 +27,7 @@ public class FluentClient<TMessageHandler> : IClient<TMessageHandler>
2027

2128
/// <summary>The formatters used for serializing and deserializing message bodies.</summary>
2229
public MediaTypeFormatterCollection Formatters { get; protected set; }
23-
24-
/// <summary>Constructs implementations for the fluent client.</summary>
25-
public IFactory Factory { get; protected set; }
26-
27-
30+
2831

2932
/*********
3033
** Public methods
@@ -43,23 +46,23 @@ public FluentClient(HttpClient client, TMessageHandler handler, string baseUri =
4346
/// <summary>Create an asynchronous HTTP DELETE request message (but don't dispatch it yet).</summary>
4447
/// <param name="resource">The URI to send the request to.</param>
4548
/// <returns>Returns a request builder.</returns>
46-
public IRequestBuilder DeleteAsync(string resource)
49+
public IRequest DeleteAsync(string resource)
4750
{
4851
return this.SendAsync(HttpMethod.Delete, resource);
4952
}
5053

5154
/// <summary>Create an asynchronous HTTP GET request message (but don't dispatch it yet).</summary>
5255
/// <param name="resource">The URI to send the request to.</param>
5356
/// <returns>Returns a request builder.</returns>
54-
public IRequestBuilder GetAsync(string resource)
57+
public IRequest GetAsync(string resource)
5558
{
5659
return this.SendAsync(HttpMethod.Get, resource);
5760
}
5861

5962
/// <summary>Create an asynchronous HTTP POST request message (but don't dispatch it yet).</summary>
6063
/// <param name="resource">The URI to send the request to.</param>
6164
/// <returns>Returns a request builder.</returns>
62-
public IRequestBuilder PostAsync(string resource)
65+
public IRequest PostAsync(string resource)
6366
{
6467
return this.SendAsync(HttpMethod.Post, resource);
6568
}
@@ -69,15 +72,15 @@ public IRequestBuilder PostAsync(string resource)
6972
/// <param name="resource">The URI to send the request to.</param>
7073
/// <param name="body">The request body.</param>
7174
/// <returns>Returns a request builder.</returns>
72-
public IRequestBuilder PostAsync<TBody>(string resource, TBody body)
75+
public IRequest PostAsync<TBody>(string resource, TBody body)
7376
{
7477
return this.PostAsync(resource).WithBody(body);
7578
}
7679

7780
/// <summary>Create an asynchronous HTTP PUT request message (but don't dispatch it yet).</summary>
7881
/// <param name="resource">The URI to send the request to.</param>
7982
/// <returns>Returns a request builder.</returns>
80-
public IRequestBuilder PutAsync(string resource)
83+
public IRequest PutAsync(string resource)
8184
{
8285
return this.SendAsync(HttpMethod.Put, resource);
8386
}
@@ -87,29 +90,28 @@ public IRequestBuilder PutAsync(string resource)
8790
/// <param name="resource">The URI to send the request to.</param>
8891
/// <param name="body">The request body.</param>
8992
/// <returns>Returns a request builder.</returns>
90-
public IRequestBuilder PutAsync<TBody>(string resource, TBody body)
93+
public IRequest PutAsync<TBody>(string resource, TBody body)
9194
{
9295
return this.PutAsync(resource).WithBody(body);
9396
}
9497

95-
/// <summary>Create an asynchronous request message (but don't dispatch it yet).</summary>
98+
/// <summary>Create an asynchronous HTTP request message (but don't dispatch it yet).</summary>
9699
/// <param name="method">The HTTP method.</param>
97100
/// <param name="resource">The URI to send the request to.</param>
98101
/// <returns>Returns a request builder.</returns>
99-
public virtual IRequestBuilder SendAsync(HttpMethod method, string resource)
102+
public virtual IRequest SendAsync(HttpMethod method, string resource)
100103
{
101104
Uri uri = new Uri(this.BaseClient.BaseAddress, resource);
102105
HttpRequestMessage message = this.Factory.GetRequestMessage(method, uri, this.Formatters);
103106
return this.SendAsync(message);
104107
}
105108

106-
/// <summary>Create an asynchronous request message (but don't dispatch it yet).</summary>
109+
/// <summary>Create an asynchronous HTTP request message (but don't dispatch it yet).</summary>
107110
/// <param name="message">The HTTP request message to send.</param>
108111
/// <returns>Returns a request builder.</returns>
109-
/// <remarks>This is the base method which executes every request.</remarks>
110-
public virtual IRequestBuilder SendAsync(HttpRequestMessage message)
112+
public virtual IRequest SendAsync(HttpRequestMessage message)
111113
{
112-
return this.Factory.GetRequestBuilder(message, this.Formatters, request => this.BaseClient.SendAsync(request.Message));
114+
return this.Factory.GetRequest(message, this.Formatters, request => this.BaseClient.SendAsync(request.Message));
113115
}
114116

115117

Client/Default/Request.cs

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Net;
5+
using System.Net.Http;
6+
using System.Net.Http.Formatting;
7+
using System.Net.Http.Headers;
8+
using System.Threading.Tasks;
9+
10+
namespace Pathoschild.Http.Client.Default
11+
{
12+
/// <summary>Builds and dispatches an asynchronous HTTP request.</summary>
13+
public class Request : IRequest
14+
{
15+
/*********
16+
** Properties
17+
*********/
18+
/// <summary>Constructs implementations for the fluent client.</summary>
19+
protected IFactory Factory { get; set; }
20+
21+
/// <summary>Executes an HTTP request.</summary>
22+
protected Func<IRequest, Task<HttpResponseMessage>> ResponseBuilder { get; set; }
23+
24+
25+
/*********
26+
** Accessors
27+
*********/
28+
/// <summary>The underlying HTTP request message.</summary>
29+
public HttpRequestMessage Message { get; set; }
30+
31+
/// <summary>The formatters used for serializing and deserializing message bodies.</summary>
32+
public MediaTypeFormatterCollection Formatters { get; set; }
33+
34+
/// <summary>Whether to handle errors from the upstream server by throwing an exception.</summary>
35+
public bool ThrowError { get; set; }
36+
37+
38+
/*********
39+
** Public methods
40+
*********/
41+
/// <summary>Construct an instance.</summary>
42+
/// <param name="message">The underlying HTTP request message.</param>
43+
/// <param name="formatters">The formatters used for serializing and deserializing message bodies.</param>
44+
/// <param name="dispatcher">Executes an HTTP request.</param>
45+
/// <param name="factory">Constructs implementations for the fluent client.</param>
46+
public Request(HttpRequestMessage message, MediaTypeFormatterCollection formatters, Func<IRequest, Task<HttpResponseMessage>> dispatcher, IFactory factory = null)
47+
{
48+
this.Message = message;
49+
this.Formatters = formatters;
50+
this.ResponseBuilder = dispatcher;
51+
this.Factory = factory ?? new Factory();
52+
this.ThrowError = true;
53+
}
54+
55+
/***
56+
** Build request
57+
***/
58+
/// <summary>Set the body content of the HTTP request.</summary>
59+
/// <param name="body">The value to serialize into the HTTP body content.</param>
60+
/// <param name="contentType">The request body format (or <c>null</c> to use the first supported Content-Type in the <see cref="Client.IRequest.Formatters"/>).</param>
61+
/// <returns>Returns the request builder for chaining.</returns>
62+
/// <exception cref="InvalidOperationException">No MediaTypeFormatters are available on the API client for this content type.</exception>
63+
public virtual IRequest WithBody<T>(T body, MediaTypeHeaderValue contentType = null)
64+
{
65+
MediaTypeFormatter formatter = this.Factory.GetFormatter(this.Formatters, contentType);
66+
string mediaType = contentType != null ? contentType.MediaType : null;
67+
return this.WithBody<T>(body, formatter, mediaType);
68+
}
69+
70+
/// <summary>Set the body content of the HTTP request.</summary>
71+
/// <param name="body">The value to serialize into the HTTP body content.</param>
72+
/// <param name="formatter">The media type formatter with which to format the request body format.</param>
73+
/// <param name="mediaType">The HTTP media type (or <c>null</c> for the <paramref name="formatter"/>'s default).</param>
74+
/// <returns>Returns the request builder for chaining.</returns>
75+
public virtual IRequest WithBody<T>(T body, MediaTypeFormatter formatter, string mediaType = null)
76+
{
77+
return this.WithBodyContent(new ObjectContent<T>(body, formatter, mediaType));
78+
}
79+
80+
/// <summary>Set the body content of the HTTP request.</summary>
81+
/// <param name="body">The formatted HTTP body content.</param>
82+
/// <returns>Returns the request builder for chaining.</returns>
83+
public virtual IRequest WithBodyContent(HttpContent body)
84+
{
85+
this.Message.Content = body;
86+
return this;
87+
}
88+
89+
/// <summary>Set an HTTP header.</summary>
90+
/// <param name="key">The key of the HTTP header.</param>
91+
/// <param name="value">The value of the HTTP header.</param>
92+
/// <returns>Returns the request builder for chaining.</returns>
93+
public virtual IRequest WithHeader(string key, string value)
94+
{
95+
this.Message.Headers.Add(key, value);
96+
return this;
97+
}
98+
99+
/// <summary>Set an HTTP query string argument.</summary>
100+
/// <param name="key">The key of the query argument.</param>
101+
/// <param name="value">The value of the query argument.</param>
102+
/// <returns>Returns the request builder for chaining.</returns>
103+
public virtual IRequest WithArgument(string key, object value)
104+
{
105+
var query = this.Message.RequestUri.ParseQueryString();
106+
query.Add(key, value.ToString());
107+
string uri = this.Message.RequestUri.GetLeftPart(UriPartial.Path) + "?" + query;
108+
this.Message.RequestUri = new Uri(uri);
109+
return this;
110+
}
111+
112+
/// <summary>Customize the underlying HTTP request message.</summary>
113+
/// <param name="request">The HTTP request message.</param>
114+
/// <returns>Returns the request builder for chaining.</returns>
115+
public virtual IRequest WithCustom(Action<HttpRequestMessage> request)
116+
{
117+
request(this.Message);
118+
return this;
119+
}
120+
121+
/***
122+
** Retrieve response
123+
***/
124+
/// <summary>Asynchronously retrieve the HTTP response.</summary>
125+
/// <exception cref="ApiException">An error occurred processing the response.</exception>
126+
public virtual Task<HttpResponseMessage> AsMessage()
127+
{
128+
return this.ValidateResponse(this.ResponseBuilder(this));
129+
}
130+
131+
/// <summary>Asynchronously retrieve the response body as a deserialized model.</summary>
132+
/// <typeparam name="T">The response model to deserialize into.</typeparam>
133+
/// <exception cref="ApiException">An error occurred processing the response.</exception>
134+
public virtual async Task<T> As<T>()
135+
{
136+
HttpResponseMessage message = await this.AsMessage();
137+
return await message.Content.ReadAsAsync<T>(this.Formatters);
138+
}
139+
140+
/// <summary>Asynchronously retrieve the response body as a list of deserialized models.</summary>
141+
/// <typeparam name="T">The response model to deserialize into.</typeparam>
142+
/// <exception cref="ApiException">An error occurred processing the response.</exception>
143+
public virtual Task<List<T>> AsList<T>()
144+
{
145+
return this.As<List<T>>();
146+
}
147+
148+
/// <summary>Asynchronously retrieve the response body as an array of <see cref="byte"/>.</summary>
149+
/// <returns>Returns the response body, or <c>null</c> if the response has no body.</returns>
150+
/// <exception cref="ApiException">An error occurred processing the response.</exception>
151+
public virtual async Task<byte[]> AsByteArray()
152+
{
153+
HttpResponseMessage message = await this.AsMessage();
154+
return await message.Content.ReadAsByteArrayAsync();
155+
}
156+
157+
/// <summary>Asynchronously retrieve the response body as a <see cref="string"/>.</summary>
158+
/// <returns>Returns the response body, or <c>null</c> if the response has no body.</returns>
159+
/// <exception cref="ApiException">An error occurred processing the response.</exception>
160+
public virtual async Task<string> AsString()
161+
{
162+
HttpResponseMessage message = await this.AsMessage();
163+
return await message.Content.ReadAsStringAsync();
164+
}
165+
166+
/// <summary>Asynchronously retrieve the response body as a <see cref="Stream"/>.</summary>
167+
/// <returns>Returns the response body, or <c>null</c> if the response has no body.</returns>
168+
/// <exception cref="ApiException">An error occurred processing the response.</exception>
169+
public virtual async Task<Stream> AsStream()
170+
{
171+
HttpResponseMessage message = await this.AsMessage();
172+
Stream stream = await message.Content.ReadAsStreamAsync();
173+
stream.Position = 0;
174+
return stream;
175+
}
176+
177+
/***
178+
** Synchronize
179+
***/
180+
/// <summary>Block the current thread until the asynchronous request completes.</summary>
181+
/// <exception cref="ApiException">The HTTP response returned a non-success <see cref="HttpStatusCode"/> and <see cref="ThrowError"/> is <c>true</c>.</exception>
182+
public void Wait()
183+
{
184+
this.AsMessage().Wait();
185+
}
186+
187+
/*********
188+
** Protected methods
189+
*********/
190+
/// <summary>Validate the HTTP response and raise any errors in the response as exceptions.</summary>
191+
/// <param name="request">The response message to validate.</param>
192+
/// <exception cref="ApiException">The HTTP response returned a non-success <see cref="HttpStatusCode"/> and <see cref="ThrowError"/> is <c>true</c>.</exception>
193+
protected async Task<HttpResponseMessage> ValidateResponse(Task<HttpResponseMessage> request)
194+
{
195+
// fetch request
196+
HttpResponseMessage response = await request;
197+
this.ValidateResponse(response);
198+
return response;
199+
}
200+
201+
/// <summary>Validate the HTTP response and raise any errors in the response as exceptions.</summary>
202+
/// <param name="message">The response message to validate.</param>
203+
/// <exception cref="ApiException">The HTTP response returned a non-success <see cref="HttpStatusCode"/> and <see cref="ThrowError"/> is <c>true</c>.</exception>
204+
protected virtual void ValidateResponse(HttpResponseMessage message)
205+
{
206+
if (this.ThrowError && !message.IsSuccessStatusCode)
207+
throw new ApiException(message, message.StatusCode, String.Format("The API query failed with status code {0}: {1}", message.StatusCode, message.ReasonPhrase));
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)