creating and consuming restful web services with wcf

Post on 23-Feb-2016

34 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

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

TRANSCRIPT

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!

ResourcesLeonard RichardsonSam Ruby

www.ronjacobs.comCodeSlides

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>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 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}

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

top related