viernes, 19 de febrero de 2010

Nombres de algunos controladores distintos en ASP.NET MVC (ii)

Bueno… Este post es la continuación / aclaración del post anterior. En el post anterior configuramos la tabla de rutas junto con un RouteHandler propio y vimos que realmente las llamadas se enrutaban al controlador que tocaba: /api/Foo me enrutaba al controlador WarFoo y /Foo me enrutaba al controlador Foo.

Lo que no comenté es lo que deja de funcionar… :)

Supongo que teneis la tabla de rutas de la siguiente manera:

routes.MapRoute("Default","{controller}/{action}/{id}", 
new { controller = "Home", action = "Index", id = "" });
routes.Add(new Route("api/{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Home", action = "Index", id = "" }),
new WarRouteHandler())
);





Suponemos también que tenemos el controlador Foo (al que queremos acceder vía (/Foo) y WarFoo al cual queremos acceder via /api/Foo). Si uso Html.ActionLink para generar los enlaces obtengo lo siguiente:



























Llamada URL obtenida URL que querria yo
Html.ActionLink("Texto", "XX","Foo") /Foo/XX /Foo/XX
Html.ActionLink("Texto", "XX", "WarFoo") /WarFoo/XX /api/Foo/XX


Vamos a ver como podemos empezar a arreglar este desaguisado… Debemos recordar que el orden de las rutas en la tabla de rutas afecta: toda URL se empieza a comprobar en cada ruta, y se usa la primera que encaje. Dado que no hay nada que impida en la ruta Default que haya un controlador llamado WarFoo se usa esa ruta, y por eso obtenemos /WarFoo/XX como URL final.



Uno puede pensar que la solució es invertir el orden de las rutas en la tabla de rutas… Si lo haceis el reultado no es mucho mejor:



























Llamada URL obtenida URL que querria yo
Html.ActionLink("Texto", "XX","Foo") /api/Foo/XX /Foo/XX
Html.ActionLink("Texto", "XX", "WarFoo") /api/WarFoo/XX /api/Foo/XX


Que nos ocurre? Lo mismo de antes, salvo que ahora la primera ruta que se evalúa es la que tiene las URLs que empiezan por api. Pero esta ruta tampoco impone ninguna restricción sobre los controladores, así cualquier nombre de controlador también encaja en esta ruta, y es por eso que obtenemos siempre URLs que empiezan por api.



Cuando tenemos dos URLs que ambas aceptan cualquier controlador y acción, es dificil que ActionLink pueda distinguir entre una u otra, así que generalmente nos usará la primera definida para generar los enlaces. Dado que por norma general no queremos poner /api/ en todas las URLs podemos dejar la ruta “Default” como la primera. Ahora entra en acción RouteLink: podemos usar RouteLink para generar las URLs que deben empezar con /api y ActionLink para las que no. P.ej. puedo usar la siguiente llamada RouteLink, para onbtener la url /api/Foo/XX:




Html.RouteLink("Texto", "api", new RouteValueDictionary(
new { controller="Foo", action="XX"}))





Aquí estoy generando un link a la ruta “api” para generar el enlace. Debemos modificar la tabla de rutas para que la ruta que genera las urls con /api/ se llame api. Esto es tan simple como poner el nombre de la ruta como primer parámetro del método Add:




routes.Add("api", new Route("api/{controller}/{action}/{id}",
new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" }),
new WarRouteHandler())
);





y todo solucionado no??? :)



Pues no… todavía nos queda un fleco… un fleco que también pase por alto en el post anterior.



Los enlaces estan bien generados, uno apunta a /Foo/XX y el otro a /api/Foo/XX. El primero funciona bien pero el segundo da un error… y eso?



Pensemos de nuevo en como ASP.NET MVC evalúa las rutas: por orden. Y la pregunta es la ruta /api/Foo/XX se puede procesar con la ruta {controller}/{action}/{id} (la Default)? Pues sí, suponiendo que controller es “api”, action es “Foo” e Id es “XX”. Es decir la ruta /api/Foo/XX me intenta invocar la acción Foo del controlador Api, pasándole XX como Id.



¿Cual es la solución entonces? Pues añadir una restricción a la ruta (Default) que impida que el nombre de los controladores sea “api”. De este modo si {controller} debe tomar el valor api para satisfacer la ruta, como tenemos la restricción la ruta no será satisfecha y ASP.NET MVC intentará usar la siguiente. Las restricciones se añaden como un nuevo parámetro en MapRoute:




routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" },
new { controller = "restriccion" });






He añadido una restricción que afecta al parámetro controller. Y como se interpreta esta restricción. Pues bien, si es una cadena se interpreta como una expresión regular que debe cumplirse. Si la restricción no se puede (o no sabemos :p) expresar como una expresión regular podemos parar un objeto que implemente IRouteConstraint. Dado que yo soy muy torpe con las expresiones regulares, me he creado una clase que me comprueba que el valor no sea igual a un determinado valor:




public class NotEqualConstraint : IRouteConstraint
{
private string _match = String.Empty;
public NotEqualConstraint(string match)
{
_match = match;
}

public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return String.Compare(values[parameterName].ToString(), _match, true) != 0;
}
}





Finalmente coloco la restricción en la ruta:




routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" },
new { controller = new NotEqualConstraint("api") });





Ahora sí que sí. Los enlaces /api/Foo/XX no pueden ser procesados por la ruta Default, ya que no se cumple mi restricción (controller vale api), y entonces son procesados por la siguiente ruta (que es lo que queríamos). Ahora pues la url /Map/XX es procesada por la primera ruta y la URL /api/Map/XX es procesada por la segunda y enrutada al controlador WarMap.



Espero que estos dos posts os hayan ayudado a ver la potencia del sistema de rutas de ASP.NET MVC!



Un saludo a todos!



PD: Esto es un crosspost desde mi blog en geeks.ms

Nombres de algunos controladores distintos en ASP.NET MVC

Hola! Un post para comentaros como he implementado una cosilla que necesitaba en ASP.NET MVC (v1). En concreto necesitaba mapear las URLs de tipo /api/{controller}/{action} al controlador especificado, pero con la salvedad de que el nombre del controlador empezaba por War. Es decir la URL /api/Foo/Index debía llamar a la acción del controlador WarFoo (en lugar del controlador Foo).

En resumen lo que quería era:

/Foo/Index –> Llamar a acción Index del controlador Foo

/api/Foo/Index –> llamar a acción Index del controlador WarFoo

/api/WarFoo/Index –> Llamar a acción Index del controlador WarFoo

La primera de las reglas se cumple fácilmente con la definición estándard de la tabla de rutas de MVC:

routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);





Para dar soporte a las otras dos reglas me he creado un RouteHandler propio…



¿Que son los RouteHandlers?



Los RouteHandlers son clases que implementan la interfaz IRouteHandler y que son las encargadas de procesar las peticiones que vienen via una ruta (casi todas en el caso de MVC). Generalmente un RouteHandler lo que hace es crear un IHttpHandler que procese la petición enrutada. ASP.NET MVC viene con un RouteHandler por defecto (MvcRouteHandler) que termina creando un IHttpHandler por defecto (MvcHandler) que es el que crea el controlador asociado y le cede el control. En mi caso el comportamiento de MvcHandler ya me va bien: no quiero cambiar la política de creación de controladores ni nada, solo quiero añadir el prefijo “War” al controlador.



Por suerte, ASP.NET MVC es muy extensible y nos lo pone realmente fácil para añadir un RouteHandler propio: basta con crearlo y vincularlo con una ruta que tengamos en la tabla de rutas.



Veamos primero como vincularíamos nuestro RouteHandler (que he llamado WarRouteHandler) con una ruta, usando el método Add de la tabla de rutas:




routes.Add(new Route("api/{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Home", action = "Index", id = "" }),
new WarRouteHandler())
);





El primer parámetro es la URL de la ruta, el segundo los valores de la ruta (a diferencia del método MapRoute que acepta un objeto anónimo para esto, aquí nosotros debemos crear específicamente el RouteValuesDictionary) y finalmente el RouteHandler a utilizar!



Ya casi estamos: Sólo nos falta crear el RouteHandler, para ello creamos una clase que implemente IRouteHandler, y en el método GetHttpHandler (que es el único) vamos a añadir “War” al nombre del controlador. Para saber el nombre del controlador nos basta con acceder a los valores de la ruta (RouteData.Values) donde están todos (controlador, acción, id y otros adicionales que hubiesen). El código es simple:




public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string controller = requestContext.RouteData.Values["controller"].ToString();
if (!controller.ToLowerInvariant().StartsWith("war"))
{
requestContext.RouteData.Values["controller"] = string.Format("War{0}", controller);
}

return new MvcHandler(requestContext);
}





Ahora ya tenemos las URLs de tipo /api/{controlador} enrutadas al controlador War{controlador}.



Un saludo!



PD: Crosspost desde mi blog en geeks.ms (para variar, pásate por allí mejor!)

viernes, 12 de febrero de 2010

Caliburn… ¿sientes el poder de MVVM en tus manos?

Los más frikis de por aquí, sabréis que Caliburn (Caliburnus para ser exactos) era el nombre de una poderosa espada que luego alguien decidió rebautizar como Excalibur… Como frikis hay en todas partes y en eso de la informática pues quizás más, Caliburn también resulta ser el nombre de un framework para aplicaciones Silverlight y WPF. Dicho así parece ser lo mismo que PRISM y en cierta manera ambos frameworks tienen el mismo objetivo y comparten muchas características. Por ejemplo ambos frameworks se abstraen del contendor IoC a utilizar (es decir requieren uno, pero no se atan a ninguno), ambos dan soporte a vistas compuestas y ambos tienen el concepto de módulo… entonces ¿en que se diferencian? Pues en como se enfocan para llegar al objetivo. El objetivo de este post es iniciar una serie de posts (no se de cuantos :P) para hablar sobre Caliburn y compararlo con PRISM. Hoy, pero vamos a empezar por lo más básico… :)

1. Preparando el entorno

No es muy complicado preparar el entorno para trabajar con Caliburn: basta con descargarse el framework (actualmente está en v1 RTW). Caliburn (de nuevo al igual que PRISM) existe en dos sabores: Silverlight y WPF. Ambas versiones son esencialmente la misma salvando las diferencias técnológicas que existen entre Silverlight y WPF. Vamos a optar en este caso por una aplicación WPF.

Abrimos VS2008 y creamos una nueva aplicación WPF. El siguiente paso es añadir referencias a los ensablados de Caliburn que estarán en el directorio Bin/NET-3.5/debug (o release, hay ambas versiones). Nota: Yo os recomiendo que os descargueis el codigo fuente y compileis Caliburn… Así será más fácil depurar!

Si en lugar de WPF nuestra aplicación fuese Silverlight entonces debemos ir al directorio Bin/Silverlight-2.0 o Silverlight-3.0 según sea necesario. Para empezar vamos a usar Caliburn.Core.dll, Caliburn.PresentationFramework.dll y Microsoft.Practices.ServiceLocation.dll.

Ahora sí! Ya estamos listos para desenvainar la espada …

2. Empezando con Caliburn

Caliburn tiene una idea muy clara sobre como se debe organizar la IU de tu aplicación: usando MVVM. Eso significa que vamos a tener un grupo de clases llamadas ViewModels que van a ser los que tengan toda la información sobre los datos a mostrar. A cada ViewModel le corresponderá una vista (View). El enlace entre las vistas y los ViewModels será a través de Bindings… por supuesto que todo esto se puede hacer sin Caliburn, pero Caliburn nos da herramientas para que sea un poco más fácil.

Para empezar vamos a crear un ViewModel y una vista y vamos a dejar que la magia de Caliburn nos lo una. Para ello, creamos una carpeta llamada ViewModel en nuestro proyecto. Es importante el nombre de esta carpeta, puesto que Caliburn asume que lo que en ella esté son ViewModels o vistas. Dentro de dicha carpeta creamos una clase tal y como sigue:

public class UserViewModel
{
public string Nombre { get; set; }
public string Foto { get; set; }
}





Ya tenemos el ViewModel de un usuario: su nombre y su foto. Ahora el siguiente paso es crear una vista. Para ello añadid un User Control que se llame UserView. El nombre de nuevo es importante: Caliburn asumirá que UserView es la vista para los ViewModels de tipo UserViewModel. Poned el user control fuera de la carpeta ViewModel. El código xaml puede ser algo parecido a:




<UserControl x:Class="CaliburnDemo.UserView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<Label Height="28" Margin="25,47,28,0" VerticalAlignment="Top" Content="{Binding Nombre}"></Label>
<Image Margin="109,81,127,0" Stretch="Fill" Width="64" Height="64" VerticalAlignment="Top"
Source="{Binding Foto}"/>
<Label Height="28" Margin="85,13,101,0" VerticalAlignment="Top">Datos del Usuario:</Label>
</Grid>
</UserControl>





Finalmente sólo nos queda un paso: modificar nuestra aplicación para que derive de CaliburnApplication. Para ello, en App.cs modificad la clase para que derive de CaliburnApplication:




public partial class App : CaliburnApplication
{
protected override object CreateRootModel()
{
return new UserViewModel();
}
}





Fijaos que redefinimos el método CreateRootModel: Este método (definido en CaliburnApplication) es el punto de entrada de nuestra aplicación. El tipo de ViewModel que creemos determinará el tipo de vista a utilizar y los datos iniciales a mostrar. Un detalle: Fijaos que no vamos a crear una ventana nunca (nuestra vista UserView es un UserControl). No hay problema, porque si el ViewModel inicial no es una ventana, Caliburn la va a crear para nosotros.



Hemos modificado la clase base de la aplicación en el fichero .cs y debemos hacer lo mismo en App.xaml:




<caliburn:CaliburnApplication x:Class="CaliburnDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:caliburn="clr-namespace:Caliburn.PresentationFramework.ApplicationModel;assembly=Caliburn.PresentationFramework">
<Application.Resources>
</Application.Resources>
</caliburn:CaliburnApplication>





No hay secreto: Declaro el namespace caliburn y modifico el tag raiz para que en lugar de Application sea CaliburnApplication que es nuestra nueva clase base. También elimino el StartupUri ya que no es necesario.



Y listos… ya podemos ejecutar!



3. Plaf! La primera en la frente!



Si has seguido mis indicaciones te vas a encontrar algo parecido a esto (si no has compilado Caliburn quizá simplemente te salga una excepción en lugar de esta “preciosa” ventana).



image



Hombre… no es muy bonito que digamos… cual es el problema? Fácil: la vista no está situada en el lugar que toca… Recordáis que os dije que la pusierais fuera de la carpeta ViewModel? Pues debe ir dentro… Así, que moved UserView dentro de la carpeta ViewModel y modificad el namespace para que incluya ViewModel:




namespace CaliburnDemo.ViewModel
{
}





De hecho lo importante es el namespace, no la ubicación física del archivo xaml, así que si no quereis moverlo no lo hagáis per el namespace de la clase UserView debe ser el mismo que el de UserViewModel.



Ahora sí que sí! Si ejecutamos vemos una triste ventana… pero es nuestra ventana:



image



Está vacía porque el ViewModel que hemos creado lo está, pero eso tiene fácil arreglo modificando el método CreateRootModel para que el UserViewModel creado tenga datos:




protected override object CreateRootModel()
{
return new UserViewModel()
{
Nombre = "Edu",
Foto = "/CaliburnDemo;component/avatar.png"
};
}





(La foto está añadida como Resource en el proyecto, de ahí esta ruta).



Ahora si que vemos ya nuestros datos:



image



¡Mola! Que es lo que ha hecho Caliburn por nosotros? Pues a partir de un ViewModel ha creado la vista correspondiente y ha asignado el ViewModel como DataContext de la vista…



Ok… no es nada que no podamos hacer nosotros mismos con pocas líneas de código… pero esto es sólo el principio! En sucesivos posts iremos viendo otras cosillas de Caliburn. Obviamente si alguien ha trabajado con Caliburn y/o con PRISM y quiere contar sus opiniones… adelante!



Un saludo a todos!



PD: Dejo el código del proyecto en este ficherillo zip! (en mi skydrive)



PD2: Obviamente… otro crosspost desde mi blog de geeks.ms… Porque no te pasas por allí, mejor?

miércoles, 10 de febrero de 2010

ASP.NET MVC: Redirecciones permanentes

Buenas! Los que estéis al tanto de las novedades de ASP.NET 4, sabreis que una de ella es Response.RedirectPermanent (de la cual Ibon habla un poco en este post). La diferencia con respecto a Response.Redirect es que esta emite un código HTTP 302 (Found) mientras que RedirectPermanent emite un código HTTP 301 (Moved Permanently).

A efectos del usuario final el resultado es exactamente el mismo: cuando el navegador recibe un HTTP 301 o bien un HTTP 302, realiza otra petición a la URL especificada en el header “Location”, con lo cual se consigue el objetivo final: que el usuario sea redirigido a otra página.

¿Entonces? Bueno, como vivimos en un mundo dominado por las máquinas y por el software, ahora resulta que no nos basta con contentar al usuario: también debemos contentar a… Google y similares. Aunque para el usuario final un HTTP 302 y un HTTP 301 sean lo mismo (una redirección) los buscadores los tratan de forma muy distinta. Cuando Google realiza una petición a una URL digamos A, y recibe un código HTTP 302 que le redirecciona a B, básicamente Google sigue manteniendo en su índice la página A, puesto que un código HTTP 302 significa, en el fondo, que la redirección es temporal. Por otro lado, cuando Google recibe un HTTP 301, elimina la página A de su índice y en su lugar guarda el resultado de la redirección…

… en el fondo lo importante de este rollo es que a efectos de SEO a Google no le gustan los HTTP 302. En este post del blog de Matt Cutts hay más información sobre 302 vs 301.

En ASP.NET MVC tenemos varias maneras de realizar redirecciones, la más común de la cual es llamar al método RedirectToAction que devuelve un objeto RedirectToRouteResult configurado para redirigir al usuario a la acción especificada del controlador indicado… El tema está en que RedirectToRouteResult utiliza el código HTTP 302 para realizar la redirección (en el fondo termina llamando a Response.Redirect).

Imaginad que tengo en el controlador Home la acción Index que me redirecciona a la acción Destination, que es la que me muestra una vista:

public ActionResult Index()
{
return RedirectToAction("Destination");
}

public ActionResult Destination()
{
return View();
}





Si abro la url /Home/Index, evidentemento soy redirigido a Home/Destination, pero si observo las peticiones del navegador (en mi caso uso firefox + firebug):



image



Observad como hay dos peticiones: la primera devuelve el código HTTP 302 y el campo Location con la URL a la que el navegador debe redirigirse. La segunda petición devuelve un HTTP 200 (código para indicar que todo ha ido OK y que mandamos la respuesta al navegador).



Ahora el quid de la cuestión: como podemos realizar redirecciones permanentes en ASP.NET MVC? Pues hasta donde yo sé, no es posible directamente, pero por suerte, dado que el framework es bastante extensible no nos va a costar nada añadir dicha posibilidad… vamos a ello!



1. Creación de un ActionResult propio



El primer paso es crearnos un ActionResult propio… dado que el RedirectToRouteResult no nos sirve, ya que termina usando Response.Redirect, vamos a crearnos uno de propio, que he llamado (en un alarde de originalidad) PermanentRedirectToRouteResult.



Esta clase tendrá un constructor con cuatro parámetros: acción, controlador, el nombre de la ruta a usar y los valores de la ruta. De hecho sólo el primero (acción) es imprescindible. El resto son “avanzados” y sirven para redirigirnos a acciones de otros controladores o bien para pasar parámetros. La definición inicial de la clase es simple:




public class PermanentRedirectToRouteResult : ActionResult
{
private RouteCollection _routes;

public PermanentRedirectToRouteResult(string action, string controller, string routeName, RouteValueDictionary routeValues)
{
Action = action;
Controller = controller;
RouteName = routeName ?? String.Empty;
RouteValues = routeValues ?? new RouteValueDictionary();
}
public string RouteName {
get;
private set;
}
public RouteValueDictionary RouteValues {
get;
private set;
}
public string Action { get; private set; }
public string Controller { get; private set; }
public override void ExecuteResult(ControllerContext context)
{
}
}





Guay! Solo nos falta rellenar el método ExecuteResult para hacer lo que queramos (en este caso una redirecciónmediante HTTP 301). Para ello, primero calculamos la URL de redirección (usando la clase UrlHelper) y luego establecemos el código HTTP en 301 y el campo Location de la Response:




public override void ExecuteResult(ControllerContext context)
{
string url = UrlHelper.GenerateUrl(RouteName, Action, Controller,
new RouteValueDictionary(), RouteTable.Routes, context.RequestContext, false);
context.HttpContext.Response.StatusCode = 301;
context.HttpContext.Response.RedirectLocation = url;
}





No uso Response.RedirectPermanent porque si no mi código sólo seria compatible con ASP.NET 4 (es decir con VS2010)… de esta manera mi código sigue siendo compatible con VS2008 :)



2. Un par de métodos de extensión…



Este punto simplemente es para que podamos hacer algo parecido a lo que hacemos con RedirectToAction: llamar a a un método que es quien crea el objeto RedirectToRouteResult. En mi caso, he creado métodos de extensión sobre la clase Controller:




public static class ControllerExtensions
{
public static PermanentRedirectToRouteResult PermanentRedirectToAction(this Controller @this,
string action)
{
return PermanentRedirectToAction(@this, action, null);
}

public static PermanentRedirectToRouteResult PermanentRedirectToAction(this Controller @this,
string action, string controller)
{
return PermanentRedirectToAction(@this, action, controller, null, null);
}

public static PermanentRedirectToRouteResult PermanentRedirectToAction(this Controller @this,
string action, string controller, string routeName, RouteValueDictionary values)
{
return new PermanentRedirectToRouteResult(action, controller, routeName, values);
}
}





Mola! Ahora ya podemos hacer nuestras redirecciones permanentes de una forma muy similar a como hacemos las normales. P.ej. puedo añadir la siguiente acción a mi controlador:




public ActionResult IndexPermanent()
{
return this.PermanentRedirectToAction("Destination");
}





Y si ahora abro la URL /Home/IndexPermanent, observo que he sido redirigido a Home/Destination, pero ahora con el código HTTP 301:



image



Y listos! Ya lo tenemos… ¿veis que fácil? :D



PD: He dejado un zip con todo el código (en mi skydrive).



PD2: Para variar… esto es un crosspost desde mi blog en geeks.ms… pásate por allí mejor!! :D

martes, 9 de febrero de 2010

[WebCast] Material de mi webcast sobre Facebook Connect

Este pasado jueves (4 de febrero de 2010) di un WebCast sobre Facebook Connect. La verdad es que era la primera vez que daba un webcast, y fue una sensación extraña: acostumbrado a dar charlas presenciales, se me hizo raro no tener el feedback visual de la gente. La verdad es que me sentí un poco como cuando hablas con un contestador automático…

Pero he de decir que la experiencia me gustó, así que espero poder repetirla algún dia de esos!

Muchas gracias a todos los que os conectasteis, espero que al menos os haya picado la curiosidad sobre connect :)

Os dejo el enlace a un fichero .zip con el código y el “super pptx” que enseñé!

Nota: El proyecto es un proyecto ASP.NET MVC RC2, así pues debéis tener instalado este framework para que os funcione!

Saludos!

PD: Esto es un crosspost de mi blog en geeks.ms… pásate por allí mejor! ;-)

lunes, 8 de febrero de 2010

NoSQL… ¿puede ser lo que necesitas?

Ultimamente se oye hablar cada vez más de BBDD no relacionales o tal y como se las conoce ahora “NoSQL”. En dosideas publicaron un interesante post al respecto de los sistemas NoSQL. La idea es renunciar a algunos de los principios (y funcionalidades) de las bases de datos tradicionales (relacionales) a cambio de obtener mayores velocidades en el acceso a datos.

Cuando nos adentramos en este mundo, debemos dejar de pensar en tablas, ya que nuestros datos dejarán de estar guardados en formato relacional. Aunque existen varios formatos en los cuales se guardan nuesteos datos parece ser que los más comunes son (clave,valor) o usar documentos que son en cierto modo una extensión de la (clave, valor). Si os pasáis por el artículo de la wikipedia sobre NoSQL hay varios enlaces a distintos sistemas NoSQL. A mi me gustaría hablaros de uno con el que he hecho algunas pruebas: MongoDB.

Montando el entorno…

Para empezar a usar el entorno, basta con descargarnos los binarios. La versión más reciente estable es la 1.2.2. MongoDB usa el esquema de numeración de versiones par, donde las versiones estables siempre son pares y las de desarrollo son impares (así actualmente en desarrollo ya existe la 1.3, que cuando se estabilice pasará a ser 1.4). Para instalar MongoDB basta con descomprimir el zip donde más os plazca :)

MongoDB está escrita en C++ y viene con una librería (.lib) y varios headers para ser usada directamente. Por suerte existe una API C# para MongoDB que os podéis descargar desde http://github.com/samus/mongodb-csharp (podéis descargaros los binarios (MongoDB.Linq.dll y MongoDB.Driver.dll) o bien el código fuente (una solución VS2008 que genera los dos assemblies mencionados).

Una vez tengáis instalado MongoDB y los dos assemblies del driver para C#… estamos listos para empezar!

Para poner en marcha el servidor de MongoDB basta con ir donde hayáis descomprimido MongoDB y lanzar el comando:

mongod --dbpath <data_path>

donde <data_path> es el directorio de datos que quereis usar.

El concepto de documentos…

MongoDB se define como base de datos de documentos, entendiendo como un documento una estructura de datos que es una colección de elementos “clave, valor”, donde las claves son cadenas y los elementos cualquier cosa que se quiera. Aunque esto puede parecerse una tabla (donde las claves sean los nombres de los campos) se diferencia del concepto de tabla en que por un lado no tiene esquema fijo (una clave puede o no aparecer en un documento) y en que los valores van más allá de los admitidos generalmente por los campos de las bases de datos relacionales (p.ej. podemos guardar colecciones de otros documentos como valores). El hecho que no haya esquema fijo hace estas bases de datos NoSql ideales para el desarrollo de soluciones que manejan datos poco estructurados.

Algunas operaciones básicas…

Para conectarnos a la BBDD basta con instanciar un objeto del tipo Mongo y llamar al método Connect. Una vez hayamos finalizado debemos llamar a Disconnect:

var srv = new Mongo();
srv.Connect();
// Operaciones con MongoDB
srv.Disconnect();





Una vez estamos conectados al servidor debemos escojer la base de datos a utilizar. Esto lo podemos hacer con el método getDB:




var db = srv.getDB("MyAppDB");





Una vez tenemos la base de datos ya podemos operar con ella. Lo que en una base de datos relacional son tablas con registros aquí son colecciones con documentos. A diferencia de una tabla relacional una MISMA colección puede tener documentos con distinto esquema (distintas claves):




// Obtenemos la coleccion 'users'
var iusers = db.GetCollection("users");
// Creamos un usuario con login y pwd
Document user = new Document();
user.Add("login", "edu");
user.Add("pwd", "mypassword");
// Insertamos el documento
iusers.Insert(user);
// Creamos otro documento. Este con login y pwd_hash
user = new Document();
user.Add("login", "edu2");
user.Add("pwd_hash", "tH23H13");
// Insertamos el documento en la MISMA colección
iusers.Insert(user);
// Obtenemos todos los elementos de la colección
var allUsers = iusers.FindAll();
int numUsers = allUsers.Documents.Count();





Un ejemplo



Vamos a ver un ejemplo de uso de MongoDB… Ahora que Lluís nos está haciendo una clase maestra sobre el membership provider, vamos a ver como podríamos implementar nuestro membership provider para que vaya contra MongoDB en lugar de contra una base de datos relacional. No voy a mostrar todo el código, sólo un par de extractos pero os dejo al final del post el enlace en formato zip con la solución de visual studio.











El primer paso es definir que base de datos de MongoDB vamos a utilizar. No me he calentado mucho la cabeza: los membership providers tienen una propiedad ApplicationName que está pensada para eso. La idea es que un mismo proveedor puede manejar datos de distintas aplicaciones. El campo ApplicationName permite saber cual es la aplicación que se está manejando. Yo asumo que la BBDD de MongoDb se llamará igual que la aplicación:




var srv = new Mongo();
srv.Connect();
var db = srv.getDB(this.ApplicationName);





Otro punto importante es no olvidarnos de llamar a Disconnect() cuando hemos terminado de trabajar con Mongo. La mejor manera de hacer esto, dado que la clase Mongo no implementa IDisposable es con try…finally:




var srv = new Mongo();
try
{
srv.Connect();
// Operaciones con MongoDb
}
finally
{
srv.Disconnect();
}





El membership provider que he creado no implementa todas las funciones, pero sí un grupo suficientemente ámplio para que sea usable: Es capaz de validar usuarios, añadir usuarios y borrar usuarios. P.ej. esta es una captura de pantalla de la aplicación de configuración de ASP.NET usando este proveedor:



image



Nada más… os dejo el enlace al código con un zip que incluye una solución de visual studio con el proveedor y una aplicación asp.net que lo utiliza (una página con un control login). Si os interesa… echadle una ojeada! ;-)



Enlace del fichero .zip (en mi skydrive).



Saludos!



PD: Como no podria ser de otra forma… esto es un crosspost de mi blog en geeks.ms! Pásate por allí mejor… ;-)

martes, 2 de febrero de 2010

Facebook Connect (iv): Que todo el mundo sepa lo que has hecho!

Se comenta que las redes sociales dan fama, mujeres y dinero aunque no necesariamente en este orden…

En los tres primeros posts sobre facebook connect vimos como permitir al usuario que hiciera login con su cuenta de facebook, como implementar el logout y como crear zonas “privadas” de nuestra web sólo para usuarios de facebook.

Hoy vamos a ir un paso más allá: vamos a ver como podemos publicar mensajes en el muro del usuario de facebook autenticado. De esta manera sus amigos verán los logros que nuestro usuario consigue en nuestra web y más importante aún: verán nuestra web! Si el mensaje es sugerente podremos conseguir un buen puñado de visitas a nuestra web! Todos ganamos! El usuario consigue fama y mujeres, y nosotros… dinero. Lo que decía al principio :)

1. Montando toda la infrastructura…

Aprovechando que ha salido ya ASP.NET MVC 2 RC, este es un buen momento para empezar a usarlo, no? ;-)

Descargaos ASP.NET MVC 2 RC e instaláoslo. Una novedad es que ahora (a diferencia de MVC 1) podemos crear una aplicación MVC vacía… parece una chorrada, pero cuando se hacen demos se agradece no ir por ahí borrando el controlador Account y todas sus vistas asociadas! ;-)

Vamos a hacer un proyecto super simple: un botón para que usuario pueda entrar sus credenciales de facebook, y una vez las haya entrado, le mostraremos una página donde podrá entrar un texto que será publicado en el perfil del usuario previa aceptación suya.

Dadle a “New project” –> “ASP.NET MVC 2 Empty Web Application”. Esto os creará un proyecto vacío, pero con la estructura de ASP.NET MVC.

Recordad que para que connect os funcione debéis tener creada vuestra aplicación facebook y el fichero xd_receiver.htm debe estar en vuestra aplicación (tal y como se cuenta en el primer post de esta serie). Ah, y no os olvideis de añadir una referencia al Facebook Developer Toolkit (facebook.dll).

2. Definiendo la aplicación

Nuestra aplicación va a ser simple: un botón de facebook connect para que el usuario se auntentique. Una vez autenticado vamos a pedirle que entre un texto que será publicado en su muro (recordad: previa aceptación).

Para ello vamos a tener un controlador (vamos a ser originales y vamos a llamarlo Home) con tres vistas: la vista inicial (Index) desde donde el usuario podrá autenticarse con su cuenta de facebook, otra vista (Welcome) donde redireccionaremos a los usuarios autenticados para que entren un texto que se publicará en su muro y finalmente la vista Publish que será la que publicará el elemento en el muro del usuario.

3. El controlador Home

Vamos a empezar por el controlador. Vamos a tener dos acciones: Index que se limitará a mostrar la vista inicial y Welcome que mostrará la vista con el campo de texto o bien lo publicará en facebook, dependiendo de si se entra via GET o via POST.

La acción Index es muy simple:

public ActionResult Index()
{
ConnectSession cs = this.GetConnectSession();
if (cs.IsConnected())
{
return RedirectToAction("Welcome");
}
return View();
}





El método GetConnectSession es un método extensor de Controller que me devuelve una ConnectSession (clase que pertence al Facebook Developer Toolkit), que está definido así:




public static class ControllerExtensions
{
public static ConnectSession GetConnectSession(this Controller self)
{
string appKey = ConfigurationManager.AppSettings["ApiKey"];
string secretKey = ConfigurationManager.AppSettings["Secret"];
return new ConnectSession(appKey, secretKey);
}
}





Efectivamente en mi web.config tengo mi clave API y mi clave secreta como elementos dentro de <appSettings />



La acción de Welcome es muy parecida a la acción Index, salvo por la diferencia de que en Index mirábamos si el usuario ya estaba autenticado para redirigirlo a Welcome y aquí haremos al revés: si no está autenticado lo mandaremos para Index:




public ActionResult Welcome()
{
ConnectSession cs = this.GetConnectSession();
if (!cs.IsConnected())
{
return RedirectToAction("Index");
}

return View();
}





4. Las vistas…



Necesitamos (de momento) dos vistas: Home/Index.aspx y Home/Welcome.aspx. La primera debe contener simplemente un botón de facebook connect, renderizado usando XFBML. No pongo el código aquí, ya que es idéntico al del primer post de esta serie y luego tenéis el adjunto con todo el código…



Me interesa más que veais la vista Welcome. Esta vista debe renderizar un cuadro de texto para que el usuario pueda entrar el texto que luego debemos publicar en el muro. El código de la vista Welcome es:




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

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Welcome
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

<h2>Welcome</h2>
<table>
<tr>
<% using (Html.BeginForm()) { %>
<td>
<%= Html.LabelFor(x=>x.Text) %>
</td>
<td>
<%= Html.EditorFor(x=>x.Text) %>
<%= Html.ValidationMessageFor(x=>x.Text) %>
</td>
<% } %>
</tr>
</table>

</asp:Content>





Lo más interesante aquí es el uso de templated helpers para renderizar un editor para la propiedad Text del modelo WallMessageModel. El código Html.EditorFor(x=>x.Text) es quien renderiza un editor para la propiedad Text del modelo. En mi caso esta propiedad es de tipo string, por lo que se va a renderizar un textbox. También es interesante observar el uso de Html.ValidationMessageFor que va a permitir poner mensajes de error en caso de que alguno de los datos entrados sea incorrecto.



Ah, el modelo WallMessageModel es super simple: solo una propiedad Text de tipo string. La peculiaridad es que vamos a usar Data Annotations para evitar que el texto que se entre sea una cadena vacía:




public class WallMessageModel
{
[Required(ErrorMessage="Text es obligatorio")]
public string Text { get; set; }
}





El uso de [Required] en la propiedad Text, convierte esta en obligatoria.



Ok, ya tenemos la vista que renderiza un formulario (Html.BeginForm). Por defecto el formulario se envía via POST a la misma URL de la vista (Home/Welcome en nuestro caso), por lo tanto vamos a necesitar una nueva acción Welcome en el controlador Home pero que trate peticiones POST:




[HttpPost()]
public ActionResult Welcome(WallMessageModel data)
{
if (ModelState.IsValid)
{
return View("Publish", data);
}
else
{
return View(data);
}
}





Fijaos en tres cosas:




  1. El uso de HttpPost hace que esta acción trate solamente peticiones via POST.


  2. El parámetro de la acción es de tipo WallMessageModel. ASP.NET MVC es capaz de crear un objeto de tipo WallMessageModel siempre y cuando la petición POST contenga todos los datos para crearlo. En nuestro caso dado que hemos metido en el formulario un editor para la única propiedad de la clase, la petición POST va a contener los datos suficientes y ASP.NET MVC nos va a poder crear un objeto WallMessageModel con los datos entrados por el usuario.


  3. El uso de ModelState.IsValid para comprobar si el modelo recibido es correcto: esto aplica las reglas de Data Annotations y devuelve true si el modelo las cumple o no. Si el usuario ha entrado un texto vació Model.IsValid será false. Si este es el caso “simplemente” mandamos el modelo “de vuelta” a la misma vista. Esto renderizará de nuevo la vista, con los valores rellenados por el usuario, y el mensaje de error (gracias al uso de Html.ValidationMessageFor). Por otro lado si todo ha ido bien, manda el usuario a la vista “Publish” y le pasa el modelo.



5. La vista de publicación



Sólo nos queda crear la vista que publica el texto en facebook. Para ello vamos a usar el método streamPublish del API de facebook. Este método publica una variable de tipo attachment que tiene varios campos (p.ej. el texto, una URL, el tipo de attachment por si queremos publicar imágenes, …). La descripción completa de todos los campos está en la definición de attachment en la wiki de facebook developer.



A modo de ejemplo vamos a usar los campos:




  • name: Título del post


  • href: Una URL que apunta a “quien ha realizado el post”.


  • caption: El subtítulo del post


  • description: El texto largo del post.



En mi caso dejo los 3 primeros fijos y en el cuarto pongo el valor que ha entrado el usuario:




<script type="text/javascript">
FB.init("TU_CLAVE_API", "/xd_receiver.htm");
FB.ensureInit(function() {
var attachment = { 'name': 'TestGeeks', 'href': 'http://geeks.ms', 'caption': '{*actor*} ha hecho algo',
'description': '<%= Model.Text %>'
};

FB.Connect.streamPublish('', attachment);
});
</script>
<h2>Mensaje publicado</h2>





Fijaos en el uso de {*actor*}: esto se sustituye por el nombre del usuario al que publicamos el mensaje…



Si ejecutais el código vereis como al entrar un texto en el textbox y pulsar enter, facebook va a pedir confirmación del texto a publicar en el muro. Si la aceptáis… el mensaje será publicado en el muro!



Notáis el tintineo de los euros que van cayendo??? :)



Saludos!!!



Os dejo el zip con todo el código (en mi skydrive). Para cualquier duda que tengáis… ya sabéis: contactad conmigo!!!



PD:Para variar esto es un crosspost desde mi blog en geeks.ms… Pásate por allí mejor!! ;-)

[CatDotNet] Material de mi charla sobre ASP.NET MVC

foto1

Hola! Este viernes, tal y como anunció José Miguel, hemos celebrado una pequeña sesión en CatDotNet donde he tenido el placer de hablar un poco sobre ASP.NET MVC…

En la charla vimos los aspectos básicos del nuevo framework, y algunas de las novedades de la próxima versión 2 (como Templated Helpers o validación del modelo). Durante la charla construimos paso a paso una mini-aplicación con MVC y fuimos comentando distintas opciones y el por qué de cada cosa.

Fue muy divertido, y me lo pasé realmente genial!

Os adjunto el material de la charla: las 11 tristes diapositivas que usé (ya digo, fue una sesión básicamente práctica) y la aplicación de demo que hicimos.

A todos los que vinisteis pese a la hora (un viernes a las 19… buf! que pereza!) muchas gracias, espero que disfrutarais como yo.

foto2

Por último sólo recordaros que en CatDotNet seguimos al pie del cañón, organizando charlas y en general lo que podemos: si eres de por la zona de la “Catalunya central” y quieres que te informemos sobre las acciones que realizamos, o bien quieres contarnos tus experiencias con la tecnologia .NET… adelante! Ponte en contacto conmigo o con José Miguel.

Os dejo el enlace al zip con la demo (en mi skydrive).

Saludos!

PD: Edito para poner algunas fotillos… :)

PD: Esto es un crosspost de mi blog en geeks.ms… porque no te pasas por allí mejor?? ;)