Una sinfonía en C#

Un pequeño aporte a la comunidad de habla hispana.

Cómo subir archivos en ASP.NET MVC?

Este es uno de esos Posts que surgen a partir de una pregunta puntual que alguien me hace: “cómo se puede hacer un file-upload en ASP.NET MVC?” si bien la respuesta no es complicada voy a explicar algunos detalles interesantes ya que siempre es mejor comprender que recordar.

Para subir un archivos se utiliza un formulario

Así es, un archivo de cualquier tipo se sube utilizando el verbo POST de HTTP, por lo tanto nos es indispensable un formulario HTML, pero con alguna particularidad

Los formularios HTML soportan más de un tipo de contenido

Sabemos que uno de los Headers más importantes de HTTP es el Content-Type el cual indica el tipo de contenido del mensaje, ya que siempre se representan como texto es necesario saber qué tipo de contenido es el representado para poder recuperarlo, para indicarlo se utiliza el estándar MIME

El tipo de contenido de un formulario es por defecto x-www-form-urlencoded

Si creamos un formulario, tanto con GET como con POST y lo enviamos sin mas, el tipo de contenido será x-www-form-urlencoded, esto indica que el contenido que habrá en el cuerpo del mensaje HTTP será un conjunto de pares clave-valor, por ejemplo: nombre=leonardo&apellido=micheloni.

Primer intento, enviar un archivo sin cosiderar el tipo de contenido

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>
</head>
<body>
    <div>
    <form action="<%=Url.Action("UploadFile") %>" method="post">
        <input type="file" name="file1" />
        <input type="submit" value="enviar" />
    </form>
    </div>
</body>
</html>

seleccionamos un archivo con el objeto file y lo enviamos

image

vemos qué dice Fiddler

image

Si queremos enviar un archivo debemos cambiar el tipo de contenido

Como dije antes el tipo de contenido por defecto de un formulario es x-www-form-urlencoded, lo cual indica que el contenido del cuerpo del mensaje HTTP será un conjunto de elementos par-valor, por lo tanto lo que vemos en el cuerpo del mensaje es justo eso, el nombre que le pusimos al objeto file y el valor…el nombre del archivo, sin dudas no es lo que esperamos.

El tipo de contenido para enviar un archivo debe ser multipart/form-data

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>
</head>
<body>
    <div>
    <form action="<%=Url.Action("UploadFile") %>" method="post" enctype="multipart/form-data">
        <input type="file" name="file1" />
        <input type="submit" value="enviar" />
    </form>
    </div>
</body>
</html>

Modificamos el formulario para que el atributo enctype tome el valor multipar/formdata el cual indica que el contenido del cuerpo del mensaje HTTP va a ser un archivo, veámos qué dice Fiddler en este caso

image

Perfecto, ahora vemos el header Content-Type con el multipar/form-data y adicionalmente un atributo boundary que indica dónde comienza y dónde termina el contenido, excelente, ahora sí, vamos a nuestra acción UploadFile a ver cómo recuperamos este contenido

public ActionResult UploadFile()
{
    // recuperamos el archivo desde el Request
    HttpPostedFileBase file = this.HttpContext.Request.Files.Get("file1");
    //lo enviámos a la vista
    var reader = new StreamReader(file.InputStream);
    //casteamos a objeto para evitar que se tome el parámetro como nombre de la vista
    return View((object)reader.ReadToEnd()); 
}

la vista

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<string>" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>UploadFile</title>
</head>
<body>
    <div>
    <textarea cols="40" rows="20">
    <%=Model %>
    </textarea>
    </div>
</body>
</html>

y el resultado del upload

image

Mágico, en la próxima vamos a ver si podemos lograr lo mismo con AJAX, hasta entonces.

Leonardo.

Vincular llamadas Jsonp entre jQuery y ASP.NET MVC

Ya hablé en otro ocasión sobre Jsonp como mecanismo para hacer llamadas asincrónicas entre diferentes sitios, bien, el tema es que si bien jQuery nos resuelve la creación del método que se llama en el retorno, nos falta el otro lado, es decir, algo del lado del sitio que genere la llamada a la función que indicamos en el parámetro callback.

Implementando un tipo de retorno para Jsonp

La forma que se me ocurrió para solucionar esto es crear un tipo de ActionResult que lea el parámetro callback y genere la llamada a la función junto con los datos.

Ejemplo auto-descriptivo

