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