Performancing Metrics

Return(GiS);

Return(GiS);

Resharper 5 y ASP.NET MVC

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, 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)

Sin duda alguna, para todos aquellos que usemos Resharper, podemos empezar a contar con sus consejos para aplicaciones ASP.NET MVC.

¡Saludos!

Posted: 9/2/2010 18:30 por Gisela | con no comments
Archivado en: ,
Intellisense para archivos .skin

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:

  1. En el menú de Visual Studio seleccionamos Tools > Options y, dentro del apartado Text Editor, nos ubicamos en File Extension

  2. 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. 

  3. 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!

Posted: 8/2/2010 23:23 por Gisela | con 9 comment(s) |
Archivado en:
Inyección de dependecias e Inversión de control

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.NETWindsor 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 :)

Posted: 7/2/2010 1:40 por Gisela | con 15 comment(s) |
T4MVC: Eliminando literales de controladores, acciones y vistas.

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!

Posted: 1/2/2010 19:27 por Gisela | con 4 comment(s)
Archivado en:
Llamar a un Ajax.ActionLink desde JQuery con temporizador

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:

  1. 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.

  2. 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.

  3. 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>
  4. 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.

  5. 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!

Posted: 25/1/2010 14:45 por Gisela | con 4 comment(s)
Archivado en: ,
Las tablas de Windows Azure Storage

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!

Creación de una nueva Storage Account en Windows Azure

Dentro de Windows Azure, no solamente podemos subir y consumir aplicaciones sino que, además, tenemos a nuestra disposición la parte de Storage (almacenamiento). Dentro de Windows Storage se encuentran las siguientes posibilidades:

  • Tablas: Se utilizan para el almacenamiento de datos estructurados pero no relacionales.
  • Queues: Las colas son útiles para almacenar mensajes en el orden en el que fueron recibidos para el procesamiento secuencial. Los objetos almacenados en Queue se insertan en un extremo y se quitan del otro.
  • Blobs: Sirven para el almacenamiento de archivos independientes.

Para utilizar cada uno de los apartados, primero debemos crear una nueva cuenta de almacenamiento, a través de los siguientes pasos:

1. Accedemos al portal de Windows Azure y seleccionamos Windows Azure y New Service.


2. En este apartado tenemos dos posibilidades: Storage Account y Hosted Services. Seleccionamos la primera de ellas.

3. Llegados a este punto, necesitamos poner un nombre al servicio y una descripción. Es importante tener en cuenta que este no es el nombre público sino más bien una forma de identificar esta cuenta de almacenamiento dentro de nuestra cuenta de Azure.

4. En este apartado debemos elegir qué nombre público deseamos que tenga este almacenamiento.

Para asegurarnos que está disponible, podemos pulsar sobre el botón Check Availability donde nos aparecerá un mensaje confirmando o denegando el nombre que acabamos de introducir.

En la parte inferior, tenemos un apartado muy importante: Dentro de la plataforma de Azure, podemos tener organizados nuestros servicios por grupos de afinidad. Todos aquellos servicios que permanezcan en el mismo grupo se encontrarán en la misma región. Si elegimos que no, nos permitirá seleccionar una región distinta. De lo contrario, podremos elegir uno de los grupos de afinidad que nos ofrece en la parte inferior, si ya tenemos alguno definido, o bien nos permitirá crear uno nuevo.

5. Por último, al pulsar sobre Create en la ventana anterior, nos mostrará un resumen de los datos relativos a la nueva cuenta de almacenamiento que acabamos crear.


Nota: CDN
Significa Content Delivery Network. El beneficio que conlleva es la mejora del rendimiento disminuyendo la latencia en la obtención de los datos del Storage, independientemente de la ubicación geográfica del usuario.

¡Saludos!

Posted: 17/1/2010 21:01 por Gisela | con no comments
Archivado en:
Subir una aplicación ASP.NET MVC a IIS 6

Cuando subimos una aplicación ASP.NET MVC a IIS 6 es muy posible que nos topemos con un error de tipo 404 si intentamos navegar por la aplicación:

Para poder solventarlo, necesitamos realizar una asignación de extensión para la aplicación (Application Extension Mapping) de la siguiente manera:

Accedemos al administrador de IIS y abrimos las propiedades de la aplicación ASP.NET MVC. Dentro de la pestaña Directorio virtual, pulsamos sobre el botón Configuración.


Pulsamos sobre el botón Insertar para asignar una extensión al directorio virtual.

Localizamos la dll aspnet_isapi.dll a través del botón Examinar, la cual está ubicada en \WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll

Deshabilitamos el campo "Comprobar si el archivo existe" y aceptamos.

Ahora todo debe ir sobre ruedas ;)

Para resolver el problema en otras versiones de IIS, podéis consultar el siguiente enlace (inglés): http://www.asp.net/learn/mvc/tutorial-08-cs.aspx

¡Saludos!

Posted: 7/1/2010 19:39 por Gisela | con 4 comment(s)
Archivado en: ,
Subir una aplicación ASP.NET MVC a Windows Azure

Uno de los primeros artículos que publiqué acerca de Windows Azure fue la manera de subir una aplicación. Actualmente, en el caso de las aplicaciones ASP.NET MVC es necesario realizar una serie de ajustes para que pueda funcionar correctamente, ya que de no ser así no conseguirá arrancar en la nube.

En Visual Studio 2010 Beta 2 ya tenemos disponible la plantilla para añadir un proyecto de tipo ASP.NET MVC en un entorno de nube pero en el caso de Visual Studio 2008 no es así, aunque no es imposible :)

Creamos un proyecto de tipo Cloud Service.

No seleccionamos ninguna plantilla puesto que vamos a añadir un proyecto ya creado.


Añadimos un proyecto existente en ASP.NET MVC y, con el botón derecho, seleccionamos Unload Proyect.
Para que Windows Azure reconozca qué tipo de rol tiene este proyecto, debemos agregar la siguiente línea en el archivo csproj.

 

<RoleType>Web</RoleType>

El archivo debe quedar de la siguiente manera:

Volvemos a pulsar con el botón derecho sobre el proyecto para seleccionar Reload Project.
Por otro lado, debemos cambiar la propiedad Copy Local a true de tres dll:

 

Al haber añadido un proyecto a una solución Cloud Service ya generada, necesitamos enlazar ambos proyectos. Para ello, nos posicionamos en la carpeta Roles y con el botón derecho seleccionamos Add => Web Role Project in solution... y elegimos el proyecto ASP.NET MVC.

Una vez realizados estos pasos, podremos subir el proyecto a la nube sin problemas =)

¡Saludos!

Posted: 6/1/2010 21:38 por Gisela | con 4 comment(s)
Access Control Service en Windows Azure Platform AppFabric

Hace algún tiempo estuve hablando de qué era AppFabric, cuáles eran los componentes que lo formaban e incluso se mostró un pequeño ejemplo de la parte de Service Bus. Para cerrar la base de Windows Azure Platform AppFabric, hoy voy a centrarme en Access Control.

Access Control Service se encarga de la autenticación y autorización de los usuarios para nuestras aplicaciones desde la nube. En esta release inicial existe la posibilidad de autenticar a los clientes a través de claves simétricas, con nombre de usuario y contraseña, y además la autenticación de clientes a través de ADFS v2 (Active Directory Federation Services). En un futuro, esperan poder soportar un número mayor de tokens como Facebook, cuentas de Google, Windows Live ID entre otros.

Antes de comenzar he de decir que parte del código ha sido tomado de alguno de los ejemplos del SDK de AppFabric o bien del Training Kit de Azure como la validación de los tokens o la validación de la cabecera y he intentado simplificar los mismos lo más posible, además de la adicción de comentarios para facilitar la compresión de los ejemplos.

¿CÓMO FUNCIONA ACCESS CONTROL SERVICE?



1. CONFIGURACIÓN DE ACCESS CONTROL SERVICE

Existen tres conceptos en cuanto a configuración se refiere: Token Policy, Issuer y Scope.

Token Policy está compuesto por un ID, un nombre y el tiempo de expiración de los tokens. Se encarga de firmar los tokens que se emiten y establecer el tiempo de vida de los mismos.

I
ssuer es un recurso de Access Control que se utiliza para registrar clientes del servicio. Cada issuer está compuesto por un ID, un nombre y dos claves. Generalmente se utiliza un issuer por cliente y cada cliente tendrá una clave a través de la cual conseguirá el token necesario para poder validar la comunicación con el servicio.

Scope se utiliza para agrupar reglas dentro de Access Control Service, enlazando una politica de token y la dirección a la que aplica. Se identifica por un ID y un nombre. Cada regla indica el Issuer asociado y las operaciones que puede realizar dentro del scope.

Existen varias opciones para crear cada uno de los recursos en el Access Control del namespace: A través de líneas de comandos (PowerShell o CMD) o bien a través de una interfaz que nos facilitan en el Windows Azure Platform Training Kit.
Si optamos por la línea de comandos, debemos dirigirnos a la ubicación donde hemos instalado el SDK de AppFabric, donde estará disponible la herramienta Acm.exe y su archivo de configuración.


En primer lugar, abrimos el archivo de configuración para establecer el nombre del Service Namespace que debemos tener creado en la nube y su clave de administración:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="host" value="accesscontrol.windows.net"/>
<add key="service" value="Service Namespace"/>
<add key="mgmtkey" value="Current Management Key"/>
</appSettings>
</configuration>

Accedemos a la consola de comandos y nos posicionamos en la carpeta Tools del SDK. Los comandos para crear cada uno de los recursos serían los siguientes:

Token Policy

acm create tokenpolicy -name:returngis -autogeneratekey

El name corresponde al nombre descriptivo que queremos darle al Token Policy que vamos a generar y además indicamos que las claves serán autogeneradas.

Scope

acm create scope -name:returngisscope -appliesto:http://localhost/Hello -tokenpolicyid:TOKEN_POLICY_ID

Al igual que el comando anterior, se indica el nombre que queremos asociar al nuevo scope y appliesto corresponde con la URL donde se alojará el servicio que se beneficiará de la autenticación de Access Control.
Cuando se creó el Token Policy en el apartado anterior, la respuesta al finalizar el comando nos facilitó el ID del token policy generado. Para generar el Scope es necesario indicarle qué token policy tiene asociado pasandole como valor el ID.

Issuer

acm create issuer -name:returngisconsumer -issuername:returngisconsumer -autogeneratekey

En el caso de issuer, no está asociado directamente con ningún otro recurso y solamente es necesario etiquetar el issuer como tal y el nombre que queremos utilizar para las credenciales. Las contraseñas serán autogeneradas.

Rule

acm create rule -name:returngisconsumer -scopeid:ID_SCOPE -inclaimissuerid:ID_ISSUER -inclaimtype:Issuer -inclaimvalue:returngisconsumer -outclaimtype:action -outclaimvalue:sayHello

Por último, debemos crear una regla para el issuer y dentro del scope generados anteriormente. En este ejemplo, estamos creando una regla donde el usuario returngisconsumer puede reclamar una acción llamada sayHello.

Desde mi punto de vista, esta opción puede ser bastante pesada ya que debemos ir recuperando a mano cada uno de los ID que nos devuelve cada comando para poder asignarselo al comando oportuno. Por ello, como comentaba anteriormente, disponemos de una interfaz que nos ayuda a realizar estos pasos. Si bien es cierto que la misma tiene algunos defectos, es bastante más cómoda.
Necesitamos descargar el Training Kit, donde disponemos de una serie de ejemplos que además he necesitado para generar este mismo. El proyecto se llama AcmBrowser y está ubicado en WindowsAzurePlatformKit\Labs\IntroAppFabricAccessControl\Source\Assets\AcmBrowser

Para poder ejecutar la aplicación es necesario acceder al proyecto, compilarlo y obtener el ejecutable para poder utilizarlo fuera de Visual Studio. De esta manera, si ejecutamos AcmBrowser.exe desde el raíz, obtendremos la siguiente interfaz:

Ya no es necesario modificar ningún archivo de configuración para añadir las credenciales que nos dan la posibilidad de administrar Access Control. En este entorno, debemos añadir las mismas cada vez que arranquemos la aplicación en los campos Service Namespace y Management Key.
Como opciones, podemos limpiar los valores introducidos en el Namespace, recuperarlos y guardarlos tanto en local como en la nube.

Nota:
En el caso del salvado, cada vez que pulsamos sobre esta acción intenta guardar cada uno de los valores que estamos visualizando en la interfaz, independientemente de si ya están incluidos en la nube o no. Por ello, recomiendo hacer una copia local, a través del disquete sin nube, y luego limpiar la nube y salvar de nuevo. De lo contrario, saltará una excepción ;)


Si pulsamos en la carpeta con la nube (Load from Cloud), y hemos realizado los comandos anteriores a través de Acm.exe, observaremos lo siguiente:

Como era de esperar, aparece cada uno de los recursos introducidos anteriormente agrupados por Issuers, Scopes y Token Policies.

ISSUERS

Si seleccionamosel issuer que creamos a través de la línea de comandos, podemos comprobar el nombre de issuer, el tipo de algoritmo utilizado (por defecto es Symmetric256BitKey) la clave actual y anterior. El cliente necesitará conocer tanto el Issuer Name como la Current Key para obtener un token válido.

TOKEN POLICIES


En este caso observamos que las únicas opciones disponibles son el tiempo de expiración de los tokens y regenerar la clave, además de facilitarnos la actual. Esta clave será necesaria para que el servicio pueda validar la petición del cliente.

Por último, podemos visualizar las opciones configuradas en la regla asociada al scope y al issuer que tenemos actualmente.

CREACIÓN DEL SERVIDOR

Para poder probar este ejemplo, he creado un servicio con un método de lo más sencillo para mostrar el modo de autentificación a través de los tokens que nos ofrece la nube. Es necesario abrir Visual Studio como administrador.

using System.ServiceModel;
namespace ServerACS
{
[ServiceContract]
public interface IService
{
[OperationContract]
string Hello(string name);
}
}

namespace ServerACS
{
public class Service : IService
{
public string Hello(string name)
{
return "Hello " + name;
}
}
}

Para crear el código de la consola, he tomado como referencia el proyecto WCFAuthorizationManager que nos ofrecen como ejemplo del SDK de AppFabric.

using System;
using System.ServiceModel;
using System.ServiceModel.Web;
using ServerACS.ValidationACS;

namespace ServerACS
{
class Program
{
static void Main()
{
const string serviceNamespace = "returngis";
const string tokenPolicyKey = "KEY=";
const string audience = "http://localhost/Hello";
const string claim = "action";
const string value = "sayHello";

var binding = new WebHttpBinding(WebHttpSecurityMode.None);
var address = new Uri(audience);

var host = new WebServiceHost(typeof(Service));
host.AddServiceEndpoint(typeof(IService), binding, address);

//Validación para los tokens, proporcionados por ACS, recibidos en las peticiones de los clientes
host.Authorization.ServiceAuthorizationManager = new AcsAuthorizationManager(
serviceNamespace,
audience,
Convert.FromBase64String(tokenPolicyKey),
claim,
value
);
host.Open();
Console.WriteLine("Service is listening....");
Console.ReadLine();
}
}
}

Para ver todo lo necesario a primera vista, no he realizado modificaciones en el web.config para poder ver claramente lo necesario del lado del servidor.
Lo más interesante es el apartado donde estamos creando un ServiceAuthorizationManager personalizado, facilitado también en el ejemplo del SDK, con algunas simplificaciones y comentarios añadidos para comprender qué hace en cada momento. Así pues recibe el service Namespace, la URL a la que aplica, la clave de token policy, el tipo que se reclama y el valor que contiene el tipo.

using System.Collections.Generic;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace ServerACS.ValidationACS
{
public class AcsAuthorizationManager : ServiceAuthorizationManager
{
private readonly TokenValidator _tokenValidator;
private readonly string _claimType;
private readonly string _claimValue;


public AcsAuthorizationManager(string serviceNamespace, string audience, byte[] tokenPolicyKey, string claimType, string claimValue)
{
_tokenValidator = new TokenValidator(serviceNamespace, audience, tokenPolicyKey);
_claimType = claimType;
_claimValue = claimValue;
}

protected override bool CheckAccessCore(OperationContext operationContext)
{
//Recuperamos las credenciales de la cabecera
string authorizationHeader = null;
if (WebOperationContext.Current != null)
{
authorizationHeader = WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Authorization];
}
//Si no hay credenciales, no seguimos comprobando. La validación ha fallado.
if (string.IsNullOrEmpty(authorizationHeader)) return false;

//Si no comienza con WRAP (Web Resource Authorization Protocol) no es una cabecera válida.
if (!authorizationHeader.StartsWith("WRAP ")) return false;

//Separamos los elementos de la cabecera
string[] values = authorizationHeader.Substring("WRAP ".Length).Split(new[] { '=' }, 2);

//Si el número de partes es distinto de dos, no es válido.
if (values.Length != 2) return false;

//Si el primer valor no corresponde con access_token, tampoco es correcto.
if (values[0] != "access_token") return false;

//Si el segundo valor no está delimitado por \, también sería incorrecto.
if (!values[1].StartsWith("\"") || !values[1].EndsWith("\"")) return false;

//Recuperamos el token
string token = values[1].Substring(1, values[1].Length - 2);

//Utilizamos el TokenValidator para comprobar el token
if (!_tokenValidator.Validate(token)) return false;

//Recuperamos la acción que se quiere realizar y el nombre de la acción
Dictionary<string, string> claims = _tokenValidator.GetNameValues(token);

//Se intenta recuperar el nombre de la acción que solicita el cliente (sayHello)
string actionValue;
if (!claims.TryGetValue(_claimType, out actionValue)) return false;

//Comprueba que el valor que se reclama es igual que el nombre de la acción que posee el servicio
if (!actionValue.Equals(_claimValue)) return false;

return true;
}
}
}

Como podemos ver, en esta clase se está heredando de ServiceAuthorizationManager y se sobreescribe el método CheckAccessCore para realizar toda la validación de la cabecera de autorización de la petición que se ha recibido. La mayoría de las comprobaciones son bastante simples, comprobando la estructura recibida.
Dentro de este código, debemos prestar especial atención al punto donde se valida el token a través de una instancia de la clase TokenValidation proporcionada también en los ejemplos del SDK.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;

namespace ServerACS.ValidationACS
{
public class TokenValidator
{
private const string IssuerLabel = "Issuer";
private const string ExpiresLabel = "ExpiresOn";
private const string AudienceLabel = "Audience";
private const string HmacSha256Label = "HMACSHA256";

private readonly byte[] _tokenKey;
private readonly string _trustedTokenIssuer;
private readonly Uri _trustedAudienceValue;

public TokenValidator(string serviceNamespace, string audience, byte[] tokenKey)
{
_tokenKey = tokenKey;
_trustedTokenIssuer = string.Format("https://{0}.accesscontrol.windows.net/", serviceNamespace.ToLowerInvariant());
_trustedAudienceValue = new Uri(audience);
}

public bool Validate(string token)
{
if (!IsHmacValid(token, _tokenKey))
{
return false;
}

if (IsExpired(token))
{
return false;
}

if (!IsIssuerTrusted(token))
{
return false;
}

if (!IsAudienceTrusted(token))
{
return false;
}

return true;
}

public Dictionary<string, string> GetNameValues(string token)
{
if (string.IsNullOrEmpty(token))
{
throw new ArgumentException();
}

return
token
.Split('&')
.Aggregate(
new Dictionary<string, string>(),
(dict, rawNameValue) =>
{
if (rawNameValue == string.Empty)
{
return dict;
}

string[] nameValue = rawNameValue.Split('=');

if (nameValue.Length != 2)
{
throw new ArgumentException("Invalid formEncodedstring - contains a name/value pair missing an = character");
}

if (dict.ContainsKey(nameValue[0]))
{
throw new ArgumentException("Repeated name/value pair in form");
}

dict.Add(HttpUtility.UrlDecode(nameValue[0]), HttpUtility.UrlDecode(nameValue[1]));
return dict;
});
}

private static ulong GenerateTimeStamp()
{
// Default implementation of epoch time
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToUInt64(ts.TotalSeconds);
}

private bool IsAudienceTrusted(string token)
{
Dictionary<string, string> tokenValues = GetNameValues(token);

string audienceValue;

tokenValues.TryGetValue(AudienceLabel, out audienceValue);

if (!string.IsNullOrEmpty(audienceValue))
{
var audienceValueUri = new Uri(audienceValue);
if (audienceValueUri.Equals(_trustedAudienceValue))
{
return true;
}
}

return false;
}

private bool IsIssuerTrusted(string token)
{
Dictionary<string, string> tokenValues = GetNameValues(token);

string issuerName;

tokenValues.TryGetValue(IssuerLabel, out issuerName);

if (!string.IsNullOrEmpty(issuerName))
{
if (issuerName.Equals(_trustedTokenIssuer))
{
return true;
}
}

return false;
}

private static bool IsHmacValid(string swt, byte[] sha256HmacKey)
{
string[] swtWithSignature = swt.Split(new[] { "&" + HmacSha256Label + "=" }, StringSplitOptions.None);

if ((swtWithSignature == null) || (swtWithSignature.Length != 2))
{
return false;
}

var hmac = new HMACSHA256(sha256HmacKey);

byte[] locallyGeneratedSignatureInBytes = hmac.ComputeHash(Encoding.ASCII.GetBytes(swtWithSignature[0]));

string locallyGeneratedSignature = HttpUtility.UrlEncode(Convert.ToBase64String(locallyGeneratedSignatureInBytes));

return locallyGeneratedSignature == swtWithSignature[1];
}

private bool IsExpired(string swt)
{
try
{
Dictionary<string, string> nameValues = GetNameValues(swt);
string expiresOnValue = nameValues[ExpiresLabel];
ulong expiresOn = Convert.ToUInt64(expiresOnValue);
ulong currentTime = Convert.ToUInt64(GenerateTimeStamp());

if (currentTime > expiresOn)
{
return true;
}

return false;
}
catch (KeyNotFoundException)
{
throw new ArgumentException();
}
}
}
}



A través de la clave del Token Policy, el token facilitado por el cliente, la dirección del service namespace y la uri sobre la que aplica nuestra política es capaz de determinar si es un token válido a través de una serie de filtros.
Cuando un cliente haga una petición, en primer lugar se comprobará la cabecera de la misma, a través del Token Validator se testeará su veracidad y, en caso de ser un token válido, realizará la llamada al método solicitado por el cliente.

CREACIÓN DEL CLIENTE

Como pudimos ver al comienzo de este post, el cliente debe realizar también dos pasos para poder realizar una comunicación con éxito. En primer lugar, debemos solicitar un token proporcionado por el Access Control y, si la obtención del token se hace con éxito, podremos montar la petición para ser enviada al servicio.

using System;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Web;
using ServerACS;

namespace Client
{
class Program
{
private const string ServiceName = "returngis";
private const string IssuerKey = "YOUR ISSUER KEY";
private const string AcsBaseAddress = "accesscontrol.windows.net";

static void Main()
{
Console.WriteLine("Enter your name:");
string name = Console.ReadLine();
string token = GetToken();

var binding = new WebHttpBinding(WebHttpSecurityMode.None);
var uri = new Uri("http://localhost/Hello");

var webChannelFactory = new WebChannelFactory<IService>(binding, uri);

var channel = webChannelFactory.CreateChannel();

//Este es el formato que debe seguir la cabecera para
//que el servidor lo reconozca como un token válido
string header = string.Format("WRAP access_token=\"{0}\"", HttpUtility.UrlDecode(token));

using (new OperationContextScope(channel as IContextChannel))
{
WebOperationContext.Current.OutgoingRequest.Headers.Add("authorization", header);

Console.WriteLine(channel.Hello(name));
}
Console.WriteLine("Presh Enter");
Console.ReadLine();

webChannelFactory.Close();
}

private static string GetToken()
{
var client = new WebClient
{
BaseAddress = string.Format("https://{0}.{1}", ServiceName, AcsBaseAddress)
};

var values = new NameValueCollection
{
{"wrap_name", "returngisconsumer"},
{"wrap_password", IssuerKey},
{"wrap_scope", "http://localhost/Hello/"}
};

byte[] responseBytes = client.UploadValues("WRAPv0.9", "POST", values);

string response = Encoding.UTF8.GetString(responseBytes);

Console.WriteLine("\nToken From Access Control Service:{0}\n", response);

return response
.Split('&')
.Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
.Split('=')[1];

}
}
}

La primera acción que realiza el cliente, nada más recuperar el texto introducido por consola, es intentar recuperar el token a través del método GetToken. En él creamos un objeto de tipo Webclient para realizar una llamada a través de POST con las credenciales generadas cuando creamos el recurso Issuer returngisconsumer. Si obtenemos un token con éxito, se mostrará por pantalla y lo retornaremos a Main.

Tanto la clave de Token Policy como de Issuer podemos recupearlas a través de la consola al finalizar la creación de los recursos o a través de la interfaz de AcmBrowser.

Facilito el ejemplo con los comentarios y algo más simplificados por si fuera de utilidad.

¡Saludos!

Posted: 3/1/2010 21:00 por Gisela | con 4 comment(s)
Archivado en:
Client Side Validation con ASP.NET MVC 2

En un post anterior, IDataErrorInfo y MVC, se hablaba de la posibilidad de validar nuestros objetos desde el modelo y comprobar el resultado de las validaciones través de ModelState.IsValid. Con ASP.NET MVC 2 llegaremos más allá de la mano de JQuery y Data Annotations.

Desde las primeras revisiones de esta segunda versión, tenemos la posibilidad de reutilizar las validaciones descritas en nuestros objetos en el lado del cliente. Para ello, necesitamos especificar las validaciones oportunas en los objetos, utilizando Data Annotations.
Siguiendo con los ejemplos basados en Twitter, en esta ocasión he creado un cliente que recupera el time line de tus amigos y además te permite responderlos o mandarles un mensaje directo.

Los mensajes directos se utilizan para realizar envíos privados a una persona en concreto. Para poder envíar un mensaje de este tipo, es necesario escribir un texto de tamaño limitado y, además, que tenga un destinatario.

using System.ComponentModel.DataAnnotations;

namespace ClientSideValidation.Models.Objects
{
public class DirectMessage
{
public long ID { get; set; }
[Required(ErrorMessage = "Tweet message is required!")]
[StringLength(131, ErrorMessage = "Your direct message is too long!")]
public string Message { get; set; }
public TwittUser Addressee { get; set; }
}
}

Los dos atributos, Required y StringLength, pertenecen a la propiedad Message donde se está indicando que el mismo es requerido y además que tiene un tamaño máximo de 131 caracteres.
La última propiedad corresponde al remitente, el cual tiene sus propias validaciones en la clase correspondiente.

using System.ComponentModel.DataAnnotations;

namespace ClientSideValidation.Models.Objects
{
public class TwittUser
{
public long ID { get; set; }
[Required(ErrorMessage = "User name is required!")]
public string UserName { get; set; }
public string PhotoURL { get; set; }
public string Status { get; set; }
}
}

Quizás la opción de decorar las propiedades de una clase dificultan la compresión de la misma. Si queremos hacer una separación de las validaciones, podemos hacer uso del atributo MetaDataType creando una clase independiente.

using System.ComponentModel.DataAnnotations;

namespace ClientSideValidation.Models.Objects
{
[MetadataType(typeof(TweetReplyMetadata))]
public class TweetReply
{
public long ID { get; set; }
public string Text { get; set; }
public TwittUser Addressee { get; set; }
public Tweet TweetToReply { get; set; }
}
}

Se decorda la principal indicando en el typeof la clase que contiene las validaciones.

using System.ComponentModel.DataAnnotations;

namespace ClientSideValidation.Models.Objects
{
public class TweetReplyMetadata
{
[Required(ErrorMessage = "Reply message is required!")]
[StringLength(131, ErrorMessage = "Your reply is too long!")]
public string Text { get; set; }
}
}

Por otro lado, debemos generar el controlador donde seguiremos utilizando ModelState para comprobar el estado de las validaciones desde el servidor.

using System.Web.Mvc;
using ClientSideValidation.Models;
using ClientSideValidation.Models.Objects;

namespace ClientSideValidation.Controllers
{
public class TweetController : Controller
{
private readonly ITweetService _twitterService;

public TweetController()
{
_twitterService = new TweetService();
}

public ActionResult Index()
{
return View(_twitterService.FetchTweets());
}


public ActionResult Reply(long id)
{
var reply = new TweetReply { TweetToReply = _twitterService.ShowTweet(id) };
return View(reply);
}

[HttpPost]
public ActionResult Reply(TweetReply reply)
{
if (ModelState.IsValid)
{
_twitterService.ReplyTweet(reply);
return RedirectToAction("Index");
}
return View(reply);
}

public ActionResult SendDM(long ID)
{
var DM = new DirectMessage { Addressee = _twitterService.RetrieveUser(ID) };
return View(DM);
}

[HttpPost]
public ActionResult SendDM(DirectMessage DM)
{
if (ModelState.IsValid)
{
_twitterService.SendDM(DM);
return RedirectToAction("Index");
}
return View(DM);
}
}
}

Una vez creadas las vistas, podríamos intentar el envío de un mensaje directo (sin introducir el texto del mismo) y obtendríamos el siguiente resultado:

Actualmente, cuando pulsamos en el botón Send, estamos acudiendo al servidor y ModelState está comprobando si nuestros objetos son válidos o no para mantenernos en la misma vista y mostrar el error o regresar de nuevo al Time Line. Quizás, si un usuario hace una acción de este tipo, no nos interesa siquiera que acuda al servidor. Aquí es donde viene la mágia :)

  1. Referenciamos las librerías javascript, ubicadas en la carpeta Scripts, en la Master Page en este orden:
    <%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

    <!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>
    <asp:ContentPlaceHolder ID="TitleContent" runat="server" />
    </title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />

    <script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>

    <script src="../../Scripts/jquery.validate.min.js" type="text/javascript"></script>

    <script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>

    <script src="../../Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>

    <script src="../../Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>


    </head>

  2. Habilitamos la línea Html.EnableClientValidation(); en las vistas donde queramos realizar la validación en el lado del cliente.
    <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ClientSideValidation.Models.Objects.DirectMessage>" %>

    <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Direct Message
    </asp:Content>
    <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>
    Direct Message</h2>
    <%Html.EnableClientValidation(); %>
    <% using (Html.BeginForm())
    {%>
    <div id="tweet">
    <img src="<%=Model.Addressee.PhotoURL%>" />
    <div>
    <%=Html.DisplayFor(m=>m.Addressee.Status) %>
    </div>
    <%=Html.HiddenFor(m=>m.Addressee.ID) %>
    </div>
    <fieldset>
    <legend>
    <%=Model.Addressee.UserName %></legend>
    <div class="editor-label">
    <%= Html.LabelFor(model => model.Message) %>
    </div>
    <div class="editor-field">
    <%= Html.TextAreaFor(model => model.Message) %>
    <%= Html.ValidationMessageFor(model => model.Message) %>
    </div>
    <p>
    <input type="submit" class="buttonStyle" value="Send!" />
    </p>
    </fieldset>
    <% } %>
    <div>
    <%=Html.ActionLink("Back to TimeLine", "Index") %>
    </div>
    </asp:Content>

Si lanzamos de nuevo la aplicación, y realizamos la misma operación que antes ¡El resultado es exactamente el mismo pero sin ir al servidor!
Si observamos el código fuente de la página, observamos que la forma de trabajar es mediante la creación de un objeto JSON donde almacena cada unas de las validaciones alojadas en nuestras clases.

//<![CDATA[CommunityServer
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"FieldName":"Message","ReplaceValidationMessageContents":true,"ValidationMessageId":"form0_Message_validationMessage","ValidationRules":[{"ErrorMessage":"Your direct message is too long!","ValidationParameters":{"minimumLength":0,"maximumLength":131},"ValidationType":"stringLength"},{"ErrorMessage":"Tweet message is required!","ValidationParameters":{},"ValidationType":"required"}]}],"FormId":"form0","ReplaceValidationSummary":false});
//]]>

Como siempre, facilito el código fuente para poder probar el ejemplo completo.

¡Saludos y feliz 2010!

Posted: 31/12/2009 19:27 por Gisela | con 2 comment(s)
Archivado en: ,
AsyncController en ASP.NET MVC 2

Una de las novedades previstas para ASP.NET MVC 2 será la posibilidad de crear controladores asíncronos. Por el momento, podemos hacer la primera toma de contacto con la versión Release Candidate de esta versión.

¿Qué conseguimos con los controladores asíncronos?

Imaginemos que necesitamos lanzar una serie de procesos con una duración más prolongada en el tiempo de lo normal. Cuando una petición llega al servidor, uno de los hilos se encarga de la misma. Hasta que la petición no finalice, ese hilo quedará bloqueado y no podrá atender otras peticiones.

En la mayoría de las ocasiones no resulta problemático dado que el pool tiene una cantidad considerable de hilos disponibles para ser bloqueados. Sin embargo, los hilos son limitados y en aplicaciones grandes, donde hay una cantidad considerable de usuarios y pueden existir múltiples hilos con procesos de larga duración, puede llegar a ser un problema produciendo un error de tipo HTTP 503 (Server too busy).

Antes de comenzar, es necesario tener en cuenta una serie de reglas a la hora de crear un controlador de este tipo:

  1. Se crean dos métodos por acción:
    • Uno iniciará el proceso de manera asíncrona y debe contener en su nombre la terminación Async.
    • Otro método, que será invocado cuando la operación asíncrona finalice, será nombrado con  Completed al final.
  2. Ambos métodos deben ser públicos y no pueden ser estáticos.
  3. No está permitido tener parámetros genéricos en las acciones.
  4. No podemos usar sobrecarga de parámetros.Solamente está permitido en los casos que se especifica otro Http Verb o declarando la sobrecarga como NoActionAttribute.
  5. El controlador heredará de AsyncController en vez de Controller.

Para verlo más claro, he creado una pequeña aplicación simulando un cliente de la red social Twitter, utilizando la librería Twitterizer. Las acciones que se podrán realizar a través de la aplicación serán las siguientes:

Para todos estos casos, necesitamos recuperar una serie de datos de la red social que podrá tardar más o menos en función de un conjunto de circunstancias. Si varios usuarios accedieran a la vez a nuestra aplicación y la tarea de recuperar sus tweets, direct messages, mentions, etcétera fuera una tarea costosa, llegaría un momento en el que nuestro servidor no dispondría de threads libres para cubrir el número de peticiones actuales.

Siguiendo las reglas comentadas anteriormente, una de las acciones del controlador podría tener la siguiente estructura:

using System.Collections.Generic;
using System.Web.Mvc;
using AsyncControllerMVC2.Models;
using AsyncControllerMVC2.Models.Objects;

namespace AsyncControllerMVC2.Controllers
{
public class TweetController : AsyncController
{
private readonly TwitterService _twitterService = new TwitterService();

public void TweetsAsync()
{
AsyncManager.OutstandingOperations.Increment();
AsyncManager.Parameters["tweets"] = _twitterService.FetchTweets();
AsyncManager.OutstandingOperations.Decrement();
}

public ActionResult TweetsCompleted(List<TwitterObject> tweets)
{
return View(tweets);
}
}
}

En primer lugar, observamos que efectivamente el controlador hereda de AsyncController para poder ofrecernos sus ventajas. Gracias a esta herencia, disponemos de AsyncManager que nos dará soporte en las operaciones asíncronas.

TweetsAsync será el método encargado de recuperar los tweets. En él podemos ver que se hace uso de OutstandingOperations, que se utiliza para notificar a ASP.NET de cuántas operaciones tiene pendientes en ese momento. Tal y como nos comentan en la documentación de MSDN, es necesario utilizarlo al comienzo del método ya que ASP.NET no es capaz de determinar cuántas operaciones fueron iniciadas por la acción o cuándo fueron completadas.

Entre medias, se realiza la llamada a un repositorio, donde se hace uso de la librería Twitterizer para recuperar el timeline de los amigos del usuario, y se guarda el resultado de la llamada en la propiedad Parameters de tipo diccionario.

using System;
using System.Collections.Generic;
using System.Threading;
using AsyncControllerMVC2.Models.Objects;
using Twitterizer.Framework;

namespace AsyncControllerMVC2.Models
{
public class TwitterService: ITwitterService
{
private const string User = "YOUR USER";
private const string Pass = "YOUR PASS";
private readonly TimeSpan _waitTime = new TimeSpan(0, 2, 0);
private readonly Twitter _twitterAccount;
public TwitterService()
{
_twitterAccount = new Twitter(User, Pass);
}

public List<TwitterObject> FetchTweets()
{
var tweets = new List<TwitterObject>();
foreach (TwitterStatus statusFriend in _twitterAccount.Status.FriendsTimeline())
{
var tweet = new TwitterObject
{
User = statusFriend.TwitterUser.UserName,
Photo = statusFriend.TwitterUser.ProfileImageUri,
Text = statusFriend.Text

};
tweets.Add(tweet);
}
Thread.Sleep(_waitTime);
return tweets;
}
}
}

Para crear un proceso con una duración considerable generé un TimeSpan con un minuto de duración para que, cuando consiga los últimos tweets de la cuenta en cuestión, espere durante un minuto antes de ser retornados de nuevo al controlador.

Cuando la propiedad OutstandingOperations sea igual a cero, ASP.NET finaliza las operaciones asíncronas y llama a la acción TweetCompleted que retorna el resultado recibido como parámetro. Para poder recibir el resultado almacenado en Parameters, es necesario respetar el tipo del objeto guardado.

Con los controladores asíncronos no significa que disminuya el tiempo de espera pero conseguiremos que el servidor no quede completamente bloqueado esperando a los hilos pendientes.

Adjunto el proyecto con todas las acciones de manera asíncrona.

Fuente: http://msdn.microsoft.com/en-us/library/ee728598%28VS.100%29.aspx

Posted: 24/12/2009 2:26 por Gisela | con 4 comment(s)
Archivado en:
InstanceContextMode en WCF

Cuando creamos un servicio con Windows Communication Foundation, debemos conocer  el comportamiento que queremos que tenga en cuanto a sus instancias se refiere. Para controlar el tiempo de vida del contexto disponemos de los siguientes modos:

  • PerCall: Cada cliente creará una nueva instancia por llamada al servicio.
  • PerSession: Se genera una instancia por sesión.
  • Single: Se utiliza la misma instancia para todas las peticiones independientemente del cliente.

 

 

Como he encontrado mucha literatura al respecto y los ejemplos han sido prácticamente el mismo en todos los sitios, he creado uno personalizado donde se realiza el envío de tweets e intento recuperar los mismos utilizando los distintos modos.
Creo un proyecto de tipo WCF Service Library:


Elimino tanto la clase y la interfaz generada por la plantilla y creo una clase Tweet.cs con el rol de DataContract.

using System.Runtime.Serialization;

namespace InstanceContextModeWCF
{
[DataContract]
public class Tweet
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string User { get; set; }
[DataMember]
public string Message { get; set; }
}
}



Una interfaz como ServiceContract

using System.Collections.Generic;
using System.ServiceModel;

namespace InstanceContextModeWCF
{
[ServiceContract]
public interface ITwitter
{
[OperationContract]
void SendTweet(Tweet tweet);

[OperationContract]
List<Tweet> GetMyTweets();
}
}

Y por último la clase que implementa nuestro contrato.

using System.Collections.Generic;
using System.ServiceModel;

namespace InstanceContextModeWCF
{
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class Twitter : ITwitter
{
private readonly List<Tweet> _tweets = new List<Tweet>();

public void SendTweet(Tweet tweet)
{
_tweets.Add(tweet);
}

public List<Tweet> GetMyTweets()
{
return _tweets;
}
}
}

En la clase Twitter he dejado comentadas las tres posibilidades. ¡Veamos cada una de ellas!

PER CALL

Cuando activamos el modo PerCall nuestro servicio se comportará de la siguiente manera: Creará una instancia para realizar la llamada al método solicitado y, una vez que haya finalizado la llamada, destruirá la instancia creada.

Para este primer ejemplo he activado la primera línea comentada y he seguido los pasos en el orden que señalo en la imagen. Como podemos ver, he creado una conexión con el programa que está actuando como servidor, he rellenado el apartado de mensaje y he enviado el mismo. En este caso, no ha sido posible recuperar el tweet al realizar una nueva llamada con PerCall.

PER SESSION

Con PerSession vamos a utilizar la misma instancia hasta que la sesión finalice. Es decir, un cliente crea un canal y tiene la posibilidad de realizar distintas llamadas a distintos métodos dentro del mismo contexto. Siguiendo el mismo ejemplo, habilitando la segunda línea y deshabilitando la primera, realizamos las mismas acciones desde el cliente.

Como la instancia permanece con vida durante toda la sesión, el cliente es capaz de poder enviar un tweet y después recuperarlo. Sin embargo, si nosotros pulsamos sobre Disconnect  e intentamos recuperar el tweet enviado a través del botón Get All!, la información ya habrá desaparecido al haber creado una nueva sesión.

private void btnConnect_Click(object sender, EventArgs e)
{
_channel = new ChannelFactory<ITwitter>("TwitterService");
_client = _channel.CreateChannel();
lblState.Text = "Connected!";
lblState.ForeColor = System.Drawing.Color.Blue;
}

private void btnDisconnect_Click(object sender, EventArgs e)
{
_channel.Close();
lblState.Text = "Disconnected!";
lblState.ForeColor = System.Drawing.Color.Red;
}

SINGLE

El modo Single se utiliza para mantener una misma instancia desde la primera petición de un cliente cualquiera hasta que el servicio se ha parado completamente.

Si habilitamos el último modo comentado en el código, y deshabilitamos el anterior, podemos realizar los siguientes pasos:

Si cerramos y abrimos de nuevo el cliente, sin cerrar el programa servidor, y pulsamos de nuevo en Get All!  descubrimos que nuestro tweet sigue con vida ;)

Adjunto el ejemplo completo, con el servicio, cliente y servidor. Están los tres proyectos en una misma solución. En cualquier caso, podemos abrir los .exe ubicados en el bin de cada proyecto como administrador o abrir dos Visual Studio con credenciales de administrador para poder debuggear en Vista o Windows 7 =)

¡Saludos!

Posted: 19/12/2009 22:47 por Gisela | con 6 comment(s) |
Archivado en:
Routing y ASP.NET MVC

Cuando hablamos de URL amigables estamos pensando en una dirección donde un usuario puede interpretar, de forma relativamente clara, el punto donde se encuentra dentro de un sitio web. Por ejemplo:

http://www.sitioweb.com/index.aspx?accion=comprar&producto=impresora&modelo=f380

http://www.sitioweb.com/Comprar/Impresora/F380

Aunque la acción que van a realizar ambas es la misma, la segunda opción ofrece más ventajas de cara "al exterior" por decirlo de alguna manera. Gracias a las URL amigables o semánticas el usuario obtendrá unas direcciones más fáciles de recordar, a la par que comprensibles, y además conseguiremos mejorar la indexación de nuestro sitio web en los distintos buscadores.

Gracias ASP.NET MVC conseguimos esta adaptación de una forma, en un principio, "transparente" para el desarrollador, nada más empezar a trabajar con un proyecto de este tipo. Para comenzar, debemos situarnos en el archivo Global.asax de nuestra aplicación, donde aparecerá por defecto el método de registro de las rutas disponibles.

using System.Web.Mvc;
using System.Web.Routing;

namespace MovieManager
{
// 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 = "Movie", action = "Index", id = "" } // Parameter defaults
);

}

protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
}

 Las rutas de la aplicación deben estar definidas justo antes de poder recibir cualquier petición. Es por ello que, de forma predeterminada, el registro de rutas se lleva a cabo en el archivo Global.asax. En él podemos observar dos métodos: RegisterRoutes y Application_Start. En RegisterRoutes es donde podemos añadir cada una de las rutas personalizadas y sus valores por defecto.

En primer lugar, se está restringiendo las peticiones con extensión .axd, como pueden ser Trace.axd, WebResource.axd, etcétera a través de routes.IgnoreRoute. En la segunda línea, aparece la llamada al método MapRoute que se utiliza para definir las rutas aceptadas. De forma automática, al generar la plantilla de ASP.NET MVC, se da de alta la ruta que aparece en estos momentos, donde podemos ver que recibe el nombre del controlador, la acción y un parámetro llamado id. Justo debajo, aparecen los valores por defecto para cada uno de ellos en el caso de no ser especificados en la petición.
Los nombres entre corchetes son parámetros, datos dinámicos que pueden ser escritos por el usuario o no. De no especificar ninguno, se utilizarán los valores por defecto descritos en la siguiente línea.

Al tener valores por defecto, la flexibilidad aumenta considerablemente y sin modificar absolutamente nada las rutas, podemos solicitar información de la siguiente manera:

Utilizando los valores por defecto:

http://www.sitio.com/

http://www.sitio.com/Movie

http://www.sitio.com/Movie/Index

Utilizando otras peticiones por GET, con valores distintos a los establecidos:

http://localhost:puerto/Movie/Edit/1

http://localhost:49312/Movie/Create

http://localhost:49312/Movie/Details/2

Pero quizás necesitamos llegar más allá o simplemente queremos que nuestras URL tengan una estructura diferente.
Supongamos que necesitamos generar una ruta donde queremos aclarar que la acción a realizar es la búsqueda por Género. Además no queremos que aparezca en dicha ruta el nombre la acción del controlador.

routes.MapRoute(
"ByGenre", // Route name
"List/Movies/Genre/{genre}", // URL with parameters
new { controller = "Movie", action = "Index", genre = (String)null } // Parameter defaults
);

En este caso, cuando solicitemos un resultado a través de List/Movies/Genre/Drama por ejemplo, aparecerán todas aquellas películas que se correspondan con el género indicado en última posición. Como ya comenté, no es necesario si quiera contemplar el nombre del controlador o el nombre real de la acción que queremos invocar ya que esa información se encuentra en los parámetros indicados más abajo.

NOTA: El orden en el cual se registran las rutas es muy importante ya que se tiene en cuenta el mismo. Si nosotros agregamos la nueva ruta para el filtrado por género después de la ruta por defecto ocurriría lo siguiente:

 


 

Por norma general, sería conveniente pensar en el registro de rutas por preferencia de aceptación: La que consideremos más importante ocupará la primera posición y en último lugar aparecerá la ruta por defecto.

Por otro lado, es posible que necesitemos restringir además los datos aceptados por nuestra ruta. Esto se consigue con la ayuda de los constraint.

routes.MapRoute(
"ByDate", // Route name
"List/Movies/Date/{date}", // URL with parameters
new { controller = "Movie", action = "Index", date = (DateTime?)null },
new { date = @"\d{2}-\d{2}-\d{4}" } // Parameter defaults
);

Si por ejemplo intentáramos introducir un valor de tipo string, aparecería la página de error mostrada anteriormente.

Por último, debemos tener en cuenta que, para poder recuperar los valores introducidos, el nombre del parámetro definido en el archivo Global.asax debe corresponder en nombre al que recibe la acción.

public ActionResult Index(string genre, DateTime? date)
{
return View(_movieRepository.ListMovies(genre, date));
}


Facilito el proyecto para probar el ejemplo entero ;)

¡Saludos!

Posted: 18/12/2009 13:18 por Gisela | con 4 comment(s) |
Archivado en:
Manipulación de elementos con JQuery

Cuando recuperamos un elemento a través de los selectores puede ser por varios motivos: Añadir un evento, modificar el estilo, enlazar algún efecto e incluso alterar de alguna manera el contenido del mismo.
Centrándome esta vez en la manipulación de elementos, voy a dedicar este post a una serie de funciones que nos ayudarán con nuestro cometido =)

  • html: Recupera el contenido HTML de un elemento.
    $("#btnHTML").click(function(e) {                
    alert($("#parrafoConHTML").html());
    });

    El resultado podría ser algo parecido a esto:



    Del mismo modo, podemos reemplazar el contenido de uno o varios elementos, con el código HTML deseado como parámetro.
    $("#btnAddHtml").click(function(e) {                
    $("#parrafoConHTML").html("codigo <b>HTML</b>");
    });

  • text: Es similiar a html() pero en este caso está obviando las etiquetas HTML.
    $("#btnText").click(function(e) {                
    alert($("#parrafoConHTML").text());
    })

    Si utilizamos el mismo caso que en el ejemplo anterior, comprobamos que no se muestra en el alert ninguna de las etiquetas introducidas en el párrafo:



    Nota: text() no puede usarse con elementos de tipo input. Para ellos, es necesario utilizar el atributo val().
    Si modificamos el contenido del elemento con text, y a su vez intentamos introducir etiquetas HTML, no las interpretará como tal.
    $("#btnAddText").click(function(e) {                
    $("#parrafoConHTML").text("codigo <b>HTML</b>");
    })


    Aunque la estructura es bastante parecida, con text() conseguiríamos lo siguiente:



  • append: Se utiliza para hacer un añadido al código HTML existente en los elementos seleccionados.
    $("#btnAppend").click(function(e){
    $("#spanParaAppend").append("<font color='yellow'>Codigo añadido a traves de <b>append()</b></font>");
    })




  • appendTo: Elegimos un elemento y seleccionamos a otro al cual queremos añadirlo.
    $("#btnAddAppend").click(function(e){
    $("label").appendTo("#spanParaAppend");
    })

    El resultado sería el mismo que con append()

  • prepend: Realiza la misma acción que append pero posicionando los elementos elegidos por delante del elemento destino.
    $("#btnPrepend").click(function(e){
    $("#spanParaPrepend")
    .prepend("<font color='blue'>Codigo añadido a traves de <b>prepend().</b></font>");
    })




  • prependTo:  Pone el elemento seleccionado delante del elemento(s) indicados en segundo lugar.
    $("#btnAddPrepend").click(function(e){
    $("#lblPretend").prependTo("#spanParaPrepend");
    })

  • after: Permite insertar código HTML al final del existente.
    $("#btnAfter").click(function(e){
    $("#spanAfter").after("<font color='pink'> con funciones como <u>after</u></font>");
    })


  • insertAfter: inserta elementos después de aquel tomado como referencia.
    $("#btnInsertAfter").click(function(e){
    $("#lblInsertAfter").insertAfter("#spanAfter");
    })


    En este caso, es importante señalar que se indica en primer lugar lo que queremos insertar y en segundo lugar a qué.
  • before: Coloca código HTML antes del existente.
    $("#btnBefore").click(function(e){
    $("#spanBefore").before("<font color='green'> Insertado con <u>before()</u>.</font>");
    })

  • insertBefore: Posiciona elementos antes del indicado.
    $("#btnInsertBefore").click(function(e){
    $("#lblInsertBefore").insertBefore("#spanBefore");
    })

  • wrap: El significado de wrap en español es envolver. Por ello, lo que conseguimos es "envolver" precisamente con el código HTML introducido a uno o varios elementos. También es posible pasarle como parámetro un elemento.
    $("#btnWrap").click(function(){
    $("#parrafoA").wrap("<a href='#'></a>");
    });



  • wrapAll: En el caso anterior, por cada elemento que encontraba, utilizaba un "envoltorio" por cada uno de ellos. En este caso envuelve todos ellos bajo un código HTML o un elemento pasado por parámetro.
    $("#btnWrapAll").click(function(){
    $("li").wrapAll("<div style='border:solid 1px black';></div>");
    });


  • wrapInner: Localiza el hijo de los elementos seleccionados y aplica el envoltorio sobre ellos, no sobre el padre.
    $("#btnWrapInner").click(function(){
    $("li").wrapInner("<div style='border:solid 1px black';></div>");
    });




  • replaceWith: reemplaza los tags HTML de los elementos por los indicados en la llamada.
    $("#btnReplaceWith").click(function(){
    $("input").replaceWith("<input type='radio'></input>");
    });


    En este caso, reemplaza todos los input que encuentre por uno de tipo radiobutton.
  • replaceAll: Localiza todos los elementos en primera instancia. Esta función realiza la misma acción que replaceWith con los parámetros a la inversa.
    $("#btnReplaceAll").click(function(){
    $("<input type='radio'></input>").replaceAll("input");
    });

  • clone: Como su propio nombre indica, se utiliza para duplicar elementos.
    $("#btnClone").click(function(){
    $(this).clone().insertAfter(this);
    });

    Si le pasamos como parámetro true, clonará además sus eventos asociados.
  • empty: Vacía el contenido de los elementos.
    $("#btnEmpty").click(function(){
    $("#parrafoA").empty();
    });

  • remove: Elimina todo el elemento(s) seleccionado.
    $("#btnEmpty").click(function(){
    $("#parrafoA").remove();
    });

¡Saludos!

Posted: 13/12/2009 1:00 por Gisela | con 2 comment(s)
Archivado en:
AJAX y ASP.NET MVC

Cuando creamos un proyecto de MVC, de forma automática se genera una carpeta llamada Scripts con los siguientes archivos js:

En el momento que queramos hacer uso de ellos, únicamente debemos importarlos, generalmente en la Master Page de nuestra aplicación:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<!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>
<asp:ContentPlaceHolder ID="TitleContent" runat="server" />
</title>
<link href="../../Content/Site.css" rel="stylesheet" type="text/css" />

<script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>

<script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>

<script src="../../Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

</head>

Para mostrar un ejemplo de la forma de trabajar con Ajax en una aplicación MVC, voy a llevar a cabo una serie de pasos para ajustar el contenido de la aplicación Movie Manager usada en otros ejemplos.

PARTIAL VIEW DEL LISTADO DE PELÍCULAS

Selecionamos la carpeta Movie, dentro de Views, y con lo el botón derecho seleccionamos Add => View. Especificamos la siguiente configuración:

Pulsando en Add, se genera una nueva vista con extensión ascx. He modificado ligeramente el contenido para configurar las acciones de detalle, edición y una última para la eliminación de películas de manera asíncrona con Ajax. Además, he añadido tres funciones utilizando JQuery.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<MovieManager.Models.Objects.Movie>>" %>
<%@ Import Namespace="MovieManager.Helpers" %>

<script type="text/javascript">

function beginMovieList(args) {
$('#divMovieList').slideUp('normal');
}
function successMovieList() {
$('#divMovieList').slideDown('normal');
}
function failureMovieList() {
alert("Could not retrieve movies.");
}

</script>

<table>
<tr>
<th>
</th>
<th>
Id
</th>
<th>
Name
</th>
<th>
Genre
</th>
<th>
Synopsis
</th>
<th>
Date
</th>
</tr>
<% foreach (var item in Model)
{ %>
<tr>
<td>
<%=Html.ImageLink("Movie", "Details", "Content\\detail.gif", new {item.Id })%>
|
<%=Html.ImageLink("Movie","Edit","Content\\edit.gif",new {item.Id }) %>
|
<%= Ajax.ActionLink("Delete", "Delete", new { id = item.Id }, new AjaxOptions { Confirm = "Delete movie?", HttpMethod = "Delete", UpdateTargetId = "divMovieList", OnBegin = "beginMovieList", OnComplete = "successMovieList", OnFailure = "failureMovieList" })%>
</td>
<td>
<%= Html.Encode(item.Id) %>
</td>
<td>
<%= Html.Encode(item.Name) %>
</td>
<td>
<%= Html.Encode(item.Genre) %>
</td>
<td>
<%= Html.Encode(item.Synopsis) %>
</td>
<td>
<%= Html.Encode(String.Format("{0:g}", item.Date)) %>
</td>
</tr>
<% } %>
</table>

En la acción Delete se ha utilizado el helper específico para los controles Ajax. Dentro del mismo, he usado ActionLink donde se usan los mismos parámetros que en un control normal del Html helper a excepción del último parámetro: AjaxOptions. Este objeto, contiene una serie de propiedades para su configuración. Las utilizadas en el ejemplo son:

  1. Confirm: Antes de realizar la acción, se tendrá que confirmar la misma a través de un cuadro de diálogo con el texto introducido en esta propiedad. 
  2. HttpMethod:Se indica el tipo de verbo http que se utilizará en la llamada. En esta ocasión se hace uso de Delete ya que estamos intentando eliminar un recurso.
  3. UpdateTargetId: Id del elemento de nuestra vista que se va a actualizar, como si se tratara de un Update Panel.
  4. OnBegin: Se le pasa el nombre de la función que se ejecutará cuando se inicie la llamada asíncrona. Por ejemplo, podemos llamar a la función beginMovieList para ocultar el listado de películas cuando comience la eliminación de una de ellas.
  5. OnComplete: Si la llamada asíncrona se completa con éxito, se lanzará la función asociada a esta propiedad. En este caso successMovieList volverá a mostrar el listado de películas actualizado.
  6. OnFailure: Si durante la llamada se produce algún tipo de error, se lanzará la función enlazada a esta propiedad en vez de la función añadida en OnComplete, pudiendo avisar al usuario de la incidencia.

MODIFICAR LA VISTA INDEX

Para poder hacer uso de la partial view, y  no perder la funcionalidad hasta ahora conseguida, debemos modificar la vista Movies/Index.aspx de la siguiente manera:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<MovieManager.Models.Objects.Movie>>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Index</h2>
<div id="divMovieList">
<% Html.RenderPartial("MovieList"); %>
</div>
<p>
<%= Html.ActionLink("Create New", "Create") %>
</p>
</asp:Content>

Se ha añadido un elemento de tipo div, el cual contiene una llamada a Html.RenderPartial que renderizará la vista parcial que acabamos de crear.
En MovieController creamos una nueva acción para eliminar la película seleccionada.

[AcceptVerbs(HttpVerbs.Delete)]
public ActionResult Delete(int id)
{
var movieToDelete = _movieRepository.GetMovie(id);
_movieRepository.Delete(movieToDelete);
return PartialView("MovieList", _movieRepository.ListMovies());
}

Cuando recupera la pelicula a través de NHibernate y elimina la misma, retorna el nuevo listado de películas a MovieList.

Los métodos Delete y GetMovie han sido añadidos tanto en la interfaz IMovieRepository como en su implementación, siempre en la parte del Modelo.

using System.Collections.Generic;
using MovieManager.Models.Objects;

namespace MovieManager.Models
{
public interface IMovieRepository
{
IList<Movie> ListMovies();
void SaveMovie(Movie movieToSave);
Movie GetMovie(int id);
void Delete(Movie movieToDelete);
}
}

using System.Collections.Generic;
using MovieManager.Models.Objects;
using NHibernate;
using NHibernate.Criterion;

namespace MovieManager.Models
{
public class MovieRepository : IMovieRepository
{
private readonly ISessionFactory _session;

public MovieRepository(ISessionFactory sessionFactory)
{
_session = sessionFactory;
}

public IList<Movie> ListMovies()
{
return _session.GetCurrentSession().CreateCriteria(typeof(Movie)).List<Movie>();
}

public void SaveMovie(Movie movieToSave)
{
_session.GetCurrentSession().SaveOrUpdate(movieToSave);
}

public Movie GetMovie(int id)
{
return (Movie)_session.GetCurrentSession().CreateCriteria(typeof(Movie))
.Add(Restrictions.Like("Id", id)).UniqueResult();
}

public void Delete(Movie movieToDelete)
{
_session.GetCurrentSession().Delete(movieToDelete);
}
}
}

Como hemos visto, podemos trabajar de una forma más simplificada con Ajax, gracias a MVC y JQuery, adaptando la aplicación de una forma poco agresiva :)

Adjunto el proyecto por si fuera de utilidad.

¡Saludos!

IDataErrorInfo y ASP.NET MVC

En la mayoría de las aplicaciones es probable que el usuario final necesite rellenar un formulario, modificar datos requeridos, etc. Como es normal, en muchas de estas ocasiones no se introducen los datos de forma correcta, obviamos alguno de los campos requeridos, etcétera.

La interfaz IDataErrorInfo nos ofrece la posibilidad de generar errores personalizados y poder mostrarlos en la interfaz de usuario correspondiente. Cuando creamos una vista de manera automática con MVC,  está preparada para mostrar estos errores gracias a los siguientes elementos: 

  • ValidationSummary: nos permite mostrar todos los errores producidos a modo de resumen en la vista.
  • ValidationMessage: puede resultar útil a la hora de mostrar cada error de forma particular, enlazando el mismo con un control de nuestra interfaz.
  • ModelState: se encargará de recopilar todos los errores producidos en nuestro objeto.

 Si generamos una vista de tipo Create o Edit, podemos ver ValidationSummary al comienzo y ValidationMessage en cada uno de los controles.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MovieManager.Models.Objects.Movie>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Edit
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Edit</h2>
<%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %>
<% using (Html.BeginForm())
{%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="Name">
Name:</label>
<%= Html.TextBox("Name", Model.Name) %>
<%= Html.ValidationMessage("Name", "*") %>
</p>
<p>
<label for="Genre">
Genre:</label>
<%= Html.TextBox("Genre", Model.Genre) %>
<%= Html.ValidationMessage("Genre", "*") %>
</p>
<p>
<label for="Synopsis">
Synopsis:</label>
<%= Html.TextBox("Synopsis", Model.Synopsis) %>
<%= Html.ValidationMessage("Synopsis", "*") %>
</p>
<p>
<label for="Year">
Year:</label>
<%= Html.TextBox("Date", String.Format("{0:g}", Model.Date)) %>
<%= Html.ValidationMessage("Date", "*") %>
</p>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<div>
<%=Html.ActionLink("Back to List", "Index") %>
</div>
</asp:Content>

 Para mostrar un ejemplo, voy a implementar IDataErrorInfo en la clase Movie para controlar la creación y edición de películas.

using System;
using System.ComponentModel;

namespace MovieManager.Models.Objects
{
public class Movie : IDataErrorInfo
{
public int Id { get; set; }
public string Name { get; set; }
public string Genre { get; set; }
public string Synopsis { get; set; }
public DateTime? Date { get; set; }

public string this[string columnName]
{
get
{
var result = string.Empty;
switch (columnName)
{
case "Name":
{
if (string.IsNullOrEmpty(Name))
result = "Movie Name is required";
break;
}
case "Genre":
{
if (string.IsNullOrEmpty(Genre))
result = "Movie Genre is required";
break;
}
case "Year":
{
if (Date.HasValue)
{
if (Date.Value == DateTime.MinValue)
{
result = "Movie Date is must be real";
}
}
break;
}
}
return result;
}
}

public string Error
{
get { return string.Empty; }
}
}
}

Debemos crear dos propiedades:

  • Item: Devuelve el mensaje de error de la propiedad solicitada entre corchetes. En este caso, se están cubriendo aquellas propiedades que se consideran requeridas o, en el caso de Date, con una fecha correcta. Un string vacío se considera como acertado.
  • Error: Devuelve el mensaje de error por el cual el objeto es incorrecto. En esta ocasión, nos interesa más el caso particular de cada propiedad. Por ello, retornamos solamente un string.empty, ya que sería su valor por defecto.

Cuando llamamos a una acción desde la vista, y bindeamos la respuesta dentro de un objeto te tipo Movie, automáticamente comprueba que cada propiedad bindeada cumpla las condiciones implementadas en la propiedad ítem de nuestra clase. Si alguna de ellas no cumple las condiciones establecidas, quedará registrada en ModelState como una propiedad inválida.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Movie newMovie)
{
if (ModelState.IsValid)
{
_movieRepository.SaveMovie(newMovie);
return RedirectToAction("Index");
}
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Movie newMovie)
{
if (ModelState.IsValid)
{
_movieRepository.SaveMovie(newMovie);
return RedirectToAction("Index");
}
return View();
}

 Si intentamos crear una nueva película y no rellenamos los campos obligatorios obtendríamos la siguiente imagen:

¡Saludos!

Posted: 6/12/2009 22:09 por Gisela | con 2 comment(s)
Archivado en:
Service Bus En Windows Azure Platform AppFabric

Después de la introducción en el post anterior a AppFabric, me arriesgo a adentrarme un poco más en cada uno de los elementos que la componen. A continuación, voy a centrarme en Service Bus y en cómo podemos crear un pequeño ejemplo para involucrarlo.

En primer lugar, cabe mencionar el patrón ESB (Enterprise Service Bus)  para poder entender el mecanismo. De una forma simplificada, podemos decir que este patrón está basado en mensajes y unas normas a seguir por los integrantes en la comunicación. Necesitamos un sistema por el cual seamos capaces de enviar un mensaje o petición al otro extremo, siendo conscientes de la existencia de firewalls, dispositivos NATs, etc. ¿Y si existiera la posibilidad de contar con un intermediario que enviara por nosotros ese mensaje? Aquí es donde entra en juego Service Bus.



En la actualidad, existen SDK's para .NET, Java y Ruby. En .NET nos resultará bastante familiar si alguna vez hemos trabajado con WCF. Para poder comenzar en nuestro entorno de Visual Studio, es necesario bajar el siguiente SDK para .NET. Además, antes de empezar a trabajar con Service Bus, debemos completar los pasos citados en el anterior post desde el portal de AppFabric.

En este post, voy a crear tres aplicaciones: Un servicio con WCF, que será la forma de comunicarse una aplicación con otra, un proyecto de consola que actuará como servidor y una aplicación winform que enviará y recibirá mensajes.

CREAR UN SERVICIO CON WCF

En primer lugar, necesitamos generar un contrato y su implementación como si de servicio web se tratara. Para ello, he creado un proyecto de tipo WCF Service Application llamado ServiceBusAppFabric.

Renombro la interfaz IService1.cs por IServiceBus.cs y la actualizo con el siguiente código:

using System.Runtime.Serialization;
using System.ServiceModel;

namespace ServiceBusAppFabric
{
[ServiceContract]
public interface IServiceBus
{
[OperationContract]
void SendMessage(string msg, string sender, string receiver);

[OperationContract]
CompositeType ReceiveMessage(string receiver);
}


[DataContract]
public class CompositeType
{
[DataMember]
public string Sender { get; set; }

[DataMember]
public string InstantMessage { get; set; }
}
}


Modifico el nombre de Service1.svc por ServiceBus.svc e implemento la interfaz que acabamos de crear.

using System;
using System.Linq;

namespace ServiceBusAppFabric
{
public class ServiceBus : IServiceBus
{
public void SendMessage(string msg, string sender, string receiver)
{
var imEntities = new InstantMessagesDB();
MessageIM iM = new MessageIM { InstantMessage = msg, Receiver = receiver, Sender = sender };
imEntities.AddToMessageIMSet(iM);
imEntities.SaveChanges();
Console.WriteLine("Received Message from:" + sender);
}

public CompositeType ReceiveMessage(string receiver)
{
var imEntities = new InstantMessagesDB();
var IMs = (from i in imEntities.MessageIMSet.ToList()
where i.Receiver == receiver
select i).FirstOrDefault();
if (IMs != null)
{
Console.WriteLine(string.Format("Sending Message: {0} from {1}", IMs.InstantMessage, IMs.Sender));
return new CompositeType { InstantMessage = IMs.InstantMessage, Sender = IMs.Sender };
}
return new CompositeType { InstantMessage = "No Messages", Sender = "No one" };
}
}
}

Como podemos ver, en la llamada SendMessage estoy recibiendo el mensaje, el remitente y el destinatario del mismo. En esta demo estoy utilizando Entity Framework para acceder a una base de datos en SQL Express llamada IM con una tabla como esta:


Creo un objeto del tipo MessageIM y lo guardo en base de datos. Por último, muestro un mensaje por consola informando del nuevo mensaje recibido.
El método ReceiveMessage recibe el nombre del destinatario y busca entre los mensajes guardados cual le corresponde. De no obtener ningún resultado informa al usuario que nadie envió nada para él.

CREACIÓN DEL SERVIDOR

Una vez que tenemos la forma de comunicarnos, necesitamos crear una aplicación que tenga el rol de servidor. Para ello, he creado la siguiente aplicación de consola:

using System.ServiceModel;
using ServiceBusAppFabric;

namespace ServerAppFabric
{
class Program
{
static void Main(string[] args)
{
System.Console.WriteLine("Waiting a response from AppFabric...");
ServiceHost host = new ServiceHost(typeof(ServiceBus));
host.Open();

System.Console.WriteLine("I'm ready =)");
System.Console.WriteLine("Press [Enter] to exit");
System.Console.ReadLine();

host.Close();
}
}
}

Es necesario importar la dll que generó nuestro proyecto de WCF y además System.ServiceModel para poder crear un objeto de tipo ServiceHost.


Ahora lo que necesitamos es configurar un endpoint de la misma manera que podríamos hacerlo para un servicio en WCF, con algunos pequeños cambios. Añadimos un archivo de tipo App.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="InstantMessagesDB" connectionString="metadata=res://*/Model.csdl|res://*/Model.ssdl|res://*/Model.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=.\SQLEXPRESS;Initial Catalog=IM;Integrated Security=True;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />
</connectionStrings>
<system.serviceModel>
<services>
<service name="ServiceBusAppFabric.ServiceBus" >
<endpoint address="sb://gisendpoint.servicebus.windows.net/ServiceBus"
behaviorConfiguration ="sharedSecretClientCredentials"
binding="netTcpRelayBinding"
contract="ServiceBusAppFabric.IServiceBus" />
</service>
</services>
<behaviors>
<endpointBehaviors >
<behavior name ="sharedSecretClientCredentials">
<transportClientEndpointBehavior credentialType="SharedSecret">
<clientCredentials>
<sharedSecret issuerName="[Issuer Name]"
issuerSecret="[Issuer Key]"/>
</clientCredentials>
</transportClientEndpointBehavior>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>


Dentro de la sección configuration tenemos un connectionString, para poder conectar con la base de datos IM, y  la sección system.serviceModel con la configuración necesaria para la parte services. En el post anterior, comentaba que era posible visualizar el detalle del namespace del servicio que acabamos de crear. Si volvemos a aquel apartado podemos localizar los valores necesarios para completar el endpoint.

  • En la propiedad address añadiremos sb://serviceNamespace.servicebus.windows.net
  • El binding  será de tipo netTcpRelayBinding. Para poder utilizar este tipo de binding sólo es necesario tener en cuenta que utiliza SSL a través del puerto 828. Por este motivo, es necesario abrir dicho puerto en nuestro router para que pueda funcionar correctamente.
  • El contrato sería la interfaz de nuestro servicio WCF creado anteriormente

Para finalizar, hemos creado un behavior un tanto especial. Esta información también es particular del namespace del servicio que creamos en AppFabric y serán necesarios tanto Default Issuer Name como Default Issuer Key.

Si arrancamos la aplicación, vemos que en un primer momento espera la contestación por parte de AppFabric y, a los pocos segundos, está listo para su uso.



CREACIÓN DEL CLIENTE

La última aplicación pendiente sería un cliente winform con el siguiente aspecto:

En realidad es bastante simple. La primera parte sería para el envío de mensajes, escribiendo tanto el remitente, como el destinatario y el mensaje en sí, y en la parte inferior podemos pasarle el nombre del usuario del cual queremos recuperar el mensaje pendiente.

using System.ServiceModel;
using System.Windows.Forms;
using ServiceBusAppFabric;

namespace ClientForm
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void btnSend_Click(object sender, System.EventArgs e)
{
ChannelFactory<IServiceBus> channel = new ChannelFactory<IServiceBus>("RelayEndpoint");
IServiceBus client = channel.CreateChannel();

client.SendMessage(txtMessage.Text, txtSender.Text, txtReceiver.Text);

lblResultSend.Text = "Sent It!";

channel.Close();
}

private void btnReceived_Click(object sender, System.EventArgs e)
{
ChannelFactory<IServiceBus> channel = new ChannelFactory<IServiceBus>("RelayEndpoint");
IServiceBus client = channel.CreateChannel();
var result = client.ReceiveMessage(txtReceiverName.Text);

lblMessagesResult.Text = "Message: " + result.InstantMessage + " From:" + result.Sender;

channel.Close();
}
}
}

En el codebehind del formulario tenemos un evento para cada botón. En ambos, abrimos un canal a través de ChannelFactory especificando el contrato de nuestro servicio y utilizamos el envío o la recepción de mensajes según el caso. El endpoint del cliente sería similar al anterior.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint address ="sb://gisendpoint.servicebus.windows.net/ServiceBusAppFabric"
binding="netTcpRelayBinding"
contract="ServiceBusAppFabric.IServiceBus"
behaviorConfiguration="sharedSecretClientCredentials"
name="RelayEndpoint" />
</client>
<behaviors>
<endpointBehaviors >
<behavior name="sharedSecretClientCredentials">
<transportClientEndpointBehavior credentialType="SharedSecret">
<clientCredentials>
<sharedSecret issuerName="[Issue Name]"
issuerSecret="[Issuer Secret]" />
</clientCredentials>
</transportClientEndpointBehavior>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

Si arrancamos el servidor y acto seguido el cliente, comprobaríamos que efectivamente podemos enviar y recibir mensajes:

Incluyo los tres proyectos a este post.

¡Saludos!

Posted: 29/11/2009 3:20 por Gisela | con 4 comment(s) |
Archivado en: ,
AppFabric

En post anteriores sobre Windows Azure, comencé a hablar de cloud computing como un concepto conocido por todos cuando realmente no es tan... sencillo. Me gustaría poder tocar poco a poco todos los servicios disponibles por Microsoft en este aspecto, para poder valorar todas las posibilidades que están ofreciendo actualmente. En esta ocasión, voy a centrarme en AppFabric, anteriormente conocido como .Net Services. Comentar que he encontrado poca información al respecto y es posible que no sea consciente aún de todas las posibilidades que ofrece.

¿QUÉ ES APPFABRIC?

Service Bus y Access Control son los servicios que componen AppFabric y que están apoyados en Windows Communication Foundation (WCF). Estos dos componentes trabajan de manera conjunta, para conseguir la conexión de forma bidireccional entre aplicaciones, facilitando el acceso a redes protegidas de una manera segura y sencilla. Access Control nos ayudará a crear conexiones seguras entre nuestras aplicaciones y servicios y Service Bus será útil para conectarnos a través de la red a organizaciones con protecciones como Firewalls, NATs, etcétera.

¿POR DÓNDE EMPIEZO?

Como expliqué en el post sobre Cómo obtener una cuenta para Windows Azure, AppFabric (.NET Services en el momento del post) no precisa de invitación y con el registro sería más que suficiente para poder comenzar.
Accedemos al portal de AppFabric y lo primero que vemos es lo siguiente:

Un proyecto en AppFabric es simplemente un contenedor donde se organizarán todos nuestros namespaces relacionados con Service Bus y Access Control. Lo veremos más adelante.
Creado el mismo y pulsando Ok, se nos muestra un listado con los proyectos creados hasta el momento. Pulsamos sobre el único que tenemos ;) para acceder a él.

Pulsamos sobre Add Service Namespace.

 

 

En este punto, tenemos que escribir un nombre para el namespace del servicio. Este nombre es público por lo que debemos conocer su disponibilidad en la red. Una vez introducimos el nombre, podemos pulsar en Validate Name para comprobar si está en desuso. Pulsamos en crear y debemos esperar a que el status aparezca Active. Solamente tardará unos segundos.


Una vez queda activado puedes seleccionarlo y visualizar los detalles del mismo:

En los próximos posts, explicaré cómo montar un ejemplo para cada elemento.

¡Saludos!

Posted: 27/11/2009 14:45 por Gisela | con 2 comment(s)
Archivado en: ,
Efectos básicos con JQuery

Para poder mejorar la experiencia de usuario, tenemos a nuestra disposición una serie de efectos listos para usar con JQuery. Es increíble la cantidad de plugins que circulan ya por la red pero, por el momento, me parece importante ir conociendo poco a poco la base de esta gran librería para poder hacer nuestro pinitos más adelante :)

En primer lugar, para no ser repetitiva, comentar que todos los efectos aceptan como parámetro una función de callback, la cual se lanzará una vez haya finalizado la animación. Veremos algunos ejemplos. Además, como primer parámetro, se puede especificar una velocidad para manipular el tiempo que durará la animación. Los valores que aceptan son: slow, normal, fast el tiempo en milisegundos.
Por otro lado, los efectos show, hide y toggle pueden ser invocados sin parámetros, obviando la velocidad de la animación.

  • Show: Muestra aquellos elementos, localizados a través del selector, modificando el estilo de display a block con un tiempo de duración específico.
    $("#btnShow").click(function() {
    $("#divEffect").show("slow", function() { alert("Terminó el efecto show") });
    });

  • Hide: Oculta los elementos seleccionados.
    $("#btnHide").click(function() {
    $("#divEffect").hide();
    });

  • Toggle: Es la función perfecta para no tener que implementar show & hide. En realidad, comprueba el valor de display y realiza la opuesta a la actual. También es posible utilizar una condición que actue de switch donde true muestra todos los elementos involucrados y false los oculta.
    $("#btnToggle").click(function() {
    $("#divEffect").toggle(1500);
    });

  • SlideDown: El efecto consiste en aumentar la altura (height)
    $("#btnSlideDown").click(function() {
    $("#divEffect").slideDown("fast");
    });

  • SlideUp: Produce el efecto contrario que slideDown, disminuyendo la altura de los elementos.
    $("#btnSlideUp").click(function() {
    $("#divEffect").slideUp(3000);
    });

  • SlideToggle: Determina el efecto a realizar entre, slideDown y slideUp.
    $("#btnSlideToggle").click(function() {
    $("#divEffect").slideToggle(200);
    });

  • FadeIn: En este caso, estamos jugando con la opacidad de los elementos. Con fadeIn podemos mostrar uno o varios elementos a la velocidad indicada aumentando la propiedad opacity de su style.
    $("#btnFadeIn").click(function() {
    $("#divEffect").fadeIn("slow");
    });

  • FadeOut: De manera contraria, disminuye la opacidad de los elementos hasta su desaparición.
    $("#btnFadeOut").click(function() {
    $("#divEffect").fadeOut(5000);
    });

  • FadeTo: Si solamente queremos llegar a un grado de opacidad, sin que desaparezca del todo, podemos utilizar fadeTo. Además de utilizar la velocidad como primero, tenemos la posibilidad de pasarle el grado de opacidad que necesitamos para nuestros elementos. Al igual que el resto, tendría un tercer parámetro para la función de callback.
    $("#btnFadeTo").click(function() {
    $("#divEffect").fadeTo(5000, 0.20);
    });

  • Animate: En realidad, si pensamos en todos los efectos anteriores, lo que estamos haciendo es jugar con los estilos de los elementos y, como valor añadido, dándole una velocidad o duración. Si quisiéramos componer nuestro propio efecto o animación la función animate podría ser bastante útil. Existen dos formas de configurar las animaciones: todos los cambios de estilo al unísono o bien de forma secuencial.
    $("#btnAnimate").click(function() {
    $("#divEffect").animate({
    width: "500px",
    height: "400px"
    }, 2000);
    });
    $("#btnAnimateSec").click(function() {
    $("#divEffect").animate({
    width: "200px"
    }, 2000)
    .animate({
    height: "200px"
    }, 2000, function() { alert("Animación finalizada!!") });
    });

  • Stop: Permite detener la animación antes de que finalice.
    $("#btnStop").click(function() {
    $("#divEffect").stop();
    });

  • jQuery.fx.off: Si actualizamos la siguiente variable global a true, deshabilitamos todas las animaciones de nuestra aplicación. Esto no significa que el cambio de estilo no se lleve a cabo pero no se tendrá en cuenta la velocidad del efecto.
    $("#btnAnimationOff").click(function() {
    jQuery.fx.off = true;
    });
    $("#btnAnimationOn").click(function() {
    jQuery.fx.off = false;
    });


Os dejo el proyecto con todas las demos de este post ;)

¡Saludos!

Posted: 24/11/2009 21:40 por Gisela | con 2 comment(s) |
Archivado en:
Más artículos Página siguiente >