/// 
/// Nuestra clase JsonpResult hereda de JsonResult
/// 
public class JsonpResult : JsonResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        var response = context.HttpContext.Response;
        response.ContentType = "application/x-javascript";
        //leemos el valor de la variable callback que genera jQuery
        var callback = context.HttpContext.Request["callback"];

        if (string.IsNullOrEmpty(callback))
        {
            throw new ArgumentException("callback query string param expected");
        }

        if (this.Data != null)
        {
            //por último generamos el contenido en formato JSON gracias al JavaScriptSerializer
            //y lo encerramos en la llamada a la función que temos en la variable callback
            response.Write(string.Format("{0}({1})",callback, new JavaScriptSerializer().Serialize(this.Data)));
        }
    }
}

Sencillo, vamos a ver cómo se usa del lado del controlador

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
    public ActionResult Test()
    {
        return new JsonpResult { Data = new {mensaje = "hola"}};
    }
}

Claro, simplemente devolvemos un tipo JsonpResult con un objeto anónimo, finalmente vamos a ver qué para del lado de la visa

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Index</title>    
    <script src="<%=Url.Content("~/Scripts/jquery-1.4.1.js")%>" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $("#boton").click(function () {
                $.getJSON("/home/test?callback=?", null, function (data) {
                    $("#contenedor").html(data.mensaje);

                });
            });
        });
    </script>
</head>
<body>
    <div>
    <input type="button" id="boton" value="probar" />
    <div id="contenedor"></div>
    </div>
</body>
</html>

Finalmente, hacemos la llamada a la función getJSON de jQuery y en la variable callback del querystring ponemos un signo ? con esto jQuery genera un nombre de función al azar y nos ahorra un pequeño paso. Espero sea útil.

Nos leemos, Leonardo.


    

Cómo crear un plugin para jQuery?

Yo creo firmemente que la potencia de jQuery está en los selectores, el resto es aplicar funciones sobre el conjunto afectado, cuando las funciones que el framework trae incorporado no son suficientes podemos acudir a repositorio de plugins y buscar uno que nos soluciones el problema, pero en ocasiones necesitamos hacer nuestros propios plugin, entonces vamos a ver unos tips iniciales sobre ello.

Algunas reglas de estilo

Una de las primeras cosas que tenemos que tener en cuenta es que nuestros plugins tiene que residir en un archivo .js cuyo nombre siga el siguiente patrón jquery.nombre. js

Ahora si, a hacer el plugin

Para este primer intento vamos a hacer un plugin bien sencillo (tal vez no tan útil) pero que nos va a permitir ejemplificar algunos detalles, nuestro plugin nos va a permitir seleccionar una caja de texto y que tenga la apariencia de las cajas de búsqueda que tiene un texto por defecto y cuando escribimos se borra, si le quitamos el foro y no hemos escrito nada queda tal cual como cuando empezó

image

Algo asi, empecemos.

Manos a la obra

Un plugin de jQuery no es más que una función que se agrega al prototipo del objeto jQuery, algo así

jQuery.prototype.searchBox = function(){
	//código de implentación
};

esto está muy bien, es semánticamente correcto, sin embargo la forma correcta de hacerlo es utilizando la propiedad fn de jQuery, que apunta al prototipo, así:

jQuery.fn.searchBox = function(){
	//código de implentación
};

perfecto, primer paso listo, declaramos la nueva funcionalidad, ahora podemos hacer así

$(":text").searchBox();	

ok, no hace nada, pero la función existe y está agregada a jQuery.

Evitando conflictos con otras librerías

Esto es bien sencillo, sin embargo falta un detalle: es bastante incómodo utilizar el nombre jQuery cada vez que queremos utilizar una funcionalidad dentro del código del plugin, lo primero que vamos a querer hacer es utilizar $ en lugar de jQuery, sin embargo esto puede no se una buena idea, si nuestro plugin convive con otras librerías de javascript como Mootools, Prototype y demás veremos que estas también utilizar $ para llamar a sus clases con lo cual podemos tener un conflicto, cómo resolvermos esto?, bien, así:

