Nancy + rest mow2012

30
Click to edit Master title style • Click to edit Master text styles – Second level • Third level – Fourth level » Fifth level 07-06-22 1 The Lightweight Approach to Building Web Based APIs with .NET MOW 2012

description

 

Transcript of Nancy + rest mow2012

Page 1: Nancy + rest   mow2012

Click to edit Master title style

• Click to edit Master text styles– Second level• Third level

– Fourth level» Fifth level

19-04-2012 1

The Lightweight Approach to Building Web Based APIs with .NET

MOW 2012

Page 2: Nancy + rest   mow2012

Who Am I?

What is “lightweight”

RestBucks

REST

Nancy

Agenda

Page 3: Nancy + rest   mow2012

3

What is lightweight?

Page 4: Nancy + rest   mow2012

4

Low ceremony

Low cruft

Conventions

What is lightweight?

Page 5: Nancy + rest   mow2012

5

Open

Agile

Inexpensive

What is lightweight?

Page 6: Nancy + rest   mow2012

6

Restbucks

Page 7: Nancy + rest   mow2012

7

REST

Page 8: Nancy + rest   mow2012

8

Level 3: Hypermedia

Level 2: HTTP Verbs

Level 1: Resources

Level 0: POX-RPC

REST – Richardsons Maturity Model

Page 9: Nancy + rest   mow2012

9

The basic building blocks of web API

Anything with a URI

http://restbucks.com/menu/

http://restbucks.com/orders/

http://restbucks.com/order/42/

http://restbucks.com/order/42/payment/

REST - Resources

Page 10: Nancy + rest   mow2012

10

REST- Representations

GET http://restbucks.com/order/19202048/ HTTP/1.1

Accept: application/vnd.restbucks+xml

<?xml version="1.0"?><order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://restbuckson.net"> <links> <link uri="http://restbucks.com/order/19202048" rel="http://restbucks.com/docs/order-get.htm" mediaType="application/vnd.restbucks+xml" /> <link uri="http://restbucks.com/order/19202048" rel="http://restbucks.com/docs/order-update.htm" mediaType="application/vnd.restbucks+xml" /> <link uri="http://restbucks.com/order/19202048" rel="http://restbucks.com/docs/order-cancel.htm" mediaType="application/vnd.restbucks+xml" /> <link uri="http://restbucks.com/order/19202048/payment" rel="http://restbucks.com/docs/order-pay.htm" mediaType="application/vnd.restbucks+xml" /> </links> <location>inShop</location> <cost>7.60000</cost> <items> <item> <name>Latte</name> <quantity>1</quantity> <milk>skim</milk> <size>large</size> </item> </items> <status>unpaid</status></order>

Page 11: Nancy + rest   mow2012

11

REST - Representations

GET http://restbucks.com/order/19202048/ HTTP/1.1

Accept: application/vnd.restbucks+json

{  "Location":inShop,  "Cost":7.60000,  "Items":[    {      "Name":"Latte",      "Quantity":1,      "Preferences":{        "milk":"skim",        "size":"large"      }    }  ],  "Status":1,  "Links":[    {      "Uri":"http://restbucks.com/order/19202048",      "Relation":"http://restbucks.com/docs/order-get.htm",      "MediaType":"application/vnd.restbucks+xml"    },    {      "Uri":"http://restbucks.com/order/19202048",      "Relation":"http://restbucks.com/docs/order-update.htm",      "MediaType":"application/vnd.restbucks+xml"    },    {      "Uri":"http://restbucks.com/order/19202048",      "Relation":"http://restbucks.com/docs/order-cancel.htm",      "MediaType":"application/vnd.restbucks+xml"    },    {      "Uri":"http://restbucks.com/order/19202048/payment",      "Relation":"http://restbucks.com/docs/order-pay.htm",      "MediaType":"application/vnd.restbucks+xml"    }  ]}

Page 12: Nancy + rest   mow2012

12

GET

POST

PUT

DELETE

HEAD

OPTIONS

PATCH

REST - verbs

Page 13: Nancy + rest   mow2012

13

Accept

Content-Type

If-None-Match

Etag

REST - Headers

Page 14: Nancy + rest   mow2012

14

Page 15: Nancy + rest   mow2012

15

“Close” to http

Very, very readable code

Very explicit routing

Embraces modularity

Embraces IoC/DI

Embraces testing

Runs anywhere

Why Nancy?

Page 16: Nancy + rest   mow2012

Organizes your routes

Nancy Basics

public class MainModule : NancyModule { public MainModule() { Get["/"] = _ => "Hello from root"; } }

public class SubModule : NancyModule { public SubModule() : base("subpath") { Get["/"] = _ => "Hello from subpath"; } }

Page 17: Nancy + rest   mow2012

Defines which verbs you accepts

HEAD and OPTIONS and automatic

Nancy Basics

public class MainModule : NancyModule { public MainModule() { Get["/"] = _ => "Hello from root"; Post["/”] = _ => DoPost(Request.Form.my_value) Delete["/{id}”] = p => Delete(p.id); Put["/”] = _ => DoPut(Request.Body); Patch["/”] = _ => DoPatch(Request.Body); } }

Page 18: Nancy + rest   mow2012

18

Restbuck on Nancy – Place an Order

public OrdersResourceHandler(IRepository<Product> productRepository, IRepository<Order> orderRepository) : base("/orders"){ this.productRepository = productRepository; this.orderRepository = orderRepository;

Post["/"] = _ => HandlePost(this.Bind<OrderRepresentation>());}

private Response HandlePost(OrderRepresentation orderRepresentation){ var order = TryBuildOrder(orderRepresentation); if (!order.IsValid()) return InvalidOrderResponse(order);

orderRepository.MakePersistent(order); return Created(order);}

Page 19: Nancy + rest   mow2012

19

RestBucks on Nancy – View an Order

public OrderResourceHandler(IRepository<Order> orderRepository) : base(”/order”){ this.orderRepository = orderRepository; Get["/{orderId}/”] = parameters => GetHandler((int) parameters.orderId); …}

public Response GetHandler(int orderId){ var order = orderRepository.GetById(orderId); if (order == null) return HttpStatusCode.NotFound;

if (order.Status == OrderStatus.Canceled) return Response.MovedTo(new ResourceLinker(CanceledOrderUri(orderId);

if (Request.IsNotModified(order)) return Response.NotModified();

return Response.WithContent(Request.Headers.Accept, OrderRepresentationMapper.Map(order,Request.BaseUri())) .WithCacheHeaders(order);}

Page 20: Nancy + rest   mow2012

20

RestBucks on Nancy – Cancel an Order

Delete["/{orderId}/"] = parameters => Cancel((int) parameters.orderId);

public Response Cancel(int orderId) { var order = orderRepository.GetById(orderId); if (order == null) return HttpStatusCode.NotFound;

order.Cancel("canceled from the rest interface"); return HttpStatusCode.NoContent; }

Page 21: Nancy + rest   mow2012

21

RestBucks on Nancy –Pay an Order

Post["/{orderId}/payment"] = parameters => Pay((int) parameters.orderId, this.Bind<PaymentRepresentation>());

public Response Pay(int orderId, PaymentRepresentation paymentArgs) { var order = orderRepository.GetById(orderId); if (order == null) return HttpStatusCode.NotFound;

order.Pay(paymentArgs.CardNumber, paymentArgs.CardOwner); return HttpStatusCode.OK; }

Page 22: Nancy + rest   mow2012

22

RestBucks on Nancy – XML or JSON

return Response.WithContent(Request.Headers.Accept, OrderRepresentationMapper.Map(order, Request.BaseUri())) .WithCacheHeaders(order);

public static Response WithContent<T>(this IResponseFormatter formatter, IEnumerable<Tuple<string, decimal>> acceptHeaders, T content) { var xmlWeight = CalculateWeightForContentType(acceptHeaders, "xml"); var jsonWeight = CalculateWeightForContentType(acceptHeaders, "json"); if (jsonWeight > xmlWeight) return formatter.AsJson(content); else return formatter.AsXml(content); }

Page 23: Nancy + rest   mow2012

23

RestBucks on Nancy – Conditional Gets

return Response.WithContent(Request.Headers.Accept, OrderRepresentationMapper.Map(order, Request.BaseUri())) .WithCacheHeaders(order);

public static Response WithCacheHeaders(this Response response, IVersionable versionable, TimeSpan? maxAge = null){ return response.WithHeaders( new { Header = "ETag", Value = string.Format("\"{0}\"", versionable.Version) }, new { Header = "Cache-Control", Value = string.Format("max-age={0}, public", maxAge ?? TimeSpan.FromSeconds(10)) }); }

Page 24: Nancy + rest   mow2012

24

RestBucks on Nancy – Conditional Gets

if (Request.IsNotModified(order)) return Response.NotModified();

public static bool IsNotModified(this Request request, IVersionable versionable) { if (!request.Headers.IfNoneMatch.Any()) return false; var etag = request.Headers.IfNoneMatch.First(); return string.Format("\"{0}\"", versionable.Version) == etag; }

public static Response NotModified(this IResponseFormatter formatter, TimeSpan? maxAge = null) { Response response = HttpStatusCode.NotModified;

return response.WithHeaders( new { Header = "ReasonPhrase", Value = "Not modified"}, new { Header = "Cache-Control", Value = string.Format("max-age={0}, public", maxAge ?? TimeSpan.FromSeconds(10)) }); }

Page 25: Nancy + rest   mow2012

Nancy.Hosting

Nancy

Your Application

Nancy.Hosting

Page 26: Nancy + rest   mow2012

Usage:> Install-Package Nancy.Hosting.*

Hosts:ASP.NET

WCF

Self

OWIN

Nancy.Hosting

Page 27: Nancy + rest   mow2012

29

Nancy.Testing

[Test] public void WhenOrderHasNotChanged_ThenReturn304() { // Arrange var orderRepo = new RepositoryStub<Order>(new Order(1, 123); var app = new Browser( new ConfigurableBootstrapper (with => { with.Dependency<IRepository<Product>>(…); with.Dependency<IRepository<Order>>(orderRepository); } )); // Act var response = app.Get("/order/123/", with => { with.HttpRequest(); with.Header("If-None-Match", "\"1\""); }); //Assert response.StatusCode.Should().Be.EqualTo(HttpStatusCode.NotModified); }

Page 28: Nancy + rest   mow2012

30

“Close” to http

Very, very readable code

Very explicit routing

Embraces modularity

Embraces IoC/DI

Embraces testing

Runs anywhere

Why Nancy?

Page 29: Nancy + rest   mow2012

31

LightweightLow ceremony

Low cruft

Follows conventions

Open

Agile

Why REST + Nancy

Page 30: Nancy + rest   mow2012

32

Restbucks on Nancy: http://github.com/horsdal/Restbucks-on-Nancy

Rest in Practice: http://restinpractice.com/book.html

Nancy: www.nancyfx.org

Me:

Twitter: @chr_horsdal

Blog: horsdal.blogspot.com

email: [email protected]

More …