ASP.NET MVC: Mandar un byte[]

Este post surge a raíz de la siguiente pregunta en los foros de ASP.NET MVC de MSDN: http://social.msdn.microsoft.com/Forums/es-ES/aspnetmvces/thread/20a6935c-5903-4efd-8ca1-f5a70a047a15. El usuario se pregunta como mandar un byte[] de la vista al controlador. Y comenta que lo hace de la siguiente manera:

<iframe src="<%: Url.Action("GenerarPdf", "Consulta", 
new { documento = Model.Documento})%>" width="725" height="725"></iframe>

En el controlador tiene definida la acción correspondiente con un parámetro llamado documento de tipo byte[]. Y comenta que siempre recibe el parámetro con el valor null.

Veamos porque ocurre esto y cual es la solución.

El porqué

Primero veamos que URL nos genera una llamada a Url.Action como la siguiente:

@{
    var data = new byte[] {0x10, 0x11, 0x12};
}
<script type="text/javascript">
    alert('@Url.Action("Index", "Home", new {documento=data})')
</script>

El código HTML generado es el siguiente:

<script type="text/javascript">
    alert('/?documento=System.Byte%5B%5D')
</script>

Qué ha ocurrido aquí? Pues que al intentar pasar un byte[] a través de la URL, el helper Url.Action ha llamado simplemente al método .ToString() de dicho byte[] (que siempre devuelve System.Byte[]). ¡Es evidente con esto que el Model Binder no va a poder recuperar los valores de dicho byte[]!

La solución

Lo que necesitamos pasar es el contenido del byte[], pero claro… en qué formato podemos hacerlo? Porque recordad que estamos pasando bytes, que es contenido binario, pero en las URLs (y en los cuerpos de las peticiones http) no hay contenido binario, hay texto (un subconjunto de ANSI). La mejor solución es usar Base64. Base64 es un mecanismo para codificar bytes (no carácteres, bytes)  a un subconjunto de carácteres ANSI.

En C# pasar un byte[] a su cadena Base64 equivalente es tan simple como llamar al método Convert.ToBase64String:

<script type="text/javascript">
    alert('@Url.Action("Index", "Home", new {documento=Convert.ToBase64String(data)})')
</script>

Ahora el código HTML generado es:

<script type="text/javascript">
    alert('/?documento=EBES')
</script>

La cadena EBES es la codificación en Base64 del array de bytes. Ahora nos toca recibir esto en el controlador. Primero voy a modificar la vista para que genere un enlace:

@{
    var data = new byte[] {0x10, 0x11, 0x12};
}
<a href="@Url.Action("Ver", "Home", new {documento=Convert.ToBase64String(data)})">Pulsar aquí</a>

Y ahora la acción Ver del controlador:

public ActionResult Ver(byte[] documento)
{
  // Código
}

Y esto es lo que recibimos en el controlador:

image

Como podemos ver ASP.NET MVC ha sido capaz de convertir automáticamentela cadena en formato BASE64 a un array de bytes!

Por supuesto aquí he usado @Url.Action para mandar el array de bytes a través de la URL, pero si usáis un @Html.Hidden para mandarlo a través de un campo Hidden os funcionará igual. La clave es que el byte[] esté codificado usando Base64.

Disclaimer

En mi post he usado MVC3, pero en el foro el usuario usaba MVC2. No tengo ahora una versión de MVC2 a mano para probar si esta conversión automática de cadenas Base64 a byte[] ocurre como en MVC3. Creo que sí (aunque debería comprobarlo) pero supongamos que no.

Supongamos que si mandas una cadena Base64 recibes un null si el parámetro es de tipo byte[]. En este caso… ¿qué deberías hacer?

Pues la solución pasa por crearte tu propio Model Binder. Este Model Binder es el que recibiría una cadena (en formato Base64) y la transformaría a un byte[]:

public class Base64ModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var data = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
        var array = Convert.FromBase64String(data);
        return array;
    }
}

Este ModelBinder convierte el valor en Base64 a un array de bytes. Por supuesto faltaría añadir código de comprobación de errores.

Ahora simplemente debemos decirle a ASP.NET MVC que use este ModelBinder cuando se encuentre con un byte[]:

ModelBinders.Binders.Remove(typeof (byte[]));
ModelBinders.Binders.Add(typeof(byte[]), new Base64ModelBinder());

Fijaos que primero elimino el ModelBinder asociado a byte[] y luego asocio mi Model Binder al tipo byte[]. La primera línea es necesaria porque ASP.NET MVC3 ya tiene un Model Binder asociado a byte[] (lógico,simplemente MVC3 trae de serie lo que estamos comentando justo ahora): Concretamente uno llamado ByteArrayModelBinder (http://msdn.microsoft.com/en-us/library/system.web.mvc.bytearraymodelbinder.aspx).

Insisto en que creo que MVC2 lo tiene también, pero bueno, estamos suponiendo que no (en este caso la llamada  a Remove no sería necesaria claro).

Y listos, con esto ya podemos enlazar cadenas en Base64 a parámetros byte[].

Un saludo!

Deja un comentario

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