Formateadores multimedia en ASP.NET Web API 2

Este tutorial muestra cómo admitir formatos de medios adicionales en ASP.NET Web API.

Tipos de medios de Internet

Un tipo de medio, también denominado tipo MIME, identifica el formato de un fragmento de datos. En HTTP, los tipos de medios describen el formato del cuerpo del mensaje. Un tipo de medio consta de dos cadenas, un tipo y un subtipo. Por ejemplo:

  • text/html
  • image/png
  • application/json

Cuando un mensaje HTTP contiene un cuerpo de entidad, el encabezado Content-Type especifica el formato del cuerpo del mensaje. Esto indica al receptor cómo analizar el contenido del cuerpo del mensaje.

Por ejemplo, si una respuesta HTTP contiene una imagen PNG, es posible que la respuesta tenga los encabezados siguientes.

HTTP/1.1 200 OK
Content-Length: 95267
Content-Type: image/png

Cuando el cliente envía un mensaje de solicitud, puede incluir un encabezado Accept. El encabezado Accept indica al servidor qué tipos de medios quiere el cliente del servidor. Por ejemplo:

Accept: text/html,application/xhtml+xml,application/xml

Este encabezado indica al servidor que el cliente quiere HTML, XHTML o XML.

El tipo de medio determina cómo la API web serializa y deserializa el cuerpo del mensaje HTTP. La API web tiene compatibilidad integrada con datos XML, JSON, BSON y codificación URL de formulario, y puede admitir tipos de medios adicionales escribiendo un formateador de medios.

Para crear un formateador de medios, derive de una de estas clases:

La derivación de BufferedMediaTypeFormatter es más sencilla, ya que no hay código asincrónico, pero también significa que el subproceso de llamada puede bloquearse durante la E/S.

Ejemplo: Crear un formateador de medios de CSV

En el ejemplo siguiente se muestra un formateador de tipo de medio que puede serializar un objeto Product en un formato de valores separados por comas (CSV). En este ejemplo se usa el tipo Product definido en el tutorial Crear una API web que admita operaciones CRUD. Esta es la definición del objeto Product:

namespace ProductStore.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}

Para implementar un formateador de CSV, defina una clase que derive de BufferedMediaTypeFormatter:

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using ProductStore.Models;

namespace ProductStore.Formatters
{
    public class ProductCsvFormatter : BufferedMediaTypeFormatter
    {
    }
}

En el constructor, agregue los tipos de medios que admite el formateador. En este ejemplo, el formateador admite un solo tipo de medio, "text/csv":

public ProductCsvFormatter()
{
    // Add the supported media type.
    SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));
}

Invalide el método CanWriteType para indicar qué tipos puede serializar el formateador:

public override bool CanWriteType(System.Type type)
{
    if (type == typeof(Product))
    {
        return true;
    }
    else
    {
        Type enumerableType = typeof(IEnumerable<Product>);
        return enumerableType.IsAssignableFrom(type);
    }
}

En este ejemplo, el formateador puede serializar objetos Product únicos, así como colecciones de objetos Product.

Del mismo modo, invalide el método CanReadType para indicar qué tipos puede deserializar el formateador. En este ejemplo, el formateador no admite la deserialización, por lo que el método simplemente devuelve false.

public override bool CanReadType(Type type)
{
    return false;
}

Por último, invalide el método WriteToStream. Este método serializa un tipo escribiendo en un flujo. Si el formateador admite la deserialización, invalide también el método ReadFromStream.

public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
{
    using (var writer = new StreamWriter(writeStream))
    {
        var products = value as IEnumerable<Product>;
        if (products != null)
        {
            foreach (var product in products)
            {
                WriteItem(product, writer);
            }
        }
        else
        {
            var singleProduct = value as Product;
            if (singleProduct == null)
            {
                throw new InvalidOperationException("Cannot serialize type");
            }
            WriteItem(singleProduct, writer);
        }
    }
}

// Helper methods for serializing Products to CSV format. 
private void WriteItem(Product product, StreamWriter writer)
{
    writer.WriteLine("{0},{1},{2},{3}", Escape(product.Id),
        Escape(product.Name), Escape(product.Category), Escape(product.Price));
}

static char[] _specialChars = new char[] { ',', '\n', '\r', '"' };

private string Escape(object o)
{
    if (o == null)
    {
        return "";
    }
    string field = o.ToString();
    if (field.IndexOfAny(_specialChars) != -1)
    {
        // Delimit the entire field with quotes and replace embedded quotes with "".
        return String.Format("\"{0}\"", field.Replace("\"", "\"\""));
    }
    else return field;
}

Agregar un formateador de medios a la canalización de API web

Para agregar un formateador de tipo multimedia a la canalización de API web, use la propiedad Formatters en el objeto HttpConfiguration.

public static void ConfigureApis(HttpConfiguration config)
{
    config.Formatters.Add(new ProductCsvFormatter()); 
}

Codificaciones de caracteres

Opcionalmente, un formateador de medios puede admitir varias codificaciones de caracteres, como UTF-8 o ISO 8859-1.

En el constructor, agregue uno o varios tipos system.Text.Encoding a la colección SupportedEncodings. Coloque primero la codificación predeterminada.

public ProductCsvFormatter()
{
    SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));

    // New code:
    SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
    SupportedEncodings.Add(Encoding.GetEncoding("iso-8859-1"));
}

En los métodos WriteToStream y ReadFromStream, llame a MediaTypeFormatter.SelectCharacterEncoding para seleccionar la codificación de caracteres preferida. Este método coincide con los encabezados de solicitud en la lista de codificaciones admitidas. Use la codificación devuelta al leer o escribir desde el flujo:

public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
{
    Encoding effectiveEncoding = SelectCharacterEncoding(content.Headers);

    using (var writer = new StreamWriter(writeStream, effectiveEncoding))
    {
        // Write the object (code not shown)
    }
}