DPS907 notes – Tue Sep 16

New RFC. Work-saving ‘template’. Data annotations refresher. Exception handling, version 1. Supporting a full range of HTTP methods.

.

Quiz today

Today’s session begins with a quiz.

.

RFC 2616 and the transition to RFC 723x

Working knowledge of RFC 2616 is very important for a web service programmer.

During the summer of 2014, the IETF replaced RFC 2616 with a series of RFCs, 7230 through 7235.

Read this article by Mark Nottingham to learn more.

During the lifetime of this web services course, we will transition our RFC usage over to the 723x series. (We started this course by referring to 2616, to take advantage of your previous awareness of that RFC, and its wider awareness in the web programming community.) Your study of these RFCs should begin with RFC 7230, and RFC 7231.

.

Using an existing project as a ‘template’ for a new project

After completing Lab 1, it is apparent that you need to perform several tasks to create and configure a project. If you had to perform those tasks for each new project, you would become motivated to reduce the repetition somehow.

In this section, you will learn how to use an existing project as a ‘template’ for a new project.

Follow the instructions in this document:
https://petermcintyre.com/topics/using-an-app-as-a-template-asp-net-mvc/

(Later in the course, you will begin using a Visual Studio feature, “Export Template”, to create a ‘template’.)

.

Data annotations overview

Most students in the course are familiar with data annotations. This section, and the next two, will refresh your memory. If you need to learn about data annotations, continue reading and studying.

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

Data annotations can help with:

  • property constraint and limits
  • input data validation
  • property display and formatting in views
  • communicating error messages to users

Some data annotations are intended only for design model classes, while others are for use with resource model classes. And, some work with both.

Their overall benefit is to reduce the amount of code that has to be written to handle typical data management scenarios.

Please note that data annotations DO NOT replace the need to validate data against business rules and/or application logic. You still need to do that. Data annotations help most by improving the quality of input data, so that it can then be inspected for its integrity and appropriateness.

.

Data annotations for design model classes

In your design model classes source code file, add a ‘using’ directive for this namespace:

System.ComponentModel.DataAnnotations

Useful data annotations for design model classes include:

[Required] (implemented as ‘not null’ in the database)

[StringLength(n)] (implemented as a specific ‘varchar’ value in the database)

You can comma-separate multiple data annotations.

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

There are a few others that you may use occasionally; we may discuss them later in the course.

Do you need a ‘default value’ for a property? A data annotation will not help.

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

.

Data annotations for resource model classes

In your resource model classes source code files, add a ‘using’ directive for this namespace:

System.ComponentModel.DataAnnotations

Useful data annotations for resource model classes include:

[Required]

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

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

[DataType(DataType.Password)] (others include EmailAddress, Url, Currency, Date, Time, and MultilineText)

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

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

As before, you can comma-separate multiple data annotations, and value types (e.g. int, double) CANNOT use the [Required] attribute (but the [Range(min, max)] attribute effectively solves that problem).

.

Custom error messages

All attributes accept a parameter named “ErrorMessage”. The value is a custom error message for the user. For example:

[Range(2, 6, ErrorMessage=”Selected gizmos must range from 2 to 6″)]

.

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 may 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 7231.

.

Coding principles

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

In this version, our manager/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 manager/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 IHttpActionResult
  • 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 IHttpActionResult. Always.

For the following explanations, the week 3 code example named “AllHttpMethods” will be used. It has an entity named “Human”.

.

“Get one” explained 

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

In the controller, call the manager/repository method.

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

Otherwise, return an Ok() result, along with the object.

.

“Add new” explained 

In the manager/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 resource model type.

In the controller, you must first check the model state (i.e. input data validation). If the data is bad, return BadRequest().

If you have a well-designed resource 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 manager/repository method. Then, return a Created<T>() result, along with the new object.

The Created<T>() result requires you to supply a URI for the new object. The runtime will use that URI as the value of the Location header. 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 BadRequest()..

This step guards against some kinds of attacks.

Alternatively, if successful, call the manager/repository method. If successful, return an Ok() result and the updated object.

.

“Delete existing” explained

In the manager/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.

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

.

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.

.

Documentation

Mike Wasson has two articles that provide more detail, on the official asp.net/webapi web site:

Routing in ASP.NET Web API

Routing and Action Selection

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

  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: