ASPNET WebAPI REST Guidance

Posted by JoshReuben on Geeks with Blogs See other posts from Geeks with Blogs or by JoshReuben
Published on Sun, 28 Oct 2012 15:22:14 GMT Indexed on 2012/10/28 17:02 UTC
Read the original article Hit count: 437

Filed under:

ASP.NET Web API is an ideal platform for building RESTful applications on the .NET Framework. While I may be more partial to NodeJS these days, there is no denying that WebAPI is a well engineered framework.

What follows is my investigation of how to leverage WebAPI to construct a RESTful frontend API.

 

The Advantages of REST Methodology over SOAP

  • Simpler API for CRUD ops
  • Standardize Development methodology - consistent and intuitive
  • Standards based à client interop
  • Wide industry adoption, Ease of use à easy to add new devs
  • Avoid service method signature blowout
  • Smaller payloads than SOAP
  • Stateless à no session data means multi-tenant scalability
  • Cache-ability
  • Testability

 

General RESTful API Design Overview

· utilize HTTP Protocol - Usage of HTTP methods for CRUD, standard HTTP response codes, common HTTP headers and Mime Types

· Resources are mapped to URLs, actions are mapped to verbs and the rest goes in the headers.

· keep the API semantic, resource-centric – A RESTful, resource-oriented service exposes a URI for every piece of data the client might want to operate on. A REST-RPC Hybrid exposes a URI for every operation the client might perform: one URI to fetch a piece of data, a different URI to delete that same data. utilize Uri to specify CRUD op, version, language, output format:

http://api.MyApp.com/{ver}/{lang}/{resource_type}/{resource_id}.{output_format}?{key&filters}

· entity CRUD operations are matched to HTTP methods:

  • · Create - POST / PUT
  • · Read – GET - cacheable
  • · Update – PUT
  • · Delete - DELETE

· Use Uris to represent a hierarchies - Resources in RESTful URLs are often chained

· Statelessness allows for idempotency – apply an op multiple times without changing the result. POST is non-idempotent, the rest are idempotent (if DELETE flags records instead of deleting them).

· Cache indication - Leverage HTTP headers to label cacheable content and indicate the permitted duration of cache

· PUT vs POST - The client uses PUT when it determines which URI (Id key) the new resource should have. The client uses POST when the server determines they key. PUT takes a second param – the id. POST creates a new resource. The server assigns the URI for the new object and returns this URI as part of the response message. Note: The PUT method replaces the entire entity. That is, the client is expected to send a complete representation of the updated product. If you want to support partial updates, the PATCH method is preferred

DELETE deletes a resource at a specified URI – typically takes an id param

· Leverage Common HTTP Response Codes in response headers

  • 200 OK: Success
  • 201 Created - Used on POST request when creating a new resource.
  • 304 Not Modified: no new data to return.
  • 400 Bad Request: Invalid Request.
  • 401 Unauthorized: Authentication.
  • 403 Forbidden: Authorization
  • 404 Not Found – entity does not exist.
  • 406 Not Acceptable – bad params.
  • 409 Conflict - For POST / PUT requests if the resource already exists.
  • 500 Internal Server Error
  • 503 Service Unavailable

· Leverage uncommon HTTP Verbs to reduce payload sizes

  • HEAD - retrieves just the resource meta-information.
  • OPTIONS returns the actions supported for the specified resource.
  • PATCH - partial modification of a resource.

· When using PUT, POST or PATCH, send the data as a document in the body of the request. Don't use query parameters to alter state.

· Utilize Headers for content negotiation, caching, authorization, throttling

o Content Negotiation – choose representation (e.g. JSON or XML and version), language & compression. Signal via RequestHeader.Accept & ResponseHeader.Content-Type

Accept: application/json;version=1.0

Accept-Language: en-US

Accept-Charset: UTF-8

Accept-Encoding: gzip

o Caching - ResponseHeader: Expires (absolute expiry time) or Cache-Control (relative expiry time)

