DPS907 notes – Tue Sep 17

Data annotations. Exception handling. Supporting all relevant HTTP methods.

.

Data annotations

Data annotations are descriptive text attributes that we add to properties in a class. A data annotation is located just before the property declaration.

They are useful when we write controller methods to receive and validate input data.

Namespace:

System.ComponentModel.DataAnnotations

Useful data annotatations:

[Required]

[StringLength(n)] or [StringLength(n, MinimumLength = m)]

[Range(min, max)] (int or double types) 

[RegularExpression(“regex“)] (a regular expression; omit the ^ and $ delimiters, because they’re assumed)

All can take an “ErrorMessage” parameter in parentheses, (ErrorMessage = “Fieldname is required”).

You can comma-separate multiple data annotations.

Do you need a ‘default value’ for a property?

A data annotation will not help.

Instead, simply create a class constructor, and set the property’s default value there.

Value types (e.g. int, double) CANNOT use the [Required] attribute.

Reminder about strings… In a regular expression, sometimes you need the backslash character (e.g. \d). In a .NET Framework string, the backslash is a quote character. So, you either precede the entire string with an at sign (@), or use a double backslash (\\d).

.

Looking for documentation? There’s a reference source for data annotations, and many articles. However, none hit the target just right for our purposes. Not even the article on the official ASP.NET Web API web site.

.

Exception handling, version 1

This is an initial entry-level treatment of exception-handling. Later in the course, we will learn more about this topic.

Our code example today will help you learn how to handle errors and exceptions in your web service.

HTTP status codes for errors

In our web APIs, if there is an error, we must return an HTTP error code.

Study the list of HTTP status codes. We will use a number of ‘client’ error codes, and maybe a few ‘server’ error codes:

Code Title Description
400 Bad request Request syntax is not correct
401 Unauthorized Authentication required; the response includes how-to-authenticate info
403 Forbidden Authentication was successful, but the request fails authorization
404 Not found
405 Method not allowed Method not allowed for the resource; response MUST include an Allow header containing a list of valid methods for the requested resource
415 Unsupported media type Request entity body format is not supported
500 Internal server error Default generic message, limit its use please
501 Not implemented Request method not implemented; cannot fulfill the request

Looking for documentation? This Wikipedia article is readable. However, the official source is RFC 2616 (section 6.1.1).

.

Coding principles

Our ‘version 1’ strategy is to handle request errors in a simple yet informative manner.

In this version, our repository methods will return data, or null. The null value will indicate that there’s no data, or an error.

In our controller methods, we will interpret null as ‘no data’ or an error. Its meaning will depend on the method’s context.

Later, our ‘version 2’ strategy will be extended to handle additional kinds of errors, in a way that covers the entire application, with less code to write and maintain.

.

Supporting all relevant HTTP methods

Our code example today will help you learn to support all relevant HTTP methods.

In addition, you will learn to create the correct response, which will be an error (as noted in the previous section), or one of the results in the following table.

Request Return type Status code Comment
GET collection One or more 200
GET collection Empty collection 200 “Empty” is not an error condition
GET one One 200
GET one No item 404 Truly is ‘not found’
POST add item The added item 201 Added item includes identity property
PUT item updated The updated item 200
DELETE item (no content) 204 The response body for DELETE requests have not been defined

.

Coding principles

In the previous section, we decided that our repository methods would return data, or null.

In our controller methods, the return types will be as follows:

  • Get all – return an entity collection, typically as an IEnumerable<T>
  • Get one, add new, update existing – return HttpResponseMessage<T>
  • Delete one – return void, which automatically returns HTTP status code 204

The “get all” case will always be successful. That’s why we can set the method’s return type as an entity collection type.

The “delete one” case may succeed, or fail. We don’t care at the controller level. By convention, a void return type returns 204.

For the others, the method’s return type will be HttpResponseMessage. Always.

For the following explanations, the “Program” entity will be used as the example.

.

“Get one” explained 

In the repository, “get one” uses the object’s identifier. If found, return the appropriate view model type. Otherwise, null.

In the controller, call the repository method.

If the return value is null, return HTTP 404, “not found”.

Otherwise, build an HTTP response object, and return the object, along with HTTP 200, “OK”.

.

“Add new” explained 

In the repository, “add new” attempts to add a new object. The object’s data passed input data validation in the controller, so the attempt should succeed. In the future, you can add business logic here if you need additional validation to be done. Return the appropriate view model type.

In the controller, you must first check the model state (i.e. input data validation). If the data is bad, return HTTP 415, “unsupported media type”.

If you have a well-designed view model class, and use data annotations, the quality of the input data rises.

In addition, this step guards against under-posting (missing data), and over-posting (too much data).

Alternatively, if successful, call the repository method. When you build the HTTP response object, you must return the object, along with HTTP 201, “created”.

Additionally, you MUST add a “Location” header to the response. Although you can enter a string, it’s better to construct a URI. That way, you can copy-then-paste this code to other methods in other controllers.

To construct a URI, use the “new Uri” constructor. The Url.Link method is a helper method (System.Web.Http.Routing.UrlHelper). The method takes two arguments:

  1. A route name, which can be seen in App_Start/WebApiConfig.cs, and
  2. A route value, which we get from the new object’s “Id” (identifier) property

The “route name” will resolve to a URI that effectively is the base URI to this controller’s actions. The “route value” is then appended.

.

“Update existing” explained

In the repository, “update existing” first attempts to fetch the existing object. If the existing object does not exist, null is returned.

Alternatively, if successful, it has a reference to the object, and will proceed to update the object, and then return the updated object:

  1. ds.Entry(p) is the reference to the object
  2. .CurrentValues is the object’s collection of current values
  3. .SetValues(updatedProgram) will update/change those values with the new supplied values

The SetValues method ignores missing properties, as well as navigation properties to associated objects.

In the controller, you must first check the model state. At the same time, for “update existing” scenarios, you must ensure that the identifier on the URI matches the identifier in the request entity body. If the data is bad, return HTTP 400, “bad request”.

This step guards against some kinds of attacks.

Alternatively, if successful, call the repository method. When you build the HTTP response object, you must return the object, along with HTTP 200, “OK”.

.

“Delete existing” explained

In the repository, “delete existing” first attempts to fetch the existing object. If the existing object does not exist, do nothing (but in the future, we’ll fix that).

Alternatively, if successful, it attempts to remove the existing object from the collection. This attempt is guarded by a try-catch block. The attempt may fail, because the existing object may be associated with other objects. For example, a “Program” has a collection of “Subject” objects. So, we cannot delete a “Program” object if it has associated “Subject” objects.

In the controller, we simply call the repository method. We don’t care if the object exists, because we don’t want to reveal too much information about our entity collection.

.

“Subjects” controller explained

You will notice that the code example includes a Subjects controller and repository.

The data-changing methods (Post, Put, Delete) of the controller have been disabled. Also, their corresponding repository methods are incomplete.

This is simply because we need to deal with the issue of “associations” (and navigation properties) before we can activate those methods.

.

Which controller method executes?

You have learned that an ASP.NET Web API web service uses the front controller pattern to determine which method in which controller to run when a request is received.

How does the algorithm work?

Before we cover the steps, think about the data that could be part of a request. A request could include simple data types, and/or complex data types.

Simple data types – in the URI and/or query string – include:

  • int, single, double, decimal
  • string, byte, char, bool, guid
  • datetime, timespan

Complex data types – in the message body – include:

  • anything else; typically any instance of a class
  • your own classes, or .NET Framework classes
  • one-of, or collections-of

Here’s the algorithm:

1. The URI is inspected, looking for a controller name match

2. The HTTP method is inspected

3. The request’s data is inspected – data could be part of the URI and/or query string, or in the message body

Interestingly, these two URIs are the same, for a ‘get-one’ request:

http://example.com/api/foo/3

http://example.com/api/foo?id=3

Then:

4. Identify all controller methods that match the HTTP method name (e.g. ‘GetSomething‘)

5. Match the simple parameter data types – that should be enough to uniquely identify the controller method

If step 5 above yields more than one match, the runtime throws an exception, with a useful message that tells you how to improve your code to avoid the problem in the future.

.

.

.

.

.

.

.

.

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: