680 likes | 1.19k Views
Creating and Consuming RESTful Web Services with WCF. Ron Jacobs Sr. Technical Evangelist Platform Evangelism Microsoft Corporation. Agenda. What is REST? Is REST SOA? Key REST principles Adventure Works REST API WCF Example Summary. 71 Slides 5 Demos I must be insane!. Resources.
E N D
Creating and Consuming RESTful Web Services with WCF Ron Jacobs Sr. Technical Evangelist Platform Evangelism Microsoft Corporation
Agenda • What is REST? • Is REST SOA? • Key REST principles • Adventure Works REST API WCF Example • Summary 71 Slides 5 Demos I must be insane!
Resources • Leonard Richardson • Sam Ruby • www.ronjacobs.com • Code • Slides
“Representational state transfer (REST) is a style of software architecture for distributed hypermedia systems such as the World Wide Web.” http://en.wikipedia.org/wiki/Representational_State_Transfer What is rest?
What is REST? • Application state and functionality are resources • Every resource has a URI • All resources share a uniform interface HTTP
“Protocol independence is a bug, not a feature”. - Mark Baker Is REST SOA?
IS REST SOA? • REST is an architectural style that allows you to implement services with broad reach • SOA is about services • SOA is not about protocol, transport, format etc. 5 HTTP Messages 18,604 bytes “You entered: 1”
“The promise is that if you adhere to REST principles while designing your application, you will end up with a system that exploits the Web’s architecture to your benefit.” -Stefan Tilkov http://www.infoq.com/articles/rest-introduction Key rest principles
Key REST Principles • Give every “thing” an ID • Link things together • Use standard methods • Resources with multiple representations • Communicate statelessly
Give every “thing” an ID • Expose thing or collection things with a scheme that is ubiquitous • Embrace the URI • How to get it (http:// or net.tcp:// or net.msmq:// etc.) • Where to get it (example.com) • What to get (customer 1234)
Give every “thing” an ID An API like this Customer C = GetCustomer(1234); Can be represented like this http://example.com/customers/1234
Link Things Together • Hypermedia as the engine of application state • Just means that you should link things together • People or apps can transition state by following links • Links can be to the same app or to some other app
Link Things Together <CustomerData> <Self>http://adventure-works.com/customer/1</Self> <CompanyName>A Bike Store</CompanyName> <CustomerID>1</CustomerID> <EmailAddress>orlando0@adventure-works.com</EmailAddress> <FirstName>Orlando</FirstName> <LastName>Gee</LastName> <Orders>http://adventure-works.com/customer/1/orders</Orders> <RowID>3f5ae95e-b87d-4aed-95b4-c3797afcb74f</RowID> </CustomerData> http://search.live.com/results.aspx?q=Ron+Jacobs&first=11...
Use Standard Methods public class Resource { Resource(Uri u); Response Get(); Response Post(Request r); Response Put(Request r); Response Delete(); Response Head(); }
Shift to Resource Thinking SQL INSERT INTO CUSTOMERS (...) VALUES (...) REST (POST) http://example.com/customers <Customer>...</Customer>
Shift to Resource Thinking SQL SELECT FROM CUSTOMERS WHERE ID=567 REST (GET) http://example.com/customers/567
Resources as operations • The result of an operation can be considered a resource API var result = CalculateShipping(“Redmond”, “NYC”); REST http://example.com/calculateShipping?from=“Redmond”&to=“NYC”
Content Negotiation • Allow the client to ask for what they want • “I want XML” • “I want JSON” • “I want …” (HTML, CSV, etc.) GET /customers/1234 HTTP/1.1 Host: example.com Accept: text/xml JSR 311 features the idea of extensions as a way to do content negotiation without the headers as in /customers.xml /customers.json GET /customers/1234 HTTP/1.1 Host: example.com Accept: text/json
Communicate Statelessly • Stateless means that every request stands alone • Session is not required • Can be bookmarked • Application State lives on the Client • Everything that is needed to complete the request must be included in the request • Resource State lives on the server(s) • Stored in durable storage (typically) • May be cached
Implementation Time adventure works REST API
“Remember that GET is supposed to be a “safe” operation, i.e. the client does not accept any obligations (such as paying you for your services) or assume any responsibility, when all it does is follow a link by issuing a GET.” -Stefan Tilkov http://www.infoq.com/articles/tilkov-rest-doubts HTTP GET
WebGet Attribute UriTemplate Query String Parameters GET CUSTOMER DeMO http://rojacobsxps/AdventureWorksDev/api/customer/1
WebGet Attribute • WebGet Indicates you want to use an HTTP GET for this method • Method name is resource name • Arguments are query string parameters // GET a customer [OperationContract] [WebGet] CustomerDataGetCustomer(string customerId); http://localhost/service.svc/GetCustomer?customerId=1
WebGetUriTemplate • UriTemplate maps the URI to parameters in your method • Using parameters in the Uri makes them mandatory, query string parameters are optional. // GET a customer [OperationContract] [WebGet(UriTemplate = "customer/{customerId}")] CustomerDataGetCustomer(string customerId); http://localhost/service.svc/Customer/1
Making your first RESTful Service • Create a WCF Service Library • Add a reference / using System.ServiceModel.Web • Decorate your method with WebGet • Modify configuration • Change the binding from wsHttpBinding to webHttpBinding • Add the webHttp endpoint behavior to the endpoint • Note: WCF will start up without this behavior though it is not very useful configuration
Get Customers • Returns a collection of customers from the database • Issues • Security – you can only see orders you are allowed to see • Paging – stateless requests decide where to start • REST API • SOAP API http://adventure-works.com/customer Customer[] GetCustomers()
Paging • Allows clients to request a subset of the collection • Use Query String parameters to specify start index and count http://adventure-works.com/customer?start=200&count=25
Gotcha! // GET customers [OperationContract] [WebGet(UriTemplate="customer?start={start}&count={count}")] CustomerGroupingDataGetCustomers(int start, int count); // POST to customers [OperationContract] [WebInvoke(UriTemplate = "customer")] CustomerDataAppendCustomer(CustomerData customer); http://adventure-works.com/customer 405 Method not allowed
Why? • The template matching engine tries to find the best match • The more specific a match is, the better • When the URL contains just the resource “customer” • The match for “customer” is a POST method • Return 405 Method not allowed
Why? • Solution • Don’t include the query string parameters in the UriTemplate • Get them instead from the WebOperationContext.Current • UriTemplate is now just “customer” for both GET and POST
Solution // GET customers [OperationContract] [WebGet(UriTemplate = "customer")] CustomerGroupingDataGetCustomers(int start, int count); // POST to customers [OperationContract] [WebInvoke(UriTemplate = "customer")] CustomerDataAppendCustomer(CustomerData customer); http://localhost/AdventureWorksDev/api/customer 200 Ok
Query String Parameters private string GetQueryString(string argName) { UriTemplateMatch match = WebOperationContext.Current.IncomingRequest.UriTemplateMatch; try { return match.QueryParameters[argName]; } catch (KeyNotFoundException) { return null; } } Query String Parametersare found in here
Caching • Use HttpRuntime.Cache to cache items on the server if it makes sense to do so // Check the cache CustomerDatacustomerData = (CustomerData)HttpRuntime.Cache[requestUri.ToString()]; // Not found in the cache if (customerData == null) { // Try to get the customer data customerData = CustomersCollection.GetCustomer(custId); // Still not found if (customerData == null) { outResponse.SetStatusAsNotFound(string.Format("Customer Id {0} not found", customerId)); } else // found { // Set the headers outResponse.LastModified = customerData.LastModified; outResponse.ETag = customerData.ETag.ToString(); CacheCustomer(requestUri, customerData); } }
Client Caching • Add Expires or Cache-Control headers to provide clients with hints on caching • WCF Default: Cache-Control: private • No caching of private results // Allow client to cache for 5 minutes outResponse.Headers.Add("Cache-Control", "300");
Conditional GET • Headers used by clients to save bandwidth if they hold cached data • If-Modified-Since: (Date) • Return the data only if it has been modified since (Date)
Conditional GET • If-None-Matches: (Etag) • Return the data only if there are no records matching this tag • If the data exists but has not been modified return 304 “Not Modified” • The server still has to verify that the resource exists and that it has not changed
Supporting If-Modified-Since • Your data should have a LastModified value • Update it whenever the data is written // Set the headers outResponse.LastModified = customerData.LastModified;
Supporting If-None-Matches • Your data should have a row version • This data is returned in an Etag header as an opaque string // Set the headers outResponse.ETag = customerData.ETag.ToString();
Conditional GET Check private static void CheckModifiedSince( IncomingWebRequestContextinRequest, OutgoingWebResponseContextoutResponse, CustomerDatacustomerData) { // Get the If-Modified-Since header DateTime? modifiedSince = GetIfModifiedSince(inRequest); // Check for conditional get If-Modified-Since if (modifiedSince != null) { if (customerData.LastModified <= modifiedSince) { outResponse.SuppressEntityBody = true; outResponse.StatusCode = HttpStatusCode.NotModified; } } } Not Modified? Suppress body Return 304 “Not Modified”
GET Response • 200 OK • GET successful • 304 Not Modified • Conditional GET did not find new data • 400 Bad Request • Problem with the request of some kind • 404 Not Found • Resource was not found • 500 Internal Server Error • Everything else
“You can use it to create resources underneath a parent resource and you can use it to append extra data onto the current state of a resource.” - RESTful Web Services http post
HTTP POST • POST is ambiguously defined in the HTTP spec • POST is the second most used RESTful verb • Often referred to as POST(a) for “Append” • Posting to a collection means to append to that collection • Allows the server to determine the ultimate URI
HTTP POST • Problem • How to detect duplicate POST requests? • Solutions • Use PUT (it’s Idempotent by nature) • Schemes involving handshaking of some kind between the client and server • Client generated identifier for POST
POST to Customers • Appends a new customer to the collection • Issues • Security – Are you allowed to create a customer? • Idempotency – is this a duplicate POST request? • REST API • SOAP API (POST) http://adventure-works.com/customers CustomerDataAppendCustomer(CustomerData customer);
POST Example public CustomerDataAppendCustomer(CustomerData customer) { OutgoingWebResponseContextoutResponse = WebOperationContext.Current.OutgoingResponse; try { CustomerDatanewCustomer = CustomersCollection.AppendCustomer(customer); if (newCustomer.CustomerID != 0) { outResponse.SetStatusAsCreated( BuildResourceUri("customer", newCustomer.CustomerID.ToString())); } return newCustomer; } catch (CustomerRowIDExistsException) { outResponse.StatusCode = HttpStatusCode.Conflict; outResponse.StatusDescription = "RowID exists, it must be unique"; return null; } catch (Exception ex) { Log.Write(ex); throw; } }
POST(a) Response • 200 OK • POST successful • 400 Bad Request • Problem with the request of some kind • 409 Conflict • Resource already exists • 500 Internal Server Error • Everything else
Testing POST Methods • Fiddler – http://www.fiddler2.com HTTP proxy • Use machine name instead of localhost in URI • IIS hosting helps with this • Build a request • Drag a GET request to the Request Builder • Open a GET in Visual Studio for easy formatting of XML • Set the Request type to POST • Set the request body to valid XML • Set the Content-Type: text/xml
PUT is an idempotent way to create / update a resource http PUT
HTTP PUT • Creates or Updates the resource • Completely replaces whatever was there before with the new content • Update the cache with new resource • Idempotent by design • Creating or Updating record 123 multiple times should result in the same value • Do NOT do some kind of relative calculation