ASP.NET WebApi – Establecer una cookie desde un ApiController

Hoy, para una prueba de concepto que estoy realizando, me he encontrado con la necesidad de establecer una cookie desde un controlador de ASP.NET WebApi (un ApiController). Por supuesto podríamos discutir largo y tendido sobre la conveniencia de hacer esto o no (si suponemos que dichos controladores van a ser accedidos por clientes que no sean navegadores y por lo tanto pueden no entender las cookies). Pero obviando esta discusión el problema está en que desde el controlador no podemos acceder al objeto Response (a diferencia de un controlador MVC tradicional). Por supuesto siempre existe la opción de usar HttpContext.Current pero eso no es deseable ya que ata mi controlador a un pipeline de ASP.NET por lo que pierdo la facilidad de pruebas.

La solución que he encontrado pasa por devolver un HttpResponseMessage desde el controlador. En mi caso la firma original del método era:

public AuthResultDTO PostLoginByUserPass(LoginDTO login)

{

}

El primer paso es modificarlo para que devuelva un HttpResponseMessage y usar el HttpResponseMessage para establecer las cookies.

Primero me he creado un método para que me crease la cookie (nota para curiosos: lo que quería hacer era usar un ticket de autenticación forms personalizado):

private static CookieHeaderValue CreateCustomCookie(string name, int id)

{

    var userData = id.ToString();

    var ticket = new FormsAuthenticationTicket(1, name, DateTime.Now, DateTime.Now.AddMinutes(30), false, userData);

    var encTicket = FormsAuthentication.Encrypt(ticket);

    var cookieValue = new CookieHeaderValue(FormsAuthentication.FormsCookieName, encTicket)

        {

            Path = "/",

            HttpOnly =  true,

        };

    return cookieValue;

}

Este método crea un ticket con un campo addicional (el id del usuario) y crea el objeto CookieHeaderValue que es el que contiene los datos de las cookies a añadir.

Ahora toca modificar el método del controlador. Lo primero es hacer que devuelva un HttpResponseMessage en lugar del AuthResultDTO que devolvía antes:

public HttpResponseMessage PostLoginByUserPass(LoginDTO login)

{

    var response = new HttpResponseMessage();

    return response;

}

El primer paso es añadir la cookie generada a nuestro mensaje:

var cookieValue = CreateCustomCookie(login.Name, id);

response.Headers.AddCookies(new CookieHeaderValue[] {cookieValue});

Pero ahora nos falta incrustar el AuthResultDTO dentro de la respuesta. Para ello debemos usar la propiedad Content del HttpResponseMessage (authResult es una variable de tipo AuthResultDTO):

response.Content = new ObjectContent(typeof(AuthResultDTO), authResult, new JsonMediaTypeFormatter());

Como vemos hemos de usar la clase ObjectContent ya que en mi caso quiero mandar el objeto serializado. El tercer parámetro indica que serializador se usa. En mi caso uso el serializador de JSON (JsonMediaTypeFormatter). Notad que aquí estoy fijando que la respuesta será siempre en JSON, con independencia de la cabecera Accept que me mande el cliente (contraviniendo lo que hace WebApi por defecto).

¡Y listos! Con eso ya hemos conseguido establecer nuestra cookie desde un controlador de WebApi.

Bonus track: ¿Cómo seleccionar el MediaTypeFormatter correcto?

Como he dicho antes el usar JsonMediaTypeFormatter directamente estoy “rompiendo” la capacidad de WebApi de seleccionar el tipo de respuesta según la cabecera Accept que envíe el cliente. Por suerte no es muy dificil restablecer dicha funcionalidad. Basta con mirar en la configuración global de WebApi todos los formatters que haya y encontrar el primero que pueda procesar el tipo especificado. Una posible implementación sería:

private MediaTypeFormatter ChooseMediaTypeFormatter(HttpHeaderValueCollection<MediaTypeWithQualityHeaderValue> accept, Type typeToWrite)

{

    var cfg = GlobalConfiguration.Configuration;

    foreach (var formatter in cfg.Formatters)

    {

        if (formatter.SupportedMediaTypes.Any(x => accept.Select(a => a.MediaType).Contains(x.MediaType)) &&

            formatter.CanWriteType(typeToWrite)) return formatter;

    }

 

    return new JsonMediaTypeFormatter();

}

Y llamaríamos a dicho método de la forma:

var mediaFormatter = ChooseMediaTypeFormatter(Request.Headers.Accept, typeof(AuthResultDTO));

Nota: Realmente esta implementación de ChooseMediaTypeFormatter no es del todo correcta ya que no tiene en cuenta la calidad especificada en la cabecera accept… Pero bueno, creo que la idea de como se podría hacer ya queda clara, no?

Saludos!

Actualización: Nicolás Herrera (@nicolocodev) me ha hecho dar cuenta de algo en lo que no he caído cuando he escrito el post. En un tweet me propone usar Request.CreateResponse en lugar de buscar manualmente el MediaTypeFormatter como estoy haciendo yo. Y tiene toda la razón del mundo 🙂 Anotado queda!

Información del Método (de extensión) CreateRequest.

Gracias! 😉

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *