DPS907 notes – Thu Sep 28

Media type formatters. Content negotiation.

 

Test today

Test 4 is today. Not sure when we’ll do it during the timeslot.

A reminder… It is possible that the test will include questions that “reach back” a bit, on topics that were presented in notes from the previous weeks. Make sure that you review those when you are preparing for today’s test.

 

Introduction to “media type formatters”

You have some web service experience working with the JSON and XML data formats. You can reliably accept and deliver JSON content to and from your web service app.

When accepting JSON content into a controller method that handles POST or PUT requests, you have seen that the model binding process materializes an object from the JSON content. But how does that happen?

Similarly, when delivering a result (an object or collection) from a controller method that handles a GET request, you have seen that the object or collection is delivered as JSON in the response. But how does that happen?

A “media type formatter” is the name of the component that actually does the work. This component is conceptually located between the requestor and the controller action/method. Every “Web API” project includes media type formatters that handle JSON and XML.

Read this ASP.NET document for more information about JSON and XML serialization.

The image below is an excerpt of the bottom part of the “message handling” image that you studied above. Click to open it full-size in its own tab/window.

It has been marked up with red arrows which shows the locations involving the media type formatters in the request-handling pipeline.

pipeline-media-handling

 

Here is a simplified diagram that shows the request and response flow. Click to open a full-size version in a new tab/window:

Formatter

 

Custom media type formatter

As you have learned, every “Web API” project includes the built-in media type formatters for JSON and XML.

How do we handle other media types?

We create a custom media type formatter.

Incoming: Our formatter will transform (or convert) an incoming media item – which is a byte stream – in the HTTP request’s entity body to an object that we can use in our web service. The HTTP byte stream is transformed into a byte array (i.e. byte[]).

You may recall that your ASP.NET MVC web apps used an HttpPostedFileBase property in the incoming view model class. This piece of goodness enabled your code to get access to a media item that was sent in a POST request, in an “input type=file” element.

We do not have access to HttpPostedFileBase in a Web API web service. Instead, we create a custom media type formatter.

Outgoing: Our formatter will also transform (or convert) an object in our app – a byte array – to a byte stream in the entity body of an HTTP response.

 

Code example: MediaUpload

Open and study the “MediaUpload” code example, as you continue reading this content. The code example enables us to learn about writing our own media type formatter.

Another code example – MediaUploadAndDeliver – will also be studied today.

We’ll use a simple “book” data entity (books – remember those?), and configure its entity class with properties that hold a photo of the book cover, and the internet media type of the photo (typically “image/png”).

Therefore, this design will work if an entity object has a single photo/image property. And conceptually, it can also make sense for that single photo/image to represent the object. (In other words, you would not use this design if you were building a media-driven photo sharing app/service.)

As a result, it will be possible for a book to have two representations:

  1. Data representation, as JSON
  2. Image/photo representation, as a PNG or JPG image

It will be possible for the requestor to ask for either representation.

 

Design and implement a media type formatter

Above, we stated that we can design and implement a media type formatter. How?

Here’s the general approach to handling images, or any byte-oriented content:

  1. Create (or add) and register a custom media type formatter
  2. Configure the design model class with properties to hold the media item
  3. Configure resource model classes with media-related properties
  4. Add manager methods to handle media-related acceptance (and delivery)
  5. Configure controller methods

Continue reading below, to learn the details. Study the code example while you do that.

 

Create (or add) and register a custom media type formatter

We need a formatter that will handle byte-oriented content. That will enable us to handle these media types:

  • images/photos (png, jpg, etc.)
  • audio (m4a, mp3, etc.)
  • video (mp4, wmv, mov, etc.)
  • documents (pdf, docx, xlsx, zip, etc.)

Create a class that inherits from BufferedMediaTypeFormatter. In the code example, study the ByteFormatter class (it’s found in the project’s ServiceLayer folder).

All formatters have a similar structure:

  • a constructor configures the media types that it will handle
  • a reader method transforms incoming content from the HTTP request entity body
  • a writer method transforms outgoing content to the HTTP response entity body

