miércoles, 10 de agosto de 2011

C# Básico: Objetos y referencias

La verdad es que ahora hacía bastantes meses que no publicaba nada de la serie “C# básico”. En esta serie pongo posts sobre temas básicos del lenguaje. No es un libro por fascículos, ni un tutorial al uso puesto que los posts no tienen orden en concreto y nacen a partir de inquietudes que observo (mayoritariamente en los foros, pero también por correos que recibo). Todos los posts de esta serie los podéis ver aquí.

En el post de hoy quiero hablar de la diferencia entre objetos y referencias ya que observo que no siempre está clara. Gente que entiende los conceptos básicos de herencia parece liarse en este punto. Muchas veces es un tema pasado rápidamente en muchos libros y tutoriales. Y es que, la verdad, es un tema muy sencillo… ;-)

MiClase miObjeto = new MiClase();





¿Qué hace este código? En muchos sitios leerás que lo que hace es crear un objeto de la clase MiClase. Eso es cierto, pero describe lo que hace lo que hay a la derecha del símbolo de asignación. Qué hace el código que está a la izquierda? Pues lo que hace es declarar una referencia de tipo MiClase. Otra palabra que se usa muchas veces en lugar de referencia es variable aunque no son técnicamente lo mismo (hay variables que no son referencias y las referencias pueden asignarse a otros elementos que no llamamos usualmente variables como p.ej. los parámetros a una función).



Las referencias contienen objetos. Yo prefiero decir que las referencias apuntan a objetos (aunque esta palabra parece como “maldita”, sin duda por culpa de los punteros) para que quede claro que un mismo objeto puede estar contenido en (apuntado por) más de una referencia.



El tipo de una referencia



Todas las refencias tienen un tipo. Este tipo es único e inmutable durante toda la vida de la referencia. El tipo de una referencia determina que objetos puede contener dicha referencia. En concreto:




  1. Objetos del mismo tipo. Es decir, una referencia de tipo MiClase puede contener objetos de la clase MiClase.


  2. Objetos de una clase derivada de la clase del tipo de la referencia. Si la referencia es de tipo MiClase puede contener objetos de cualquier clase derivada de MiClase.


  3. Objetos de cualquier clase que implemente el tipo de la referencia. Eso aplica sólo si el tipo de la referencia es una interfaz. En este caso la referencia puede contener un objeto de cualquier clase que implemente dicha interfaz.



Todas las clases en .NET derivan de Object. Por lo tanto, según el punto (2) una referencia de tipo Object, puede contener cualquier objeto de cualquier clase:




Object objeto = new CualquierClase();





¿Condiciona alguna cosa más el tipo de la referencia? Pues sí: el tipo de la referencia condiciona como vemos al objeto contenido en dicha referencia. Es decir, la referencia es como un disfraz para el objeto. Le permite “ocultar su tipo real” y mostrarse como el “tipo de la referencia”.



P.ej. dado el siguiente código:




class MiClase
{
public void Foo() {}
}

class MiClaseDerivada : MiClase
{
public void Bar() {}
}

MiClase c1 = new MiClaseDerivada();
MiClaseDerivada c2 = new MiClaseDerivada();





Podemos ver como MiClase define un método (Foo) y MiClaseDerivada que deriva de MiClase añade el método Bar. Luego c1 es una referencia de tipo MiClase que contiene un objeto de MiClaseDerivada (puede según el punto 2 anterior). Y c2 es una referencia de tipo MiClaseDerivada que contiene un objeto de MiClaseDerivada (posible según el punto 1 anterior). Entonces tenemos que:




c1.Foo();   // Ok.
c1.Bar(); // No compila.
c2.Foo(); // Ok.
c2.Bar(); // Ok.





La llamada c1.Bar() no compila. ¿Por que? Pues simplemente porque la referencia es de tipo MiClase. Y MiClase no tiene ningún método Bar. Da igual que el objeto contenido por dicha referencia sea de tipo MiClaseDerivada, que sí que tiene el método Bar. El compilador no se fija en los tipos de los objetos. Se fija en los tipos de las referencias.



Objetos compartidos



Como hemos dicho antes un mismo objeto puede estar contenido por más de una referencia:




MiClaseDerivada c1 = new MiClaseDerivada();
MiClase c2 = c1;





En este punto tenemos dos referencias. Pero un sólo objeto. Es decir, c1 y c2 contienen el mismo objeto, que es un objeto de tipo MiClaseDerivada. Si accedo al objeto a través de c1 lo veo como un objeto de tipo MiClaseDerivada (ya que este es el tipo de c1). Por otro lado si accedo al objeto a través de c2 lo veo como un objeto de tipo MiClase (al ser este el tipo de c2). Por lo tanto c1.Bar() es correcto y c2.Bar() no compila.



Pero insisto: son el mismo objeto. Observad el siguiente código:




class Program
{
public static void Main()
{
MiClaseDerivada c1 = new MiClaseDerivada();
MiClase c2 = c1;
c1.Incrementar();
c2.Incrementar();
Console.WriteLine("El valor de c1 es:" + c1.Valor);
Console.WriteLine("El valor de c2 es:" + c2.Valor);
}
}


class MiClase
{
private int valor;

public int Valor { get { return this.valor; } }
public void Incrementar()
{
valor++;
}
}

class MiClaseDerivada : MiClase
{
// Código
}





¿Cual es la salida por pantalla de dicho código? Pensadlo con detenimiento. Pues  la siguiente:



El valor de c1 es:2

El valor de c2 es:2



Eso es debido porque c1 y c2 contienen el mismo objeto. Por lo tanto inicialmente tenemos que el valor de dicho objeto es 0. Al llamar a c1.Incrementar() el valor pasa a ser 1. Y al llamar a c2.Incrementar(), el valor pasa a ser 2, ya que el objeto que contiene c2 es el mismo que el objeto que contiene c1.



Así pues recordadlo siempre: Asignar una referencia a otra NO crea un nuevo objeto. Simplemente hace que la referencia contenida a la izquierda de la asignación contenga EL MISMO objeto que la referencia situada a la derecha.



Comparando objetos y referencias.



De nuevo la forma más fácil es verlo con un código de ejemplo:




class Program
{
public static void Main()
{
Persona p1 = new Persona();
p1.Nombre = "Pepito";
p1.Edad = 20;
Persona p2 = p1;
Persona p3 = new Persona();
p3.Nombre = "Pepito";
p3.Edad = 20;
bool b = p2 == p1;
bool b2 = p3 == p2;
}
}

class Persona
{
public string Nombre { get; set; }
public int Edad { get; set; }
}





¿Cual es el valor de b y b2?




  • b vale true porque p1 y p2 contienen el mismo objeto


  • b2 vale false porque p3 y p2 contienen objetos distintos. Da igual que los dos objetos sean del mismo tipo y sean idénticos. En este caso son dos Personas idénticas: mismo nombre y edad. Pero el operador == compara referencias, no objetos.



Así pues recuerda: El operador == al comparar referencias devuelve true sólo si las dos referencias contienen el mismo objeto. En caso contrario devuelve false (aunque las dos referencias apunten a dos objetos idénticos).




Nota: Este comportamiento del operador == puede modificarse para que compare el valor de los objetos en lugar de indicar si las dos referencias contienen el mismo objeto. P.ej. la clase string tienen modificado dicho operador para comparar el valor de las cadenas. Esto queda fuera del alcance de este post.




La comparación de objetos (es decir, determinar si dos objetos son idénticos pese a ser dos objetos distintos) es algo que por norma general depende de la clase. P.ej. dos Personas serán iguales si tienen el mismo nombre y edad. Dos cadenas serán iguales si contienen los mismos carácteres. Depende de cada clase determinar que significa que dos objetos son iguales. Para estandarizar un poco la comparación de objetos, en .NET tenemos el método Equals. Dicho método está definido en la clase Object y por lo tanto, por herencia, existe en todas las clases. Si quiero indicarle al framework como comparar dos objetos de tipo Persona puedo añadir a la clase Persona el siguiente código:




public override bool Equals(object obj)
{
if (obj is Persona)
{
Persona otro = (Persona) obj;
return otro.Edad == Edad &&
otro.Nombre == Nombre;
}
return false;
}



Y para comparar los objetos, debo llamar a Equals en lugar del operador ==




bool b2 = p3.Equals(p2);





Conversiones (castings)



En el código del método Equals anterior hay el siguiente código:




Persona otro = (Persona)obj;





El código (Persona) es lo que se llama casting. El casting lo que hace es cambiar el tipo de una referencia. Es decir en el caso anterior obj era una referencia de tipo object (los parámetros también pueden ser referencias). Recordad que las referencias de tipo object pueden contener cualquier objeto. Pero yo quiero acceder a Nombre y Edad que son campos definidos en la clase Persona y por ello necesito una referencia de tipo Persona que me contenga el mismo objeto que la referencia obj.



Si directamente probáramos:




Persona otro = obj;





Dicho código no compila. ¿Porque? Pues porque otro es una referencia de tipo Persona y por lo tanto solo puede contener:




  1. Un objeto de tipo Persona


  2. Un objeto de cualquier clase que derive de Persona



Pero obj es una referencia de tipo object y puede contener un objeto de tipo object o bien un objeto de cualquier clase que derive de object… es decir, de cualquier clase. Imaginad, entonces:




object obj = new Perro();
Persona otro = obj;





Es evidente que el objeto contenido por obj es un perro y no una persona. Si el código de la segunda línea compilase estaríamos viendo un perro como una persona y bueno… se supone que no se puede, no? Por eso, como el compilador no puede garantizar que el objeto (recordad que el compilador no se fija en objetos) contenido por la referencia obj sea de un tipo válido para la referencia otro, se cura en salud y no nos deja compilar el código.



Pero… tu no eres el compilador y tu sí te fijas en los objetos. ¿Qué pasa en aquellos casos en que tu sabes que el objeto contenido por la referencia obj es de un tipo válido para la referencia Persona? Pues que debes decírselo al compilador. ¿Cómo? Usando el casting:




Persona otro = (Persona)obj;





Aquí le estás diciendo al compilador: Quiero que la referencia otro contenga el mismo objeto que la referencia obj y tranquilo, no te quejes porque yo te digo que el objeto es de tipo Persona. Con el casting el compilador te cree y te deja hacer la asignación.



Eh… que te crea el compilador no significa que te crea el CLR. El CLR no se fía ni de su madre, así que si tu haces:




object perro = new Perro();
Persona persona = (Persona)perro;





El compilador no se quejará, pero cuando ejecutes, vas a recibir una hermosa InvalidCastException. El CLR sí que se fija en los objetos, como tu :)



Ah! Y aunque el compilador no se fije en objetos… no lo insultes, eh? No intentes algo como:




Perro perro = new Perro();
Persona persona = (Persona)perro;





Eso no compila. La razón es porque no es necesario fijarse en los objetos para ver que una referencia de tipo Persona nunca podrá contener el mismo objeto que una referencia de tipo Perro: Persona y Perro no tienen nada en común. El compilador puede no fijarse en los objetos, pero no es tonto!



Un saludo!

miércoles, 11 de mayo de 2011

[Webcast–AUGES] Introducción a ASP.NET MVC

Seguramente la mayoría ya sabréis que gracias al empuje del maestro Luis Ruiz Pavón (que nos ha ido convenciendo a varios), se ha creado AUGES, el grupo de usuarios de ASP.NET de España.

Para mi es un honor y un placer poder formar parte de este grupo, pero todavía es un placer más grande inaugurar la agenda de eventos del grupo. Y como no podía ser de otro modo el evento será un Webcast de ASP.NET MVC. :)

La fecha? El Miércoles 18. La hora? A las 19:30 (hora española peninsular).

La idea es hacer un evento 100% introductorio: explicar que es esto de ASP.NET MVC, ver ejemplos y comentar cosillas básicas. Más adelante ya tendremos tiempo de hacer eventos más hardcore sobre el tema! ;-)

Así que, ya sabes… si has oído a hablar de MVC y quieres ver de que se trata, o si conoces implementaciones de MVC en otros sistemas y quieres ver la de Microsoft, o si simplmente quieres escucharme un rato (mmmm…. :p) pásate por la web de registro y… nos conectamos el miércoles!!

Saludos… y nos vemos! ;-)

PD: Como siempre… crosspost desde mi blog de geeks.ms

domingo, 3 de abril de 2011

[ASP.NET MVC] Pasar parámetros a través del PathInfo

¡Muy buenas! Bueno, el título del post no queda demasiado claro, pero a ver si consigo explicar un poco la idea. ;-)

Los que habéis usado ASP.NET MVC estáis muy acostumbradas a las URLs del estilo /controlador/accion/id, es decir algo como:

  • /Home/Index/10
  • /Articles/View/Eiximenis
  • /Blog/View/10293

Sabemos que gracias a la tabla de rutas podemos pasar tantos parámetros como queramos, y así podríamos tener URLs del tipo:

  • /Articles/View/Eiximenis/MVC/2011

Que podría devolverme los articulos de “Eiximenis” con el tag “MVC” y del año 2011.

El único punto a tener presente es que el orden de los parámetros importa, es decir no es lo mismo /Articles/View/Eiximenis/MVC/2011 que /Articles/View/2011/MVC/Eiximenis. En el primer caso buscamos los artículos de Eiximenis sobre MVC en el 2011 y en el segundo caso buscaríamos los artículos del blogger 2011, sobre MVC en el año de Eiximenis. Y sin duda Fra Francesc Eiximenis, fue un gran escritor, pero que yo sepa todavía no se le ha dedicado un año (algo totalmente injusto, por supuesto :p).

En este artículo quiero enseñaros una manera para que podáis gestionar URLs del tipo:

  • /Articles/View/Author/Eiximenis/Tag/MVC/Year/2011
  • /Articles/View/Tag/MVC/Year/2011/Author/Eiximenis

Y que ambas URLs sean tratadas de forma idéntica. En este caso estaríamos pasando tres parámetros: Author, Tag y Year.

Para conseguir este efecto nos bastan dos acciones muy simples: definir un route handler nuevo y una entrada a la tabla de rutas.

El route handler lo único que debe hacer es recoger la información de la URL y parsearla en “tokens” (usando el ‘/’ como separador). Y por cada par de tokens añadir una entrada en los valores de ruta (route values). El código es muy simple:

public class UrlRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var path = requestContext.RouteData.Values["pathInfo"];
if (path != null)
{
var tokens = path.ToString().Split('/');
for (var idx =0; idx<tokens.Length; idx+=2)
{
if (idx+1 < tokens.Length)
{
requestContext.RouteData.Values.Add(tokens[idx], tokens[idx+1]);
}
}
}

return base.GetHttpHandler(requestContext);
}
}





Una pequeña nota es que la cadena que separamos en tokens, no es toda la URL sino “pathInfo” un parámetro de ruta que ya nos vendrá dado. Este parámetro de ruta contendrá todo aquello que no es ni el controlador ni la acción. Es decir en la URL /Articles/View/Author/Eiximenis/Tag/MVC/Year/2011 el valor de pathInfo será Author/Eiximenis/Tag/MVC/Year/2011 (que son justo los parámetros).



Ahora nos queda añadir la entrada a la tabla de rutas. En mi ejemplo yo he eliminado la entrada “Default” que genera VS2010 y la he sustituído por:




routes.Add("Default", new Route(url: "{controller}/{action}/{*pathInfo}",
routeHandler: new UrlRouteHandler(),
defaults: new RouteValueDictionary(new {controller = "Home", action = "Index"})));





La clave aquí está en el {*pathInfo}. Aquí le digo al sistema de rutas que coja todo lo que venga después de /{controller}/{action} y me lo añada a un parámetro de ruta llamado pathInfo. Además de eso, en esta ruta le indico que su routeHandler será una instancia de la clase UrlRouteHandler que hemos creado antes.



Y listos! Una vez los datos están en el route value ya puede entrar en acción el sistema de binding de ASP.NET MVC lo que quiere decir que puedo crear un controlador como el siguiente:




public class ArticlesController : Controller
{
public ActionResult View(string author, string tag, int? year)
{
dynamic data = new ExpandoObject();
data.Author = author ?? "Sin formato";
data.Tag = tag ?? "Sin confirmación";
data.Year = year.HasValue ? year.ToString() : "Sin año";
return View(data);
}
}





Que recibiría los parámetros de las URLs que hemos visto anteriormente.



Un saludo a todos!



PD: De nuevo eso es un crosspost desde mi blog en geeks.ms!

lunes, 28 de marzo de 2011

[HTML/JS] Module pattern

Muy buenas! Cuando creas un sitio web, es normal que vayas añadiendo cada vez más código javascript en él. Al final, seguramente terminaréis desarollando una mini-api, propia que muchas veces reside en un archivo .js, lleno de funciones. Algo como:

function HazAlgo(id, params, callback)
{
}

function _HazAlgoHelper(domObj, params)
{
}





En este caso tenemos dos funciones, HazAlgo y _HazAlgoHelper. La función _HazAlgoHelper es realmente una función privada, es decir está pensada para ser llamada únicamente dentro de HazAlgo. Pero en Javascript no existe directamente el concepto de función privada así que le ponemos un idicador (que empieze por _) y asumimos que todas las funciones que empiecen por _, son privadas.



Como podemos ver, el código javascript no está muy “organizado”: tenemos todas las funciones juntas y además podemos ver aquellas que no deberíamos. El patrón de módulo existe para solventar esos problemas.



El patrón de módulo



La idea del patrón de módulo (module pattern) en Javascript es simular el concepto de una clase estática con métodos públicos y métodos privados. Lo de clase estática viene porque no quiero ir creando objetos de mi módulo, simplemente quiero tener ahí, las funciones públicas. Pero sin ver las privadas. Para los que no lo sepáis: en javascript no existe el concepto de variable estática.



La idea que hay detrás del patrón de módulo es muy simple, y como casi siempre pasa simple significa también brillante: Se trata de crear un objeto anónimo, cuyos campos públicos sean las funciones públicas. Finalmente se crea una instancia de ese objeto y se asigna al namespace global (eso es, al objeto window).



El código de base sería algo como:




(function(wnd) {
var miModulo = function() {
var _HazAlgoHelper = function(domObj, params) {alert("método privado");};
var _HazAlgo = function(id, params, callback) { _HazAlgoHelper(); alert("método público"}; };

return {
HazAlgo : _HazAlgo
};
}

wnd.m$ = miModulo();
})(window);





Bien… se que el código puede parecer lioso, la primera vez, pero vamos a analizarlo.



Podemos ver que estamos definiendo una función anónima que acepta un parámetro (llamado wnd).



¿Y que hace esa función anónima)? Pues para empezar define una variable miModulo que resulta ser… una función (var miModulo = funcion() { …}).



Y que hará esa función cuando se invoque? Pues tres cosas:




  1. Definir una variable llamada _HazAlgoHelper… que es otra función.


  2. Definir una variable llamada _HazAlgo… que es otra función.


  3. Devolver un objeto anónimo con un campo, llamado HazAlgo. El valor de HazAlgo es el mismo que _HazAlgo (es decir una función).



Con eso tenemos una función (miModulo) que cuando la invoquemos nos devolverá un objeto con un solo campo (llamado HazAlgo). Así, teoricamente:




miModulo.HazAlgo();    // Valido
miModulo._HazAlgo(); // No válido
miModulo._HazAlgoHelper(); // NO válido





Fijaos que la gracia está en que HazAlgo() realmente es lo mismo que _HazAlgo()… y desde _HazAlgo() podemos llamar sin ningún problema a _HazAlgoHelper() que es nuestra función privada. Así la clave es mapear las funciones públicas como campos del objeto anónimo devuelto.



¡Con eso hemos simulado el concepto de funciones privadas!



Pero, miModulo es una variable local, es local a la función anónima que estamos definiendo. Así que todavía nos queda un paso más: Guardar miModulo en alguna propiedad del parámetro wnd. Eso es lo que hacemos con la línea:




wnd.m$ = miModulo();





Invocamos miModulo (con parentesis, pues es una función) y guardamos el resultado (el objeto anónimo con el campo HazAlgo) en la propiedad m$ del parámetro wnd.



Ya casi estamos… Hasta este punto hemos definido simplemente una función anónima. Ahora toca invocarla. Fijaos en un detalle importante: la primera línea empieza con un paréntesis abierto. Con eso, de hecho, estamos invocando nuestra función anónima. Pero nuestra función anónima espera un parámetro (wnd) así que debemos pasárselo. Eso es lo que hacemos en la última línea: le pasamos window. Y porque window? Pues porque window es un namespace global: todo lo que esté en window es global.



Por lo tanto ahora, desde cualquier parte de mi página puedo hacer:




<script>m$.HazAlgo(1,2,3);</script>





Y eso es correcto, mientras que hacer.




<script>m$._HazAlgoHelper(1,2);</script>





Nos dará un error.



Para tener nuestro módulo listo para ser usado, basta con tenerlo en un .js propio e incluirlo en aquellas páginas donde lo necesitemos. ¡Y listos!



Espero que esto os parezca interesante y os ayude a organizar vuestro código javascript!



Un saludo!



PD: Como siempre… un crosspost desde mi blog en geeks.ms!

martes, 15 de marzo de 2011

ASP.NET MVC: Previsualizar imágenes subidas (2)

Buenas! Donde dije digo, digo Diego… Sí, ya sé que dije que el segundo post sería como hacerlo con Ajax, pero bueno… la culpa es de twitter, concretamente de @pablonete con el que hemos empezado a hablar sobre si es posible evitar el guardar la imágen físicamente en el servidor. Hay un mecanismo obvio, que es usar la sesión (guardar el array de bytes que conforman la imágen en la sesión). Pero… hay otra? Pues sí: usar data urls!

Data urls

Lo que mucha gente no conoce es que el formato de URL permite incrustar datos que son interpretados por el navegador como si se los hubiese descargado externamente. P.ej. la siguiente url es válida:

data:image/jpeg;base64,xxxxxxx

donde xxxxxxx es la codificación en base64 de la imágen.

P.ej. eso es totalmente correcto:

<img src=”data:image/jpeg;base64,xxxxxx/>





Veamos como podríamos modificar nuestro proyecto anterior, para usar data urls en lugar de un fichero temporal en el servidor… Fijaos que eso sólo evita guardar el fichero, la imagen debe ser subida al servidor.



Las modificaciones son muy simples, por un lado primero vamos a modificar la acción del controlador, para que quede así:




[HttpPost]
public ActionResult SendImage(HttpPostedFileBase img, string base64, string contenttype)
{
if (base64 != null && contenttype != null && img==null)
{
// Aquí podríamos guardar la imagen (en base64 tenemos los datos)
return View("Step2");
}
var data = new byte[img.ContentLength];
img.InputStream.Read(data, 0, img.ContentLength);
var base64Data = Convert.ToBase64String(data);
ViewBag.ImageData = base64Data;
ViewBag.ImageContentType = img.ContentType;
return View("Index");
}





La acción recibe tres parámetros:




  1. img: El fichero seleccionado (contenido del <input type=”file”).


  2. base64: Codificación en base64 de la imagen que se está previsualizándo.


  3. contenttype: Content-type de la imagen que se está previsualizando.



Por supuesto base64 y contenttype sólo se envían si se está previsualizando una imagen. En caso contrario valen null.



Fijémonos lo que hace la acción, si base64 y contenttype valen null, o bien el usuario ha seleccionado una imagen nueva (img != null):




  1. Obtenemos los datos en base64 de la imagen enviada por el usuario (base64Data).


  2. Pasamos esos datos (junto con el content-type) a la vista, usando el ViewBag.



Y la vista que hace? Pues poca cosa:




<form enctype="multipart/form-data" method="post" action="@Url.Action("SendImage")">
<label for="img">Seleccionar imagen:</label>
<input type="file" name="img" /> <br />
<input type="submit" />

@if (ViewBag.ImageData != null)
{
<img src="data:@ViewBag.ImageContentType;base64,@ViewBag.ImageData" />
<input type="hidden" value="@ViewBag.ImageData" name="base64" />
<input type="hidden" value="@ViewBag.ImageContentType" name="contenttype" />
}
</form>





Tenemos el formulario con el input type=”submit”, y luego si en el ViewBag vienen los datos de la imagen que se debe previsualizar, genera 3 campos más:




  1. Una imagen, cuyo src es una data url (formato data:content-type;base64,[datos en base64])


  2. Dos campos hidden (para guardar el content-type y los datos para mandarlos de vuelta al servidor).



Y listos! Con eso tenemos la previsualización de las imágenes sin necesidad de generar fichero temporal alguno.



Finalmente, tres aclaraciones:




  1. Fijaos que la codificación en base64 se incrusta en la página (en este caso se incrusta dos veces, aunque con un poco de javascript podría incrustarse solo una), por lo que esto puede generar páginas muy grandes si la imagen ocupa mucho.


  2. Si no voy errado, los navegadores sólo están obligados a soportar URLs de hasta 1024 bytes. Todo lo que exceda de aquí… depende del navegador. Firefox p.ej. acepta URLs muy largas, pero recordad que Base64 genera fácilmente URLs no muy largas, sinó descomunalmente largas (si la imágen ocupa 100Ks, la URL en Base64 ocupará más de 100Ks). Así que para imágenes pequeñas igual tiene sentido, pero para imágenes largas, honestamente no creo que sea una buena solución.


  3. La mayoría de navegadores modernos soportan data urls (IE lo empezó a hacer con su versión 8).



En fin… bueno, como curiosidad no está mal, eh? ;-)



Gracias a Pablo Nuñez por la divertida e interesante conversación en twitter!



Saludos!



PD: Enlace al post anterior de esa serie: http://geeks.ms/blogs/etomas/archive/2011/03/15/asp-net-mvc-previsualizar-im-225-genes-subidas-1.aspx



PD2: Otro crosspost des de mi blog en geeks.ms

ASP.NET MVC: Previsualizar imágenes subidas (1)

Buenas! Una pregunta que últimamente parece que se pregunta varias veces en los foros de ASP.NET MVC es como previsualizar una imagen que se quiere subir al servidor.

Antes que nada aclarar que, técnicamente, la pregunta está mal hecha: no es posible previsualizar la imagen antes de que sea subida. Antiguamente en algunos navegadores, y con un poco de javascript, eso era posible, pero ahora por suerte eso ya no funciona :)

Básicamente previsualizar una imagen consiste en:

  1. Recibir los datos de la imagen
  2. Guardarla en algún sitio “temporal”
  3. Mandarla de vuelta al navegador
  4. Borrar la imagen del sitio “temporal” si el usuario no la acepta

En este primer post vamos a ver como hacerlo sin usar Ajax. Luego habrá un segundo post y veremos como hacerlo usando Ajax (no es tan sencillo porque XMLHttpRequest no soporta el formato de datos multipart/form-data que es el que se usa cuando se mandan ficheros).

Bien, vamos a verlo rápidamente, ya veréis cuan sencillo es :)

Lo primero es tener la vista que tenga el formulario para enviar los datos. La siguiente servirá (si queréis más detalles de como hacer upload de ficheros en MVC mirad mi post al respecto):

<h2>Index</h2>

<form enctype="multipart/form-data" method="post" action="@Url.Action("SendImage")">
<label for="img">Seleccionar imagen:</label>
<input type="file" name="img" /> <br />
<input type="submit" />
</form>





Trivial: un form con multipart/form-data que enviará sus datos a la acción SendImage. La acción podría ser algo como:




[HttpPost]
public ActionResult SendImage(HttpPostedFileBase img)
{
var data = new byte[img.ContentLength];
img.InputStream.Read(data, 0, img.ContentLength);
var path = ControllerContext.HttpContext.Server.MapPath("/");
var filename = Path.Combine(path, Path.GetFileName(img.FileName));
System.IO.File.WriteAllBytes(Path.Combine(path, filename), data);
ViewBag.ImageUploaded = filename;
return View("Index");
}





Vale, fijaos que recibimos el HttpPostedFileBase (llamado img como el atributo name del input type=”file”). Lo que hacemos en la acción es muy simple:




  1. Guardamos la imagen en disco (en este caso por pura pereza uso el directorio raíz de la aplicación web. Por supuesto lo suyo es usar un directorio configurado en web.config y con los permisos NTFS correspondientes).


  2. Coloco en el ViewBag el nombre de la imagen que se ha guardado (luego vemos porque).


  3. Devuelvo la vista “Index” (la misma de la cual venimos)



Ok, tal y como lo tenemos ahora, si lo probáis veréis que podéis hacer un upload de la imagen, y la imagen se graba en el disco (en el directorio raíz de la aplicación web), pero no se previsualiza nada. Vamos a modificar la vista Index para que haya esa previsualización.



Para previsualizar la imagen basta con añadir el siguiente código, despues del </form> en la vista:




@if (!string.IsNullOrEmpty(ViewBag.ImageUploaded))
{
<img src="@Url.Action("Preview", new {file=ViewBag.ImageUploaded})" />
}





Simple, no? Si en el ViewBag existe la entrada ImageUploaded generamos un tag <img> cuya dirección es la acción “Preview” y le pasamos el parámetro file con el nombre de la imagen. Y como es la acción Preview? Pues super sencilla:




public ActionResult Preview(string file)
{
var path = ControllerContext.HttpContext.Server.MapPath("/");
if (System.IO.File.Exists(Path.Combine(path, file)))
{
return File(Path.Combine(path, file), "image/jpeg");
}
return new HttpNotFoundResult();
}





Simplemente leemos la imagen guardada y la devolvemos usando un FilePathResult. Simple, eh? ;-)



Con eso, si subís una imagen, cuando le deis a submit, aparecerá de nuevo la vista, pero ahora previsualizando la imagen.



Ahora sólo nos queda el punto final: Que el usuario pueda aceptar esa imagen como correcta. Para ello lo más simple es hacer lo siguiente:




  1. Si el usuario está previsualizando una imagen y NO ha seleccionado otra, cuando hace el submit se entiende que acepta dicha imagen.


  2. Si el usuario NO está previsualizando una imagen, o bien está previsualizando una pero selecciona otra, al hacer el submit se entiende que descarta la imagen anterior y quiere previsualizar la nueva.



Para ello, tenemos que hacer que la vista le indique al controlador si se está previsualizando una imagen, y cual és. Por suerte eso es muy sencillo. Basta con modificar el <form> de la vista para que su atributo action quede como:




<form enctype="multipart/form-data" method="post" 
action="@Url.Action("SendImage", new {previewed=ViewBag.ImageUploaded})">





Fijaos que añadimos un parámetro previewed que la vista mandará a la acción y que será el nombre de la imagen que se está previsualizando.



Vamos a modificar la acción para que reciba ese parámetro y actúe en consecuencia:




[HttpPost]
public ActionResult SendImage(HttpPostedFileBase img, string previewed)
{
if (!string.IsNullOrEmpty(previewed) && img == null)
{
ViewBag.ImageName = previewed;
return View("Step2");
}

// Código tal cual estaba
}





Añadimos ese if al inicio: Si el usuario está previsualizando una imagen y no ha seleccionado otra, entendemos que acepta esta imagen. Entonces guardamos el nombre en el ViewBag y lo mandamos a la siguiente vista (Step2).



La vista es muy sencilla:




<h2>Step2</h2>

Siguiente paso. El usuario ha aceptado la imagen: <br />

<img src="@Url.Action("Preview", new {file=ViewBag.ImageName})" />





Simplemente mostramos la imagen. Fijaos que de nuevo usamos la acción Preview, puesto que la imagen ya la tenemos guardada en el servidor.



Notas finales



Faltarían, al menos, dos cosas para que dicho proyecto funcionase “de forma aceptable”:




  1. Borrar las imagenes descartadas por el usuario (en la acción SendImage si se recibe una imagen nueva y se estaba previsualizando una, borrar esta ya que el usuario la ha descartado).


  2. La acción  Preview siempre devuelve content-type a image/jpeg, eso debería hacerse según la extensión de la imagen, o guardarlo en algún sitio.



Ambas cosas son triviales y no las añado porque lo único que consiguiría es liar el código un poco más ;-)



En el próximo post… lo mismo pero usando Ajax.



Saludos!



PD: Pues sí… otro crosspost de mi blog de geeks.ms! Que raro, no? :P

viernes, 25 de febrero de 2011

Rendering de vistas parciales en Razor y MVC3

Buenas! Una de las dudas que he visto que se van repitiendo por ahí tiene que ver con como renderizar vistas parciales en MVC3 usando Razor.

En MVC2 y anteriores (o en MVC3 usando el ViewEngine de WebForms) la forma de renderizar una vista parcial era sencilla:

<% Html.RenderPartial("VistaParcial", modelo); %>





Mucha gente traduce eso a Razor y usa lo siguiente para renderizar una vista parcial:




@Html.RenderPartial("VistaParcial")





Y se obtiene un error, quizá un poco críptico, que dice lo siguiente: CS1502: The best overloaded method match for 'System.Web.WebPages.WebPageExecutingBase.Write(System.Web.WebPages.HelperResult)' has some invalid arguments



El error no está en que Html.RenderPartial no pueda usarse con Razor, el error está en la sintaxis que estamos usando. Cuando en Razor usamos la @ para indicar el inicio de código de servidor, la expresión que viene a continuación debe devolver un valor, que será incluído en la respuesta a enviar al navegador. La excepción a esa norma es cuando lo que sigue a la @ es una palabra clave reservada de Razor (como @model) o una palabra clave reservada del lenguaje que estemos usando (como @foreach). Esos casos especiales Razor los sabe tratar y actúa en consecuencia. Pero en el resto de casos siempre, siempre, siempre la expresión debe devolver un valor.



Hablando en términos del engine de WebForms, el código Razor:




@Html.RenderPartial("VistaParcial", modelo)





Se corresponde a:




<%: Html.RenderPartial("VistaParcial",modelo) %>





Que es erróneo (y da el error CS1502: The best overloaded method match for 'System.Web.HttpUtility.HtmlEncode(string)' has some invalid arguments).



Entonces… como usar Html.RenderPartial en Razor? Fácil: usando llaves para indicarle al motor de Razor que eso es un código que debe ejecutar, en lugar de un valor que debe incrustar en la respuesta:




@{ Html.RenderPartial("VistaParcial"); }





Así pues: Html.RenderPartial puede usarse en Razor sin ningún problema… como el resto de Helpers que conozcáis. Si el método lo usábais con <% … %> en Razor es @{ … }, mientras que si usábais <%: … %> en Razor es simplemente @…



Otras maneras de incrustar vistas parciales



De todas formas hay un par de métodos más para incrustar vistas parciales.



El primer método es Html.Partial() un método de extensión adicional. Para llamarlo se usan los mismos parámetros que Html.RenderPartial. La diferencia es que Html.Partial devuelve una IHtmlString con los contenidos de la vista renderizada. Por lo tanto, para incrustar una vista usando Html.Partial() usamos:




// Razor
@Html.Partial("VistaParcial")
// Webforms viewengine
<%: Html.Partial("VistaParcial") %>





El segundo método es propio de Razor, ya que está definido dentro del framework que se conoce como “WebPages” y es usar el método RenderPage, definido en la clase WebPageBase de la cual heredan las vistas Razor.



Dicho método acepta dos parámetros:




  1. La localización de la vista. Ojo! No el nombre, sinó su localización (incluyendo directorios)


  2. Parámetros a pasar a la vista (params object[]).



P.ej. para renderizar la vista parcial usaríamos:




@RenderPage("~/Views/Home/VistaParcial.cshtml")





Fijaos en que se debe usar el nombre del archivo de la vista a incluir (incluyendo extensión .cshtml).



Si se pasan parámetros a la vista parcial, estos no están disponibles usando la propiedad Model en la vista, sinó que debe usarse la propiedad PageData. P.ej. podríamos pasar una cadena y un entero a la vista:




@RenderPage("~/Views/Home/VistaParcial.cshtml", "Parametro 1", 10)





Y mostrarlos desde la vista con el uso de PageData:




@foreach (var item in PageData)
{
<div>@item.Value</div>
}





Mi opinión sobre el método RenderPage (en MVC): Sobra totalmente, y espero que nunca, nunca, nunca lo uséis. Porque? Pues porque RenderPage rompe la encapsulación del framework (en lugar de especificar un nombre de vista debéis especificar un fichero). Es evidente que existe para dar soporte a WebPages pero WebPages y MVC se parecen sólo porque usan Razor como sintaxis, pero en concepción son dos cosas totalmente distintas… Aunque por razones (supongo que técnicas) Razor depende de WebPages y esa dependencia se arrastra a MVC3, cosa que personalmente no me gusta demasiado. Pero es lo hay… ;-)



Conclusiones




  1. Html.RenderPartial funciona correctamente en Razor, al igual que el resto de métodos de siempre. Sólo debemos tener cuidado en usar la sintaxis correcta.


  2. Html.Partial es un método adicional para renderizar vistas parciales. La diferencia con Html.RenderPartial() es que este último escribe en la response directamente el contenido de la vista, mientras que Partial() lo devuelve dentro de una cadena. No tengo claras las implicaciones de rendimiento que puede tener empezar a crear mutltiud de cadenas que serán eliminadas por el GC casi de inmediato.


  3. RenderPage es el método de WebPages para renderizar vistas parciales. Desde el punto de vista de MVC es un método que sobra totalmente y que rompe la encapsulación del framework.



Espero que os sea útil!



Un saludo!

jueves, 17 de febrero de 2011

De los requerimientos…

Mi hermano y yo ambos somos arquitectos. Aunque en su caso él tiene un título universitario que lo acredita como tal y yo sólo una tarjeta de trabajo donde mi empresa ha decidido poner eso… y en inglés que se supone que queda mejor. Los dos nos dedicamos a pensar y diseñar cosas: él piensa y diseña espacios habitables (o sea pisos y casas) y yo pienso y diseño soluciones informáticas (o sea programas).

Ayer mi hermano (que no sólo es arquitecto, sinó que regenta una empresa de obras) me vino riéndose por una petición de presupuesto en broma que había recibido. El correo electrónico decía, más o menos, así…

Apreciado hermano de eiximenis,

Nos dirigimos a Ud. para que nos haga un presupuesto para la construcción de una nueva mansión para un concejal cualquiera (a partir de ahora “el cliente”) que, con un golpe de fortuna, ha ganado unos dinerillos que gastar. A continuación le detallamos como debe la mansión:

  1. La mansión contará con el número de habitaciones que el cliente estime cómo necesarias para el desarrollo de sus funciones de descanso y/o copulación. Además el cliente debe poder realizar otras funciones (a especificar en algún momento del futuro) sin menoscabo de la comodidad mínima exigida.
  2. Los materiales de construcción deben tener el nivel de calidad requerido por el cliente, nivel que por supuesto no aparece en ningún estándard y que sólo el cliente conoce y que puede variar en función de su estado de ánimo.
  3. Deberán integrarse, adaptarse o construirse distintos módulos de diversión, para aquellas actividades de ocio que el cliente considere necesarias. No es necesario especificar que las necesidades de ocio del cliente pueden variar en cualquier momento durante el periodo de tres años que se exige como mantenimiento.
  4. El número de baños deberá ser el justo y necesario para que pueda usarlos el cliente, así como los invitados a sus fiestas. El número de invitados no ha sido proporcionado por el cliente.
  5. Se valorará positivamente que los garajes estén listos al cabo de dos semanas y más positivamente si lo están al cabo de una.
  6. El cliente ha comprado un módulo de domótica que deberá ser instalado y configurado. El modelo es desconocido al igual que el fabricante.
  7. Existe un periodo de tres años de garantía, donde el cliente puede solicitar cambios en los materiales, disposición de los módulos, módulos de ocio nuevos, etc, etc

Dado que el cliente es un concejal debe asignar la obra de forma totalmente legal y por ello se deberán entregar tres sobres:

  1. El primero con la propuesta económica final. Dicha propuesta contendrá el precio exacto de la obra, incluyendo materiales y mano de obra y mantenimiento durante los 3 años. El mantenimiento implica adaptar la mansión a las nuevas necesidades que le puedan surgir al cliente. Hay total libertad de precio, siempre que no pase de 500000€
  2. El segundo con el calendario exacto de las obras: cuando estará el garaje, las distintas habitaciones, la fachada principal, los módulos de ocio, etc. El inclumpimiento del calendario acarreará sanciones económicas a determinar.
  3. El tercero con los detalles técnicos, planos de obra, materiales usados, etc

La valoración por concurso será tal y como sigue:

  1. Valoración económica: Un 50% (De 0 a 5 puntos)
  2. Calendario: Un 40% (de 0 a 4 puntos)
  3. Detalles técnicos: Un 10% (de 0 a 1 punto)

Se pide envío de los tres sobres antes de 2 días. El cliente no está disponible para contactar ya que está de vacaciones.

Mi hermano entre carcajadas y carcajada, me insistía en que le viera la gracia a la broma, que si “fíjate que aquí no concretan nada, que si eso es buenísimo porque ni dicen el número de las habitaciones, en que si tal y cual”…

… yo, la verdad es que sonreí un poco por compromiso mientras por dentro pensaba en las muchas veces que mi trabajo parece una broma… y de mal gusto.

Saludos!