Envío de datos de formulario HTML en ASP.NET Web API: datos con codificación URL de formulario

Parte 1: datos con codificación URL de formulario

En este artículo se muestra cómo publicar datos con codificación de URL de formulario en un controlador de API web.

Información general de formularios HTML

Los formularios HTML usan GET o POST para enviar datos al servidor. El atributo method del elemento form proporciona el método HTTP:

<form action="api/values" method="post">

El método predeterminado es GET. Si el formulario usa GET, los datos del formulario se codifican en el URI como una cadena de consulta. Si el formulario usa POST, los datos del formulario se colocan en el cuerpo de la solicitud. Para los datos POSTed, el atributo enctype especifica el formato del cuerpo de la solicitud:

enctype Descripción
application/x-www-form-urlencoded Los datos del formulario se codifican como pares nombre-valor, de forma similar a una cadena de consulta de URI. Este es el formato predeterminado para POST.
multipart/form-data Los datos del formulario se codifican como un mensaje MIME de varias partes. Use este formato si va a cargar un archivo en el servidor.

La parte 1 de este artículo examina el formato x-www-form-urlencoded. La parte 2 describe MIME de varias partes.

Envío de tipos complejos

Normalmente, enviará un tipo complejo, compuesto de valores tomados de varios controles de formulario. Tenga en cuenta el siguiente modelo que representa una actualización de estado:

namespace FormEncode.Models
{
    using System;
    using System.ComponentModel.DataAnnotations;

    public class Update
    {
        [Required]
        [MaxLength(140)]
        public string Status { get; set; }

        public DateTime Date { get; set; }
    }
}

Este es un controlador de API web que acepta un objeto Update a través de POST.

namespace FormEncode.Controllers
{
    using FormEncode.Models;
    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Net.Http;
    using System.Web;
    using System.Web.Http;

    public class UpdatesController : ApiController
    {
        static readonly Dictionary<Guid, Update> updates = new Dictionary<Guid, Update>();

        [HttpPost]
        [ActionName("Complex")]
        public HttpResponseMessage PostComplex(Update update)
        {
            if (ModelState.IsValid && update != null)
            {
                // Convert any HTML markup in the status text.
                update.Status = HttpUtility.HtmlEncode(update.Status);

                // Assign a new ID.
                var id = Guid.NewGuid();
                updates[id] = update;

                // Create a 201 response.
                var response = new HttpResponseMessage(HttpStatusCode.Created)
                {
                    Content = new StringContent(update.Status)
                };
                response.Headers.Location = 
                    new Uri(Url.Link("DefaultApi", new { action = "status", id = id }));
                return response;
            }
            else
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }
        }

        [HttpGet]
        public Update Status(Guid id)
        {
            Update update;
            if (updates.TryGetValue(id, out update))
            {
                return update;
            }
            else
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
        }

    }
}

Nota:

Este controlador usa el enrutamiento basado en acciones, por lo que la plantilla de ruta es "api/{controller}/{action}/{id}". El cliente publicará los datos en "/api/updates/complex".

Ahora vamos a escribir un formulario HTML para que los usuarios envíen una actualización de estado.

<h1>Complex Type</h1>
<form id="form1" method="post" action="api/updates/complex" 
    enctype="application/x-www-form-urlencoded">
    <div>
        <label for="status">Status</label>
    </div>
    <div>
        <input name="status" type="text" />
    </div>
    <div>
        <label for="date">Date</label>
    </div>
    <div>
        <input name="date" type="text" />
    </div>
    <div>
        <input type="submit" value="Submit" />
    </div>
</form>

Observe que el atributo action del formulario es el URI de la acción del controlador. Este es el formulario con algunos valores especificados en:

Screenshot of the Complex Type H T M L form with a Status field and a Date field filled in with values.

Cuando el usuario hace clic en Enviar, el explorador envía una solicitud HTTP similar a la siguiente:

POST http://localhost:38899/api/updates/complex HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Content-Type: application/x-www-form-urlencoded
Content-Length: 47

status=Shopping+at+the+mall.&date=6%2F15%2F2012

Observe que el cuerpo de la solicitud contiene los datos del formulario, con el formato de pares nombre-valor. La API web convierte automáticamente los pares nombre-valor en una instancia de la clase Update.

Envío de datos de formulario a través de AJAX

Cuando un usuario envía un formulario, el explorador se aleja de la página actual y representa el cuerpo del mensaje de respuesta. Es correcto cuando la respuesta es una página HTML. Sin embargo, con una API web, el cuerpo de la respuesta suele estar vacío o contiene datos estructurados, como JSON. En ese caso, tiene más sentido enviar los datos del formulario mediante una solicitud AJAX para que la página pueda procesar la respuesta.

En el siguiente código se muestra cómo publicar datos de formulario mediante jQuery.

<script type="text/javascript">
    $("#form1").submit(function () {
        var jqxhr = $.post('api/updates/complex', $('#form1').serialize())
            .success(function () {
                var loc = jqxhr.getResponseHeader('Location');
                var a = $('<a/>', { href: loc, text: loc });
                $('#message').html(a);
            })
            .error(function () {
                $('#message').html("Error posting the update.");
            });
        return false;
    });
</script>

La función submit de jQuery reemplaza la acción de formulario por una nueva función. Esto invalida el comportamiento predeterminado del botón Enviar. La función serialize serializa los datos del formulario en pares nombre-valor. Para enviar los datos del formulario al servidor, llame a $.post().

Cuando se completa la solicitud, el controlador de .success() o .error() muestra un mensaje adecuado para el usuario.

Screenshot of the Complex Type H T M L form with a local host error displayed in bold text to the user.

Envío de tipos simples

En las secciones anteriores, hemos enviado un tipo complejo, que la API web deserializa a una instancia de una clase de modelo. También puede enviar tipos simples, como una cadena.

Nota:

Antes de enviar un tipo simple, considere la posibilidad de ajustar el valor en un tipo complejo en su lugar. Esto le proporciona las ventajas de la validación del modelo en el servidor y facilita la ampliación del modelo si es necesario.

Los pasos básicos para enviar un tipo simple son los mismos, pero hay dos diferencias sutiles. En primer lugar, en el controlador, debe decorar el nombre del parámetro con el atributo FromBody.

[HttpPost]
[ActionName("Simple")]
public HttpResponseMessage PostSimple([FromBody] string value)
{
    if (value != null)
    {
        Update update = new Update()
        {
            Status = HttpUtility.HtmlEncode(value),
            Date = DateTime.UtcNow
        };

        var id = Guid.NewGuid();
        updates[id] = update;

        var response = new HttpResponseMessage(HttpStatusCode.Created)
        {
            Content = new StringContent(update.Status)
        };
        response.Headers.Location = 
            new Uri(Url.Link("DefaultApi", new { action = "status", id = id }));
        return response;
    }
    else
    {
        return Request.CreateResponse(HttpStatusCode.BadRequest);
    }

De forma predeterminada, la API web intenta obtener tipos simples del URI de solicitud. El atributo FromBody indica a la API web que lea el valor del cuerpo de la solicitud.

Nota:

La API web lee el cuerpo de la respuesta como máximo una vez, por lo que solo un parámetro de una acción puede proceder del cuerpo de la solicitud. Si necesita obtener varios valores del cuerpo de la solicitud, defina un tipo complejo.

En segundo lugar, el cliente debe enviar el valor con el siguiente formato:

=value

En concreto, la parte de nombre del par nombre/valor debe estar vacía para un tipo simple. No todos los exploradores admiten esto para formularios HTML, pero se crea este formato en el script como se indica a continuación:

$.post('api/updates/simple', { "": $('#status1').val() });

Este es un formulario de ejemplo:

<h1>Simple Type</h1>
<form id="form2">
    <div>
        <label for="status">Status</label>
    </div>
    <div>
        <input id="status1" type="text" />
    </div>
    <div>
        <input type="submit" value="Submit" />
    </div>
</form>

Y este es el script para enviar el valor del formulario. La única diferencia del script anterior es el argumento pasado a la función post.

$('#form2').submit(function () {
    var jqxhr = $.post('api/updates/simple', { "": $('#status1').val() })
        .success(function () {
            var loc = jqxhr.getResponseHeader('Location');
            var a = $('<a/>', { href: loc, text: loc });
            $('#message').html(a);
        })
        .error(function () {
            $('#message').html("Error posting the update.");
        });
    return false;
});

Puede usar el mismo enfoque para enviar una matriz de tipos simples:

$.post('api/updates/postlist', { "": ["update one", "update two", "update three"] });

Recursos adicionales

Parte 2: carga de archivos y MIME de varias partes