Upload
mada
View
31
Download
0
Embed Size (px)
DESCRIPTION
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. - PowerPoint PPT Presentation
Citation preview
Creating and Consuming RESTful Web Services with WCFRon JacobsSr. Technical EvangelistPlatform EvangelismMicrosoft Corporation
AgendaWhat is REST?Is REST SOA?Key REST principlesAdventure Works REST API WCF ExampleSummary
71 Slides5 DemosI must be insane!
WHAT IS REST?
“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?Application state and functionality are resources Every resource has a URIAll resources share a uniform interface
HTTP
IS REST SOA?
“Protocol independence is a bug, not a feature”.
- Mark Baker
SOAP RESTWCF Test Client Notepad
Internet Explorer
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 Messages18,604 bytes“You entered: 1”
KEY REST PRINCIPLES
“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 Tilkovhttp://www.infoq.com/articles/rest-introduction
Key REST PrinciplesGive every “thing” an ID Link things together Use standard methods Resources with multiple representations Communicate statelessly
Give every “thing” an IDExpose thing or collection things with a scheme that is ubiquitousEmbrace 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
Customer C = GetCustomer(1234);
http://example.com/customers/1234
An API like this
Can be represented like this
Link Things TogetherHypermedia as the engine of application state
Just means that you should link things togetherPeople or apps can transition state by following linksLinks 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>[email protected]</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 ThinkingOperation SQL Command HTTP VerbCreate a resource INSERT POST(a), PUTRead a resource SELECT GETUpdate a resource UPDATE PUT, POST(p)Delete a resource DELETE DELETEQuery Metadata (Systables) HEAD
INSERT INTO CUSTOMERS (...) VALUES (...)SQL
(POST) http://example.com/customers<Customer>...</Customer>
REST
Shift to Resource ThinkingOperation SQL Command HTTP VerbCreate a resource INSERT POSTRead a resource SELECT GETUpdate a resource UPDATE PUT (POST)Delete a resource DELETE DELETEQuery Metadata (Systables) HEAD
SELECT FROM CUSTOMERS WHERE ID=567SQL
(GET) http://example.com/customers/567REST
Resources as operationsThe result of an operation can be considered a resource
var result = CalculateShipping(“Redmond”, “NYC”);API
http://example.com/calculateShipping?from=“Redmond”&to=“NYC”
REST
Content NegotiationAllow the client to ask for what they want“I want XML”
“I want JSON”
“I want …” (HTML, CSV, etc.)
GET /customers/1234 HTTP/1.1Host: example.com Accept: text/xml
GET /customers/1234 HTTP/1.1Host: example.com Accept: text/json
JSR 311 features the ideaof extensions as a wayto do content negotiationwithout the headers as in/customers.xml /customers.json
Communicate StatelesslyStateless means that every request stands alone
Session is not requiredCan be bookmarked
Application State lives on the ClientEverything 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
ADVENTURE WORKS REST API
Implementation Time
AdventureWorks Customer APIURI Metho
dCollection Operation
/customers POST Customers Create
/customers/{custId} GET Customers Read
/customers/{custId} PUT Customers Update
/customers/{custId} DELETE Customers Delete
/customers/{custId}/Orders
GET Sales Read customer orders
HTTP GET
“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
GET CUSTOMER DEMO
WebGet AttributeUriTemplateQuery String Parameters
http://rojacobsxps/AdventureWorksDev/api/customer/1
WebGet AttributeWebGet Indicates you want to use an HTTP GET for this methodMethod name is resource nameArguments are query string parameters
// GET a customer[OperationContract][WebGet]CustomerData GetCustomer(string customerId);
http://localhost/service.svc/GetCustomer?customerId=1
WebGet UriTemplateUriTemplate 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}")]CustomerData GetCustomer(string customerId);http://localhost/service.svc/Customer/1
Making your first RESTful Service
Create a WCF Service LibraryAdd a reference / usingSystem.ServiceModel.Web
Decorate your method with WebGetModify configuration
Change the binding from wsHttpBinding to webHttpBindingAdd the webHttp endpoint behavior to the endpoint
Note: WCF will start up without this behavior though it is not very useful configuration
Get CustomersReturns a collection of customers from the databaseIssues
Security – you can only see orders you are allowed to seePaging – stateless requests decide where to start
REST API
SOAP API
http://adventure-works.com/customer
Customer[] GetCustomers()
PagingAllows clients to request a subset of the collectionUse 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}")]CustomerGroupingData GetCustomers(int start, int count);
// POST to customers[OperationContract][WebInvoke(UriTemplate = "customer")]CustomerData AppendCustomer(CustomerData customer);
405 Method not allowedhttp://adventure-works.com/customer
Why?The template matching engine tries to find the best matchThe more specific a match is, the betterWhen 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 UriTemplateGet them instead from the WebOperationContext.CurrentUriTemplate is now just “customer” for both GET and POST
Solution // GET customers[OperationContract] [WebGet(UriTemplate = "customer")]CustomerGroupingData GetCustomers(int start, int count);
// POST to customers[OperationContract][WebInvoke(UriTemplate = "customer")]CustomerData AppendCustomer(CustomerData customer);
200 Okhttp://localhost/AdventureWorksDev/api/customer
Query String Parametersprivate 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
CachingUse HttpRuntime.Cache to cache items on the server if it makes sense to do so
// Check the cacheCustomerData customerData = (CustomerData)HttpRuntime.Cache[requestUri.ToString()];
// Not found in the cacheif (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 CachingAdd Expires or Cache-Control headers to provide clients with hints on cachingWCF Default: Cache-Control: private
No caching of private results
// Allow client to cache for 5 minutesoutResponse.Headers.Add("Cache-Control", "300");
Conditional GETHeaders used by clients to save bandwidth if they hold cached dataIf-Modified-Since: (Date)
Return the data only if it has been modified since (Date)
Conditional GETIf-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-SinceYour data should have a LastModified valueUpdate it whenever the data is written
// Set the headersoutResponse.LastModified = customerData.LastModified;
Supporting If-None-MatchesYour data should have a row versionThis data is returned in an Etag header as an opaque string
// Set the headersoutResponse.ETag = customerData.ETag.ToString();
Conditional GET Checkprivate static void CheckModifiedSince( IncomingWebRequestContext inRequest, OutgoingWebResponseContext outResponse, CustomerData customerData){ // 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 bodyReturn 304 “Not
Modified”
GET Response200 OK
GET successful304 Not Modified
Conditional GET did not find new data400 Bad Request
Problem with the request of some kind404 Not Found
Resource was not found500 Internal Server Error
Everything else
HTTP POST
“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 POSTPOST is ambiguously defined in the HTTP specPOST is the second most used RESTful verbOften referred to as POST(a) for “Append”
Posting to a collection means to append to that collectionAllows the server to determine the ultimate URI
HTTP POSTProblem
How to detect duplicate POST requests?Solutions
Use PUT (it’s Idempotent by nature)Schemes involving handshaking of some kind between the client and serverClient generated identifier for POST
POST to CustomersAppends a new customer to the collectionIssues
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
CustomerData AppendCustomer(CustomerData customer);
POST Examplepublic CustomerData AppendCustomer(CustomerData customer){ OutgoingWebResponseContext outResponse = WebOperationContext.Current.OutgoingResponse;
try { CustomerData newCustomer = 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) Response200 OK
POST successful400 Bad Request
Problem with the request of some kind409 Conflict
Resource already exists500 Internal Server Error
Everything else
Testing POST MethodsFiddler – http://www.fiddler2.com HTTP proxyUse machine name instead of localhost in URI
IIS hosting helps with thisBuild a request
Drag a GET request to the Request BuilderOpen a GET in Visual Studio for easy formatting of XML
Set the Request type to POSTSet the request body to valid XMLSet the Content-Type: text/xml
HTTP PUT
PUT is an idempotent way to create / update a resource
HTTP PUTCreates or Updates the resource
Completely replaces whatever was there before with the new contentUpdate the cache with new resource
Idempotent by designCreating or Updating record 123 multiple times should result in the same valueDo NOT do some kind of relative calculation
PUT and IDsIf you can allow the client to define an ID within a context that is unique, PUT can insert, otherwise PUT is used to update resourcesREST API
SOAP API
Note: the first arg comes from the URI, the customer data comes from the request body
(PUT) http://adventure-works.com/customers/123
CustomerData PutCustomer(string customerId, CustomerData customer);
PUT Response200 OK
Update successful201 Created
Insert Successful400 Bad Request
Problem with the request of some kind404 Not Found
Resource to update was not found500 Internal Server Error
Everything else
HTTP DELETE
DELETE is for uh... well... um... deleting things
DELETEUsed to delete a resourceIssues
Security – can you delete a resourceCache – need to remove it from the server cache
DELETE Response200 OK
Delete successful400 Bad Request
Problem with the request of some kind404 Not Found
Resource to delete was not found500 Internal Server Error
Everything else
URI MappingURI Method Maps Toapi/customer GET api.svc/customerapi/customer POST api.svc/customerapi/customer/{ID} GET api.svc/customer/{ID}api/customer/{ID} DELETE api.svc/customer/{ID}api/customer/{ID} PUT api.svc/customer/{ID}api/customer/{ID} HEAD api.svc/customer/{ID}
UriMapper HTTP ModuleRESTful people do not like ugly URIs
ScottGu has Many ways to rewrite URIsHttpModules can rewrite the URIs as they come inJon Flanders blog
Using WCF WebHttpBinding and WebGet with nicer Urls Modified it a bit to support my scenario
http://adventure-works.com/service.svc/customer/1
Gotchca! I installed my HttpModule in web.config under <system.web><httpModules>Not workingSearched blogs to find out that for IIS 7 you must install under <system.webServer><modules>If your HttpModule does not rewrite the URL correctly you will get 404 errors and have a hard time understanding why
Content NegotiationTrend is toward an extension syntax
Unfortunately you must specify the response format in the WebGet, WebInvoke attributeYou can dynamically choose your format by making your service return a stream and then serializing your content directly to the stream
http://adventure-works.com/customers.xhtmlhttp://adventure-works.com/customers.xmlhttp://adventure-works.com/customers.json
Two Servicesapixml.svc for XMLapijson.svc for JSONUriMapper code looks for an extension on the resource and maps it to the appropriate service
default is XML2 .SVC files means two classes that implement 2 different contracts
Class Diagram
URI MappingURI Method Maps Toapi/customer GET apixml.svc/customerapi/customer POST apixml.svc/customerapi/customer/{ID} GET apixml.svc/customer/{ID}api/customer/{ID} DELETE apixml.svc/customer/{ID}api/customer/{ID} PUT apixml.svc/customer/{ID}api/customer/{ID} HEAD apixml.svc/customer/{ID}api/customer.json GET apijson.svc/customerapi/customer.json POST apijson.svc/customerapi/customer/{ID}.json
GET apijson.svc/customer/{ID}
api/customer/{ID}.json
DELETE apijson.svc/customer/{ID}
api/customer/{ID}.json
PUT apijson.svc/customer/{ID}
api/customer/{ID}.json
HEAD apijson.svc/customer/{ID}
Content Negotiation Demo
http://rojacobsxps/AdventureWorksDev
Consuming a RESTful ServiceUse WCF or HttpWebRequestNeed a ServiceContract interface
Copy from serviceBuild from scratch
Build UriTemplates that will match up to the service
Data Contract SchemaExport Schema from Assembly
svcutil foo.dll /dconlyGenerate data contracts from xsd files
svcutil *.xsd /dconly
Using WCF on the Clientpublic CustomerData GetCustomer(string customerId, Guid? eTag, DateTime? modifiedSince){ using (var factory = new WebChannelFactory<IAdventureWorksServiceXml>("AdventureWorks")) { IAdventureWorksServiceXml service = factory.CreateChannel();
using (OperationContextScope scope = new OperationContextScope( (IContextChannel)service)) { OutgoingWebRequestContext request = WebOperationContext.Current.OutgoingRequest;
if (eTag != null) request.IfNoneMatch = eTag.ToString();
if (modifiedSince != null) { DateTimeFormatInfo formatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
// RFC1123Pattern request.IfModifiedSince = modifiedSince.Value.ToString("r", formatInfo); } return service.GetCustomer(customerId); } }}
You must createa scope to access the
context
SummaryRESTful services extend the reach of HTTP to your SOARESTful design is harder than you might thinkImplementation has some tricky issues to overcome