o Authorization - basic HTTP authentication uses the RequestHeader.Authorization to specify a base64 encoded string "username:password". can be used in combination with SSL/TLS (HTTPS) and leverage OAuth2 3rd party token-claims authorization.

Authorization: Basic sQJlaTp5ZWFslylnaNZ=

o Rate Limiting - Not currently part of HTTP so specify non-standard headers prefixed with X- in the ResponseHeader.

X-RateLimit-Limit: 10000

X-RateLimit-Remaining: 9990

· HATEOAS Methodology - Hypermedia As The Engine Of Application State – leverage API as a state machine where resources are states and the transitions between states are links between resources and are included in their representation (hypermedia) – get API metadata signatures from the response Link header - in a truly REST based architecture any URL, except the initial URL, can be changed, even to other servers, without worrying about the client.

· error responses - Do not just send back a 200 OK with every response. Response should consist of HTTP error status code (JQuery has automated support for this), A human readable message , A Link to a meaningful state transition , & the original data payload that was problematic.

· the URIs will typically map to a server-side controller and a method name specified by the type of request method. Stuff all your calls into just four methods is not as crazy as it sounds.

· Scoping - Path variables look like you’re traversing a hierarchy, and query variables look like you’re passing arguments into an algorithm

· Mapping URIs to Controllers - have one controller for each resource is not a rule – can consolidate - route requests to the appropriate controller and action method

· Keep URls Consistent - Sometimes it’s tempting to just shorten our URIs. not recommend this as this can cause confusion

· Join Naming – for m-m entity relations there may be multiple hierarchy traversal paths

· Routing – useful level of indirection for versioning, server backend mocking in development

ASPNET WebAPI Considerations

ASPNET WebAPI implements a lot (but not all) RESTful API design considerations as part of its infrastructure and via its coding convention.

Overview

When developing an API there are basically three main steps:

1. Plan out your URIs

2. Setup return values and response codes for your URIs

3. Implement a framework for your API.

 

Design

· Leverage Models MVC folder

· Repositories – support IoC for tests, abstraction

· Create DTO classes – a level of indirection decouples & allows swap out

· Self links can be generated using the UrlHelper

· Use IQueryable to support projections across the wire

· Models can support restful navigation properties – ICollection<T>

· async mechanism for long running ops - return a response with a ticket – the client can then poll or be pushed the final result later.

· Design for testability - Test using HttpClient , JQuery ( $.getJSON , $.each) , fiddler, browser debug. Leverage IDependencyResolver – IoC wrapper for mocking

· Easy debugging - IE F12 developer tools: Network tab, Request Headers tab

 

 

Routing

· HTTP request method is matched to the method name. (This rule applies only to GET, POST, PUT, and DELETE requests.)

· {id}, if present, is matched to a method parameter named id.

· Query parameters are matched to parameter names when possible

· Done in config via Routes.MapHttpRoute – similar to MVC routing

· Can alternatively:

  • o decorate controller action methods with HttpDelete, HttpGet, HttpHead,HttpOptions, HttpPatch, HttpPost, or HttpPut., + the ActionAttribute
  • o use AcceptVerbsAttribute to support other HTTP verbs: e.g. PATCH, HEAD
  • o use NonActionAttribute to prevent a method from getting invoked as an action

· route table Uris can support placeholders (via curly braces{}) – these can support default values and constraints, and optional values

· The framework selects the first route in the route table that matches the URI.

Response customization

· Response code: By default, the Web API framework sets the response status code to 200 (OK). But according to the HTTP/1.1 protocol, when a POST request results in the creation of a resource, the server should reply with status 201 (Created). Non Get methods should return HttpResponseMessage

· Location: When the server creates a resource, it should include the URI of the new resource in the Location header of the response.

public HttpResponseMessage PostProduct(Product item)

{
    item = repository.Add(item);

    var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);

    string uri = Url.Link("DefaultApi", new { id = item.Id });

    response.Headers.Location = new Uri(uri);

    return response;

}

Validation

· Decorate Models / DTOs with System.ComponentModel.DataAnnotations properties RequiredAttribute, RangeAttribute.

· Check payloads using ModelState.IsValid

· Under posting – leave out values in JSON payload à JSON formatter assigns a default value. Use with RequiredAttribute

· Over-posting - if model has RO properties à use DTO instead of model

· Can hook into pipeline by deriving from ActionFilterAttribute & overriding OnActionExecuting

Config

· Done in App_Start folder > WebApiConfig.cs – static Register method: HttpConfiguration param: The HttpConfiguration object contains the following members.

Member

Description

DependencyResolver

Enables dependency injection for controllers.

Filters

Action filters – e.g. exception filters.

Formatters

Media-type formatters. by default contains JsonFormatter, XmlFormatter

IncludeErrorDetailPolicy

Specifies whether the server should include error details, such as exception messages and stack traces, in HTTP response messages.

Initializer

A function that performs final initialization of the HttpConfiguration.

MessageHandlers

HTTP message handlers - plug into pipeline

ParameterBindingRules

A collection of rules for binding parameters on controller actions.

Properties

A generic property bag.

Routes

The collection of routes.

Services

The collection of services.

· Configure JsonFormatter for circular references to support links: PreserveReferencesHandling.Objects

Documentation generation

· create a help page for a web API, by using the ApiExplorer class.

· The ApiExplorer class provides descriptive information about the APIs exposed by a web API as an ApiDescription collection

· create the help page as an MVC view

