ASP.NET WebApi y X-HTTP-Method-Override

 ·  ☕ 5 min  ·  ✍️ eiximenis

    Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí

    Muy buenas! Después de largo tiempo vuelvo a la carga con otro post sobre ASP.Net WebApi. En un post anterior vimos como WebApi usaba automáticamente el verbo http usado para invocar el método correspondiente del controlador. Eso está muy bien en aquellos casos en que el cliente es una aplicación de escritorio y tiene acceso a todos los verbos http posibles. Pero si el cliente es una aplicación web es posible que tan solo tenga acceso a dos de los verbos http: GET y POST.

    En una aproximación REST, habitualmente GET se usa para recuperar datos y POST para modificar datos existentes. Se usa DELETE para eliminar datos y PUT para insertar datos nuevos. Si tenemos un controlador WebApi con el siguiente código:

    public class PlayerController : ApiController

    {

        public string GetById(string id)

        {

            // Devolver el jugador con el id indicado

            return id;

        }

        public string Put(string name)

        {

            // Crear un jugador nuevo y devolver su id

            return "new player created " + name;

        }

    }

    Si queremos invocar el método put debemos usar el verbo HTTP PUT pero eso, en navegadores puede no ser posible (puedes ver que métodos soporta tu navegador a través de XmlHttpRequest en http://www.mnot.net/javascript/xmlhttprequest/).

    Para seguir permitiendo el enrutamiento según verbo http pero a la vez dar soporte a navegadores que no soporten otros verbos http salvo GET y POST se inventó la cabecera X-HTTP-Method-Override. La idea es guardar en esta cabecera el verbo http que se querría haber usado (p.ej. PUT) y usar el verbo http POST para realizar la llamada. Esta cabecera HTTP no es un estandard reconocido, comenzó a ser utilizada por Google creo y su uso se ha extendido (hay variantes de dicha cabcera, p.ej. la especificación de OData usa X-HTTP-Method en lugar de X-HTTP-Method-Override pero la idea es la misma).

    Lamentablemente ASP.NET WebApi no tiene soporte para X-HTTP-Method-Override (ni para X-HTTP-Method ni ninguna otra variante). Si usas el verbo POST todas las llamadas se enrutarán a alguno de los métodos cuyo nombre empiece por Post con independencia del valor de estas cabeceras.

    DelegatingHandlers

    Por suerte dentro de ASP.NET WebApi no se incluye tan solo la idea de “haz controladores que devuelvan objetos y deja que se enruten a través del verbo http”. También se incluyen un conjunto de clases nuevas para interaccionar con la pipeline de Http… y entre esas encontramos los DelegatingHandlers.

    Si tienes experiencia con los HttpModules, la idea es similar: un DelegatingHandler intercepta una petición http (también puede interceptar la respuesta si fuese necesario). A nivel de WebApi existe una cadena de DelegatingHandlers y la petición va viajando de uno a otro. Cada DelegatingHandler puede modificar los datos de la petición acorde a lo que necesite y pasar la petición al siguiente DelegatingHandler de la cadena. Lo mismo aplica a la respuesta http por supuesto.

    ¿Ya tienes la solución, verdad? 😉

    Efectivamente, basta con crear un DelegatingHandler que recoja la petición, examine las cabeceras, mire si existe X-HTTP-Method-Override y establezca como nuevo verbo HTTP el valor que se indique en dicha cabecera.

    El código puede ser algo parecido a:

    public class OverrideMethodDelegatingHandler : DelegatingHandler

    {

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

        {

            if (request.Method == HttpMethod.Post && request.Headers.Contains("X-HTTP-Method-Override"))

            {

                var newMethod = request.Headers.GetValues("X-HTTP-Method-Override").FirstOrDefault();

                if (!string.IsNullOrEmpty(newMethod)) {

                    request.Method = new HttpMethod(newMethod);

                }

            }

            return base.SendAsync(request, cancellationToken);

        }

    }

    ¿Creo que no necesita mucha explicación verdad? Simplemente validamos que la petición sea POST (si alguien envía X-HTTP-Method-Override a través de GET lo ignoramos) y si la cabecera existe modificamos el verbo http de la petición.

    Ahora tan solo nos queda el punto final: registrar nuestro DelegatingHandler. Como todo en WebApi (y ASP.NET MVC) se sigue la filosofia de usar una clase estática con la configuración:

    GlobalConfiguration.Configuration.MessageHandlers.Add(new OverrideMethodDelegatingHandler());

    Y listos! Con esto ya podemos usar X-HTTP-Method-Override en nuestras APIs WebApi. Para probarlo nos basta con el siguiente código:

    DOCTYPE html>

     

    <html>

        <head>

            <title>titletitle>

            <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js">script>

        head>

        <body>

            <script type="text/javascript">

                $(document).ready(function() {

                    $("#myfrm").submit(function (evt) {

                        var url = @Url.RouteUrl("DefaultApi", new {httproute="", controller="Player", name="xxx"}).

                            replace("xxx", $("#name").val());

                         $.ajax({

                             url: url,

                            type: ‘post’, // usamos http post

                            headers: {

                                "X-HTTP-Method-Override" : ‘PUT’

                            }

                        });

                        evt.preventDefault();

                    });

                });

     

            script>

    <p>
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <span style="color: blue"><</span><span style="color: maroon">div</span><span style="color: blue">></span>
    </p>
    
    <p style="margin: 0px">
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <span style="color: blue"><</span><span style="color: maroon">form</span> <span style="color: red">method</span><span style="color: blue">="POST"</span> <span style="color: red">id</span><span style="color: blue">="myfrm"></span>
    </p>
    
    <p style="margin: 0px">
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <span style="color: blue"><</span><span style="color: maroon">input</span> <span style="color: red">type</span><span style="color: blue">="text"</span> <span style="color: red">name</span><span style="color: blue">="name"</span> <span style="color: red">id</span><span style="color: blue">="name"</span> <span style="color: blue">/></span>
    </p>
    
    <p style="margin: 0px">
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <span style="color: blue"><</span><span style="color: maroon">input</span> <span style="color: red">type</span><span style="color: blue">="submit"/></span>
    </p>
    
    <p style="margin: 0px">
      &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <span style="color: blue"></</span><span style="color: maroon">form</span><span style="color: blue">></span>
    </p>
    
    <p style="margin: 0px">
      &#160;
    </p>
    
    <p style="margin: 0px">
      &#160;&#160;&#160;&#160;&#160;&#160;&#160; <span style="color: blue"></</span><span style="color: maroon">div</span><span style="color: blue">></span>
    </p>
    
    <p style="margin: 0px">
      &#160;&#160;&#160; <span style="color: blue"></</span><span style="color: maroon">body</span><span style="color: blue">></span>
    </p>
    
    <p style="margin: 0px">
      <span style="color: blue"></</span><span style="color: maroon">html</span><span style="color: blue">></span>
    </p>
    

    Y si colocais un breakpoint en el método Put del controlador vereis como la llamada llega a pesar de estar usando POST 😉

    Un saludo!

    Si quieres, puedes invitarme a un café xD

    eiximenis
    ESCRITO POR
    eiximenis
    Compulsive Developer