DPS907 notes – Fri Sep 27

Handling internet media types, such as images.

.

Handling internet media types, such as images

The sep24images code example enables us to learn about writing our own media type formatter.

You know that your web service will handle JSON and XML. That’s built in. The content negotiation process (sometimes abbreviated to ‘conneg’) powers this built-in ability, as well as the ability to handle other internet media types.

From the last session, recall the general design approach:

1. Configure the app domain model class with properties to hold the photo. You need a byte array (byte[]) for the photo, and a string for the internet media type.

2. Configure a view model class with these properties. Typically, you will add attributes to make the JSON and XML serializers ignore the photo-related properties.

3. Add the appropriate repository methods (e.g. update an object with a new photo).

4. Configure the appropriate controller methods. As you will learn, you will have to pay attention to request and response headers.

Reminder: We’ll use a simple car/vehicle data entity, and configure its entity class with properties that hold a photo of the car/vehicle, 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, if it makes sense for that single photo/image to represent the object. Examples include personal profile avatars, or ‘favicon‘ images for a web site.

.

Do NOT deliver image bytes as a property value in an object (or collection).

It’s the web! Deliver only the image by setting its content type header.

.

Get and study the code example. Here are some highlights.

1. Configure the app domain model class with properties to hold the photo. You need a byte array (byte[]) for the photo, and a string for the internet media type.

public string ContentType { get; set; }
// Internet media types are stored as byte arrays
public byte[] Photo { get; set; }

.

2. Configure a view model class with these properties. Typically, you will add attributes to make the JSON and XML serializers ignore the photo-related properties.

// more...
using Newtonsoft.Json;
// Before typing the next statement, go to Solution Explorer,
// right-click References, and choose 'Add Reference...'
// Then, from the Assemblies > Framework list, choose/check
// System.Runtime.Serialization, and click OK
using System.Runtime.Serialization;

Also:

// These are image-related properties
// For this code example, we do NOT expose them
// during a normal JSON or XML serialization
[JsonIgnore, IgnoreDataMember]
public byte[] Photo { get; set; }
[JsonIgnore, IgnoreDataMember]
public string ContentType { get; set; }

.

3. Add the appropriate repository methods (e.g. update an object with a new photo).

The existing ‘get-one’ (i.e. GetById(int id)) method already returns a VehicleFull object. Therefore, it does not need to be changed.

If it had an ‘update-existing’ method, that would also remain unchanged.

An ‘add-new’ method would also accept a VehiclePublic object, which does not include the photo properties. Therefore, when adding a new car/vehicle, it will be a two-step operation: 1) First, ‘add-new’, with the basic details, and then 2) ‘update-existing-photo’ to configure the photo.

So, the only really new repository method is shown below, UpdateExistingPhoto. Notice its argument list.

// Update existing, photo property only
public VehicleFull UpdateExistingPhoto(int id, string contentType, byte[] photo)
{
    var v = ds.Vehicles.Find(id);

    if (v == null)
    {
        return null;
    }
    else
    {
        v.Photo = photo;
        v.ContentType = contentType;
        ds.SaveChanges();
        return Mapper.Map<VehicleFull>(v);
    }
}

.

4. Configure the appropriate controller methods. As you will learn, you will have to pay attention to request and response headers.

The current design of the MediaTypeFormatter (and BufferedMediaTypeFormatter) classes do not provide a way to set the response’s Content-Type header dynamically. We need this capability, in order to support a range of popular image formats (JPG, PNG, GIF).

Therefore, before the controller delivers the object to the formatter, it must set/configure the Content-Type header. That’s what the extra code does below (when compared to a typical ‘get-one’ method):

// GET api/vehicles/5
public HttpResponseMessage Get(int id)
{
    var v = r.GetById(id);

    // HTTP 404 if the vehicle doesn't exist, or it doesn't have a photo
    if (v == null | v.Photo.Length < 1)
    {
        return Request.CreateResponse(HttpStatusCode.NotFound);
    }
    else
    {
        var response = Request.CreateResponse(HttpStatusCode.OK, v);

        // The repository method has returned a VehicleFull object
        // We need to know if the request asked for an image to be returned.
        // If so, we need to configure the Content-Type header BEFORE the object
        // is passed on to the 'image' media formatter.
        // Unfortunately, the current design of the media formatter does not
        // allow us to dynamically set the response's Content-Type header.
        // See this post - http://stackoverflow.com/a/12565530

        // Step 1 - look for an Accept header that starts with 'image'
        var imageHeader = Request.Headers.Accept
            .SingleOrDefault(a => a.MediaType.ToLower().StartsWith("image"));
        // Step 2 - if found, set the Content-Type header value
        if (imageHeader != null)
        {
            response.Content.Headers.ContentType =
                new System.Net.Http.Headers.MediaTypeHeaderValue(v.ContentType);
        }

        return response;
    }
}

The method that updates a car/vehicle object with a photo is shown below. Notice its argument list.

To call the method, notice the URI with the query string.

// PUT api/vehicles/5?photo
public HttpResponseMessage PutPhoto(int id, string photo, byte[] photoBytes)
{
    // Notice the special URI with a query string

    // Update the existing object...

    // Get the Content-Type header from the request
    var contentType = Request.Content.Headers.ContentType.MediaType;

    // Call the repository method
    var v = r.UpdateExistingPhoto(id, contentType, photoBytes);

    if (v == null)
    {
        // If we cannot update the object for some reason
        // Not sure if this is the best response
        return Request.CreateResponse(HttpStatusCode.InternalServerError);
    }
    else
    {
        // Return the updated object
        return Request.CreateResponse<VehicleFull>(HttpStatusCode.OK, v);
    }
}

.

Study the code in the ImageFormatter class.

All media type formatters are similar, and they are fairly simple. They have a constructor and up to four methods – a pair of ‘read’ methods, and a pair of ‘write’ methods.

Note: These formatters work well for small- to medium-sized digital assets. If your asset is less than a few hundred kilobytes, then you can use this design, which is synchronous and therefore blocking.

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

.

.

.

.

.

.

.

.

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