root / trunk / hammock / README.markdown @ 0eea575a
History | View | Annotate | Download (19.3 kB)
1 |
# Hammock - REST, easy. |
---|---|
2 |
|
3 |
#### Welcome to the Programmable Web |
4 |
|
5 |
If you haven't had the requirement to connect with another web service in your projects, you soon will. |
6 |
In an increasingly connected world, where the presence of an Application Programming Interface (API) is |
7 |
as essential to a growing company's online success as a blog was five years ago, it's inevitable that you |
8 |
will need to build software that integrates with multiple sources of data from numerous partners. |
9 |
This is achieved through API programming, and by way of adoption this is most often accomplished through |
10 |
HTTP programming, based on the REST architectural style (to varying degrees), which provides convenient |
11 |
metaphors for creating, retrieving, updating, and deleting data over HTTP, as well as providing smooth |
12 |
transitions from one set of data to the next through the URIs embedded in a literal web of results. |
13 |
|
14 |
Notable companies that provide a REST-like API for their customers include Twitter, Facebook, Google, Amazon, |
15 |
and Microsoft. There are too many more to mention. When you are tasked with consuming multiple APIs, you need a |
16 |
simple, expressive toolset that allows you to rapidly build client code that is easy to understand, while |
17 |
allowing you to meet the needs of multiple styles, authentication requirements, and messaging. |
18 |
This is where Hammock comes in. |
19 |
|
20 |
#### The Philosophy and Purpose of Hammock |
21 |
|
22 |
Hammock's philosophy is simple: _REST, easy._ |
23 |
Hammock's purpose is to provide a common foundation for RESTful communication in .NET that gives developers a |
24 |
clean and easy way to build RESTful client libraries, or consume RESTful services from their applications, |
25 |
without compromising time or quality. It shares similar aspirations with other web API utilities, but focuses on |
26 |
completeness, performance, and getting out of your way. |
27 |
|
28 |
#### A Brief History of Hammock |
29 |
|
30 |
Hammock grew from the networking code in TweetSharp (http://tweetsharp.com), .NET's most popular Twitter API library. |
31 |
When the library's authors found themselves maintaining numerous API projects, they turned to TweetSharp's foundation again |
32 |
and again, pulling in snippets and adding new features. As APIs continued to explode online, it made sense to consolidate |
33 |
the code into a common library, add an abstraction layer to sweeten its use, and open it up to the public to benefit. |
34 |
The project was launched feature complete in April 2010 and is still in active development. |
35 |
|
36 |
#### What Does It Do? |
37 |
|
38 |
Hammock provides classes to help you consume RESTful services or develop your own client API libraries with a |
39 |
minimum of effort. It supports asynchronous operation, query caching and mocking, periodic and rate limited tasks, |
40 |
object model serialization, multi-part forms, Basic and OAuth authentication, and timeout and retry policies. |
41 |
It is designed to allow you to extend it easily, so you can provide your own serialization scheme, custom validation, |
42 |
rate limiting rules, and web credentials. It also helps you unify projects across a wide range of platforms and |
43 |
devices, as it works on .NET, .NET Compact Framework, Silverlight, Windows Phone, Mono, MonoTouch, and MonoDroid. |
44 |
|
45 |
|
46 |
|
47 |
## What You Need to Get Started |
48 |
[] |
49 |
|
50 |
|
51 |
|
52 |
## Creating REST Requests |
53 |
|
54 |
This section covers creating a RESTful request issued from your application to the service you intend to consume. |
55 |
Building requests in Hammock uses the language of HTTP, to make it easier to find the methods you're looking for. |
56 |
If you need to add an HTTP header to your outgoing HttpRequest, you'd use the AddHeader method, and so on. |
57 |
|
58 |
### RestClient, RestRequest, and RestResponse |
59 |
These three classes form the backbone of Hammock, and provide all of the properties you need to build RESTful |
60 |
requests and process their results. RestClient and RestRequest have a special relationship; any property that can |
61 |
be set on both classes obeys a hierarchical relationship, so you are free to set common values on RestClient, |
62 |
and override them with RestRequest, depending on your needs. This makes it easy and efficient to create a |
63 |
RestClient with the most common request values, and then use specific request values for each request sent through |
64 |
the client. An obvious example of that could be credentials; if your authorization requirements never change between |
65 |
requests for the client you are consuming, then setting credentials on RestClient makes the most sense, and will allow |
66 |
you to omit setting them on every RestRequest. |
67 |
|
68 |
As a working example, here is a simple Twitter API RestClient that stores basic information about the service, and a |
69 |
RestRequest that sets values, like credentials specific to the user, and the target API path, using your application. |
70 |
|
71 |
BasicAuthCredentials credentials = new BasicAuthCredentials |
72 |
{ |
73 |
Username = "username", |
74 |
Password = "password" |
75 |
}; |
76 |
RestClient client = new RestClient |
77 |
{ |
78 |
Authority = "http://api.twitter.com", |
79 |
VersionPath = "1" |
80 |
}; |
81 |
RestRequest request = new RestRequest |
82 |
{ |
83 |
Credentials = credentials, |
84 |
Path = "statuses/home_timeline.json" |
85 |
}; |
86 |
RestResponse response = client.Request(request); |
87 |
|
88 |
|
89 |
### Building Request Paths |
90 |
|
91 |
The final URL for a request path is constructed first from the RestClient's Authority property. From there, either RestRequest |
92 |
or RestClient is inspected for the VersionPath, and Path values. The VersionPath is always inserted between the Authority and |
93 |
Path values. It is useful for targeting specific versions of an API endpoint, or for any URI path fragment that varies between |
94 |
the authority and the target path. Hammock can handle secure endpoints, just ensure you include the HTTPS scheme when declaring |
95 |
your authority. |
96 |
|
97 |
// TODO |
98 |
|
99 |
Once your path is defined, you still need to add request state. State in a REST request refers to the query string parameters, |
100 |
POST parameters or fields, request headers, or entity body contents that make up the data sent to the specified path. |
101 |
In Hammock, you can do this one of two ways: using a traditional method API, or applying attributes to a custom class that is passed |
102 |
to Hammock. |
103 |
|
104 |
### Request State Using Method Style |
105 |
|
106 |
Properties and methods on both RestClient and RestRequest form the basis of most requests in Hammock: WebMethod, UserAgent, AddHeader, |
107 |
AddParameter, AddFile, AddField, and Entity form the list of major members. Methods that begin with Add allow setting multiple values, |
108 |
while the properties allow setting a single value. All of these members are allowable on either class, making it easier to build up |
109 |
clients with default behavior. The UserAgent property is a formalized way of setting the User-Agent request property, but it can be |
110 |
set using either the property or the AddHeader method. If both are explicitly set... |
111 |
|
112 |
[] |
113 |
|
114 |
The Entity property allows you to set the body of a POST or PUT request, and it can be set to any object. If you do not specify a |
115 |
WebMethod before setting the Entity, Hammock will assume you are making a POST request. |
116 |
|
117 |
[] |
118 |
|
119 |
### Request State Using Attribute Style |
120 |
|
121 |
Some APIs, like TweetSharp, need a more stateful approach to assembling REST requests. For example, you may want to collect information |
122 |
about a particular request over time in your own class, and at request time, transform it into the appropriate header, parameter, |
123 |
user agent, and entity body values. You can certainly do this using the previous method style, but it would result in a lot of |
124 |
"left right" coding, or mapping state from your code to the most appropriate RestClient or RestRequest properties. To avoid having to |
125 |
perform this work, and to enable some advanced validation and transforming capabilities, you can use Hammock's attributes style. |
126 |
|
127 |
[] |
128 |
|
129 |
### Using IWebQueryInfo |
130 |
|
131 |
The IWebQueryInfo interface is an empty marker interface you can attach to the class you intend to pass to Hammock to build requests. |
132 |
Once your class implements IWebQueryInfo, you can use the built-in specialized attributes to declare essential request data like |
133 |
parameters, headers, user agent, and entity, and you can use ours or your own validation attributes to transform values or throw |
134 |
exceptions based on property values at the time of the request. Attributes are only applied to public properties defined in your class. |
135 |
Once you have an instance of your IWebQueryInfo class with attributes applied, you can set the Info property of RestClient or |
136 |
RestRequest to that instance, and Hammock will set all request values appropriately. As always, the Info set on RestRequest will |
137 |
override the Info set on RestClient. |
138 |
|
139 |
[] |
140 |
|
141 |
### Specialized Attributes |
142 |
|
143 |
Specialized attributes set fundamental HTTP API elements on outgoing requests. By marking a public property with UserAgentAttribute, |
144 |
its value is used as the request's "User-Agent" header value. ParameterAttribute and HeaderAttribute are used to set request URL or |
145 |
POST parameters and headers respectively, and require a name string to identify the name of the name-value pair to use. The next |
146 |
listing demonstrates an IWebQueryInfo class with some fundamental request values using attributes. |
147 |
|
148 |
[] |
149 |
|
150 |
### Validation Attributes |
151 |
|
152 |
Hammock includes a number of validation attributes for your use, that we've found are useful for building libraries. The purpose of a |
153 |
validation attribute is to transform, at request time, the value of the decorated public property. This gives you an opportunity to |
154 |
course correct for your needs (such as is the case with DateFormatAttribute and BooleanToIntegerAttribute), or prevent issuing requests |
155 |
that don't meet specific criteria (as in SpecificationAttribute and RequiredAttribute). You can also derive from ValidationAttribute |
156 |
to create your own custom validation or transformations. The following example demonstrates an IWebQueryInfo class with typical |
157 |
validation attributes, including the use of a Hammock-provided ValidEmailSpecification specification and SpecificationAttribute. |
158 |
In the example, a ValidationException will throw if the value of Email is not a valid email address at the time of a request, and if |
159 |
it were, the value of the valid email address would be passed in a parameter named "Contact" (either in the URL with GET or in the POST |
160 |
body, depending on the request). |
161 |
|
162 |
// A IWebQueryInfo implementation for custom validation |
163 |
public class MyCustomInfo : IWebQueryInfo |
164 |
{ |
165 |
[Parameter("Contact")] |
166 |
[Specification(typeof(ValidEmailSpecification))] |
167 |
public string Email { get; set; } |
168 |
} |
169 |
|
170 |
// Example code that would throw an exception due to an invalid email address |
171 |
IWebQueryInfo info = new MyCustomInfo { Email = "nowhere" }; |
172 |
|
173 |
RestClient client = new RestClient |
174 |
{ |
175 |
Authority = "http://nowhere.com", |
176 |
Info = info |
177 |
}; |
178 |
|
179 |
RestRequest request = new RestRequest |
180 |
{ |
181 |
Path = "fast" |
182 |
}; |
183 |
|
184 |
client.Request(request); |
185 |
|
186 |
// From Hammock.Validation namespace, for illustration purposes |
187 |
public class ValidEmailSpecification : HammockSpecification<string> |
188 |
{ |
189 |
// Accepts names, i.e. John Smith <john@johnsmith.com> |
190 |
private static readonly Regex _names = |
191 |
new Regex( |
192 |
@"\w*<([-_a-z0-9'+*$^&%=~!?{}]+(?:\.[-_a-z0-9'+*$^&%=~!?{}]+)*@(?:(?![-.])[-a-z0-9.]+(?<![-.])\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d+)?)>", |
193 |
RegexOptions.Compiled | RegexOptions.IgnoreCase |
194 |
); |
195 |
|
196 |
// Just an email address |
197 |
private static readonly Regex _explicit = |
198 |
new Regex( |
199 |
@"^[-_a-z0-9'+*$^&%=~!?{}]+(?:\.[-_a-z0-9'+*$^&%=~!?{}]+)*@(?:(?![-.])[-a-z0-9.]+(?<![-.])\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d+)?$", |
200 |
RegexOptions.Compiled | RegexOptions.IgnoreCase |
201 |
); |
202 |
|
203 |
public override bool IsSatisfiedBy(string instance) |
204 |
{ |
205 |
var result = _explicit.IsMatch(instance) || _names.IsMatch(instance); |
206 |
return result; |
207 |
} |
208 |
} |
209 |
|
210 |
|
211 |
### Writing a Custom Validation Attribute |
212 |
|
213 |
You can supply your own transformation or validation behaviors by extending the abstract ValidationAttribute class and providing an |
214 |
implementation for the single method defined. The next code example shows a custom implementation of a validation attribute that |
215 |
uppercases a string value, and converts null values to string literals. The value passed to TransformValue could be _null_, so your |
216 |
validation attribute should account for this. |
217 |
|
218 |
### Combining Method and Attribute Styles |
219 |
|
220 |
You can use a combination of method and attribute style programming to build your requests, just keep in mind that Hammock will |
221 |
honor the RestClient and then RestRequest values you specify before using attributes. In other words, if you specify a parameter or |
222 |
user agent attribute on a class with IWebQueryInfo, that has the same name as a parameter you pass to RestClient, the value of that |
223 |
parameter or the user agent will take the RestClient value, not the attribute value. Headers will continue to combine values from all |
224 |
sources. |
225 |
|
226 |
|
227 |
|
228 |
## Sending Web API Requests |
229 |
|
230 |
After your request is ready for transport, you can execute it sequentially or asynchronously to return a RestResponse object. |
231 |
RestResponse is also available as a generic class scoped to the expected return value. This is useful if you are expecting a response |
232 |
entity that you can deserialize into a common class, making the process of converting content from the HTTP seam to regular .NET |
233 |
classes transparent; in this case the object is available in `RestResponse<T>`'s ContentEntity property. If you do not specify an |
234 |
`IDeserializer` on `RestRequest` or `RestClient`, but you expect a `ContentEntity` anyway, it will return a null value. If you are not |
235 |
intending to capture strongly typed responses, you can obtain the raw results from the Content property or cast the `ContentEntity` |
236 |
yourself provided you also supplied a ResponseEntityType along with your non-generic request, otherwise the deserializer would not know |
237 |
which target type you intended. Other diagnostic information about the request is available in the RestResponse class, and Hammock |
238 |
will output essential information about requests and responses through .NET's tracing capability, so you can attach your own listeners |
239 |
to log important details. |
240 |
|
241 |
[] |
242 |
|
243 |
### Basic Operation |
244 |
|
245 |
There are two methods available to send sequential requests when your application will wait for the results: `Request`, and `Request<T>`. |
246 |
The only difference is whether you expect to deserialize the response body as a .NET class before building the RestResponse object. |
247 |
|
248 |
<pre> |
249 |
using System; |
250 |
using System.IO; |
251 |
using Hammock.Serialization; |
252 |
using Newtonsoft.Json; |
253 |
|
254 |
namespace Hammock.Extras |
255 |
{ |
256 |
public class HammockJsonDotNetSerializer : ISerializer, IDeserializer |
257 |
{ |
258 |
private readonly JsonSerializer _serializer; |
259 |
|
260 |
public HammockJsonDotNetSerializer(JsonSerializerSettings settings) |
261 |
{ |
262 |
_serializer = new JsonSerializer |
263 |
{ |
264 |
ConstructorHandling = settings.ConstructorHandling, |
265 |
ContractResolver = settings.ContractResolver, |
266 |
ObjectCreationHandling = settings.ObjectCreationHandling, |
267 |
MissingMemberHandling = settings.MissingMemberHandling, |
268 |
DefaultValueHandling = settings.DefaultValueHandling, |
269 |
NullValueHandling = settings.NullValueHandling |
270 |
}; |
271 |
|
272 |
foreach (var converter in settings.Converters) |
273 |
{ |
274 |
_serializer.Converters.Add(converter); |
275 |
} |
276 |
} |
277 |
|
278 |
#region IDeserializer Members |
279 |
|
280 |
public virtual object Deserialize(string content, Type type) |
281 |
{ |
282 |
using (var sr = new StringReader(content)) |
283 |
{ |
284 |
using (var jtr = new JsonTextReader(sr)) |
285 |
{ |
286 |
return _serializer.Deserialize(jtr, type); |
287 |
} |
288 |
} |
289 |
} |
290 |
|
291 |
public virtual T Deserialize<T>(string content) |
292 |
{ |
293 |
using (var sr = new StringReader(content)) |
294 |
{ |
295 |
using (var jtr = new JsonTextReader(sr)) |
296 |
{ |
297 |
return _serializer.Deserialize<T>(jtr); |
298 |
} |
299 |
} |
300 |
} |
301 |
|
302 |
#endregion |
303 |
|
304 |
#region ISerializer Members |
305 |
|
306 |
public virtual string Serialize(object instance, Type type) |
307 |
{ |
308 |
using (var sw = new StringWriter()) |
309 |
{ |
310 |
using (var jtw = new JsonTextWriter(sw)) |
311 |
{ |
312 |
jtw = Formatting.Indented; |
313 |
jtw = '"'; |
314 |
|
315 |
_serializer.Serialize(jtw, instance); |
316 |
var result = sw(); |
317 |
return result; |
318 |
} |
319 |
} |
320 |
} |
321 |
|
322 |
public virtual string ContentType |
323 |
{ |
324 |
get { return "application/json"; } |
325 |
} |
326 |
|
327 |
public virtual Encoding ContentEncoding |
328 |
{ |
329 |
get { return Encoding.UTF8; } |
330 |
} |
331 |
|
332 |
#endregion |
333 |
} |
334 |
} |
335 |
</pre> |
336 |
|
337 |
### Asynchronous Operation |
338 |
[] |
339 |
|
340 |
### Using RetryPolicy and Timeout |
341 |
[] |
342 |
|
343 |
### Caching Responses |
344 |
[] |
345 |
|
346 |
### Mocking Responses |
347 |
[] |
348 |
|
349 |
### Authenticating Requests |
350 |
[] |
351 |
|
352 |
#### Using BasicAuthCredentials |
353 |
[] |
354 |
|
355 |
#### Using OAuthCredentials |
356 |
[] |
357 |
|
358 |
#### Creating Custom Credentials |
359 |
[] |
360 |
|
361 |
### Request Tracing |
362 |
[] |
363 |
|
364 |
### Entity Serialization |
365 |
|
366 |
Hammock provides hooks to allow you to serialize objects into your preferred format, usually JSON or XML when sending a POST or PUT |
367 |
request. This is a common scenario in many APIs that expect an entity when creating or updating records. Similarly, you can attach a |
368 |
deserializer to automatically convert a server entity into a regular class, to make it easy to work with in your code. |
369 |
|
370 |
Hammock doesn't push a particular serialization strategy on you. There are already too many ways to serialize and deserialize objects |
371 |
(communiy favorites are JSON.NET http://codeplex.com/json and ServiceStack TypeSerializer http://code.google.com/p/servicestack/wiki/TypeSerializer, |
372 |
and we don't want to introduce another one. |
373 |
|
374 |
You decide how you will serialize or deserialize your entities, and provide an implementation of ISerializer and IDeserializer to do |
375 |
the work. That said, Hammock does include stock .NET serializers that will serialize and deserialize entities based on |
376 |
DataContractSerializer, DataContractJsonSerializer, JavaScriptSerializer, and XmlSerializer, so if you are already using those schemes |
377 |
in your code, hooking them up is straightforward. |
378 |
|
379 |
#### Creating Custom Serializers |
380 |
|
381 |
If you are using a different serialization scheme, you will need to provide an implementation of ISerializer, IDeserializer, or both, |
382 |
typically in the same concrete class, depending on your needs. One popular serialization library for JSON (with support for XML) is |
383 |
JSON.NET, which is used in TweetSharp. Here is an example implementation of a custom Hammock serializer that will let you use JSON.NET |
384 |
to serialize and deserialize your request and response entities, respectively. This example is provided in the Hammock.Extras project |
385 |
included with the source code. |
386 |
|
387 |
<pre> |
388 |
</pre> |
389 |
|
390 |
#### Achieving POCO for XML and JSON Entities |
391 |
|
392 |
#### Dynamic serialization in .NET 4.0 |
393 |
|
394 |
### Periodic Tasks |
395 |
|
396 |
### Rate Limiting |
397 |
|
398 |
## Handling REST Responses |
399 |
### Deserializing Responses |
400 |
### Handling Errors |
401 |
|
402 |
## Walkthrough: Postmark (JSON) |
403 |
|
404 |
## Walkthrough: FreshBooks (XML) |
405 |
|
406 |
## Walkthrough: Twitter (OAuth 1.0a) |
407 |
### OAuth 1.0a Authentication |
408 |
### Credential Proxies using OAuth Echo |
409 |
|
410 |
## Walkthrough: Facebook Graph (OAuth 2.0) |
411 |
### OAuth 2.0 Authentication |
412 |
|
413 |
|