ASPNET WebAPI REST Guidance
- by JoshReuben
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