Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
miércoles, 23 de julio de 2008
IdentidadY es que va a ser verdad eso de que en internet hay de todo. No sé que estaba buscando cuando me he topado con FakeNameGenerator, una web en la que indicando el sexo, la nacionalidad y el idioma a utilizar nos genera una ficha personal completa, con datos aleatorios, como la siguiente:

Benicio Romero Santacruz
Padre Caro, 61
13592 Mestanza

Email Address: BenicioRomeroSantacruz@fontdrift.com
This is a real email address. Click here to use it!

Website: Demimba.com
It looks like Demimba.com is available! Click here to register it!

Birthday: June 17, 1980

Visa: 4929 7908 0245 3619
Expires: 6/2011

UPS Tracking Number: 1Z F00 647 14 8092 598 8

Como podéis ver, incluye un nombre, la dirección una cuenta de correo (tipo Mailinator) que podéis usar para recibir mensajes simplemente pulsando el enlace mostrado, fecha de nacimiento, datos de una tarjeta VISA (no válidos, claro), y un código de seguimiento de UPS.

Puedes generar tu identidad española siguiendo este enlace.

En fin, que no es algo especialmente útil, pero curioso lo es un rato...

Publicado en: http://www.variablenotfound.com/.
domingo, 20 de julio de 2008
ASP.NETEl hecho de que las acciones de los controladores de ASP.NET MVC retornen ActionResults abre un gran abanico de posibilidades para los desarrolladores de sistemas web.

Ya comenté en otra ocasión los distintos subtipos de ActionResult incluidos en el framework, y la posibilidad de crear nuevos descendientes. También tratamos en otro post el retorno de JSON desde los controladores como una potente vía de devolución de datos estructurados a la capa cliente, tras una llamada Ajax.

En esta ocasión vamos a profundizar un poco más, creando nuestro propia subclase de ActionResult para crear acciones con resultados personalizados utilizando la Preview 3 de esta tecnología. Concretamente, crearemos la clase ImageResult, que permitirá a las acciones retornar imágenes JPEG como la mostrada en la siguiente captura:

Demo de ImageResult

Como se puede observar, aparece una imagen con la hora actual sobre un fondo degradado, que es generada en tiempo real desde el servidor al producirse una petición desde la vista mediante un tag <img> estándar:

<h3>La hora es:</h3>
<img src='<%= Url.Action("GetImage") %>' />
 
La llamada al método Url.Action("GetImage") retorna una cadena con la dirección URL de la acción indicada en el parámetro, en este caso GetImage(). De esta forma, cuando el navegador necesite cargar la imagen para mostrarla, realizará una llamada a dicha acción, cuyo código aparece en el controlador de la siguiente forma:
  public ImageResult GetImage()
{
return new ImageResult
{
Width = 400,
Height = 100,
Text = DateTime.Now.ToLongTimeString()
};
}
 
Lo único que hace es retornar un objeto ImageResult inicializado con el ancho y alto deseado, y el texto a escribir. Fijaos en el uso del inicializador de objetos.

Veamos por último con la clase ImageResult:
  public class ImageResult: ViewResult
{
public int Width { get; set; }
public int Height { get; set; }
public string Text { get; set; }

public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "image/jpeg";

Rectangle rect = new Rectangle(0, 0, Width, Height);
Bitmap bmp = new Bitmap(rect.Width, rect.Height);
Graphics g = Graphics.FromImage(bmp);
Font font = new Font("Arial", Height / 1.6f);
LinearGradientBrush brush =
new LinearGradientBrush(rect, Color.Blue, Color.Navy, 90f);

g.FillRectangle(brush, rect); // Pinta el degradado...
g.DrawString(Text, font, Brushes.Black, 2, 2); // ... la sombra del texto...
g.DrawString(Text, font, Brushes.White, 0, 0); // ... y el texto.

g.Dispose();
font.Dispose();
brush.Dispose();

bmp.Save(context.HttpContext.Response.OutputStream, ImageFormat.Jpeg);
bmp.Dispose();
}
}
 
Aparte de la definición de las propiedades necesarias para su funcionamiento, el único truco a la hora de devolver contenidos personalizados es sobrescribir el método ExecuteResult de la clase base ViewResult. Ya en su implementación, a través del contexto que nos llega como parámetro podemos modificar el tipo de contenidos de la respuesta (Content-Type) y escribir a través del canal de salida lo que deseemos (en este caso utilizamos el método Save del bitmap para ello).

El resto del código no tiene demasiado interés, es la creación de la imagen dinámica utilizando GDI+. Por cierto, no he realizado validaciones de las propiedades, os los dejo de deberes ;-); Tampoco sé si tantos Dispose() explícitos serán necesarios, supongo que no, pero me da algo de repelús no ponerlos... debe ser algo así como un trauma infantil adquirido años atrás usando el API de Windows...

Proyecto para ASP.NET MVC CTP3 Descargar proyecto (Visual Web Developer 2008 + SP1 + ASP.NET MVC Preview 3).

Publicado en: www.variablenotfound.com.
domingo, 13 de julio de 2008
Cuestiones enviadas por lectoresHace unos días Pedro dejaba una consulta en los comentarios del post "Usando ASP.NET Ajax para el intercambio de entidades de datos" sobre un problema que le había surgido a la hora de referenciar desde script, en el lado cliente, una clase propia que utilizaba para intercambiar datos entre éste y el servidor.

En dicho post se mostraba la forma en que era posible intercambiar información estructurada con Ajax, definiendo en el servidor una clase propia y viendo cómo el ScriptManager, mágicamente, creaba un proxy (o espejo) en cliente que permitía su manipulación de forma muy cómoda y transparente al otro lado de la red.

De hecho, partíamos de una definición en el servidor así:
  public class Mensaje
{
public string Remitente;
public string Destinatario;
public DateTime Fecha;
public int Numero;
}
 
Y veíamos como desde cliente podíamos manipularla, en javascript, de la siguiente forma:
  msg = new Mensaje();
msg.Remitente = $get("nombre").value;
msg.Numero = 1;
msg.Destinatario = "servidor";
msg.Fecha = new Date();
 
El problema que comentaba este lector es que, a diferencia del ejemplo, su entidad de datos se encontraba definida en un ensamblado y espacio de nombres diferente al del WebMethod que lo utilizaba, lo que provocaba la aparición de un error indicando que su clase no estaba definida.

Aunque al principio sospeché en que podía existir alguna limitación en la seriación JSON de los datos, después de indagar un poco dí con la solución. La lección que he aprendido es:
para usar desde cliente una clase generada de forma automática por el ScriptManager, es necesario referenciarla precedida del namespace en el que se encuentra definida
O en otras palabras, si la clase Mensaje está definida dentro del espacio de nombres A.B, la referencia en cliente deberá ser:
  var x = new A.B.Mensaje();
 ¿Y por qué funciona bien el ejemplo AjaxPingPong, si la referencia a la clase Mensaje no incluía su espacio de nombres? Pues debido a que estaba definida en el namespace por defecto del proyecto...

¡Gracias, Pedro, por participar en Variable Not Found!

Publicado en: www.variablenotfound.com.
lunes, 7 de julio de 2008
Hace unos días descubrí un portal que considero muy interesante para todos los que estamos intentando seguir de cerca el nuevo framework ASP.NET MVC de Microsoft.

Se trata de un proyecto personal de Dan Hounshell que recopila de forma automática posts, noticias, rumores y vídeos de feeds RSS de diversas fuentes sobre ASP.NET MVC y tecnologías relacionadas. Ah, y todos estos contenidos los ofrece en RSS, por lo que es una fuente ideal para estar al día.

Visitar el portal ASP.NET MVC


Que aproveche.

Publicado en: www.variablenotfound.com.

miércoles, 2 de julio de 2008
LupaMucho se ha hablado (por ejemplo aquí, aquí, aquí, aquí...) acerca de lo terrible que puede resultar para el rendimiento de nuestro sitio web el dejar la directiva debug=true en el Web.config de servidores en producción debido a la sobrecarga que implica que la compilación de las páginas se realice con el modo de depuración activo.

El problema es, primero, que hay que acordarse de cambiarlo antes de subir el sitio al servidor de producción y, segundo, que a los despistados nos suele ocurrir que tras subir una nueva versión de la aplicación podemos volver a dejarlo como estaba de forma involuntaria.

Supongo que habrá otras formas para controlarlo, pero hoy me he encontrado con una muy sencilla que nos permite averiguar en tiempo de ejecución si el sitio web está funcionando con la depuración activa o no: IsDebuggingEnabled, una propiedad de la clase HttpContext.

Así, podemos introducir un código como el siguiente en el Global.asax, que provocará la parada de la aplicación cuando intentemos iniciarla, desde un equipo remoto, si la directiva debug está establecida a true en el archivo de configuración.
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
if (HttpContext.Current.IsDebuggingEnabled
&& !HttpContext.Current.Request.IsLocal)
{
throw new Exception("¡Depuración habilitada!");
}
}
}
 
Otro ejemplo menos violento podría ser la inclusión de un mensaje de alerta en el pie todas las páginas mostradas cuando se estén ejecutando en modo depuración, para lo que habría que escribir el siguiente método en la clase Global del Global.asax:
protected void Application_EndRequest(object sender, EventArgs e)
{
if (HttpContext.Current.IsDebuggingEnabled)
HttpContext.Current.Response.Write(
"<div style='background: red; color: white'>Sitio en depuración</div>"
);
}
 
Pero ojo, que esta propiedad sólo detecta el valor presente en el Web.config, no tiene en cuenta si a una página se le ha establecido Debug=true en sus directivas @Page, o si los ensamblados usados se compilaron en modo release o depuración.

Publicado en: www.variablenotfound.com.
lunes, 30 de junio de 2008
Una cascadaLos desplegables en cascada (también llamados cascading dropdowns o enlazados) son elementos muy frecuentes en todo tipo de aplicaciones, pues suponen una gran ayuda al usuario y dotan de mucho dinamismo al interfaz.

En pocas palabras, consiste en llenar una lista desplegable con elementos elegidos en función de una decisión previa, como la selección en otra lista. El ejemplo típico lo encontramos en aplicaciones donde debemos introducir una dirección y nos ponen por delante un desplegable con las provincias y otro con los municipios, y en éste último sólo aparecen aquellos que pertenecen a la provincia seleccionada.

Esto, que en aplicaciones de escritorio no tiene dificultad alguna, en el entorno Web se complica un poco debido a la falta de comunicación entre la capa cliente (HTML, javascript) y servidor (ASP, .NET, JSP, PHP...), propia de un protocolo desconectado como HTTP.

Años atrás (e incluso todavía hoy) una forma de solucionar el problema es forzando un postback, es decir, una recarga de la página completa, con objeto de llenar el desplegable dependiente en función del valor seleccionado en el principal. Supongo que no es necesario detallar los inconvenientes: tiempos de espera, asombro del usuario ante el pantallazo, problemas asociados al mantenimiento del estado...

La aparición de Ajax (¡en minúsculas!) supuso una revolución para las aplicaciones Web, añadiendo a las amplias capacidades de Javascript para la manipulación del interfaz e información en cliente mecanismos ágiles para conseguir comunicación en tiempo real con el lado del servidor.

En este post explicaré cómo conseguir desplegables en cascada utilizando la plataforma ASP.NET MVC (Preview 3) en el lado servidor y Javascript en el lado cliente, apoyándome en la librería jQuery, que tanto juego da. Aprovecharé de paso para comentar algunos aspectos interesantes de la preview 3 del framework MVC, de jQuery y de novedades de C# 3.0. Tocho de post, por tanto ;-)

El resultado será un formulario como el mostrado en la siguiente imagen:

El proyecto en ejecución

Antes de nada: estructurar la solución

Para lograr el objetivo pretendido respetando el patrón MVC, debemos contar con los siguientes elementos:
  • El modelo, que representará la información manejada por el sistema. Para no complicar el ejemplo (o complicarlo, según se mire ;-)), vamos a crear una colección en memoria sobre la que realizaremos las consultas.

  • La vista, que será un formulario en el que colocaremos los dos desplegables. El primero de ellos permitirá seleccionar una categoría tomada del Modelo, y el segundo mostrará los elementos de dicha categoría de forma dinámica.

  • El controlador, en el que incluiremos acciones, en primer lugar, para inicializar y mostrar el formulario y, en segundo lugar, para obtener la lista de elementos relacionados con la categoría seleccionada. Esta última acción será invocada por la vista cuando el primer desplegable cambie de valor.

Primero: el modelo

En escenarios reales será el conjunto de entidades que representen el modelo de datos del sistema. En este caso montaremos un ejemplo muy simple, basado en un diccionario en memoria, y un par de métodos de consulta de la información en él mantenida.
public class Datos
{
private static Dictionary<string, string[]> datos =
new Dictionary<string, string[]>
{
{ "Animal", new[] { "Perro", "Gato", "Pez"} },
{ "Vegetal", new[] { "Flor", "Árbol", "Espárrago"} },
{ "Mineral", new[] { "Cuarzo", "Pirita", "Feldespato"} }
};

public static IEnumerable<string> GetCategories()
{
return datos.Keys;
}

public static IEnumerable<string> GetElementsForCategory(string category)
{
string[] els = null;
datos.TryGetValue(category, out els);
return els;
}
}

Destacar del código anterior los siguientes aspectos:
  • La información se almacenará en un Dictionary<string,string[]>. Esto, por si no estás muy familiarizado con los generics o tipos genéricos, sólo quiere decir que se trata de un diccionario cuya clave será de tipo string (las categorías) y el valor será un array de strings (los elementos asociados a cada categoría).

    De esta forma, podremos obtener fácilmente la lista de categorías simplemente enumerando las claves (Keys) del diccionario, y los elementos contenidos en una categoría sólo con devolver el valor asociado a la misma, como vemos en los métodos GetCategories() y GetElementsForCategories().

  • Para inicializar el diccionario he utilizado un inicializador de colecciones, una característica de C# 3.0 ya comentada en un post anterior.

  • Los dos métodos de consulta de datos retornan un IEnumerable<string> (¡me encantan los generics!), flexibilizando el tipo devuelto. Así, podríamos devolver cualquier tipo de objeto que implemente este interfaz, prácticamente todas las listas, colecciones y arrays, siempre que contengan en su interior un string.


Segundo: la vista

Para nuestro ejemplo sólo necesitamos una vista, el formulario con los desplegables, que crearemos en el archivo Index.aspx. Además de componer el interfaz, la vista gestionará la interacción con el usuario haciendo que ante la selección de una opción del desplegable de categorías se invoque al controlador para solicitarle la lista de elementos de la categoría seleccionada, y cargar con ella el segundo desplegable.

El código fuente del proyecto completo lo podéis encontrar en un enlace al pie del post, pero incluiré aquí las porciones más importantes. En primer lugar, el formulario HTML se compone así:
<form method="post" action="#">
<fieldset><legend>Formulario</legend>
<label for="ddCategory">Categoría:</label>
<%= Html.DropDownList("ddCategory") %>
<label for="ddElement">Elemento:</label>
<%= Html.DropDownList("ddElement") %>
</fieldset>
</form>

Fijaos un momento en el código. Los desplegables, en lugar de utilizar el tag HTML <select> se han definido utilizando los métodos de ayuda (Html helpers) incluidos en el framework MVC. Estos, además de generar las etiquetas apropiadas, realizan otra serie de tareas como establecer valores de la lista, que nos ahorrarán mucho tiempo. En el controlador veremos cómo se le inyectan los elementos (los distintos <option>) desde código de forma muy sencilla.

jQuery logoDespués del formulario, llegamos a la parte de scripting de la página, la implementación de la obtención y llenado del desplegable de elementos en función de la opción seleccionada, al más puro estilo Ajax. Utilizaremos jQuery, pero los conceptos son idénticos para otros sistemas; de hecho, esto mismo podríamos realizarlo con cualquier otra librería Javascript que permita enviar peticiones JSON, e incluso, aunque es bastante más trabajoso, sería posible hacerlo "a pelo" utilizando las facilidades nativas (HttpRequest).
<script type="text/javascript">

// Inicialización

$(document).ready(function() {
$("#ddCategory").change(function() {
cambiaElementos($("#ddCategory").val());
});
cambiaElementos($("#ddCategory").val());
});

// Carga el desplegable de elementos en función
// de la categoría que le llega como parámetro.


function cambiaElementos(cat) {

var dd = document.getElementById("ddElement");
dd.options.length = 0;
dd.options[0] = new Option("Espere...");
dd.selectedIndex = 0;
dd.disabled = true;

// Control de errores

$("#ddElement").ajaxError(function(event, request, settings) {
dd.options[0] = new Option("Categoría incorrecta");
});

// Obtenemos los datos...

$.getJSON(
'<%= Url.Action("GetElements") %>', // URL a la acción
{ category: cat }, // Objeto JSON con parámetros
function(data) { // Función de retorno exitoso
$.each(data, function(i, item) {
dd.options[i] = new Option(item, item);
});
dd.disabled = false;
});
}
</script>
 
En la primera parte se utiliza el evento jQuery ready, ejecutada cuando la página está lista para ser manipulada, para introducir código de inicialización. En este momento se asocia al evento change del desplegable de categorías la llamada apropiada a la función cambiaElementos(), pasándole la categoría seleccionada. También se aprovecha para realizar la primera carga de elementos.

Carga del desplegable en procesoSeguidamente está la función cambiaElementos, que se encargará de actualizar el desplegable con los elementos asociados a la categoría seleccionada. Para ello, en primer lugar, se introduce en el dropdown un elemento con el texto "Espere...", que aparecerá mientras el sistema obtiene los datos desde el servidor, a la vez que se deshabilita el control para evitar manipulación del usuario.

Después se define la función que se ejecutará si durante la llamada Ajax se produce un error. Para ello utilizamos el evento ajaxError, al que asociamos una función anónima que, simplemente, cargará en el desplegable secundario el texto "Categoría incorrecta" y lo dejará deshabilitado.

Por último, utilizamos el método getJSON para efectuar la llamada al servidor y realizar la carga del desplegable con la información devuelta. Son tres los parámetros que se le envían al método:
  • La URL de la acción, obtenida utilizando el helper Url.Action que devuelve la dirección de la acción cuyo nombre le pasamos como parámetro.

  • El objeto, en formato JSON, uyas propiedades representan los parámetros a enviar al controlador.

  • La función a ejecutar cuando se reciban datos del servidor. El parámetro de entrada data contendrá el array de string con el que tenemos que llenar el desplegable, enviado desde el servidor según la notación JSON.

    Aunque el recorrido de este vector y la carga de opciones en el control podría haberse realizado de forma sencilla mediante un bucle, he utilizado el método each para ilustrar el uso de este tipo de iteradores, tan de moda y muy al estilo funcional empleado, por ejemplo, en las expresiones lambda. Este método recibe dos parámetros; el primero es la colección sobre la cual se va a iterar, y el segundo es una función que se ejecutará para cada uno de los elementos recorridos.


Tercero: el controlador

Es el intermediario en toda esta historia: recibe las peticiones del cliente a través la invocación de acciones, ejecuta las tareas apropiadas y provoca la aparición o actualización de las vistas, utilizando para ello las clases ofrecidas por el Modelo.

El controlador define dos acciones: la primera de ellas (Index) es la encargada de cargar los valores iniciales de los desplegables y enviar al usuario la vista correspondiente (Index.aspx). La segunda es la responsable de devolver un objeto en notación JSON con los elementos de la categoría enviada como parámetro.

El código es el siguiente:

public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["ddCategory"] = new SelectList( Datos.GetCategories() );
ViewData["ddElement"] = new SelectList( new [] {"(Selecciona)"} );
return View();
}

public ActionResult GetElements(string category)
{
IEnumerable<string> elements = Datos.GetElementsForCategory(category);
if (elements == null)
throw new ArgumentException("Categoría " + category +" no es correcta");

Thread.Sleep(1000); // Simulamos tiempo de proceso...

return Json(elements);
}
}
 
Hay varios puntos que me gustaría destacar en este código. En primer lugar, observad que ya las acciones siguen la nueva convención introducida en la preview 3, retornando objetos de tipo ActionResult. En el primer método, el return View() (sin parámetros) hace que el sistema envíe al cliente la vista cuyo nombre coincide con la acción actual (es decir, Index.aspx); en el segundo método se retorna un objeto de tipo JSonResult que serializará la información suministrada en formato JSON.

También es destacable la ayuda introducida en la tercera Preview para llenar listas de formularios utilizando la clase SelectList. Como podéis ver, basta con crear en el diccionario ViewData un elemento con el mismo nombre que el del desplegable y asignarle una instancia del SelectList inicializada con una colección de elementos (cualquier tipo que implemente IEnumerable). Él se encargará, al renderizar la página, de iterar sobre la colección y crear las etiquetas <option> oportunas.

Asimismo, se puede observar el uso del Modelo en ambos métodos para leer los datos. En este caso se utiliza una clase que encapsularía la lógica de obtención de información, pero nada impediría, por ejemplo, asumir que las clases del Modelo son las generadas desde el diseñador de Linq2Sql y utilizar consultas Linq desde el Controlador.

Observad también el lanzamiento de la excepción cuando la categoría especificada no existe. Lo que llega al navegador en este caso es un error HTTP que es capturado por la función que especificamos para el evento ajaxError, haciendo que en el desplegable enlazado aparezca un mensaje de error.

Por último, la inclusión de la orden Thread.Sleep() es por pura estética, para que se vea lo bien que queda el mensaje "Espere..." en el desplegable mientras carga los datos. En un escenario real este tiempo sería consumido por las comunicaciones cliente-servidor y la obtención de datos desde el modelo, que habitualmente utilizará una base de datos u otro mecanismo de persistencia.

Descargar proyecto Descargar proyecto (Visual Web Developer Express 2008 + SP1 + ASP.NET MVC Preview 3).

Publicado en: http://www.variablenotfound.com/.