Note: This formatter design works well for small- to medium-sized media items. If your media item is less than a few hundred kilobytes, then you can use this design, which is synchronous and therefore blocking.

For larger media items, use the asynchronous approach, where the base class is simply MediaTypeFormatter. A future code example will show you how to do this.

The formatter must be registered and initialized when the app starts. Look at the WebApiConfig class, in its Register method, for the statement that does this work.

At this point in time, the web service can handle these media types in its requests and responses, and automatically transform/convert between them and C# objects:

  1. JSON
  2. XML
  3. byte-oriented media items

 

Configure the design model class with properties to hold the media item

You need two properties:

  1. A byte array (byte[]) for the photo (named “Media” or “Photo” or something similar), and
  2. A string for the internet media type (named “ContentType”).

DesignModelClasses

 

Configure resource model classes with media-related properties

Remember the rule above: In a request or response data package, do NOT package a media item inside another container.

However, during processing, while doing work between the controller, to/from the manager, to/from the data store, it is natural and obvious to consider the media item to be a property of an entity object.

Resource models can and should acknowledge the presence of the media item. Study the diagram below, which shows the code example’s resource model classes. Click to open the diagram full-size in its own tab/window:

book_vmexcerptnomedianolink

 

 

The BookWithMediaInfo class includes metadata about the media item. The controller and manager can use the metadata to make processing decisions. The requestor can also do that.

 

Add manager methods to handle media-related acceptance (and delivery)

Today’s code example MediaUpload focuses on the acceptance (upload) of a media item.

Above, you learned that an entity object must exist before its media item can be configured. This situation is similar to any PUT request that modifies properties in an existing entity object.

Therefore, the manager method’s signature includes parameters for the object’s identifier, in addition to the media item properties.

 

Configure controller methods

The code example has a BookPhoto() method that handles the request, which includes:

  1. The request URI, with the entity object identifier
  2. A request header (Content-Type), which declares the media type of the entity body
  3. An entity body, which holds a byte stream

Study the method parameters:

  • an identifier for the object
  • a byte array for the media item

We can easily understand the identifier. For example, we will be working with object with Id = 234.

What about the request header? It is available in the controller’s Request property.

What about the byte array? How does it get materialized from the request’s entity body?

The media type formatter does this work. Automatically.

 

Studying the code example

Here are a few other design features of the MediaUpload code example.

Manager:

The “BookGetAll”, “BookGetById” and “BookAdd” methods return metadata for the media item. They do NOT return the media item bytes.

Controller:

As noted above, the “get” and “add new” methods return metadata for the media item. You can inspect the property values to determine whether an “add new” request was successful (and resulted in the acceptance/upload of a media item).

When testing the “BookPhoto” command method in Fiddler, use this technique:

  1. Set the method to PUT, and set the appropriate URI (/api/books/{id}/setphoto)
  2. For the request body, click the “Upload file…” link, and browse to the media item you wish to use

This is what it should look like in the Fiddler “composer” tab, after you have selected the media item:

fiddler-upload-media-item

 

Delivering a media item in a response

There are two techniques for delivering a media item in a response:

  1. Determined by the web service (and you, the programmer)
  2. Negotiated between the requestor and the web service

 

Predetermined

There’s a new MediaUploadAndDeliver code example today. Open and study it as you go through the content below.

It has a GetPhoto() method, which returns a media item.

The request includes a URI, and no other configuration. In other words, the request does not need an Accept header configured, because the method simply returns the media item as is.

 

Negotiated

The other “get one” method, Get(), uses content negotiation.

The request includes a URI. It also includes an Accept header.

The method will attempt to fulfill this request. If it cannot, it will process the request in a predetermined manner (which is decided by the programmer).

 

Content negotiation

Content negotiation is described formally in RFC 7231. Read that section now.

It is also described in this Wikipedia article.

In summary:

The requestor and the server must negotiate the media type of the response.

If the requestor has no preference (i.e. the request does NOT have an Accept request header), the server will deliver its preferred media type.

If the requestor includes an Accept request header, the server will attempt to deliver the resource as the requested media type. You, as the web service programmer, have complete control over this process.

What if the requested media type not available?

What do you deliver as a response?

This RFC 7231 section clearly states that you must return HTTP 404, because “the origin server did not find a current representation for the target resource”.

 

Implementing content negotiation

The MediaUploadAndDelivery code example is based on the previously-studied MediaUpload code example. As its name suggests, it adds “delivery” of media items as a feature. The following additions/changes were made:

  1. Resource model class
  2. Manager method return types
  3. Controller methods

Each is described below in more detail.

 

Resource model class

A new BookWithMedia resource model class was created. It includes the bytes for the media item.

It should be used only in scenarios where you are working with one entity object. In other words, do not do a “get all” and deliver a BookWithMedia collection to another place in your code. Why? Think about it: If you have thousands of entity objects, and each object includes a large media item, then the memory and processing load on the app server will be immense.

book_vmexcerptwithmedia

 

 

Manager method return types

The “get one” method DOES return the media item bytes, and the metadata.

A controller method will determine whether the media item bytes are returned in the response.

 

Controller methods

There are two methods that service the “get one” use case:

  1. GetPhoto(int? id), URI is /api/books/{id}/photo
  2. Get(int? id), URI is /api/books/{id}

Remember the rule: In a request or response, do NOT package a media item inside another container.

 

1. GetPhoto(int? id), URI is /api/books/{id}/photo

This method implements the predetermined technique.

Notice the custom route. When using this route, the requestor really is intending to get the media item in the response.

It is not necessary for the requestor to include an Accept header in the request. The method will simply return the media item in the response, and set the Content-Type header accordingly. This approach is similar to the one seen in web app (that uses a browser as a user interface), where the method returns a File() result.

 

2. Get(int? id), URI is /api/books/{id}

This method implements the negotiated technique.

Notice the logic flow:

  • It looks for a suitable Accept header value
    • If available, it looks for a media item
      • If available, it delivers the media item; the media type formatter transforms the byte array to a byte stream
      • If not available, it responds with HTTP 404
    • If not available, it handles this as a “normal” request, and responds with a JSON result

 

The important point is that the method’s return value is a byte array – the actual media item. The custom media type formatter transforms the byte array to a byte stream in the response.

 

Extra topic: Object with a collection of media items

This new section covers the second scenario in the previous class/session notes section titled “Media Handling Scenarios”, where an object could have a collection of zero-or-more media items.

Here’s how to get started with its implementation.

 

Create a design model class to hold the media item

First, create a design model class to hold the media item.

It obviously needs a byte array for the media, and a string for media type.

Also, add any other metadata properties that would be useful in your app. What will be useful? Think about the reason for your app, and for existence of the media items. Think about the use cases. Use the results to answer the question.

 

Configure the design model classes to have an association

In the “MediaUpload” and “MediaUploadAndDeliver” code examples, the entity class was Book.

Modify the design of Book, so that it does NOT have media properties. Instead, it will have a collection property that points to the media item class.

Here’s an example of what this design could look like:

DesignModelClasses

 

Configure the manager and controller logic

The “add media item” approach is similar in concept:

First, create the principal item, the Book.

Then, knowing the Book identifier, you can create (POST) a new media item to the BookCovers collection, associating it with the principal Book item.

Finally, knowing the BookCover identifier, you can set its photo property (PUT), using the technique you learned recently. Obviously, in the manager method, it will end up doing an “add new” BookCover task.

 

Summary

Today, we introduced content negotiation, and the concepts and techniques for handling internet media types other than JSON and XML.

You learned about a media type formatter, which gets added to the request-handling pipeline.

We also covered the tasks that need to be completed to support these media items in your web service.

 

 

 

 

 

 

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

Advertisements
%d bloggers like this: