It will take about an hour for you to go through that content, and its code examples. Do it now. Before continuing.

In a web app, you learned how to accept (i.e. “upload”) a media item, and how to deliver a media item. You probably coded these kinds of solutions:

Accept (upload)

HTML Form

Includes an “input type=file” element

The form may also have included other input elements

Deliver

Specialized controller, or specialized methods in an existing controller

Returns a File() ActionResult object

The web service solution

Accept (upload)

Well, a web service obviously does not have an HTML Form. Therefore, accepting a media item must be done as an atomic and specialized task. The requestor must send only the media item, and its media type, in the request.

If the media item is to be “linked” to an entity object, then that object must exist, before adding/uploading the media item.

In other words, if you are coding a solution that enables a requestor to, for example, create a new “product” object that has a photo as one of its properties, the solution requires the requestor to create two separate requests. First, create the “product” object by supplying data that can be expressed in the “application/json” media type. Then, using the new object’s identifier, create another request to modify the object, and send along the media item as an “image/png” media type.

Deliver

This part of the solution will be similar to the web app solution. The only difference is that the method return type will be different, because it can take advantage of some functionality that you can build into the web service.

Media handling scenarios

There are at least three scenarios for handling media:

Object with one media item
An entity that can also be represented with a media item.
For example, a Seneca “student” object has a single photo item.
Or, a “song” object has a single audio (e.g. m4a, mp3) item.

Object with many media items
An entity that may have zero or more associated media items.
This scenario requires a separate “media” entity, associated to its parent entity.
For example, a “product” object has a collection of product photo items.

Media driven app
In this scenario, the media drives the app’s purpose and design.
The “media” entity is central to the design model, and will be associated (by definition, or logically) with other entities.
For example, a photo app, like Instagram.
Or, a video app, like YouTube.
Or, a music app, like Apple Music.
Or, a document (file) app, like DropBox.

Rules

There are at least two important rules that you must follow when working with media items:

Rule:
In a request or response, do NOT package a media item inside another container.
Accept or deliver the media item as-is, even if it means that the requestor must create another request to fetch the item.

Rule:
When storing a media item, its media type name MUST also be stored.
Therefore, two properties are required to store a media item:
1. A byte array (in C# + Entity Framework + SQL Server), maybe named “Content” or “Media”, and
2. A string for the type name, maybe named “ContentType”
Yes, you can add other metadata properties (e.g. location, caption, tags, etc.) for the media item, if you wish.

Message lifecycle (aka “request-processing pipeline”)

Before continuing, let’s look at a topic that has been briefly and gently introduced a couple of times already.

You are probably aware that a request message flows through a request-processing pipeline before a response is returned to the requestor.

It’s time to learn more about the message lifecycle, so that you can modify or affect a message as it flows through the pipeline. In a few weeks, you will learn how to handle exceptions, and the HTTP OPTIONS method. Both tasks require you to create code that “plugs in” to the pipeline. The pipeline can be modified, customized, and extended.

The image below shows the message lifecycle. Click to open a PDF full-size in its own tab/window.

The result of the controller action (method) is converted to an HttpResponseMessage

If the result was an error, the built-in error handler is invoked

Web server returns the HTTP response to the requestor

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.

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.

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

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 be studied in our next class/session.

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:

Data representation, as JSON

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:

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

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

Configure resource model classes with media-related properties

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

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

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 WebApiApplication class, in its Application_Start 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:

JSON

XML

byte-oriented media items

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

You need two properties:

A byte array (byte[]) for the photo (named “Media” or “Photo” or something similar), and

A string for the internet media type (named “ContentType”).

Configure resource model classes with media-related properties

Remember the rule above: In a request or response, 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:

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:

The request URI, with the entity object identifier

A request header (Content-Type), which declares the media type of the entity body

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:

Set the method to PUT, and set the appropriate URI (/api/books/{id}/setphoto)

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:

Summary

Today, we introduced 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.