(function($){
	$.fn.searchBox = function(){
})(jQuery);

ponemos nuestro código dentro de una función autoejecutable, definimos en esta función un parámetro al cual llamamos $ (pero esto nombre es válido sólo en el ámbito de la función, con lo cual no hay conflicto) al final de la declaración de la función autoejecutable le pasamos el objeto jQuery y listo, tenemos la funcionalidad $ que apunta a jQuery y no hay posiblidad de conflicto.

Implementando el código

(function($){
	$.fn.searchBox = function(){
	//acá escribimos el código del plugin
	};
})(jQuery);

por el momento tenemos el esqueleto del plugin (el menos en uno de los modos posibles de hacer uno) ahora vamos a un truco sencillo pero que nos va a ahora problemas dentro de dos minutos

Quién es this?

Dentro del ámbito de la función searchbox this tiene el valor de los elementos afectados por el selector, por todos, encerrados en un wrapper de jQuery, es una muy buena idea ponerlo dentro de una variable para tenerlo a mano, otra gran idea es que el nombre de la varible comience con $ para recordar el wrapper

(function($){
	$.fn.searchBox = function(){
		var $el = this;
	};
})(jQuery);

bien, ahora empezamos, primero vamos a crear algunas opciones por defecto para el plugin, por ejemplo el texto que muestra la caja por defecto y la clase css que le da el efecto “marca de agua”

(function($){
	$.fn.searchBox = function(){
		var $el = this;
		var defaults = {defaultStyle: "gris", defaultText: "ingrese texto"};
	};
})(jQuery);

ponemos las opciones en una variable a la que llamamos defaults, con dos valores, por supuesto que tenemos una clase de css llamada “gris”.

Utilizando $.extend para combinar las opciones con los valores por defecto

Para dar mayor flexibilidad al plugin es interesante permitir que estos valores por defecto puedan ser cambiados por el usuario, la forma de hacerlo es recibirlos como parámetro en la función a la que apunta searchBox y recibir allí el parámetro, para que el usuario pueda configurar alguno, todos o ninguno de los parámetros vamos a valernos de la función extend de jQuery, el código quedaría así:

(function($){
	$.fn.searchBox = function(options){
		var $el = this;
		var defaults = {defaultStyle: "gris", defaultText: "ingrese texto"};
		$.extend(defaults, options);
	};
})(jQuery);

Perfecto, ahora empecemos a implementar la funcionalidad, lo primero es asignar el texto y el estilo, simple, son dos líneas con jQuery

(function($){
	$.fn.searchBox = function(options){
		var $el = this;
		var defaults = {defaultStyle: "gris", defaultText: "ingrese texto"};
		$.extend(defaults, options);

		$el.val(defaults.defaultText);
		$el.addClass(defaults.defaultStyle);
	};
})(jQuery);

Ahora la idea es que cuando tome el foco el control se borre el texto y se quite el estilo, con jQuery son nuevamente dos líneas

(function($){
	$.fn.searchBox = function(options){
		var $el = this;
		var defaults = {defaultStyle: "gris", defaultText: "ingrese texto"};
		$.extend(defaults, options);

		$el.val(defaults.defaultText);
		$el.addClass(defaults.defaultStyle);
		
		$el.focus(function(){
			$el.val("");
			$el.removeClass(defaults.defaultStyle);
		});
	};
})(jQuery);

por último, cuando el control pierda el foco debemos verificar si se ingresó un texto y en caso que no sea así nuevamente poner el texto por defecto y el estilo, por lo tanto vamos a agrupar las dos líneas que hacen esto en una función y llamarlas desde el evento blur en caso que no haya texto, nuestro plugin quedaría así:

(function($){
	$.fn.searchBox = function(options){
		var $el = this;
		var defaults = {defaultStyle: "gris", defaultText: "ingrese texto"};
		$.extend(defaults, options);

		function init(){
			$el.val(defaults.defaultText);
			$el.addClass(defaults.defaultStyle);
		}
		
		$el.focus(function(){
			$el.val("");
			$el.removeClass(defaults.defaultStyle);
		}).blur(function(){
			if($el.val().length==0)	init();
		});
	
		init();
	};
})(jQuery);

Permitiendo que se pueda usar la interfaz fluyente

Un detalle que falta, es que en caso que lo creamos necesario, podemos querer que después de llamar a nuestro plugin se pueden seguir concatenando funciones de jQuery así:

$(function(){
	$(":text").searchBox({defaultStyle: "gris", defaultText: "buscar..."})
	.click(function() { //hacer algo});
});

con la implementación como está no va a funcionar, necesitamos devolver el mismo objeto (el conjunto afectado por el selector encerrado en un objeto jQuery) para que se puedan seguir concatenando llamadas, finalmente el código queda así:

(function($){
	$.fn.searchBox = function(options){
		var $el = this;
		var defaults = {defaultStyle: "gris", defaultText: "ingrese texto"};
		$.extend(defaults, options);

		function init(){
			$el.val(defaults.defaultText);
			$el.addClass(defaults.defaultStyle);
		}
		
		$el.focus(function(){
			$el.val("");
			$el.removeClass(defaults.defaultStyle);
		}).blur(function(){
			if($el.val().length==0)	init();
		});
	
		init();		
		return $el;
	};
})(jQuery);

Listo, por supuesto que tiene algunos detalles de implementación, como que siempre borra el contenido al tomar el foco, pero es un ejemplo de un modo de hacer un plugin, espero sea útil.

Nos leemos.

Tips de Javascript: Determinar los miembros de un objeto y sus valores

En ocasiones que no son pocas nos pasa que tenemos que recuperar alguna propiedad de un objeto en Javascript pero no recordamos bien su nombre o diréctamente no lo conocemos, más allá del intellisense o de las ventanas de inspección de algunas herramientas a veces nos es útil poder listar tanto las propiedades como las funciones de un objeto que, por ejemplo, recibimos en una función.

Leer los miembros de un objeto

La forma de leer los miebros de un objeto en Javascript es bien siemple, veámos el siguiente ejemplo:

	var p = {nombre: 'leonardo', apellido: 'micheloni', 
	saludar: function(){
		return 'hola mi nombre es: ' + this.nombre;
	}};

tenemos un lindo objeto (en prinicipio no conocemos los miebros) y necesitamos saberlos de una manera rápida y sencialla bien, con la ayuda de for-in podemos lograrlo, sería algo así:

	var p = {nombre: 'leonardo', apellido: 'micheloni', 
	saludar: function(){
		return 'hola mi nombre es: ' + this.nombre;
	}};

	for(i in p){
		console.log(i);
	}

for-in nos permite iterar los miebros de un objeto del mismo modo que un array, el resultado es el siguiente:

image

Mágico, ahora vamos a listarlo junto con sus valores del siguiente modo:

	var p = {nombre: 'leonardo', apellido: 'micheloni', 
	saludar: function(){
		return 'hola mi nombre es: ' + this.nombre;
	}};

	for(i in p){
		console.log(i + " : " + p[i]);
	}

 image

Simplemente tratamos al objeto como un array accedemos a sus miebros como un índice, mágico.

Hasta la próxima.

jQuery extend, utilidad para combinar valores de objetos

Muchas veces nos encontramos desarrollando una función que acepta varios parámetros y hemos decidido que la forma de pasar estos parámetros sea a través de un objeto que los represente, el problema es cuando estas opciones son muchas y no es indispensable que quien usa la función indique todos los parámetros sino que tenemos un grupo de valores por omisión y tenemos que determinar qué valores indica el usuario de la función y cuáles tenemos que tomar de los que son por “default”, bien, nuestra querida jQuery tiene una utilidad para esto, extend

jQuery.extend

El tema es así, tenemos un objeto que contiene las propiedades, pero también tenemos otro objeto en el cual sus propiedades son valores por omisión, vamos a ver cómo combinamos sus valores son jQuery

(function (){
	//no indicamos ninguna opción
	var opcionesDeUsuario = {};
	//estas son nuestras opciones por omisión
	var opcionesPorDefecto = {nombre:'leonardo', apellido:'micheloni'};
	//utlizamos jQuery.extend para "mergear" ambos objetos
	$.extend(opcionesPorDefecto, opcionesDeUsuario);
	//el resultado queda en opcionesPorDefecto
	console.dir(opcionesPorDefecto);
})();

Lo ejecutamos y vemos que resulta

image

Pero es el caso más simple, no indicamos nada, vamos a ver si indicamos alguna de los valores qué pasa

(function (){
	//indicamos el valor de la propiedad "nombre"
	var opcionesDeUsuario = {nombre:'gabriel'};
	//estas son nuestras opciones por omisión
	var opcionesPorDefecto = {nombre:'leonardo', apellido:'micheloni'};
	//utlizamos jQuery.extend para "mergear" ambos objetos
	$.extend(opcionesPorDefecto, opcionesDeUsuario);
	//el resultado queda en opcionesPorDefecto
	console.dir(opcionesPorDefecto);
})();

image

Perfecto, reemplazó el valor de la propieada nombre tal cual indicamos, vamos por una más difícil, indicar una propiedad que no está definida en el objeto que tomamos por omisión

(function (){
	//indicamos el valor de la propiedad "nombre" y una nueva propiedad "edad"
	var opcionesDeUsuario = {nombre:'gabriel', edad:35};
	//estas son nuestras opciones por omisión
	var opcionesPorDefecto = {nombre:'leonardo', apellido:'micheloni'};
	//utlizamos jQuery.extend para "mergear" ambos objetos
	$.extend(opcionesPorDefecto, opcionesDeUsuario);
	//el resultado queda en opcionesPorDefecto
	console.dir(opcionesPorDefecto);
})();

image

y funciona perfectamente, ahora la variable opcionesPorDefecto tiene un “merge” entre sus valores iniciales y aquellos que sobre-escribió y agregó la variable opcionesDeUsuario.

Hasta la próxima.