public ILookup<string, ApiDescription> GetApis()
        {
            return _explorer.ApiDescriptions.ToLookup(
                api => api.ActionDescriptor.ControllerDescriptor.ControllerName);

· provide documentation for your APIs by implementing the IDocumentationProvider interface. Documentation strings can come from any source that you like – e.g. extract XML comments or define custom attributes to apply to the controller

[ApiDoc("Gets a product by ID.")]
[ApiParameterDoc("id", "The ID of the product.")]
public HttpResponseMessage Get(int id)

· GlobalConfiguration.Configuration.Services – add the documentation Provider

· To hide an API from the ApiExplorer, add the ApiExplorerSettingsAttribute

Plugging into the Message Handler pipeline

· Plug into request / response pipeline – derive from DelegatingHandler and override theSendAsync method – e.g. for logging error codes, adding a custom response header

· Can be applied globally or to a specific route

Exception Handling

· Throw HttpResponseException on method failures – specify HttpStatusCode enum value – examine this enum, as its values map well to typical op problems

· Exception filters – derive from ExceptionFilterAttribute & override OnException. Apply on Controller or action methods, or add to global HttpConfiguration.Filters collection

· HttpError object provides a consistent way to return error information in the HttpResponseException response body.

· For model validation, you can pass the model state to CreateErrorResponse, to include the validation errors in the response

public HttpResponseMessage PostProduct(Product item)
{
    if (!ModelState.IsValid)
    {
        return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);

Cookie Management

· Cookie header in request and Set-Cookie headers in a response - Collection of CookieState objects

· Specify Expiry, max-age

resp.Headers.AddCookies(new CookieHeaderValue[] { cookie });

Internet Media Types, formatters and serialization

· Defaults to application/json

· Request Accept header and response Content-Type header

· determines how Web API serializes and deserializes the HTTP message body. There is built-in support for XML, JSON, and form-urlencoded data

· customizable formatters can be inserted into the pipeline

· POCO serialization is opt out via JsonIgnoreAttribute, or use DataMemberAttribute for optin

· JSON serializer leverages NewtonSoft Json.NET

· loosely structured JSON objects are serialzed as JObject which derives from Dynamic

· to handle circular references in json:

json.SerializerSettings.PreserveReferencesHandling =    PreserveReferencesHandling.All à {"$ref":"1"}.

· To preserve object references in XML

[DataContract(IsReference=true)]

· Content negotiation

  • Accept: Which media types are acceptable for the response, such as “application/json,” “application/xml,” or a custom media type such as "application/vnd.example+xml"
  • Accept-Charset: Which character sets are acceptable, such as UTF-8 or ISO 8859-1.
  • Accept-Encoding: Which content encodings are acceptable, such as gzip.
  • Accept-Language: The preferred natural language, such as “en-us”.

o Web API uses the Accept and Accept-Charset headers. (At this time, there is no built-in support for Accept-Encoding or Accept-Language.)

· Controller methods can take JSON representations of DTOs as params – auto-deserialization

· Typical JQuery GET request:

function find() {

    var id = $('#prodId').val();

    $.getJSON("api/products/" + id,

        function (data) {

            var str = data.Name + ': $' + data.Price;

            $('#product').text(str);

        })

    .fail(

        function (jqXHR, textStatus, err) {

            $('#product').text('Error: ' + err);

        });

}           

· Typical GET response:

HTTP/1.1 200 OK

Server: ASP.NET Development Server/10.0.0.0

Date: Mon, 18 Jun 2012 04:30:33 GMT

X-AspNet-Version: 4.0.30319

Cache-Control: no-cache

Pragma: no-cache

Expires: -1

Content-Type: application/json; charset=utf-8

Content-Length: 175

Connection: Close

[{"Id":1,"Name":"TomatoSoup","Price":1.39,"ActualCost":0.99},{"Id":2,"Name":"Hammer", "Price":16.99,"ActualCost":10.00},{"Id":3,"Name":"Yo yo","Price":6.99,"ActualCost": 2.05}]

True OData support

· Leverage Query Options $filter, $orderby, $top and $skip to shape the results of controller actions annotated with the [Queryable]attribute.

[Queryable
public IQueryable<Supplier> GetSuppliers()
 

· Query:

~/Suppliers?$filter=Name eq ‘Microsoft’

· Applies the following selection filter on the server:

GetSuppliers().Where(s => s.Name == “Microsoft”) 

· Will pass the result to the formatter.

· true support for the OData format is still limited - no support for creates, updates, deletes, $metadata and code generation etc

· vnext: ability to configure how EditLinks, SelfLinks and Ids are generated

Self Hosting

no dependency on ASPNET or IIS:

using (var server = new HttpSelfHostServer(config))

{
    server.OpenAsync().Wait();

Tracing

· tracability tools, metrics – e.g. send to nagios

· use your choice of tracing/logging library, whether that is ETW,NLog, log4net, or simply System.Diagnostics.Trace.

· To collect traces, implement the ITraceWriter interface

public class SimpleTracer : ITraceWriter
{
    public void Trace(HttpRequestMessage request, string category, TraceLevel level,
        Action<TraceRecord> traceAction)
    {
        TraceRecord rec = new TraceRecord(request, category, level);
        traceAction(rec);
        WriteTrace(rec);

· register the service with config

· programmatically trace – has helper extension methods:

Configuration.Services.GetTraceWriter().Info(

· Performance tracing - pipeline writes traces at the beginning and end of an operation - TraceRecord class includes aTimeStamp property, Kind property set to TraceKind.Begin / End

Security

· Roles class methods: RoleExists, AddUserToRole

· WebSecurity class methods: UserExists, .CreateUserAndAccount

· Request.IsAuthenticated

· Leverage HTTP 401 (Unauthorized) response

· [AuthorizeAttribute(Roles="Administrator")] – can be applied to Controller or its action methods

· See section in WebApi document on "Claim-based-security for ASP.NET Web APIs using DotNetOpenAuth" – adapt this to STS.--> Web API Host exposes secured Web APIs which can only be accessed by presenting a valid token issued by the trusted issuer. http://zamd.net/2012/05/04/claim-based-security-for-asp-net-web-apis-using-dotnetopenauth/

· Use MVC membership provider infrastructure and add a DelegatingHandler child class to the WebAPI pipeline - http://stackoverflow.com/questions/11535075/asp-net-mvc-4-web-api-authentication-with-membership-provider - this will perform the login actions

· Then use AuthorizeAttribute on controllers and methods for role mapping- http://sixgun.wordpress.com/2012/02/29/asp-net-web-api-basic-authentication/

· Alternate option here is to rely on MVC App : http://forums.asp.net/t/1831767.aspx/1

© Geeks with Blogs or respective owner