
Hace ya más de un año apareció una nueva librería para la creación de gráficos, tanto para Web Forms como Win Forms, con una enorme cantidad de posibilidades. Hoy en día se nos puede plantear la posibilidad de utilizar estos mismos controles para una aplicación ASP.NET MVC y cómo sería la forma más sencilla de adaptarlo.
Si bien he leído y probado varias opciones, voy a escribir sobre la que me ha parecido más sencilla y más práctica.
INSTALACIÓN DE LIBRERÍAS Y COMPONENTES
Para poder trabajar con estos controles, es necesario descargar dos ejecutables:
La librería que nos permite la creación de un gráfico se llama System.Web.DataVisualization, la cual está disponible instalando el primero de los paquetes. El segundo de ellos es interesante instalarlo para poder visualizar en el cuadro de herramientas el control de tipo Chart. Existen numerosas combinaciones y propiedades configurables para este tipo de controles, por lo que hay veces que es más práctico crear una aplicación Web Forms de prueba, en modo diseño arrastrar el control y personalizarlo. De esta forma podemos ver el código generado en el aspx y las propiedades utilizadas para ese chart en concreto. Otra de las posibilidades es descargar el proyecto de ejemplos y localizar alguno que ya esté definido y adaptarlo.
CREACIÓN DEL GRÁFICO
Para entender mejor el código, podríamos decir que un chart se compone de 4 partes principalmente: Chart, que es el control principal, Legend, encargado de la leyenda del gráfico, ChartArea, el cual representa el área donde se mostrarán los valores y las Series que son los valores en sí.
El primer paso es agregar una referencia a la librería System.Web.DataVisualization. Una vez añadida, creamos una nueva acción donde se instanciarán los controles necesarios y, finalmente, nos devolverá la imagen generada según las propiedades establecidas.
public ActionResult GetChart()
{
const string legend = "Legend";
const string chartArea = "chartArea";
var chart = ChartFactory.CreateChart("ASP.NET MVC and MSChart by GiS!");
ChartFactory.CreateSerie(chart, "januarySeries", chartArea,
legend, "Emboss", Color.ForestGreen,
ChartValueType.DateTime, ChartValueType.Double,
_repository.GetMonthData(1));
ChartFactory.CreateSerie(chart, "februarySeries", chartArea,
legend, "Cylinder", Color.DodgerBlue,
ChartValueType.DateTime, ChartValueType.Double,
_repository.GetMonthData(1));
ChartFactory.CreateSerie(chart, "marchSeries", chartArea,
legend, "Wedge", Color.DarkViolet,
ChartValueType.DateTime, ChartValueType.Double,
_repository.GetMonthData(3));
var memoryStream = new MemoryStream();
chart.SaveImage(memoryStream);
return File(memoryStream.GetBuffer(), @"image/png");
}
Para intentar refactorizar parte del código necesario para generar el gráfico, he creado una clase llamada ChartFactory en la cual podemos formar tanto el chart, la leyenda, el chartArea y las series que se mostrarán.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Web.UI.DataVisualization.Charting;
using ChartControlASPNETMVC.Models;
namespace ChartControlASPNETMVC
{
public static class ChartFactory
{
public static Chart CreateChart(string chartTitle)
{
//Chart
var chart = new Chart
{
Height = 296,
Width = 412,
BorderWidth = 2,
ImageType = ChartImageType.Png,
BackColor = ColorTranslator.FromHtml("#F3DFC1"),
BorderlineDashStyle = ChartDashStyle.Solid,
BackGradientStyle = GradientStyle.LeftRight,
BorderColor = Color.FromArgb(181, 64, 1),
BorderSkin = { SkinStyle = BorderSkinStyle.Sunken },
};
//Title
var title = chart.Titles.Add("chartTitle");
title.ShadowColor = Color.FromArgb(32, 0, 0, 0);
title.ShadowOffset = 3;
title.ForeColor = Color.FromArgb(26, 59, 105);
title.Font = new Font("Trebuchet MS", 14, FontStyle.Bold);
title.Text = chartTitle;
title.Alignment = ContentAlignment.TopLeft;
//Legend
var legend = chart.Legends.Add("chartLegend");
legend.LegendStyle = LegendStyle.Row;
legend.Font = new Font("Trebuchet MS", 10, FontStyle.Bold);
legend.Enabled = true;
legend.Name = "Legend";
legend.BackColor = Color.Transparent;
legend.Position.Y = 95;
legend.Position.X = 5;
legend.Position.Height = 20;
legend.Position.Width = 100;
//Chart Area
var chartArea = chart.ChartAreas.Add("chartArea");
chartArea.BorderColor = Color.FromArgb(64, 64, 64, 64);
chartArea.BackSecondaryColor = Color.White;
chartArea.BackColor = Color.OldLace;
chartArea.ShadowColor = Color.Transparent;
//Chart Area 3D Style
chartArea.Area3DStyle.Enable3D = true;
chartArea.Area3DStyle.Rotation = -20;
chartArea.Area3DStyle.Perspective = 8;
chartArea.Area3DStyle.Inclination = 18;
chartArea.Area3DStyle.IsRightAngleAxes = false;
chartArea.Area3DStyle.WallWidth = 0;
chartArea.Area3DStyle.IsClustered = false;
chartArea.Area3DStyle.PointDepth = 50;
chartArea.Area3DStyle.PointGapDepth = 200;
chartArea.AxisY.LineColor = Color.FromArgb(64, 64, 64, 64);
chartArea.AxisY.IsLabelAutoFit = false;
chartArea.AxisY.LabelStyle.Font = new Font("Trebuchet MS", 8, FontStyle.Bold);
chartArea.AxisY.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64);
chartArea.AxisX.LineColor = Color.FromArgb(64, 64, 64, 64);
chartArea.AxisX.IsLabelAutoFit = false;
chartArea.AxisX.LabelStyle.Font = new Font("Trebuchet MS", 8, FontStyle.Bold);
chartArea.AxisX.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64);
return chart;
}
public static void CreateSerie(Chart chart, string serieName, string chartAreaName, string legendName, string drawingStyle, Color color, ChartValueType xValue, ChartValueType yValue, List<TableSample> monthData)
{
var newSerie = chart.Series.Add(serieName);
newSerie.ChartArea = chartAreaName;
newSerie.XValueType = xValue;
newSerie.YValueType = yValue;
newSerie.Name = serieName;
newSerie.ShadowColor = Color.Transparent;
newSerie.BorderColor = color;
newSerie.Color = color;
newSerie.Legend = legendName;
/**** Drawing Style ****/
/*Cylinder - Data points are drawn as cylinders.
Emboss - Data points are drawn with an embossed effect.
LightToDark - Data points are drawn with a light-to-dark effect.
Wedge - Data points are drawn with a wedge effect.
Default - Data points are drawn as cubes.*/
newSerie["DrawingStyle"] = drawingStyle;
newSerie["PointWidth"] = "0.8";
WriteSeries(newSerie, monthData);
}
private static void WriteSeries(Series serie, IEnumerable<TableSample> list)
{
foreach (TableSample t in list)
{
serie.Points.Add(new DataPoint
{
YValues = new Double[] { t.Amount },
LegendText = t.Date.Day.ToString(),
AxisLabel = t.Date.Day.ToString()
});
}
}
}
}
Seguramente podremos refactorizar aún más y hacer una librería que nos permita mayor flexibilidad, pero el objetivo de este post es mostrar que existe la posibilidad de utilizar este tipo de controles.
La función de este control, a fin de cuentas, es generar una imagen con las propiedades y los datos obtenidos. Para llamar a la acción GetChart desde nuestra vista únicamente debemos asignar la misma al source de una imagen.
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Home Page
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>
<%= Html.Encode(ViewData["Message"]) %></h2>
<div id="container">
<img src="<%=Url.Action("GetChart") %>" alt="Chart Sample" />
</div>
</asp:Content>
Si arrancamos la aplicación, el resultado sería el siguiente:

Y, lo mejor de todo, el renderizado de la página:
!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
html xmlns="http://www.w3.org/1999/xhtml">
head><title>
Home Page
/title><link href="Content/Site.css" rel="stylesheet" type="text/css" />
/head>
body>
<div class="page">
<div id="header">
<div id="title">
<h1>
My MVC Application</h1>
</div>
<div id="logindisplay">
[ <a href="/Account/LogOn">Log On</a> ]
</div>
<div id="menucontainer">
<ul id="menu">
<li>
<a href="/">Home</a></li>
<li>
<a href="/Home/About">About</a></li>
</ul>
</div>
</div>
<div id="main">
<h2>
Chart Control Sample!</h2>
<div id="container">
<img src="/Home/GetChart" alt="Chart Sample" />
</div>
<div id="footer">
</div>
</div>
</div>
/body>
/html>
Por último, debido a que esta librería es adicional al framework, sería recomendable modificar las propiedades de la misma para tener una copia en el paquete final.

Adjunto el proyecto por si fuera de utilidad.
¡Saludos!
Si todavía tenemos que trabajar con Windows XP y estamos utilizando proyectos en ASP.NET MVC es posible que queramos publicar los mismos en el IIS local para realizar las pruebas necesarias. En este caso, vamos a aprovecharnos de un bug de la versión 5.1 de Internet Information Server para hacer funcionar nuestro proyecto:
- Accedemos a las propiedades del directorio virtual donde tengamos alojada la aplicación.
- Nos aseguramos de que tenemos permisos para ejecutar sólo secuencias de comandos.

- Pulsamos sobre el botón Configuración... y, en la pestaña Asignaciones, pulsamos en Agregar.

- Rellenamos la ventana con los siguientes datos:
- En el campo Ejecutable debemos seleccionar la DLL aspnet_isapi.dll, ubicada en la siguiente ruta: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll.
- En el campo Extensión escribimos .*
- Por último, eliminamos el check en Comprobar si el archivo existe.
- Al hacer estos pasos nos daremos cuenta de que el botón de Aceptar aparece deshabilitado debido a que la extensión proporcionada no es válida.
- Para poder aceptar los cambios, hacemos click sobre el campo Ejecutable y ... ¡magia! :P

Espero que sea de utilidad.
Fuente
¡Saludos!
Si bien es cierto que ASP.NET MVC te da mayor control sobre la vista que genera, puede resultar un retraso en nuestro proyecto el intentar generar controles algo más elaborados si no disponemos de unos conocimientos previos como diseñadores.
Para evitarnos esta búsqueda y aprendizaje, os propongo el uso de los controles de Telerik para ASP.NET MVC. Existen dos tipos de licencia: Una Open Source que está diseñada para el público en general, en la que podemos visualizar y modificar el código de los controles y, por otro lado, existe una licencia comercial para recibir más beneficios y soporte. En este post me centro en la versión Open Source.
Para crear estos controles, disponemos de una serie de Helpers:
- Grid

<%= Html.Telerik().Grid(Model)
.Name("Grid")
.Pageable()
%>
- PanelBar

<%= Html.Telerik().PanelBar()
.Name("PanelBar")
.Items(items =>
{
items.Add().Text("Item 1");
items.Add().Text("Item 2");
})
%>
- Calendar

<%= Html.Telerik().Calendar()
.Name("Calendar")
%>
- DatePicker

<%= Html.Telerik().DatePicker()
.Name("DatePicker")
%>
- Menu

<%= Html.Telerik().Menu()
.Name("Menu")
.Items(items =>
{
items.Add().Text("Item 1");
items.Add().Text("Item 2");
})
%>
- Numeric Textbox

<%= Html.Telerik().NumberTextBox()
.Name("NumberTextBox")
.Value(12.10)
%>
- TabStrip

<%
Html.Telerik().TabStrip()
.Name("TabStrip")
.Items(items =>
{
items.Add().Text("Item 1")
.Content(()=>
{%>
<p>Content</p>
<%});
items.Add().Text("Item 2");
})
.Render();
%>
- TreeView

<%= Html.Telerik().TreeView()
.Name("TreeView")
.Items(items =>
{
items.Add().Text("Item 1");
items.Add().Text("Item 2");
})
%>
Todos los controles que requieran una carga de datos, tales como menús o el grid, podemos realizar el bindeo a través de Ajax, del objeto Model del cual hereda nuestra vista, a través de un web service, utilizando el ViewData, utilizando SiteMap o bien personalizar la información de una forma más manual.
Cada uno de ellos dispone de una serie de funcionalidades diferentes. En el caso del Grid podemos paginar, filtrar, agrupar, etcétera de una forma bastante sencilla y clara.
Para utilizarlos, solamente debemos registrarnos en la página y posteriormente bajar el ejecutable. Tiene una documentación lo bastante clara y organizada para no tener problemas a la hora de querer usar cualquiera de los controles. Por otro lado comentar que disponen de una comunidad muy activa que conviene visitar.
Espero que sirvan de utilidad y ayuden a ganar tiempo ;)
¡Saludos!

El próximo 23 de Marzo tendremos la suerte de poder contar con Hadi Hariri, evangelista tecnológico en JetBrains, que nos hablará de BDD (Behaviour Driven Development) como metodología ágil de desarrollo.
En esta charla tendremos la oportunidad de conocer las ideas detrás de BDD y ver de qué forma nos ayuda a lidiar a los desarrolladores con las especificaciones del cliente.
Como siempre, el evento tendrá lugar en el edificio de Microsoft Ibérica en Madrid. Para más información os facilito el enlace.
¡Saludos!

Muchas de las personas que no han tenido aún la oportunidad de pelearse con este producto desconocen qué es Sharepoint o cuál es su finalidad. Sé que en Geeks.ms muchas personas trabajan con ello y han tenido que lidiar con algunas de sus "peculiaridades", pero voy a intentar simplificarlo a mi manera para aquellos que justo ahora comienzan con este mundo.
Antiguamente, cuando las personas de una organización necesitaban compartir ficheros entre ellos, existían varias posibilidades:
- Se mandaba un correo con el archivo adjunto y una vez enviado no se sabía nada más o bien podían existir unas idas y venidas de correos con distintas versiones del mismo archivo con el riesgo que aquello suponía.
- Se compartía una carpeta en el pc de turno y la gente depositaba los documentos que creían convenientes para el resto de la empresa. Si al menos se tenía en cuenta una serie de permisos sobre esa carpeta, la cosa no terminaba en catástrofe... Si por el contrario existía el libre albedrío esperemos que, al menos, existiera un backup ;)
- [...]
Obviamente estas alternativas y otras que podemos suponer nos "ayudan" a descentralizar la información, no tener un control real de las versiones de esos documentos, no saber siquiera si tenemos todos los documentos que realmente deberíamos, etcétera.
¿Qué es Sharepoint?
Sharepoint se encarga de aquellos puntos que no conseguíamos de una forma eficaz con otros sistemas:
- Centralizar la información de la organización.
- Control de versiones de la documentación.
- Compartir los recursos con otros miembros.
- Control de acceso a la información.
- Buscar personas dentro de la empresa.
- Etcétera. (Más información aquí)
¿Cómo se consigue?
Gracias a los conceptos Portal y sitios podemos disponer de la información de la empresa a través de la intranet y la extranet utilizando nuestras credenciales.
Este producto consta actualmente de dos partes para sacar el mayor provecho posible:
- Windows Sharepoint Services (WSS) es la base para la creación de portales y los sitios web dentro de los mismos. Gracias a él podemos almacenar la información, controlar el acceso a la misma, administrar los usuarios, hacer uso del control de versiones, flujo de trabajo y el motor de búsqueda.
- Microsoft Office Sharepoint Services 2007 (MOSS) se trata de una extensión de WSS, la cual nos aporta mayor funcionalidad en nuestros portales. Gracias a MOSS podemos crear formularios de InfoPath dinámicamente, integración con Reporting Services, BI, podemos hacer uso de bases de datos externas al sistema, utilizar Excel como un servidor cálculo, permitir a los usuarios generar sitios individuales, etcétera.
A día de hoy ya se está trabajando en Sharepoint 2010 y está prevista su fecha de lanzamiento para el día 12 de mayo de este año.
A medida que vaya conociendo cada una de las funcionalidades intentaré dedicarle el tiempo que se merezcan :)
Como desarrolladora, mi intención es dedicar la mayor parte de mi tiempo a descubrir las posibilidades que ofrece la plataforma para que los desarrolladores podamos extender aún más su funcionalidad.
Herramientas para el desarrollo
El entorno de trabajo es Visual Studio para el cual disponemos un SDK con las plantillas de Sharepoint. Están disponibles desde Visual Studio 2005 en adelante. Para poder descargar la versión oportuna podemos hacerlo a través de este link. En Visual Studio 2010 RC dichas plantillas ya están incluidas.
¿Os acordáis de FrontPage? Para aquellos que pensabais como yo que se había extinguido os comunico que para nada, su nombre actual es Sharepoint Designer y será nuestro aliado a la hora de diseñar ;)
Requisitos mínimos para una instalación stand-alone
- Procesador: 2.5 GHz
- RAM: 1 GB
- Disco duro: 3 GB
- Software: Windows Server 2003/2003 R2/2008
- Visual Studio 2005/2008/2010
- SQL Server 2005 (En la instalación de Sharepoint nos proporciona un SQL Server)
Más información.
Existe una máquina virtual ofrecida por Microsoft en el siguiente enlace. La misma está compuesta de veinte partes y se hace bastante pesada su descarga. Comentaros que yo intenté hacer uso de ella pero, desconozco si fue por un problema puntual, al intentar descomprimir la máquina me avisó de que una de las partes estaba corrupta y no pude continuar :(
La instalación es bien sencilla pero debemos tener en cuenta el siguiente orden para no volvernos loc@s. En este caso estamos suponiendo que la instalación es para un entorno de desarrollo:
- Una vez que tengamos preparado un Windows Server, debemos añadir el rol de Servidor Web para dejar preparado el IIS.
- Iniciamos la instalación de Sharepoint (Dentro de las opciones avanzadas, mantuve la instalación Independiente). Cuando la misma finaliza, podremos configurar Sharepoint en ese momento o ejecutar el asistente más adelante a través del acceso directo Sharepoint Products and Technologies. Por otro lado, para crear y configurar nuevos portales existe otro acceso directo llamado Sharepoint 3.0 Central Administration. Por el momento, para la serie de post que pretendo realizar, he creado un nuevo portal con un sitio de tipo Team Site.
- Instalamos Visual Studio y el SDK indicado para su versión.
A partir de este momento, estamos listos para comenzar a trastear con Sharepoint :)
¡Comencemos!
¿Qué es XRM? ¿Cómo puedo sacarle partido como herramienta de negocio? Estas preguntas y muchas más podremos discutir en el evento que se llevará a cabo el día 11 de Marzo en el edificio de Microsoft Ibérica en Madrid.
Gracias a la colaboración de Marco Amoedo, MVP de CRM, en Mad.Nug tendremos la posibilidad de conocer los conceptos básicos de XRM, técnicas de desarrollo, etcétera.
Para más información y acceder al registro tenemos el siguiente enlace.
¡Os esperamos!
Hace ya algunos días estuve definiendo el primer layout con Silverlight, donde teníamos una primera toma de contacto con XAML. En esta ocasión pretendo dar una visión un poco más amplia acerca de algunos de los elementos que podemos usar en el diseño de la UI y cómo es posible enlazar los eventos a los mismos.
Tenemos tres entornos a nuestra disposición a la hora de diseñar la interfaz de usuario con XAML:
- Visual Studio 2008: Podemos utilizar la ventana de Herramientas (Toolbox) para arrastrar los elementos pero directamente en el código XAML. En esta versión de Visual Studio no tenemos vista de diseño para XAML.

Por otro lado, existe la ventana Document Outline para poder visualizar el esquema de la página que estamos creando.

- Visual Studio 2010: En la nueva versión de VS tenemos una vista previa del código XAML implementado, lo cual facilita bastante su codificación. Actualmente podemos hacer uso de la versión Release Candidate.

- Expression Blend: Es la herramienta por excelencia para trabajar con las UI de Silverlight y WPF. Si bien para la creación de elementos nos podemos apañar con Visual Studio, en el caso de las animaciones la cosa resulta algo más compleja de realizar codificando.

Desde Visual Studio podemos seleccionar cualquier archivo XAML y, con el botón derecho sobre él, podemos dar la orden de abrirlo en Expression Blend y poder trabajar de forma paralela con uno y otro (Expression Blend para el diseño y Visual Studio para el desarrollo).
ALGUNAS DIFERENCIAS RESPECTO A ASP.NET
- Para asignar un ID a un elemento utilizamos x:Name
- Si queremos posicionar un elemento dentro de un contenedor debemos tener en cuenta lo siguiente:
- Si estamos utilizando un contenedor de tipo Grid necesitamos establecer en primer lugar la estructura del mismo para poder posicionarlos dentro de él. De lo contrario no visualizaremos los elementos.
- Para los contenedores Canvas, podemos ubicarlos a través de Canvas.Top, Canvas.Left y Canvas.ZIndex.
- En el caso de los StackPanels, podemos apilar varios y darle una orientación horizontal o vertical al contenedor a través de la propiedad Orientation.
- En lugar de utilizar la propiedad Text para algunos controles como en Web Forms, para definir el texto que aparece en los elementos que lo requiera, utilizaremos Content.
- En ASP.NET la mayoría de los controles tienen un alto y ancho predeterminado que, en ocasiones, no es necesario alterar. En Silverlight es necesario especificarlo para obtener un tamaño considerable. Si no se especifica, se adaptará al tamaño del texto del Content si lo tiene.
ALGUNOS ELEMENTOS BÁSICOS
Al igual que en ASP.NET disponemos de elementos tales como :
Button
<Button x:Name="myButton" Content="Click me!"
Canvas.Left="100" Canvas.Top="120" Width="100" Height="25"></Button>
Textbox
<TextBox x:Name="myTextbox" Text="Write your name here"
Canvas.Left="100" Canvas.Top="150" Height="25"/>
Calendar
<controls:Calendar x:Name="myCalendar" Canvas.Left="280" Canvas.Top="120"
ToolTipService.ToolTip="Select your birthday" Cursor="Arrow"/>
Combobox
<ComboBox x:Name="myCombobox" Canvas.Left="100" Canvas.Top="195" Width="130" >
<ComboBoxItem Background="#FF0200FF" Foreground="White" Content="Blue"/>
<ComboBoxItem Background="#FFFF0500" Foreground="White" Content="Red"/>
<ComboBoxItem Background="#FFFF00CA" Foreground="White" Content="Pink" IsSelected="True"/>
</ComboBox>
Listbox
<ListBox Canvas.Left="100" Canvas.Top="250" Width="130" >
<ListBoxItem Background="#FF0200FF" Foreground="White" Content="Blue"/>
<ListBoxItem Background="#FFFF0500" Foreground="White" Content="Red" IsSelected="True"/>
<ListBoxItem Background="#FFFF00CA" Foreground="White" Content="Pink" />
</ListBox>
Label
<dataInput:Label x:Name="myLabel" Content="Common Label"
Canvas.Left="100" Canvas.Top="80" Width="130" Height="24"/>
Radiobutton
<StackPanel Orientation="Vertical">
<RadioButton Margin="10,10,0,0" GroupName="Languages" Foreground="Blue" Content="C#"/>
<RadioButton Margin="10,10,0,0" GroupName="Languages" Foreground="Orange" Content="VB.NET"/>
<RadioButton Margin="10,10,0,0" GroupName="Languages" Foreground="Green" Content="Java"/>
<RadioButton Margin="10,10,0,0" GroupName="Languages" Foreground="Purple" Content="PHP"/>
</StackPanel>
Checkbox
<CheckBox x:Name="myCheckBox" Content="I want a million euros"
Canvas.Left="100" Canvas.Top="450"></CheckBox>
Datagrid
<data:DataGrid x:Name="myDataGrid" Height="105"
Canvas.Left="100" Canvas.Top="345" ></data:DataGrid>
Para realizar la carga, en vez de utilizar DataSource sería ItemsSource.
public MainPage()
{
InitializeComponent();
var person = new Person { Name = "Gisela", LastName = "Torres", Location = "Madrid", Web = "http://geeks.ms/blogs/gtorres/" };
var persons = new List<Person> {person};
myDataGrid.ItemsSource = persons;
}
[...]
Existen más controles de los que aquí se indican y, además, podemos hacer uso de Silverlight Toolkit, de forma gratuita, descargándolo desde su sitio en Codeplex para ampliar el repertorio.
LOS EVENTOS EN SILVERLIGHT
Por último, para asociar un evento a un elemento de nuestra interfaz existen distintos procedimientos según el entorno:
Visual Studio 2008
Deberemos seleccionar el evento que necesitemos a través del Intellisense y, una vez añadido al elemento, nos dará la posibilidad de asociar uno existente o crear uno nuevo.

Visual Studio 2010
Si estamos trabajando con la vista previa, podemos hacer doble click sobre el elemento para autogenerar el evento click (De la misma forma que en Web Forms) o bien haciendo doble click sobre el evento que queramos en la ventana propiedades.

Expression Blend
En este último entorno, al igual que en Visual Studio 2010, disponemos de la pestaña Eventos dentro de propiedades del elemento seleccionando el icono que señalo en la imagen.

Hasta aquí la primera toma de contacto con los elementos XAML para Silverlight :)
¡Saludos!
Dado que hoy en día lo que se estila es el uso de las expresiones lambda, voy a intentar resumir en un solo post qué son y cómo se utilizan. Sobre todo voy a procurar simplificarlo lo más posible :)
Las expresiones lambda se utilizan para crear funciones anónimas las cuales pueden ser asignadas a delegados o a árboles de expresión.
¿Por qué anónimas?
Porque no necesitamos asignarles un nombre para referirnos a ellas.
¿Qué es un delegado?
Es un objeto que hace referencia a uno o más métodos. Más información aquí.
¿Qué es un árbol de expresión?
Un árbol de expresión representa una estructura en forma de árbol de las expresiones lambda en memoria. Generalmente se potencia su uso en LINQ.
¿Cómo se utilizan?
Para saber cómo utilizar y/o leer una expresión lambda debemos tener en cuenta los siguientes puntos:
- Parte izquierda: Representa los parámetros que intervienen en la expresión.
- Parte central: Se trata del operador lambda, necesario en todas las expresiones de este tipo.
- Parte derecha: Se definen las acciones a realizar.

Puntos importantes:
- Las expresiones lambda no utiliza la instrucción return, excepto aquellas que utilicen un bloque encerrado entre llaves.
suma = (a, b) =>
{
return a + b;
};
- No es necesario especificar el tipo de los parámetros, ya que siempre van asociados a un delegado que ya contiene esta información.
delegate string Delegado(int edad);
[...]
Delegado edad = e => string.Format("Tu edad es {0}", e);
- Se pueden usar tanto por delegados creados por nosotros como por aquellos definidos por el framework.
<%= Html.TextBoxFor(m => m.Name) %>
LOS DELEGADOS GENÉRICOS: ACTION Y FUNC
Por otro lado, podemos utilizar los siguientes delegados genéricos en lugar de delegate. Con ellos conseguimos una sintaxis algo más refinada y simple.
Action se utiliza para aquellas expresiones lambda que no retornan ningún valor.
Action<string> saludo = s => Console.Write("Hola {0}!", s);
saludo("Gisela");
Func para aquellas expresiones que retornen un valor.
Func<int, int, int> suma = (a, b) => a + b;
int resultado = suma(3, 5);
Tanto en el caso de Func como en Action el número máximo de parámetros permitido es cuatro (Func figurará como cinco ya que el último es considerado el valor de retorno).
Espero que haya servido como una pequeña introducción al respecto ;)
¡Saludos!
Uno de los principales beneficios de crear una aplicación con ASP.NET MVC es que obtenemos mayor control en cuanto al código HTML se refiere. Aún así, es posible que al ver una vista generada para este patrón, resulte complicada la lectura para aquellos acostumbrados a trabajar con WebForms.
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%
if (Request.IsAuthenticated) {
%>
Welcome <b><%= Html.Encode(Page.User.Identity.Name) %></b>!
[ <%= Html.ActionLink("Log Off", "LogOff", "Account") %> ]
<%
}
else {
%>
[ <%= Html.ActionLink("Log On", "LogOn", "Account") %> ]
<%
}
%>
Si bien este es un pequeño ejemplo del user control del LogOn que viene con la plantilla de ASP.NET MVC, la cosa se puede llegar a complicar bastante y puede dificultar su lectura y comprensión.
¿QUÉ ES VIEW ENGINE?
ASP.NET MVC utiliza un motor de vistas (View Engine) para reemplazar los métodos de renderizado de las aplicaciones por código HTML. Generalmente se está usando el proporcionado por ASP.NET MVC pero es posible utilizar otras alternativas creadas por la comunidad. Para ver un ejemplo de uno de los más conocidos, vamos a modificar una aplicación para utilizar Spark View Engine.
CONFIGURAR SPARK VIEW ENGINE
Nota: Para este ejemplo he utilizado una plantilla de ASP.NET MVC 1.0 ya que la versión actual de Spark no soporta ASP.NET MVC 2 RC 2.
La configuración es bastante simple. Para ello, debemos bajar la última versión del proyecto de CodePlex y adjuntar las siguientes librerías a nuestro proyecto:

Una vez que tenemos las librerías agregadas, necesitamos añadir Spark como motor de vistas a través del archivo Global.asax.
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Add(new SparkViewFactory());
}
También es necesario generar un archivo donde especificamos las librerías necesarias para el renderizado. Este debe llamarse _global.spark y contedrá los siguientes assemblies (también puede definirse en el web.config).
<use namespace="System" />
<use namespace="System.Web.Mvc.Html" />
<use namespace="System.Collections.Generic" />
<use namespace="System.Linq" />
Esto sería lo mínimo necesario para comenzar a trabajar con Spark View Engine.
LAS VISTAS UTILIZANDO SPARK VIEW ENGINE
Para que el motor de Spark reconozca la vistas como suyas, es necesario que las mismas tengan extensión .spark. Una cosa importante a tener en cuenta es que la master page toma un nombre distinto: Application.spark. Si modificamos la master page por defecto de la plantilla de ASP.NET MVC quedaría de la siguiente manera:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>
<use content="title">Default title</use>
</title>
<link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="page">
<div id="header">
<div id="title">
<h1>
My MVC Application</h1>
</div>
<div id="logindisplay">
<LogOnUserControl/>
</div>
<div id="menucontainer">
<ul id="menu">
<li>${Html.ActionLink("Home", "Index", "Home")}</li>
<li>${Html.ActionLink("About", "About", "Home")}</li>
</ul>
</div>
</div>
<div id="main">
<use content="view" />
<div id="footer">
</div>
</div>
</div>
</body>
</html>
Como diferencias podemos encontrar:
- Para declarar los ContentPlaceHolder utilizamos use content.
<title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
<title>
<use content="title">Default title</use>
</title>
- Para incluir un control de usuario utilizamos tags con el nombre del mismo, por ejemplo <LogOnUserControl />
<div id="logindisplay">
<% Html.RenderPartial("LogOnUserControl"); %>
</div>
<div id="logindisplay">
<LogOnUserControl/>
</div>
- En cuanto al uso de helpers, variables y todo aquello relacionado con el servidor, podemos utilizarlo con la misma sintaxis que hasta ahora, a excepción de <% %>. Con Spark View Engine utilizaremos ${}
<ul id="menu">
<li>${Html.ActionLink("Home", "Index", "Home")}</li>
<li>${Html.ActionLink("About", "About", "Home")}</li>
</ul>
Si queremos usar varias master pages en nuestra aplicación, basta con incluir la etiqueta <use master=""/> en las vistas que lo requiera.
Los controles de usuarios también tienen extensión .spark y el nombre del archivo debe comenzar con guión bajo para ser reconocidos. Además es case sensitive cuando se utiliza el "modo tag":
Si no se tienen en cuenta estas dos indicaciones, no lo reconocerá y tampoco mostrará ningún tipo de error. Si quisieramos "traducir" el user control a lenguaje Spark quedaría de esta forma:
<if condition="Request.IsAuthenticated">
Welcome <b>${Context.User.Identity.Name}</b>!
[ !{Html.ActionLink("Log Off", "LogOff", "Account")} ]
</if>
<else>
[ !{Html.ActionLink("Log On", "LogOn", "Account")} ]
</else>
Podemos crear condiciones a través de tags e intercalar código HTML con código del servidor de una forma, quizás, más clara.
Por último, comentar que para el caso de las vistas no utilizamos un guión bajo al inicio para el nombre del archivo y que la extensión sigue siendo .spark, al igual que el resto de archivos.
<content name="title">
Index
</content>
<h2>
Index</h2>
<p>
This is the Message in ViewData: ${ViewData["Message"]}</p>
<p>
${Html.ActionLink("About us", "About")}</p>
<p>
The time is ${DateTime.Now}.</p>

Además de Spark View Engine, disponemos de otros motores como pueden ser Brail, NVelocity, NHaml entre otros. A gusto del consumidor ;)
Adjunto el proyecto por si fuera de utilidad.
¡Saludos!

En este mes de Febrero, desde Mad.Nug, queremos mostraros las novedades de Visual Basic .NET 2010 de la mano de Jorge Serrano.
Repasaremos la historia del lenguaje así como las mejoras ofrecidas para esta nueva etapa.
El evento será el jueves 25 de Febrero.
¡Os esperamos!
Más información
Generalmente, el hecho de no controlar las excepciones de una aplicación, y mostrar una página como la siguiente al usuario, supone una pérdida de calidad palpable en el producto.

En ASP.NET MVC podemos controlar estos mensajes redirigiendo al usuario a unas páginas más amigables y mostrando un mensaje menos alarmante de la aplicación. Si lo único que necesitamos es controlar excepciones personalizadas o aquellas que no pertenezcan a la rama de HttpException podríamos realizar los siguientes pasos:
- Habilitamos customErrors en el archivo web.config de la aplicación.
<customErrors mode="On"/>
- Utilizamos el action filter HandleError, ofrecido por ASP.NET MVC, en el controlador, el cual redirige todas las excepciones a la página Error.aspx por defecto ubicada en Views/Shared, aunque podemos indicarle otra vista en el atributo.
using System;
using System.Web.Mvc;
namespace HandleErrorASPNETMVC2RC2.Controllers
{
[HandleError(Order = 2)]
public class HomeController : BaseController
{
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
[HandleError(ExceptionType = typeof(DivideByZeroException), View = "DivideByZeroError", Order = 1)]
public ActionResult DivideByZeroExceptionAction()
{
throw new DivideByZeroException("DivideByZeroException is handled!");
}
}
}
Si quisiéramos controlar excepciones de tipo HttpException como la siguiente:
public ActionResult NotFoundHttpExcepcion()
{
throw new HttpException(404, "Not Found duh!");
}
No sería posible solamente con el action filter proporcionado.

Tenemos varias alternativas para manejar estas excepciones. En este post, voy a exponer las dos que me han resultado más factibles:
HACIENDO USO DE APPLICATION_ERROR (GLOBAL.ASAX)
Podríamos definir un comportamiento para los errores de la aplicación, a través de Application_Error, de la siguiente manera (obtenido a través de stackoverflow):
protected void Application_Error(object sender, EventArgs e)
{
var exception = Server.GetLastError();
Response.Clear();
var httpException = exception as HttpException;
var routeData = new RouteData();
routeData.Values.Add("controller", "HttpError");
if (httpException == null)
routeData.Values.Add("action", "Index");
else
{
switch (httpException.GetHttpCode())
{
case 404:
routeData.Values.Add("action", "NotFound");
break;
case 403:
routeData.Values.Add("action", "Forbidden");
break;
default:
routeData.Values.Add("action", "Default");
break;
}
}
routeData.Values.Add("error", exception);
Server.ClearError();
IController errorController = new HttpErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
En este caso tenemos un controlador llamado HttpError con una serie de vistas: Index, NotFound, Forbidden y Default. La parte positiva de esta forma es que, además de tener la posibilidad de redirigir a una vista personalizada, podemos realizar alguna acción, a través del controlador, antes de finalizar la petición. Además podemos utilizar este método de forma conjunta con HandleError para cubrir todas las excepciones posibles.
SOBREESCRIBIENDO ONEXCEPTION
Por otro lado, si lo que queremos es utilizar un atributo común para todas las excepciones, incluyendo HttpExceptions, podemos sobrescribir el action filter HandleError ampliando su funcionalidad de la siguiente manera:
using System.Web;
using System.Web.Mvc;
namespace HandleErrorASPNETMVC2RC2
{
public class HandleErrorHttpException : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (!filterContext.IsChildAction && (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled))
{
var httpException = filterContext.Exception as HttpException;
if (httpException != null)
{
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
var viewResult = new ViewResult
{
ViewName = View,
MasterName = Master,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
filterContext.Result = viewResult;
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = httpException.GetHttpCode();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; //Deshabilita el manejo de errores de IIS
}
else
base.OnException(filterContext);
}
}
}
}
De esta manera, todas aquellas excepciones que pertenezca a HttpException pasarán por el primer bloque y para cualquier otro tipo de excepción utilizará el método de la clase base.
Implementando esta segunda opción, podemos utilizar el nuevo action filter para controlar los errores de nuestros controladores.
using System;
using System.Web;
using System.Web.Mvc;
namespace HandleErrorASPNETMVC2RC2.Controllers
{
[HandleErrorHttpException(Order = 2)]
public class HomeController : BaseController
{
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
[HandleErrorHttpException(ExceptionType = typeof(DivideByZeroException), View = "DivideByZeroError", Order = 1)]
public ActionResult DivideByZeroExceptionAction()
{
throw new DivideByZeroException("DivideByZeroException is handled!");
}
public ActionResult Fordibben()
{
throw new HttpException(403, "Forbidden duh!");
}
[HandleErrorHttpException(ExceptionType = typeof(HttpException), View = "NotFound", Order = 1)]
public ActionResult NotFoundHttpExcepcion()
{
throw new HttpException(404, "Not Found duh!");
}
[HandleErrorHttpException(ExceptionType = typeof(InvalidOperationException), View = "InvalidOperationException", Order = 1)]
public ActionResult ViewDoesntExist()
{
return View("Doesnt Exist");
}
}
}
Nota: Cuando intentamos realizar un retorno a una vista que no existe el tipo de excepción es InvalidOperationException como se muestra en el último caso.
Espero que haya sido de utilidad.
¡Saludos!
Después de conocer los distintos tipos de proyectos disponibles con Silverlight 3, a día de hoy última versión estable (Silverlight 4 Beta disponible), podemos comenzar a crear una aplicación de lo más sencilla. Antes de comenzar con el layout, vamos a crear un proyecto de tipo Silverlight Application.

Al pulsar en OK nos aparece una nueva ventana:

En este cuadro de diálogo nos están ofreciendo la posibilidad de agregar además un proyecto web a la solución. Toda aplicación Silverlight necesita un soporte web donde insertarse, debido a la plataforma en la que se basa.
Seleccionamos la opción por defecto, que es ASP.NET Web Application Project, y pulsamos OK para que adjunte este proyecto a la solución.
Por defecto, la página de inicio en este caso sería PrimeraAppSLTestPage.aspx donde está incrustada la aplicación Silverlight, PrimeraAppSL, para poder mostrarla en el navegador. ¿Dónde está alojada la misma?

Si echamos un vistazo a la página generada observamos lo siguiente:
<%@ Page Language="C#" AutoEventWireup="true" %>
<!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>PrimeraAppSL</title>
<style type="text/css">
html, body {
height: 100%;
overflow: auto;
}
body {
padding: 0;
margin: 0;
}
#silverlightControlHost {
height: 100%;
text-align:center;
}
</style>
<script type="text/javascript" src="Silverlight.js"></script>
<script type="text/javascript">
function onSilverlightError(sender, args) {
var appSource = "";
if (sender != null && sender != 0) {
appSource = sender.getHost().Source;
}
var errorType = args.ErrorType;
var iErrorCode = args.ErrorCode;
if (errorType == "ImageError" || errorType == "MediaError") {
return;
}
var errMsg = "Unhandled Error in Silverlight Application " + appSource + "\n" ;
errMsg += "Code: "+ iErrorCode + " \n";
errMsg += "Category: " + errorType + " \n";
errMsg += "Message: " + args.ErrorMessage + " \n";
if (errorType == "ParserError") {
errMsg += "File: " + args.xamlFile + " \n";
errMsg += "Line: " + args.lineNumber + " \n";
errMsg += "Position: " + args.charPosition + " \n";
}
else if (errorType == "RuntimeError") {
if (args.lineNumber != 0) {
errMsg += "Line: " + args.lineNumber + " \n";
errMsg += "Position: " + args.charPosition + " \n";
}
errMsg += "MethodName: " + args.methodName + " \n";
}
throw new Error(errMsg);
}
</script>
</head>
<body>
<form id="form1" runat="server" style="height:100%">
<div id="silverlightControlHost">
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/PrimeraAppSL.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="3.0.40818.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40818.0" style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style:none"/>
</a>
</object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div>
</form>
</body>
</html>
A parte de una pequeña definición de estilos y de la creación de una función javascript, que trata los errores que se puedan producir de Silverlight, vemos que se especifica un div dentro del formulario del body, el cual contiene un objeto con los parámetros relativos a nuestra aplicación, tales como el tipo de aplicación y versión, la ubicación de la aplicación, la función a la que debe llamar en caso de error (la función javascript mencionada anteriormente), el color de fondo, si está permitida la actualización del plugin de manera automática y un link de referencia al mismo por si no está instalado en el cliente.
LAYOUT
Para comenzar con algo simple, y suponiendo que tenemos nociones de Web Forms, vamos a enfocarlo de la siguiente manera: Imaginemos que tenemos un formulario con Web Forms de lo más simple con una serie de campos para rellenar y queremos pasarlo a Silverlight.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="AppWebForm._Default" %>
<!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></title>
</head>
<body>
<form id="formulario" runat="server">
<div>
<div>
<asp:Label runat="server" AssociatedControlID="txtName" ID="lblName" Text="Name:" />
<asp:TextBox runat="server" ID="txtName" />
</div>
<div>
<asp:Label runat="server" AssociatedControlID="txtLastName" ID="lblLastName" Text="Last Name:" />
<asp:TextBox runat="server" ID="txtLastName" />
</div>
<div>
<asp:Label runat="server" AssociatedControlID="dddlGenre" ID="lblGenre" Text="Genre:" />
<asp:DropDownList runat="server" ID="dddlGenre">
<asp:ListItem Text="Female" Value="F" />
<asp:ListItem Text="Male" Value="M" />
</asp:DropDownList>
</div>
<div>
<asp:Label runat="server" AssociatedControlID="calBirthday" ID="lblBirthday" Text="Birthday" />
<asp:Calendar runat="server" ID="calBirthday" Caption="Birthday"></asp:Calendar>
</div>
<asp:Button runat="server" ID="btnEnviar" Text="Send" />
</div>
</form>
</body>
</html>
En Silverlight no utilizamos HTML para la creación de la interfaz de usuario sino XAML (eXtensible Application Markup Language), el cuál ya se utilizaba para Windows Presentation Foundation.
En el proyecto de Silverlight tenemos dos archivos con extensión xaml: App.xaml y MainPage.xaml. El primero de ellos se utiliza para declarar recursos compartidos y manejar eventos globales, de la misma manera que un archivo Global.asax en aplicaciones ASP.NET, y el segundo es donde definiremos la interfaz de usuario.
Para comenzar a "dibujar" la UI, abrimos el archivo MainPage.xaml donde inicialmente observamos el siguiente contenido:
<UserControl x:Class="PrimeraAppSL.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
</Grid>
</UserControl>
En el primer etiquetado tenemos la defición del control, asociado a una clase que actuará como code behind, una serie de espacios de nombres y por último las medidas del control. Dentro de él, de forma predeterminada, se define un Grid.
Llegados a este punto, conviene hacer una distinción: Existen generalmente dos tipos de elementos dentro del layout: contenedores y controles. Todos ellos están incluidos dentro de System.Windows.Controls. Los que heredan de Panel se consideran contenedores, los cuales nos ayudarán a ubicar a los controles dentro de la interfaz. Los contenedores más comunes son el Grid, Canvas y StackPanel, aunque hay disponibles otros que veremos más adelante.
A grandes rasgos, podemos decir que el Grid posiciona sus elementos a semejanza de una tabla o cuadrilla, el Canvas los ubica de una forma similar a la que acostumbramos con CSS (Top, Left) y en el caso del StackPanel podemos utilizarlo, generalmente, como un conjunto de controles en línea dándole un sentido vertical o horizontal. Para este ejemplo utilizaremos el Grid como contenedor.
Antes de comenzar a añadir los controles, necesitamos definir cómo será nuestro Grid.
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PrimeraAppSL.MainPage"
d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot" Margin="5" Height="400" Width="400">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="60" />
<RowDefinition Height="Auto" MinHeight="60"/>
<RowDefinition Height="Auto" MinHeight="60"/>
<RowDefinition Height="Auto" MinHeight="60"/>
<RowDefinition Height="Auto" MinHeight="60"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition MinWidth="60"/>
</Grid.ColumnDefinitions>
</Grid>
</UserControl>
En este caso, le estamos pidiendo que el contenedor esté compuesto de 5 líneas con un alto automático y un mínimo de 60 y dos columnas, la primera con un width determinado y la segunda con un mínimo de 60.
Por último, necesitamos añadir y posicionar los controles dentro del grid definido, por ejemplo de la siguiente forma:
<UserControl xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Class="PrimeraAppSL.MainPage"
d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot" Margin="5" Height="400" Width="400">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="60" />
<RowDefinition Height="Auto" MinHeight="60"/>
<RowDefinition Height="Auto" MinHeight="60"/>
<RowDefinition Height="Auto" MinHeight="60"/>
<RowDefinition Height="Auto" MinHeight="60"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition MinWidth="60"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="ForeName" Text="Name:" Width="50" Height="25" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right"/>
<TextBox x:Name="txtName" Width="150" Height="25" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left"/>
<TextBlock x:Name="LastName" Text="Last Name:" Height="25" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right"/>
<TextBox x:Name="txtLastName" Width="150" Height="25" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"/>
<TextBlock x:Name="Genre" Text="Gerne:" Height="25" Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right"/>
<ComboBox x:Name="comboGenre" Width="150" Height="25" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left">
<ComboBoxItem Content="Female"/>
<ComboBoxItem Content="Male"/>
</ComboBox>
<TextBlock x:Name="Calendar" Text="Birthday:" Height="25" Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right"/>
<controls:Calendar x:Name="BirthdayCalendar" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left"/>
<Button x:Name="buttonGrid" Content="Send" Width="100" Height="25" Grid.Row="4" Grid.Column="1"/>
</Grid>
</UserControl>
Para posicionar los controles dentro del Grid, tomando como primer valor el 0, usamos Grid.Row para indicar la fila y Grid.Colum para la columna.
Si arrancamos la aplicación para comprobar el resultado, obtendríamos lo siguiente:

En una primera toma de contacto con XAML, hemos comprobado como podemos montar un formulario, bastante similar al anterior mostrado con Web forms, y posiblemente de una forma algo más atractiva sin realizar modificaciones de estilo considerables.
Adjunto el proyecto por si fuera de utilidad.
¡Saludos!

Dropbox se trata de un servicio que te permite almacenar tus archivos de forma remota y, a su vez, que estos mismos estén sincronizados en distintos dispositivos. Actualmente tiene soporte para Windows, Linux, Mac e iPhone.
Lo que más me ha interesado de Dropbox es utilizarlo como un simple controlador de versiones para proyectillos personales =) La cuenta gratuita son 2 GB de capacidad y es necesario instalar un cliente en cada uno de los equipos donde queremos realizar la sincronización. El resultado será una carpeta, por defecto en Mis documentos, con el siguiente aspecto:

Una vez instalado, todo lo que arrastremos a esta carpeta será alojado en el servidor remoto y comenzará a sincronizar con todos aquellos dispositivos que tengamos enlazados.
Como opciones tenemos la posibilidad de obtener el link para poder compartirlo con otras personas, ver el histórico de versiones del archivo y visualizar Dropbox mediante web.

Si por ejemplo tengo una solución dentro de Dropbox y además la tengo compartida con una serie de personas, podría visualizar quién y cuándo modificó un archivo e incluso volver a una versión anterior.

A mi, personalmente, me va a ayudar a dejar algo más de lado el pendrive ¡Me pareció lo más cómodo!
Espero que a alguien más le sirva =)
¡Saludos!

Uno de los objetivos de este año 2010 iba a ser justamente este: Comenzar con Silverlight.
Realmente no creo que sea necesaria una presentación exhaustiva sobre el producto pero, para comenzar con mi tira de post relacionados, comenzaré hablando de los aspectos básicos que nos afectan a los desarrolladores en el modo de trabajar.
En primer lugar, para comenzar a trabajar con Silverlight, es necesario descargar las herramientas para Visual Studio. Podemos usar Web Platform Installer o bien realizar una descarga directa a través de este enlace para la versión 3.
TIPOS DE PROYECTOS PARA SILVERLIGHT
Si arrancamos Visual Studio y comprobamos las nuevas posibilidades observamos que existen 3 tipos distintos dentro de la pestaña Silverlight:
- Silverlight Application: Se utiliza para generar una aplicación desde cero con Silverlight.

El archivo de entrada de la aplicación es App.xaml el cual tiene un archivo cs ligado que actúa como code behind. La extensión del mismo proviene de XAML utilizado en Windows Presentation Foundation para definir la interfaz de usuario. A través de él comienza la aplicación y se determina qué control de usuario se iniciará en primer instancia.
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new MainPage();
}
Por defecto, se genera un archivo llamado MainPage.xaml que corresponde a un control de usuario en XAML.
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
</Grid>
</UserControl>
- Silverlight Class Library: Se utiliza para generar librerías relacionadas con Silverlight.

En este punto, es posible que nos preguntemos cuál es la diferencia entre un proyecto de librería para Silverlight y uno tradicional, puesto que las diferencias (a parte de las referencias) no parecen para nada visibles. Si vemos las propiedades de proyecto de ambas, veríamos lo siguiente:
Class Library

Class Library For Silverlight

- No permite referenciar ningún objeto de inicio ni especificar opciones de build.
- El CRL de Silverlight es diferente al de .NET.
- Algunas de las referencias habituales de .NET no son soportadas por el pluging de Silverlight.
- Silverlight Navigation Application: Este último proyecto tiene como cometido las aplicaciones RIA (Aplicaciones de Internet Enriquecidas)

Según vayamos viendo cada uno de los proyectos, se intentará profundizar más en cada uno de ellos. Por el momento, me parecía interesante ver qué tipos están disponibles y ponernos manos a la obra en los que, espero, sean unos muy entretenidos posts =)
¡Saludos!

En el día de hoy, se ha puesto a nuestra disposición la nueva versión de Resharper 5 Beta (Build 1612), compatible con Visual Studio 2010 RC. Si bien es cierto que llevamos disfrutando de distintas versiones desde el pasado mes de Octubre, voy a aprovechar esta ocasión para comentar algunas de las novedades que presenta esta gran herramienta en cuanto a ASP.NET MVC se refiere.
Todas las versiones publicadas están disponibles en el siguiente enlace.
EN LAS VISTAS
Si no utilizamos plantillas T4 en nuestros proyectos con ASP.NET MVC, tenemos el inconveniente de no poder navegar de las vistas a los controladores, acciones, etcétera a menos que usemos Resharper :)

En aquellos links que aparezcan subrayados dentro de nuestras vistas, tendremos la posibilidad de navegar a su implementación pulsando Control + click izquierdo sobre el link o bien posicionándonos sobre el texto subrayado y pulsando F12.
Por otro lado, tenemos la capacidad de generar código a través de estos mismos literales utilizando el mismo sistema que para cualquier otro método, propiedad, etcétera que no esté implementado.


Nota: Para mostrar el popup de Resharper, basta con seleccionar el link resaltado en rojo y pulsar sobre la bombilla o bien Alt + Intro.
EN LOS CONTROLADORES
Para seguir la misma línea, podemos navegar desde las acciones a las vistas pulsando Control + click sobre View o bien F12.
.
En este caso, además, nos presenta dos posibilidades: Acudir a la vista como tal, en este caso Index.aspx, o bien acceder a la implementación del método View que se encuentra en System.Web.Mvc.
Por último, podemos localizar tipos, comprobar los usos, mostrar intellisense para la localización de vistas, etcétera de la misma forma que lo hacíamos con el resto de proyectos.
Control + T (Localización de tipos)
.
Shift + F12 (Encontrar usos)

Control + space (Intellisense)
Tanto en controladores:

Como en las vistas:

Sin duda alguna, para todos aquellos que usemos Resharper, ya podemos empezar a contar con sus consejos para aplicaciones ASP.NET MVC.
¡Saludos!
Hoy estuve trasteando con los archivos .skin y me sorprendió el hecho de que el intellisense no estuviera habilitado. Este va a ser un post muy corto pero creo que estoy muy mal acostumbrada y sin intellisense no puedo vivir =P
Para habilitarlo sólo debemos seguir los siguientes pasos:
- En el menú de Visual Studio seleccionamos Tools > Options y, dentro del apartado Text Editor, nos ubicamos en File Extension
.
- En la parte superior de este apartado, tenemos un recuadro donde podemos escribir la extensión y seleccionar el editor. En este caso, escribimos skin y seleccionamos User Control Editor. Para finalizar pulsamos en Add y OK.

- Para que el cambio tenga efecto, es necesario cerrar todos los archivos de skins.
¡Y esto es todo! Ya podemos disfrutar de intellisense en estos archivos. ¡Uf! Necesitaba comentarlo para la gente que sufre por estas cosas como yo =D
¡Saludos!
La verdad, no soy consciente de cuántos son los desarrolladores que conocen el significado de Inyección de Dependencias (Dependency Injection) o Inversión de control (Inversion of Control). Para ser sincera, nunca trabajé con ello en ningún proyecto real pero si he intentado recopilar información y conceptos para poder aplicarlos a mis proyectos personales.
Generalmente, cuando tenemos una clase que depende de otras para ciertas acciones, necesitamos inicializar instancias de las mismas para poder utilizarlas posteriormente. En ocasiones, una de las alternativas puede ser crear un objeto de dichas clases de forma privada e inicializarlas, utilizando el constructor de la clase principal.
Si vemos estas acciones desde el punto de vista de la Inyección de Dependencias y la Inversión de Control, no sería la forma más óptima debido a que la clase que sufre estas dependencias no debería ser la responsable de la creación de las mismas.
¿QUÉ CONSEGUIMOS?
- Desacoplamiento.
- Mejora la posibilidad de testeo de la aplicación.
- Mejor mantenimiento a la hora de realizar cambios de los componentes, gracias a la modularidad.
- [...]
Ambos conceptos están tan ligados que, en ocasiones, no se hace distinción. Se utiliza el concepto Inversión de Control para delegar en otro componente, un framework por ejemplo, la responsabilidad de crear las instancias necesarias en lugar de crearlas nosotros mismos. Por otro lado, la Inyección de Dependencias es el término utilizado cuando una clase depende de otra y, a través del constructor generalmente acepta un parámetro del tipo del cual depende.
Para llevar a cabo el patrón de diseño de Inyección de Dependencias, es necesario el uso de interfaces y, lo más óptimo sería utilizar alguno de los frameworks disponibles para llevar a cabo la Inversión de Control. Algunos de estos frameworks son: Spring.NET, Windsor Container, StructureMap, Unity, etcétera.
EJEMPLO A EVITAR
Por ver un ejemplo, supongamos que tenemos el siguiente código:
using System.Web.Mvc;
using IoC.Models;
namespace IoC.Controllers
{
[HandleError]
public class HomeController : Controller
{
private readonly ITwitterService _twitterService;
public HomeController()
{
_twitterService = new TwitterService();
}
public ActionResult Index()
{
return View(_twitterService.FetchTweets());
}
}
}
Tenemos un controlador, en una aplicación ASP.NET MVC, donde estamos haciendo uso de una librería que conecta con Twitter. Cuando se solicita la acción Index de este controlador, el controlador se crea, a través del constructor inicializa la variable _twitterService y realiza la llamada a FetchTweets. Esto funciona sin problemas, pero supone un inconveniente a la hora de realizar pruebas unitarias.
Por otro lado, si el día de mañana queremos, por ejemplo, utilizar otra clase que implemente ITwitterService bien porque hemos cambiado de librería, porque la forma de trabajar con Twitter es totalmente distinta, etcétera, deberíamos modificar a su vez este controlador(es) para modificar en el constructor la clase que implementa la interfaz a partir de ahora. Este es un caso bien simple pero ¿Y si nuestros controladores son dependientes de más de una clase y las mismas están en constante revisión, actualización, modificación, etcétera? La solución es bien simple:
using System.Web.Mvc;
using IoC.Models;
namespace IoC.Controllers
{
[HandleError]
public class HomeController : Controller
{
private readonly ITwitterService _twitterService;
public HomeController(ITwitterService twitterService)
{
_twitterService = twitterService;
}
public ActionResult Index()
{
return View(_twitterService.FetchTweets());
}
}
}
Gracias a la inyección de dependencias, liberamos al controlador de la carga de generar las instancias que necesita, lo abstraemos del tipo de clase que implementa la interfaz en este momento y conseguimos modularizar la aplicación.
PRUEBAS UNITARIAS
Si quisiéramos hacer un test, por ejemplo, que comprobara que al llamar a la acción Index el método FetchTweets es llamado, sin realizar la llamada real a Twitter e incluso sin hacer uso de la conexión que esto requiere a Internet, podríamos hacerlo de la siguiente manera:
using IoC.Controllers;
using IoC.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
namespace IoC.Tests.Controllers
{
[TestClass]
public class HomeControllerTest
{
[TestMethod]
public void HomeController_AlLlamarALaAccionIndex_FetchTweetsEsLlamado()
{
//Arrange
var mockTwitterService = MockRepository.GenerateMock<TwitterService>();
mockTwitterService.Stub(m => m.FetchTweets()).Return(null);
var homeController = new HomeController(mockTwitterService);
//Act
homeController.Index();
//Assert
mockTwitterService.AssertWasCalled(m => m.FetchTweets());
}
}
}
Nota: En este ejemplo he utilizado la librería RhinoMocks para crear la prueba unitaria.
Esto es realmente importante si queremos hacer pruebas unitarias de la aplicación, pero también es cierto que se nos presenta el siguiente problema ¿Cada vez que llame a HomeController voy a tener que encargarme y asegurarme en cada caso de que reciba una instancia de las interfaces que solicita el constructor? Aquí es donde entra en juego IoC y los numerosos frameworks existentes para este rol.
Para ver un pequeño ejemplo de cómo podríamos delegar esta funcionalidad en uno de los frameworks que soportan Inversión del Control, voy a utilizar StructureMap como demostración. Para descargar la última versión del framework podemos dirigirnos a este enlace.
CONFIGURANDO STRUCTUREMAP
En este ejemplo, vamos a configurar StructureMap de tal forma que sepa cómo actuar en el caso de requerir una instancia para una interfaz de tipo ITwitterService. Para ello, me he creado la siguiente clase:
using IoC.Models;
using StructureMap;
namespace IoC.StructureMapConfiguration
{
public static class BootStrapper
{
public static void SetupContainer()
{
ObjectFactory.Configure(s => s.For<ITwitterService>().Use<TwitterService>());
}
}
}
En una sola línea, le estoy indicando que para la interfaz ITwitterService, debemos usar una instancia de la clase TwitterService. La magia de todo esto es que, si el día de mañana esta interfaz es implementada por otra clase, y además esta interfaz es usada en numerosos sitios de nuestra aplicación, solamente debemos modificar esta línea para que la clase que la implementa comience a servirse como dependencia en los casos que lo requiera.
CONTROLADORES ASP.NET MVC YSTRUCTUREMAP
Por otro lado, si lo que queremos es trabajar con ASP.NET MVC, debemos realizar una serie de cambios: Cuando nosotros hacemos una petición en una aplicación con ASP.NET MVC, la clase ControllerBuilder genera de forma automática el controlador solicitado, se despacha la petición y el controlador finaliza. Para poder utilizar las propiedades de StructureMap, necesitamos crear una clase que herede de la factoría de controladores. De esta manera, controlaremos el momento en el cual se solicita una instancia de un controlador y, si este tiene dependencias, poder administrarlas con la configuración realizada anteriormente para StructureMap en la clase BootStraper.
using System;
using System.Web.Mvc;
using System.Web.Routing;
using StructureMap;
namespace IoC.StructureMapConfiguration
{
internal class StructureControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType != null)
return ObjectFactory.GetInstance(controllerType) as IController;
return base.GetControllerInstance(requestContext, controllerType);
}
}
}
INICIALIZANDO LA CONFIGURACIÓN DE STRUCTUREMAP Y ASOCIADO LA NUEVA FACTORÍA DE CONTROLADORES
Para finalizar, necesitamos inicializar tanto la configuración creada en BootStraper, para que StructureMap reconozca la interfaz especificada, como la asignación de la nueva factoría de controladores a la aplicación ASP.NET MVC. La mejor ubicación para este caso concreto, podría ser el archivo Global.asax.
using System.Web.Mvc;
using System.Web.Routing;
using IoC.StructureMapConfiguration;
namespace IoC
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
BootStrapper.SetupContainer();
ControllerBuilder.Current.SetControllerFactory(new StructureControllerFactory());
}
}
}
Si arrancamos la aplicación, comprobaríamos que efectivamente se crea un controlador con su dependencia y delegamos esta acción gracias a la Inversión de Control.
Adjunto el proyecto por si fuera de utilidad.
¡Saludos!
Gracias a Hadi Hariri por sus enseñanzas :)
T4MVC es una plantilla T4 (Text Template Transformation Toolkit), creada por David Ebbo, que genera de forma automática un conjunto de helpers que nos ayudarán a eliminar el uso de literales de nuestra aplicación ASP.NET MVC. Gracias al uso de esta plantilla, podemos utilizar intellisense para localizar el nombre de Controladores, acciones, vistas, scripts etcétera y conseguir un código con menos errores y mucho más mantenible.
Para poder descargarla, bastará con acceder al proyecto ubicado en Codeplex.
INSTALACIÓN
La generación del código, se consigue nada más agregar los archivos T4MVC.tt y T4MVC.settings.t4

¿Por qué es necesario agregar también el archivo .settings?
El archivo T4MVC.settings.t4 contiene un conjunto de constantes que ayudarán a generar el código en base a ellas. Por ejemplo:
// The prefix used for things like MVC.Dinners.Name and MVC.Dinners.Delete(Model.DinnerID)
const string HelpersPrefix = "MVC";
Esta constante nos indica el prefijo que precede a los helpers generados por T4MVC.
// The folder under the project that contains the areas
const string AreasFolder = "Areas";
El nombre de la carpeta que agrupa las áreas.
// The folder under the project that contains the controllers
const string ControllersFolder = "Controllers";
// The folder under the project that contains the views
const string ViewsRootFolder = "Views";
El nombre de las carpetas que contienen los controladores y las vistas.
// Folders containing static files for which links are generated (e.g. Links.Scripts.Map_js)
readonly string[] StaticFilesFolders = new string[] {
"Scripts",
"Content",
};
La carpeta donde figuran los archivos javascript y los estilos.
// If true,the template output will be split into multiple files.
static bool SplitIntoMultipleFiles = true;
La plantilla generará varios archivos de salida.
Si modificamos cualquiera de las constantes y compilamos la solución, se regenerará el código de salida de la plantilla.
En el momento de agregar los archivos, o al recompilar, nos aparecerá el siguiente cuadro de diálogo:

Solamente es un aviso y nos están advirtiendo de que el archivo que estamos intentando agregar/recompilar puede dañar nuestro equipo :) y que no se ejecute si proviene de una fuente de la que no confiamos. En este momento podemos pulsar OK, lo cual significa que arrancará la plantilla de forma automática o bien podemos cancelar y se parará el proceso. Pulsamos OK y arriesgamos ;)
Si nos fijamos en el Solution explorer, ha generado varios archivos .cs ligados a la plantilla .tt.

Como la constante SplitIntoMultipleFiles es igual a true, nos generará tantos archivos como controladores tenga el proyecto.
EJEMPLOS
Controladores

Acciones / Vistas


Links dentro de Content/Scripts

En el controlador
return RedirectToAction("Index");
T4MVC
return RedirectToAction(MVC.Tweet.Actions.Index());
Global.asax
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Tweet", action = "Index", id = "" } // Parameter defaults
);
T4MVC
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = MVC.Tweet.Name, action = MVC.Tweet.ActionNames.Index, id = "" } // Parameter defaults
);
Scripts
<script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
T4MVC
<script src="<%=Links.Scripts.jquery_1_3_2_min_js %>" type="text/javascript"></script>
Render Partial
<% Html.RenderPartial("TimeLine"); %>
T4MVC
<% Html.RenderPartial(MVC.Tweet.Views.TimeLine); %>
Action Links
<%=Html.ActionLink("Back to TimeLine", "Index") %>
T4MVC
<%=Html.ActionLink("Back to TimeLine", MVC.Tweet.Actions.Index()) %>
¡Saludos!
Hace unos días me encontré con un caso en el que era necesario llamar de forma asíncrona a una acción de un controlador ASP.NET MVC, para refrescar una pantalla con los nuevos resultados. Además, era necesario que se hiciera cada X tiempo para que no me denegaran el servicio.
Desconozco si existe una forma más óptima de realizar esta funcionalidad en ASP.NET MVC pero, si sirve de ayuda, muestro el código de mi solución "temporal" a este caso en concreto:
- Creo un enlace con las propiedades de Ajax de la siguiente manera.
<%=Ajax.ActionLink("Refresh","RefreshTags", new { tag = ViewData["tags"] }, new AjaxOptions { HttpMethod = "Post", OnBegin = "onBeginRetrievingTags", OnComplete = "onCompleteRetrivingTags", UpdateTargetId = "tweets" },new{Id="ActionLinkTags"})%>
Para más información sobre Ajax con ASP.NET MVC, puedes consultar este otro post.
- Por el lado del controlador, creo la acción que va a devolver el listado actualizado, en este caso, de tweets.
[HttpPost]
public ActionResult RefreshTags(string tag)
{
ViewData["tags"] = tag;
List<Tweet> entries = _serviceTweet.FilterBy(tag);
return PartialView("TweetList", entries);
}
Si observamos detenidamente la acción, vemos que nos redirige a una vista parcial llamada TweetList donde muestro el resultado obtenido.
- Ejecutamos la aplicación, vemos que al renderizar la página en cuestión, nos devuelve el siguiente elemento para nuestro link.
<a Id="ActionLinkTags" href="/Tweet/RefreshTags?tag=aspnetmvc" onclick="Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, httpMethod: 'Post', updateTargetId: 'tweets', onBegin: Function.createDelegate(this, onBeginRetrievingTags), onComplete: Function.createDelegate(this, onCompleteRetrivingTags) });">Refresh</a>
- Para llevar a cabo la llamada a través de JQuery, en un archivo js, he creado las siguientes funciones.
function launchConfigTags() {
$("#ActionLinkTags").dblclick(function(e) {
Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(e),
{
insertionMode: Sys.Mvc.InsertionMode.replace,
httpMethod: 'Post',
updateTargetId: 'tweets',
onBegin: Function.createDelegate(this, onBeginRetrievingTags),
onComplete: Function.createDelegate(this, onCompleteRetrivingTags)
});
});
Timer();
}
function Timer() { setTimeout("refreshTags()", 60000); }
function refreshTags() { $("#ActionLinkTags").trigger("dblclick"); }
De manera conjunta, realizan exactamente la misma llamada que si pulsaramos el link con un tiempo estimado de un minuto.
- Si queremos que el refresco se realice desde el primer momento que la página está disponible, podemos lanzar la función que bindea el evento doble click al link en el evento ready, iniciando de esta manera el ciclo de llamadas.
$(document).ready(function() { launchConfigTags(); });
Espero que sea de utilidad.
¡Saludos!
En este post, voy a centrarme en las tablas de Windows Azure Storage, las cuales podemos definirlas como un conjunto de datos estructurados pero no relacionales. Es decir, tenemos un conjunto de registros con sus correspondientes columnas y primary keys pero no es posible la relación entre una tabla y otra del Storage. En el caso de necesitar tablas relacionales, tenemos SQL Azure.
Para comenzar, vamos a crear una pequeña aplicación donde almacenamos perfiles de usuario. Abrimos Visual Studio como administrador y creamos un nuevo proyecto de tipo Cloud Service con un Web Role.

Una vez creado, nos aparecerán dos proyectos: El web role y el proyecto de configuración de Windows Azure.
CREACIÓN DE LOS DATOS ESTRUCTURADOS
Como he mencionado antes, el contenido de las tablas de Storage son datos estructurados. Cada registro de esa tabla será una entidad y el conjunto de registros será una colección de entidades. En este caso, nuestra entidad se llamará Profile y estará compuesta por un nombre y un email. Para representar esta entidad/registro de nuestra tabla, creamos la siguiente clase en el proyecto ProfileManager:
using System;
namespace ProfileManager
{
public class Profile : Microsoft.WindowsAzure.StorageClient.TableServiceEntity
{
public Profile()
{
//PartitionKey y RowKey son requeridas para cada entidad añadida a la tabla
PartitionKey = "Users";
RowKey = string.Format("{0:10}_{1}", DateTime.MaxValue.Ticks - DateTime.Now.Ticks, Guid.NewGuid());
}
public string UserName { get; set; }
public string Email { get; set; }
}
}
Lo más importante a tener en cuenta en esta clase es que hereda de TableServiceEntity, que está dentro de una de las librerías de Windows Azure.
Hemos definido un constructor, donde inicializamos dos propiedades heredadas, PartitionKey y RowKey, las cuales son requeridas para poder insertar, modificar y eliminar correctamente una entidad en la tabla. De lo contrario, generaría una excepción.

Las propiedades heredadas por cada entidad son:
- PartitionKey, se utiliza para determinar en qué partición debe estar la entidad que se está creando. Esto es debido a que las tablas están particionadas para soportar el balanceo de carga, a través de nodos de almacenamiento. Las entidades que pertenecen a una misma PartitionKey permanecen juntas. Además, esta clave forma parte de la primary key del registro. Para mejorar la escalabilidad de la aplicación, debemos tener en cuenta que se considera recomendable clasificar nuestros datos entre varias Partition Keys.
- RowId, se trata de la segunda parte de la primary key. Es un identificador único dentro de la partición.
- Timespan, es una propiedad de tipo DateTime que se utiliza para guardar la fecha de modificación de la entidad.
CONTEXTO DE LA TABLA
Por otro lado, necesitamos generar una serie de operaciones para que podamos trabajar con la tabla que, en un futuro, estará ubicada en la nube. Para ello, creamos una clase que herede de TableServiceContext, donde agregaremos las opciones de recuperar y añadir entidades en la tabla Profiles.
using System.Linq;
using Microsoft.WindowsAzure;
namespace ProfileManager
{
public class ProfileTableServiceContext : Microsoft.WindowsAzure.StorageClient.TableServiceContext
{
public ProfileTableServiceContext(string baseAddress, StorageCredentials credentials) : base(baseAddress, credentials) { }
public IQueryable<Profile> Profiles
{
get { return CreateQuery<Profile>("Profiles"); }
}
public void AddProfile(string userName, string email)
{
AddObject("Profiles", new Profile { UserName = userName, Email = email });
SaveChanges();
}
}
}
Lo primero que observamos es que recupera el constructor de la clase base. Es un paso obligatorio ya que la misma no contiene un constructor sin parámetros, lo cual es lógico, debido a que es necesario una dirección que nos indique dónde está el endpoint donde queremos operar y, como segundo parámetro, las credenciales para tener acceso.
Nota: Es necesario añadir la dll System.Data.Services.Client.
CADENA DE CONEXIÓN A DESARROLLO
Antes de realizar las pruebas oportunas contra la nube, podemos realizar un testeo inicial con el entorno de desarrollo Development Storage y Development Fabric. Para realizar las pruebas en local, creamos una entrada en la sección connectionStrings del archivo de configuración con el siguiente valor:
<connectionStrings>
<add name="AzureConnection" connectionString="UseDevelopmentStorage=true"/>
</connectionStrings>
CREACIÓN DE LA TABLA
Tanto en local como en la nube, antes de añadir registros, necesitamos crear la tabla en el Storage. Si bien es un paso que solamente debemos realizar una vez, podemos utilizar el archivo de inicio WebRole.cs que se generó cuando creamos el proyecto. Dentro del método OnStart(), añadimos el siguiente código:
using System.Configuration;
using System.Linq;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
namespace ProfileManager
{
public class WebRole : RoleEntryPoint
{
public override bool OnStart()
{
DiagnosticMonitor.Start("DiagnosticsConnectionString");
// For information on handling configuration changes
// see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
RoleEnvironment.Changing += RoleEnvironmentChanging;
CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
configSetter(ConfigurationManager.ConnectionStrings[configName].ConnectionString));
var storageAccount = CloudStorageAccount.FromConfigurationSetting("AzureConnection");
CloudTableClient.CreateTablesFromModel(typeof(ProfileTableServiceContext),
storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);
return base.OnStart();
}
Lo primero que debemos especificar es de qué manera podemos recuperar la configuración necesaria para conectar a la cuenta de Storage a través de SetConfigurationSettingPublisher.
Una vez indicado el modo, recuperamos la cadena de conexión utilizando FromConfigurationSetting y, por último, creamos la tabla llamado a CreateTablesFromModel, pasándole como parámetros la clase que creamos con las operaciones que podemos realizar contra nuestra tabla, y que estaba heredando de TableServiceContext, el endpoint y las credenciales.
Para probar el ejemplo, he creado un pequeño formulario con dos textbox donde añadimos un nombre y un email y, pulsando en el botón Add Profile, añadimos esos datos en forma de entidad a la tabla del Storage y mostramos las entidades almacenadas hasta el momento.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ProfileManager.Default" %>
<!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></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<div>
<asp:Label runat="server" ID="lblUserName" Text="UserName:"></asp:Label>
<asp:TextBox runat="server" ID="txtUserName"></asp:TextBox>
<asp:Label runat="server" ID="lblEmail" Text="Email:"></asp:Label>
<asp:TextBox runat="server" ID="txtEmail"></asp:TextBox>
<asp:Button runat="server" ID="btnAdd" Text="Add Profile" OnClick="btnAdd_Click" />
</div>
<div>
<asp:GridView ID="grvProfiles" runat="server" BackColor="White" BorderColor="#E7E7FF"
BorderStyle="None" BorderWidth="1px" CellPadding="3" GridLines="Horizontal">
<RowStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" />
<FooterStyle BackColor="#B5C7DE" ForeColor="#4A3C8C" />
<PagerStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" HorizontalAlign="Right" />
<SelectedRowStyle BackColor="#738A9C" Font-Bold="True" ForeColor="#F7F7F7" />
<HeaderStyle BackColor="#4A3C8C" Font-Bold="True" ForeColor="#F7F7F7" />
<AlternatingRowStyle BackColor="#F7F7F7" />
</asp:GridView>
</div>
</div>
</form>
</body>
</html>
using System;
using Microsoft.WindowsAzure;
namespace ProfileManager
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnAdd_Click(object sender, EventArgs e)
{
var storage = CloudStorageAccount.FromConfigurationSetting("AzureConnection");
var operations = new ProfileTableServiceContext(storage.TableEndpoint.ToString(), storage.Credentials);
operations.AddProfile(txtUserName.Text, txtEmail.Text);
grvProfiles.DataSource = operations.Profiles;
grvProfiles.DataBind();
}
}
}

CADENA DE CONEXIÓN PARA WINDOWS AZURE STORAGE
Cuando ya estemos listos para utilizar el Storage de Azure, necesitamos crear una cuenta de almacenamiento y cambiar la cadena de conexión:
<connectionStrings>
<add name="AzureConnection" connectionString="DefaultEndpointsProtocol=https;AccountName=YOUR_ACCOUNTNAME;AccountKey=PRIMARY_ACCESS_KEY"/>
</connectionStrings>
Nota: Existen una serie de combinaciones para realizar la conexión pero, por el momento, usaremos la mencionada a modo introductorio.
Si arrancamos de nuevo la aplicación y añadimos un par de registros, obtendríamos el siguiente resultado.

CLOUD STORAGE STUDIO
Para finalizar me gustaría mencionar la siguiente aplicación, la cual es bastante útil para visualizar, crear, modificar y eliminar elementos del Storage de Azure (tanto tablas, como blobs y queues). Su nombre es Cloud Storage Studio y, si bien es de pago, tenemos la posibilidad de probarla de forma gratuita durante 30 días.
Si accedemos a la tabla que acabamos de generar con esta demo, podemos ver todos los detalle de la misma tal y como se muestra en la siguiente captura.

¡Saludos!
Más artículos
Página siguiente >