Hola a todos! El otro día os comentaba lo mucho que me está gustando pure… Hoy, a modo de ejemplo, os presento como realizar fácilmente un lector de Twitter que muestre los últimos tweets…

El proyecto asp.net mvc

No voy a mencionar mucha cosa del proyecto asp.net mvc en general puesto que al final hay un enlace con todo el código. Consta de dos controladores:

  1. Home: Que tiene las acciones Index, Login y Tweets. La acción Index redirige a Login o bien a Tweets en función de si el usuario ha entrado o no login y password de twitter. La acción Login muestra un formulario de login y redirige a la acción tweets, que es la que hace el trabajo.
  2. La acción Tweets se limita a mostrar una vista que es la que hace gran parte del trabajo.

1. La vista Home/Tweets

Esta vista, hace una llamada ajax a la acción List del controlador Tweeter (nuestro segundo controlador). Esta acción debe devolver la información json con los últimos tweets del usuario. El código, es gracias a jquery, trivial:

$(document).ready(function() {
$.getJSON (
"<%= Url.Action("List","Twitter") %>",
function(val)
{
renderPure(val);
});
});

El método renderPure será el que usara Pure para mostrar los datos.

2. El controlador Twitter

Twitter dispone de una fantástica API REST con la cual podemos hacer casi cualquier cosa. Esta API está preparada para devolver datos usando distintos formatos, entre ellos json.

Algunas de las funciones de dicha API son públicas, pero otras (como la  de ver los últimos tweets) requieren que nos autentiquemos en twitter. Hay dos formas de hacerlo: usar OAuth o bien basic authentication.

OAuth es un protocolo de autenticación, pensado para integrar distintas aplicaciones, y una de sus ventajas es que el usuario no debe dar su contraseña en ningún momento, además de que puede granularizar sus permisos (puede dar permisos de lectura pero no de escritura p.ej.). Es sin duda el método recomendado para integrarnos con Twitter. Si usais cualquier cliente Twitter (web o escritorio) que no os haya pedido el password y que además hayais tenido que darle permisos desde la web de twitter, entonces este cliente está usando OAuth.

Basic authentication por otro lado es muy simple: se basa en poner una cabecera en la petición http, con nombre Authorization y cuyo valor sea Basic login:password, donde la cadena “login:password” está en Base64. Es un método totalmente inseguro (salvo que se uso junto con SSL) y además el usuario debe proporcionar su usuario y su contraseña al cliente (lo que bueno… mucha confianza no genera). Pero es muy sencillo de implementar, y es el que yo he usado.

El controlador Twitter tiene una sola acción List que realiza una llamada a la API REST de Twitter y pasa el resultado (json) tal cual:

public ActionResult List()
{
UserModel um = this.Session["user"] as UserModel;
string url = "http://api.twitter.com/1/statuses/friends_timeline.json";

// Realiza la petición a twitter
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Headers.Add("Authorization", string.Format("Basic {0}", um.Base64Data));
req.Method = "GET";
HttpWebResponse res = (HttpWebResponse)req.GetResponse();

string data = null;
if (res.StatusCode == HttpStatusCode.OK)
{
using (StreamReader sr = new StreamReader(res.GetResponseStream()))
{
data = sr.ReadToEnd();
}
}

return !string.IsNullOrEmpty(data) ? (ActionResult)this.RawJson(data) : (ActionResult)View("Error");
}

Fijaos que el código es muy simple: Recojemos un objeto UserModel de la sesión. Ese objeto contiene el login y password de twitter del usuario. Con esos datos construimos una petición a la URL de la API REST de Twitter y añadimos la cabecera Authorization. Finalmente leemos el valor devuelto (una cadena json, salvo error) y lo mandamos tal cual.

El método RawJson es un método mío de extensión que devuelve un objeto RawJsonActionResult:

public static RawJsonActionResult RawJson(this Controller @this, string data)
{
return new RawJsonActionResult() { RawData = data };
}

La clase RawJsonActionResult es un ActionResult mío, que me permite devolver una cadena json. No uso el JsonResult de MVC porque ese está pensado para construir una cadena json a partir de un objeto .NET, y yo ya tengo la cadena json. Como podeis ver, el código es trivial:

public class RawJsonActionResult : ActionResult
{
public string RawData { get; set; }

public override void ExecuteResult(ControllerContext context)
{
var response = context.HttpContext.Response;
response.ContentType = "application/json";
response.Write(RawData);
response.Flush();
}
}

Quizá alguien se pregunta porque hago una llamada a un controlador mío (Twitter) que lo único que hace es consultar a la API REST de Twitter y mandarme el mismo resultado (sin tratar). La respuesta es para evitar problemas de cross-domain: de esta manera desde la vista Home/Tweets la única llamada ajax que hay es una llamada a otro controlador del mismo dominio, lo que seguro que no me va a generar ningún problema.

3. La vista Home/Tweets (ii)

Una vez la vista Home/Tweets recibe el objeto json (resultado de la llamada a la acción List del controlador Twitter), debemos generar el html necesario para “dibujarlo”… Aquí entra en acción PURE.

El objeto json que nos devuelve twitter es bastante grandote (aunque gracias a firebug se puede ver muy fácilmente). Por cada tweet yo voy a mostrar:

  1. El avatar de quien ha hecho el tweet
  2. El texto del tweet
  3. El nombre de usuario y el nombre real

Para ello usaré la siguiente plantilla:

<div id="purecontent" style="display:none">
<div class="tweet">
<img class="picprofile"></img>
<p>
<span id="text"></span>
</p>
<div class="userdata"><span id="user"></span> (<span id="realname"></span>)</div>
</div>
</div>

Dentro del div “purecontent” se generaran tantos div “tweet” como tweets haya… Veamos el código del método renderPure:

function renderPure(data)
{
var directive = {
'div.tweet' : {
'tweet<-': {
'span#text' : 'tweet.text',
'span#text@id+' : 'tweet.id',
'span#user' : 'tweet.user.screen_name',
'span#user@id+' : 'tweet.id',
'span#realname' : 'tweet.user.name',
'span#realname@id+' : 'tweet.id',
'img.picprofile@src' : 'tweet.user.profile_image_url'
}
}
};
$("#purecontent").render(data, directive);
$("#purecontent").toggle();
}

Lo más importante (como no) es la directiva, que le indica a pure como renderizar los datos a partir de la plantilla y el objeto json. En concreto esa plantilla dice lo siguiente:

  1. Por cada elemento del elemento json (tweet<-) haz lo siguiente:
    1. En el span cuyo id es text (span#text) pon el valor de la propiedad text del elemento.
    2. En este mismo span modifica su id para que el nuevo valor sea el id previo (text) más el valor de la propiedad Id del elemento.
    3. Haz lo mismo con los spans user y realname, pero usando las propiedades screen_name y name del objeto user que está dentro del elemento.
    4. Finalmente en el atributo src de la img cuya clase es picprofile (img.picprofile@src) coloca el valor de la propiedad profile_image_url del objeto user que está dentro del elemento.

 

Que… fácil, eh? Al principio igual cuesta acostumbrarse pero una vez se le pilla el truquillo es muy potente y natural.

Finalmente llamamos al método render de PURE y por último hacemos visible el div padre (purecontent) mediante el método toggle de jQuery…

Y este es el resultado (por cierto José M. Aguilar… ponte ya un avatar :p)

image

Os dejo un zip con el proyecto entero para que le echeis un vistazo.

Saludos!!!

con 3 comment(s)
Archivado en: ,

Estos días he empezado a trastear con PURE (cuyas siglas significan PURE Unobstrusive Rendering Engine). El objetivo de PURE es proporcionar un mecanismo para transformar datos en JSON a HTML. Cada vez más existen multitud de servicios que devuelven datos en formato JSON, y cada vez más es normal consumir estos servicios desde aplicaciones web, via javascript. Si el resultado final es mostrar los datos debemos realizar una conversión a mano y generar usando javascript el HTML que deseemos. Esto es lento, tedioso y pesado.

PURE viene a ayudarnos en este punto: básicamente coje datos en JSON y usando una plantilla HTML, genera código HTML que luego “incrusta” en alguna parte del documento final… Además se integra con jQuery (y otras librerías javascript). Lo poco que he visto de PURE me ha encantado, así que quiero compartirlo con vosotros :)

1. Creación del proyecto web

Usando ASP.NET MVC 2 RC2 (ya lo teneis todos instalado, no?? ;-)) creamos un proyecto ASP.NET MVC vacío (ASP.NET MVC 2 Empty Web Application) y nos ponemos manos a la obra!

El primer paso es descargarnos PURE desde su página web. Tendremos un zip bastante grandote (algo más de 200 KBs), pero de todos los archivos que tiene, sólo nos interesa el pure.js dentro de la carpeta lib. Copiamos este archivo dentro de la carpeta scripts de nuestra aplicación MVC.

Vamos a crear ahora la master page de nuestro proyecto: no vamos a meter mucha cosa en la master page, simplemente vamos a incluir las dos librerias javascript que usaremos: jQuery y PURE. Para ello en View/Shared le damos a Add New Item y seleccionamos MVC 2 View Master Page le damos como nombre Site.master y le añadimos los tags <script> necesarios (dentro del head):

<script src="../../Scripts/jquery-1.4.1.js"></script>
<script src="../../Scripts/pure.js"></script>

Ahora vamos a crear un controlador que nos devuelva datos en JSON… Vamos a la carpeta Controllers, le damos a Add Controller y le damos un nombre (en mi caso HomeController). Esto nos creará el archivo HomeController.cs, con la clase HomeController con un método Index.

Vamos a crear ahora una clase cualquiera con datos que vamos a devolver. En la carpeta Models agregamos la clase TwitterUsers:

public class TwitterUser
{
public string Name { get; set; }
public string Twitter { get; set; }
}

Finalmente en el método Index de nuestro HomeController, creamos una Lista de TwitterUsers y la devolvemos:

public ActionResult Index()
{
var data = new List<TwitterUser> {
new TwitterUser() { Name="Eduard Tomàs", Twitter="eiximenis"},
new TwitterUser() { Name="José Miguel Torres", Twitter="alegrebandolero"},
new TwitterUser() { Name="Gisela Torres", Twitter="0GiS0"},
new TwitterUser() { Name="David Salgado", Twitter="davidsb"},
new TwitterUser() { Name="Toni Recio", Twitter="stormc23"},
};
return Json(data, JsonRequestBehavior.AllowGet);
}

Fíjaos en el uso de Json para devolver los datos en formato JSON y el parámetro JsonRequestBehavior para permitir devolver datos JSON usando GET (ver el post de José M. Aguilar para más información).

 

 

Si ponemos el proyecto en marcha y dirigimos Firefox a la URL /Home/Index veremos (gracias Firebug!) nuestros datos en JSON:

image

Es fácil mandar datos en JSON usando ASP.NET MVC, eh?? ;-)

2. Crear una vista para ver los datos

Vamos ahora a crear una vista para ver esos datos usando jQuery y PURE. Para ello primero debemos crear una acción en nuestro controlador Home que nos muestre la vista:

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

Una vez hecho añadimos la carpeta Home dentro de Views y creamos una vista (Add View) llamada List.

Ahora nos toca añadir el código en la vista para:

  • Hacer una llamada AJAX a la url /Home/Index para obtener los datos en JSON
  • Usar PURE para mostrarlos

El primer punto es casi trivial gracias a jQuery. Añadimos el siguiente tag <script> justo antes del <h2>:

<script type="text/javascript">
$(document).ready(function() {
var url="<%= Url.Action("Index", "Home") %>";
$.getJSON(url, process);
});

function process(data)
{
// Código para procesar el resultado json
}
</script>

El método getJSON de jQuery es quien realiza todo el trabajo: Llama a una url usando AJAX y cuando la llamada devuelve llama a una función de callback (process).

 

Vamos ahora a usar PURE para convertir los datos en JSON a datos en HTML.

3. Usando PURE…

Para usar PURE necesitamos tres cosas:

  1. Unos datos en JSON (ya los tenemos!)
  2. Una plantilla HTML
  3. Unas reglas de conversión (directivas).

La plantilla HTML es simple y se coloca en la propia página, en el sitio donde queremos que se coloque el HTML generado por pure. En nuestro caso en la vista List:

<div id="puredata">
<ul>
<li></li>
</ul>
</div>

El div puredata es nuestra plantilla, en nuestro caso vamos a generar una lista (ul) de elementos (li) a partir de los datos JSON.

Ahora biene lo “bueno”… las reglas de conversión.

En PURE las reglas de conversión (directivas les llaman ellos) se especifican usando variables javascript que básicamente tienen este formato:

var directive={'selector' : 'valor'};

Donde selector es un selector (CSS) para seleccionar un elemento dentro de la plantilla y valor es un valor (propiedad) del elemento json. Nuestro caso es un poco más complejo, ya que queremos mostrar una lista de valores. En este caso debemos usar la sintaxis extendida de directivas:

var directive={
'selector' : {
'variable-loop<-coleccion json': {
directivas-del-loop
}
};

Escrito así parece un poco lioso, pero veamos un ejemplo de como sería nuestra directiva si lo que queremos es mostrar el nombre de nuestros usuarios de Twitter:

var directive = {
'li' :{
'user<-':{
'.': 'user.Name'
}
}
};

Si diseccionamos por parte la directiva:

  • user<- Significa que vaya iterando directamente sobre los elementos de los datos json (nuestro objeto json ya es por sí un array).
  • El operador punto (.) se refiere al propio elemento que se está generando.

Así estamos indicando que por cada elemento del array json genere un tag li y que coloque como texto del propio tag li el valor de la propiedad Name del elemento actual.

Finalmente sólo nos queda realizar la llamada para que PURE realice la generación del HTML… como PURE se integra con jQuery, eso es tan sencillo como:

$("#puredata").render(data, directive);

Con esto le decimos a PURE que use la plantilla dentro del div cuyo id es “puredata” y que la aplique a los datos indicados con las reglas que le decimos.

Y el resultado es el que esperamos:

image

Que… impresionante, eh??? :)

Otra demo… vamos a generar junto con el nombre, el enlace al twitter de cada persona.

Primero modificamos la plantilla para que quede de la siguiente manera:

<div id="puredata">
<ul>
<li><span></span> <a href="http://twitter.com/">Ver su twitter</a></li>
</ul>
</div>

El tag <span> contendrá el nombre y en el atibuto href del tag <a> vamos a añadir su nombre de usuario de twitter… La directiva que debemos usar es:

var directive = {
'li' :{
'user<-':{
'span': 'user.Name',
'a@href+' :'user.Twitter'
}
}
};

Con esta directiva le indicamos a PURE que: Por cada elemento del array json:

  1. Coja el tag <span> dentro del <li> y coloque el valor de la propiedad Name del elemento
  2. Coja el tag <a> dentro del <li> coja el valor del atributo href y le concatene (el + del final) el valor de la propiedad Twitter del elemento.

Y este es el resultado:

image

Impresionante… verdad?

Espero que el post os haya servido para ver un poco en que consiste PURE y el enorme potencial que atesora…

Os dejo el .zip con el proyecto final (en mi skydrive).

Un saludo!!!!

con 5 comment(s)
Archivado en:

Hola a todos! Hoy, por temas que no vienen al caso, estaba mirando el tutorial de MVC que hay en asp.net. Hay dos apartados dedicados a explicar como se pueden realizar modelos usando Linq to Sql y EF. Hasta ahí, ningún problema.

El problema viene, cuando en el apartado dedicado a Linq to Sql, una vez han dado un ejemplo de uso de las clases de Linq to Sql desde un controlador, dicen que esta solución, aunque correcta, implica que si en un futuro cambiamos el proveedor de acceso a datos vamos a tener que tocar todos nuestros controladores, ya que usamos las clases Linq to Sql desde ellos. También se menciona que el uso del patrón Repositorio (repository pattern) nos permite aislarnos de Linq to Sql de modo que si más adelante migramos, digamos a EF, no tengamos que modificar nuestros controladores.

Cuando usamos Linq to Sql se nos generan por un lado una clase que hereda de DataContext y que representa nuestra base de datos, y por otro un conjunto de clases que representan a nuestras tablas (nuestros datos). Esas clases no son POCO y están generadas y controladas por Linq to Sql.

Esta es el ejemplo que proporcionan en asp.net sobre el uso del patrón repositorio:

namespace MvcApplication1.Models
{
public class MovieRepository : IMovieRepository
{
private MovieDataContext _dataContext;

public MovieRepository()
{
_dataContext = new MovieDataContext();
}
public IList<Movie> ListAll()
{
var movies = from m in _dataContext.Movies
select m;
return movies.ToList();
}
}
}

El repositorio MovieRepository es quien nos da acceso a los datos contenidos en la base de datos y nos independiza de la clase Linq to Sql MovieDataContext. ¡Bien!

El ejemplo de uso que proporcionan desde un controlador es el siguiente:

public class MoviesController : Controller
{
private IMovieRepository _repository;
public MoviesController(IMovieRepository repository)
{
_repository = repository;
}
public ActionResult Index()
{
return View(_repository.ListAll());
}
}

Aprovechan además para comentarnos que el parámetro repository del constructor podría esta inyectado por un contenedor IoC (con lo que estoy totalmente de acuerdo). Luego nos enfatizan de que la dependencia de este controlador es solamente con IMovieRepository, con lo que por un lado podemos pasar un Mock de IMovieRepository cuando usamos tests unitarios y por otro si algún dia migramos a EF, nos basta con crear un EFMovieRepository y… listos.

Pues no.

Veis el problema? No? Y si cambio el código del método Index() por el siguiente código equivalente?

 public ActionResult Index()
{
IList<Movie> movies = _repository.ListAll();
return View(movies);
}

Detectais ahora el problema? El controlador MoviesController no depende de MovieDataContext, de acuerdo, pero sigue dependiendo de las clases generadas por Linq-to-Sql (en este caso de la clase Movie). Con el ejemplo que hay en asp.net tal y como está, de poco nos sirve implementar el patrón repositorio: cuando migremos a otro proveedor de datos (pongamos EF) vamos a tener que tocar igualmente todos los controladores.

Hay solución para el problema? Como (casi) todo en esta vida tiene solución, pues si que la hay: podemos forzar a Linq to SQL a que utilice clases POCO aunque perdemos algunas de sus capacidades (y por lo que he visto tampoco es que sea trivial). Existe una buena explicación al respecto en http://blogs.msdn.com/digital_ruminations/archive/2007/08/28/linq-to-sql-poco-support.aspx.

Si buscais “Repository Pattern” “Linq to Sql” en google encontrareis interesantes discusiones y posibles implementaciones al respecto, pero el objetivo de mi post no era tanto ofrecer soluciones al problema, si no hacer notar el pequeño problema en el ejemplo de asp.net y que, como siempre, no debemos creernos directamente todo lo que vemos.

Un saludo!

con no comments
Archivado en:

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!

con 1 comment(s)
Archivado en:

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!

Nota: Para que todo funcione bien, esta segunda ruta debe estar en la table de rutas antes que la ruta anterior. Más detalles en el post continuación de este!

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!

Nota: Este post no está completo. Si te interesa el tema lee la continuación, puesto que no todo es tan fácil!

con 4 comment(s)
Archivado en:

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)

con 2 comment(s)
Archivado en: ,,,

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

con 3 comment(s)
Archivado en:

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!

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!

con 2 comment(s)
Archivado en:

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

con 3 comment(s)
Archivado en: ,

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… :)

con 1 comment(s)
Archivado en: ,

Hola a todos! Este post es sólo para informaros que el próximo 4 de febrero a las 19:00, voy a realizar un webcast para la gente del Club .NET UOC. Bajo el título Aprovecha el poder de facebook en tu web, voy a contar algunas cosillas sobre como integrar facebook en tu aplicación ASP.NET: usar connect para implementar un SSO (single sign-on), poner mensajes en el muro, obtener información del perfil del usuario…

La idea es ver lo fácil que resulta la integración y las ventajas que podemos obtener al integrarnos con una red social en general, y en una de las más usadas en particular…

Os dejo el enlace para apuntaros. Cualquier cosa que queráis comentar… ya sabéis donde encontrarme!!! ;-)

Saludos!

PD: Evidentemente, muchas gracias a la gente del Club .NET UOC y a Jesús Bosch en particular, por invitarme a realizar el webcast… es un autentico placer!

con 2 comment(s)
Archivado en: ,

Hola! Un post cortito, sobre un error que me he encotrado… Al compilar un proyecto, marcado para interoperabilidad COM VS.NET se me ha quejado con el siguiente error:

c:\WINDOWS\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets(3019,9): error MSB3217: Cannot register assembly "C:\Teamserver\Phoenix\Refactoring\Core\DevelopmentCore-WI5825-SIO4\bin\Debug\PhoenixContainer.dll". Method 'GetDefaultIWorkspace' in type 'CaixaPenedes.Phoenix.Core.CompositeUI.ShellUserControl' from assembly 'Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=77c76132715b70fa' does not have an implementation.

Yendo al directorio bin/Debug y ejecutar regasm PhoenixContainer.dll daba el mismo error.

La situación era la siguiente:

  1. Tengo dos soluciones distintas, una de las cuales compila (entre otros) el ensamblado Core.dll, donde está la clase ShellUserControl que supuestamente tiene el método no implementado. Por supuesto el método está implementado. La otra solución compila PhoenixContainer.dll, el ensamblado que me da el error.
  2. PhoenixContainer.dll tiene una referencia (entre otros a Core.dll).
  3. Ambas soluciones compilan contra el mismo directorio de salida.

Buscando por ahí, he visto que más gente tenía el mismo error… en esta página dan con la que parece ser la causa principal: que al ejecutar regasm, los ensamblados que regasm usen no sean los mismos contra los que se ha compilado el proyecto. Generalmente eso puede ser debido a versiones incorrectas en la GAC.

Este no era mi caso: Yo tenia Core.dll y PhoenixContainer.dll en el mismo directorio y no uso la GAC, así que es imposible que me estuviese pillando alguna otra Core.dll.

Finalmente he decidido poner en marcha fuslogvw y que me mostrase todos los "bind failures”, es decir todas las veces que el CLR intenta cargar un ensamblado, y por la razón que sea no lo encuentra. Y touché: Ha aparecido un bind failure: Regasm.exe intentaba cargar Microsoft.Practices.Composite.UI.dll (este ensamblado forma parte de CAB). El ensamblado Core.dll tiene una referencia contra Microsoft.Practices.Composite.UI.dll, pero con copy local a false, puesto que usamos un directorio “compartido” donde hay varios ensamblados externos a nuestro proyecto. Sospecho que la razón por la cual Regasm.exe intenta cargar Microsoft.Practices.Composite.UI.dll (y no otros ensamblados también referenciados por Core.dll) es porque el método GetDefaultIWorkspace es público y devuelve un IWorkspace, tipo definido en este ensamblado. Lo curioso, es que este método nunca es utilizado desde PhoenixContainer.dll (aunque el tipo ShellUserControl sí).

Así en el bin/debug tenía Core.dll pero no Microsoft.Practices.Composite.UI.dll y esa era la causa del error: modificando la referencia con copy local a true, todo ha funcionado correctamente!

Cada vez tengo más claro que el copy local a false, sólo trae que compilaciones, a excepción que los archivos referenciados estén en la GAC…

Saludos!

con no comments
Archivado en:

Nota: Este post es el segundo post de la serie Objetos que notifican sus cambios de propiedades.

En el post anterior vimos como configurar Unity para que no tener que añadir código adicional para implementar la interfaz INotifyPropertyChanged. En este post quiero hablaros de un patrón que se utiliza mucho cuando hablamos de aplicaciones complejas: el patrón del publicador – suscriptor. En este patrón tenemos básicamente dos conceptos:

  1. El publicador: Cuando un objeto quiere notificar algo al respecto de su estado, se limita a publicar un mensaje con la información deseada.
  2. El suscriptor: Los subscriptores reciben todos aquellos mensajes a los que están suscritos, con independencia de quien los haya publicado.

Este patrón se diferencia del modelo de eventos estándard de .NET, en que para realizar una suscripción a un tipo de mensaje no es necesario tener referencia alguna a quien pueda publicar este mensaje. En el sistema de eventos no és así: si quiero recibir información sobre el click de un botón, debo tener una referencia a este botón, para poder registrar la función gestora del evento:

button1.Click += new EventHandler(button1_Click);

Este modelo de eventos directos tiene sus limitaciones y da en Winforms bastantes quebraderos de cabeza (especialmente cuando tenemos un formulario con un usercontrol formado por varios usercontrols que a su vez están formados por más usercontrols y queremos propagar un evento del usercontrol  más interno al formulario). Es cierto que WPF introduce dos mejoras interesantes como los routed events (que ayudan precisamente a solventar este problema de usercontrols anidados) y los commands, pero ninguno de ambos mecanismos ofrece la misma flexibilidad que el modelo de publicación – suscripción.

Créeme: si desarrollas una aplicación compleja, ya sea en winforms o en WPF, te beneficiará mucho el uso de un modelo de publicación – suscripción (no en vano tanto CAB+SCSF como PRISM incorporan uno).

Vamos a ver como podemos implementarnos uno que, aunque sencillito, sea lo suficientemente funcional…

1. El notificador de mensajes

Lo primero que debemos crear es el notificador de mensajes, es decir el objeto que usamos para publicar un mensaje y el que usamos también para informar a que tipo de mensajes queremos suscribirnos.

El notificador de mensajes va a tener esta interfaz:

public interface ICommandNotifier
{
/// <summary>
/// Devuelve la lista de los commands actuales. Los commands se
/// añaden automáticamente cuando se realiza un publish de cualquier
/// tipo nuevo.
/// </summary>
IEnumerable<Type> Commands { get; }
/// <summary>
/// Añade una suscripción al tipo de command TPayload
/// </summary>
/// <typeparam name="TPayload">Tipo de command al que nos suscribimos</typeparam>
/// <param name="func">Acción a ejecutar cuando se publique el command</param>
/// <param name="filterFunc">Método que se evalúa sobre el payload para determinar
/// si el command se pasa o no al suscriptor.</param>
/// <returns>Token de suscripción</returns>
SubscriptionToken Subscribe<TPayload>(Action<TPayload> func, Func<TPayload, bool> filterFunc);

/// <summary>
/// Publica un command. El tipo de command es el tipo de la clase del payload.
/// </summary>
/// <param name="payload">Payload (datos) del commanad</param>
void Publish(object payload);

Básicamente sólo tiene un método para suscribirse a un determinado tipo de mensajes y otro método para publicarlos. Una implementación más compleja nos permitiría también eliminar suscripciones (es decir cuando ya no me interesa seguir recibiendo notificaciones de determinados commands)… pero eso lo dejamos como ejercicio :)

La implementación tampoco es excesivamente compleja (no pongo el código aquí, ya que lo tenéis en el zip que adjunto al final del post). Básicamente lo que hace es:

  1. Mantiene una lista de todos los tipos de mensajes que se hayan lanzado. Lo que determina si un mensaje es de un tipo u otro es su clase (en la implementación una lista de objetos CommandInfo).
  2. Por cada mensaje de esa lista mantiene una lista con todos los suscriptores (en la implementación objetos de la clase AllTimeSubscriber).
  3. Por cada suscriptor de cada mensaje mantiene básicamente dos delegates:
    1. El delegate que sirve para decidir si se envía este mensaje a este suscriptor (parámetro filterFunc del método Subscribe)
    2. El delegate que debe invocarse en el suscriptor (parámetro func del método Subscribe).

Sólo un apunte: el notificador de mensajes vamos a registrarlo en Unity como un singleton, eso significa que existirá sólo uno y que estará vivo durante toda la ejecución del programa. Por lo tanto, si guardamos directamente los delegates en el notificador de mensajes, impedirá al garbage collector actuar sobre los suscriptores (recordad que un delegate mantiene una referencia a un objeto en concreto y a un método). Para solucionar esto me he creado una clase, que he llamado WeakDelegate, que tiene la misma información que un delegate, pero usa una WeakReference para apuntar al objeto (el suscriptor) y de esta manera permitir actuar al garbage collector. Recordad: siempre que guardeis referencias en un singleton considerad el uso de WeakReference!

2. Cambiar la implementación de nuestro handler

Una vez tenemos un notificador de mensajes, sólo debemos cambiar la implementación de nuestro ICallHandler (clase AutoPropertyChangedHandler) de Unity, para usar dicho notificador. Para ello en el método Invoke en lugar de llamar al método RaiseEvent (para lanzar el evento PropertyChanged) como hacíamos en el post anterior, vamos a usar el notificador para publicar un mensaje de tipo PropertyChangedCommand:

// Si el setter no produce excepción, publicamos un command de tipo PropertyChangedCommand
if (raiseEvt && msg.Exception == null)
{
cmdNotifier.Publish(new PropertyChangedCommand(propName, input.Target));
}

La clase PropertyChangedCommand es una clase que nos hemos creado nosotros que no hace nada más que guardar el nombre de la propiedad que ha cambiado y el objeto sobre el cual ha cambiado la propiedad.

Como recibe la clase AutoPropertyChangedHandler el notificador de mensajes? Pues se le pasa en el constructor:

public AutoPropertyChangedHandler(ICommandNotifier cmdNotifier)
{
this.cmdNotifier = cmdNotifier;
}

Ahora sólo debemos modificar la clase AutoPropertyChangedAttribute para que cuando cree el objeto AutoPropertyChangedHandler  le pase el notificador de mensajes. La forma más fácil es aprovechar que en AutoPropertyChangedAttribute tenemos acceso a Unity, para devolver el objeto AutoPropertyChangedHandler usando Resolve y que de esa manera Unity inyecte el notificador de mensajes:

public override ICallHandler CreateHandler(IUnityContainer container)
{
return container.Resolve<AutoPropertyChangedHandler>();
}

3. El suscriptor

Finalmente nos queda crear el suscriptor. Los suscriptores son clases normales que usan el notificador de mensajes para suscribirse a tipos de mensajes. P.ej. el siguiente suscriptor se suscribe a los mensajes cuyo tipo sea PropertyChangedCommand:

public Suscriptor(ICommandNotifier cmdNotif)
{
cmdNotif.Subscribe<PropertyChangedCommand>(this.DoPropertyChangedCommand, this.CanDoPropertyChangedCommand);
}

private void DoPropertyChangedCommand(PropertyChangedCommand payload)
{
Console.WriteLine("Propiedad {0} modificada", payload.PropertyName);
}

private bool CanDoPropertyChangedCommand(PropertyChangedCommand payload)
{
bool retVal = !payload.PropertyName.Equals("Name");
Console.WriteLine("CanDoPropertyChanged con prop {0} devuelve {1}", payload.PropertyName, retVal);
return retVal;
}

Fíajos en la función CanDoPropertyChangedCommand: esta función se evalúa cada vez que alguien publica un command y sólo en el caso que devuelva true se ejecutará la función DoPropertyChangedCommand que es la que “procesa” el mensaje. En este caso, este suscriptor está interesado en recibir todos los cambios de calquier propiedad excepto “Name”.

Finalmente sólo nos queda crear un suscriptor y probar el código. En el método Main() tengo:

container.RegisterType<ICommandNotifier, CommandNotifier>(new ContainerControlledLifetimeManager());
A2 a2 = container.Resolve<A2>();
Suscriptor subs = container.Resolve<Suscriptor>();
a2.Name = "edu";
a2.Edad = 10;
// Registramos el notificador como singleton
container.RegisterType<ICommandNotifier, CommandNotifier>(new ContainerControlledLifetimeManager());
// Creamos un A2...
A2 a2 = container.Resolve<A2>();
// ... y un suscriptor
Suscriptor subs = container.Resolve<Suscriptor>();
a2.Name = "edu";
a2.Edad = 10;

Y listos! Sí lo ejecutais veréis que la salida es:

CanDoPropertyChanged con prop Name devuelve False
CanDoPropertyChanged con prop Edad devuelve True
Propiedad Edad modificada

Ya tenemos implementado nuestro propio publicador-suscriptor!

Os dejo un zip con todo el código (en skydrive).

Un saludo!

con no comments
Archivado en: ,,

Nota: Este post es el primer post de la serie Objetos que notifican sus cambios de propiedades.

En este post vamos a ver como configurar la intercepción de Unity, para poder inyectar nuestro código cada vez que se modifiquen las propiedades de un objeto.

Los que desarrolléis en WPF sabréis que existe una interfaz llamada INotifyPropertyChanged, que se puede implementar para notificar a la interfaz de usuario de que las propiedades de un objeto (generalmente ligado a la interfaz) han modificado, y que por lo tanto la interfaz debe actualizar sus datos.

Esta interfaz define un solo evento, llamado PropertyChanged que debe lanzarse para informar del cambio de propiedad. Es responsabilidad de cada clase lanzar el evento cuando sea oportuno:

public class A : INotifyPropertyChanged
{
private string _name;

// Evento definido por la interfaz
public event PropertyChangedEventHandler PropertyChanged;

// Lanza el evento "PropertyChanged"
private void NotifyPropertyChanged(string info)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
// Propiedad que informa de sus cambios
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
NotifyPropertyChanged("Name");
}
}
}
}

Este código es pesado de realizar en clases con muchas propiedades y es fácil cometer errores… además no podemos utilizar las auto-propiedades. Vamos a ver como con el sistema de intercepción de Unity podemos hacer que este evento se lance de forma automática.

1. Configurando el sistema de intercepción de Unity

Para usar el sistema de intercepción de Unity, debéis añadir los assemblies Microsoft.Practices.ObjectBuilder2, Microsoft.Practices.Unity y Microsoft.Practices.Unity.Interception a vuestro proyecto (los tres assemblies forman parte de Unity).

El primer paso es crear una clase que implemente la interfaz ICallHandler, esta clase es la encargada de proporcionarnos un punto dónde inyectar el código:

class AutoPropertyChangedHandler : ICallHandler
{
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
// Aquí podremos inyectar nuestro código
IMethodReturn msg = getNext()(input, getNext);
return msg;
}
public int Order { get; set; }
}

El siguiente paso es crear un atributo que permita indicar a Unity que ICallHandler debe usar cuando se opere con objetos de la clase. Esta clase debe derivar de HandlerAttribute y debe redefinir el método CreateHandler para devolver una instancia del ICallHandler a utilizar:

class AutoPropertyChangedAttribute : HandlerAttribute
{
public override ICallHandler CreateHandler(IUnityContainer container)
{
return new AutoPropertyChangedHandler();
}
}

El código es trivial, eh?? Simplemente devuelve un AutoPropertyChangeHandler, de esa manera Unity usará este AutoPropertyChangeHandler para todas las clases que estén decoradas con el atributo AutoPropertyChangedAttribute. De esta manera es como le indicamos a Unity qué ICallHandler debe usar por cada tipo de clase.

Finalmente queda configurar el propio contenedor. Para ello debemos debemos añadir la extensión de intercepción a Unity y indicarle que interceptor queremos utilizar para cada clase:

IUnityContainer container = new UnityContainer();
container.AddNewExtension<Interception>();
container.Configure<Interception>().SetInterceptorFor<A2>(new VirtualMethodInterceptor());

Le he indicado a Unity que para la clase A2 utilice el interceptor VirtualMethodInterceptor. Este interceptor puede interceptar todas las llamadas a métodos virtuales.

Finalmente ya podemos definir la clase A2:

[AutoPropertyChanged()]
public class A2 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual string Name { get; set; }
}

Fijaos en las diferencias entre A2 y la clase A antigua:

  1. A2 está decorada con el atributo AutoPropertyChangedAttribute que hemos definido antes.
  2. La propiedad Name es virtual
  3. No tenemos ningún código adicional para lanzar el evento PropertyChanged.

Con eso el mecanismo de intercepción está listo. Si obteneis una instancia de A2 usando el método Resolve del contenedor y miráis con el debugger de que tipo real es el objeto, veréis que no es de tipo A2, sinó de un tipo raro: el proxy que crea Unity para poder interceptar los métodos virtuales (comparad el wach para a2 y a2newed:

image

2. Implementando el código en nuestro Handler

Vamos a modificar el método Invoke de AutoNotifyPropertyHandler para que lance el evento cada vez que se llame a un set de una propiedad… La clase completa queda tal y como sigue: Básicamente en el método Invoke, miramos si el nombre del método que se ha llamado empieza por “set_”, y si es el caso asumimos que es el Setter de una propiedad. Recogemos el valor actual de la propiedad usando reflection y si no son el mismo, lanzamos el evento PropertyChanged usando reflection. Y listos! :)

class AutoPropertyChangedHandler : ICallHandler
{
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
bool raiseEvt = false;
string propName = null;
INotifyPropertyChanged inpc = input.Target as INotifyPropertyChanged;
if (inpc != null)
{
// Si el nombre del método empieza por "set_" es un Setter de propiedad
if (input.MethodBase.Name.StartsWith("set_"))
{
propName = input.MethodBase.Name.Substring(4);
MethodInfo getter = input.Target.GetType().GetProperty(propName).GetGetMethod();
object oldValue = getter.Invoke(input.Target, null);
object newValue = input.Arguments[0];
// Si los valores de newValue y oldValue son distintos
// debemos lanzar el evento (la comparación la hacemos por
// Equals si es posible).
raiseEvt = newValue == null ?
newValue != oldValue :
!newValue.Equals(oldValue);
}
}
IMethodReturn msg = getNext()(input, getNext);
// Si el setter no produce excepción, lanzamos el evento
if (raiseEvt && msg.Exception == null)
{
RaiseEvent(inpc, propName);
}
return msg;
}

public int Order { get; set; }

// Método que lanza el evento PropertyChanged usando reflection
private void RaiseEvent(INotifyPropertyChanged inpc, string propertyName)
{
Type type = inpc.GetType();
FieldInfo evt = null;
// Buscamos el evento (no estará en el propio Type ya que el propio Type
// será un proxy, pero iteraremos por los tipos base hasta encontrarlo)
do
{
evt = type.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
type = type.BaseType;
} while (evt == null && type.BaseType != null);
// Invocamos el evento
if (evt != null)
{
MulticastDelegate mcevt = evt.GetValue(inpc) as MulticastDelegate;
if (mcevt != null)
{
mcevt.DynamicInvoke(inpc, new PropertyChangedEventArgs(propertyName));
}
}
}
}

Ahora podemos comprobar como se lanza el evento automáticamente al modificar una propiedad de A2.

Un saludo!!!!

PD: Dije en el post introductorio que no pondría código, pero finalmente os incluyo el zip con el código de este post (en SkyDrive).

PD2: Echad un post a INotifyPropertyChanged with Unity Interception AOP del blog de Dmitry Shechtman que me ha servido de inspiración para el ejemplo (yo tenía pensado uno mucho más tonto en este primer post).

con no comments
Archivado en: ,,

Hola a todos!!! Como ha ido la despedida del 2009 y la bienvenida del 2010!!! Espero que os hayáis portado bien y que los reyes os hayan traído muuuuchos regalitos!

En este post quiero dejar de lado la serie que estaba haciendo sobre facebook connect, para ver como, gracias a Unity, podemos crear objetos que nos notifiquen cuando cambian sus propiedades, sin que nosotros debamos añadir (casi) ningún código adicional!

Pienso que es un muy buen ejemplo del poder de usar un contenedor IoC, además de resolver una situación que se da muchas veces: quiero enterarme de los cambios sobre las propiedades de un objeto, pero no quiero codificar dicho objeto de ninguna forma especial (es decir, no poner ningún tipo de código a la clase de cuyos cambios de propiedad deseo enterarme).

Donde queremos llegar…

Cuando hablamos de contenedores IoC, nos vienen a la cabeza dos grandes patrones: Dependency Injection y Service Locator… pero hay otra poderosísima razón para usarlos: las intercepciones. Esta capacidad permite “enchufar” código a los objetos en tiempo de ejecución, lo que permite añadir capacidades de AOP.

En este caso vamos a configurar la intercepción de Unity, para enchufar código cada vez que se lea/modifique una propiedad de un objeto. Dicho código nos notificará la lectura o escritura de la propiedad.

Además vamos a hacerlo configurable, de forma que no recibamos notificaciones de todas las propiedades, sino sólo de aquellas que nos interesen! La configuración de què propiedades queremos recibir notificación vamos a tenerla en otra clase. El objetivo es llegar a un código como el que sigue:

[PropertyNotifier(typeof(UfoNotifications))]
public class Ufo
{
public virtual string Name { get; set; }
public virtual int Edad { get; set; }
}

public class UfoNotifications : PropertyNotifications<Ufo>
{
public UfoNotifications()
{
this.OnProperty(x => x.Name).Set.Notify.WithParameters();
this.OnProperty(x => x.Name).Get.Notify.WithoutParameters();
this.OnProperty(x => x.Edad).Get.Notify.WithoutParameters();
}
}

El primer código nos define una clase Ufo. Es una clase totalmente normal, salvo por dos detalles:

  1. El atributo PropertyNotifier nos indica que queremos recibir notificaciones cuando se lean/modifiquen propiedades de dicha clase, y indica que la configuración sobre cuales son las propiedades que deseamos notificar se encuentra en la clase UfoNotifications
  2. Todas las propiedades son virtuales.

El segundo código contiene la configuración sobre qué propiedades y qué notificaciones queremos recibir. En este caso usamos una aproximación tipo fluent interface cuya ventaja principal es que el código es mucho más fácil de leer (y de escribir).

Esta serie va a constar de tres posts que publicaré en breve (a medida que publique los posts iré modificando éste para añadir los enlaces):

  1. Como configurar el mecanismo de intercepción de Unity para enchufar nuestro código (Añadido el 13/01/2009).
  2. Como podemos notificar los cambios de propiedades, sin obligar a que quien quiera recibirlos tenga que tener una referencia al objeto que los notifica (lo que nos impide usar eventos tradicionales de .NET) (Añadido el 14/01/2009).
  3. Como crear la fluent interface  para configurar las notificacioes

No voy a adjuntar código en cada post porque sólo tengo la solución completa implementada… en el último post adjuntaré todo el código, junto con un ejemplo. Al final sí que adjunto un zip en cada post :)

Nos leemos!

con 7 comment(s)
Archivado en: ,,

Saludos! Un post cortito, cortito, cortito :)

Si renombramos una máquina cliente de TFS, vemos que perdemos los mappings ya que el workspace está asociado a un usuario + nombre de máquina.

Aunque podemos crearnos un workspace nuevo y borrar el antiguo también podemos modificar el workspace antiguo y cambiar el nombre de máquina, aunque para ello deberemos usar la herramienta de línea de comandos tf.exe:

tf workspaces /owner:TuUsuarioDeTFS /updateComputerName:NombreAntiguoDeLaMaquina /s:URLDelTFS

(El parámetro /owner: no se si es estrictamente necesario, yo lo he metido por si acaso).

Sí, sí… cambiar el nombre de máquina no es muy habitual, pero si se os ocurre hacerlo como yo, con n check-ins pendientes… Pues se agradece la opción :)

Sacado de http://blogs.msdn.com/buckh/archive/2006/03/03/update-workspace.aspx donde además dicen como modificar el workspace si lo que cambia es tu nombre de usuario.

Saludos!

PD: Esta vez ha sido cortito de veras, eh??? ;-)

con 4 comment(s)
Archivado en:

Este genial post de José M. Aguilar sobre como procesar peticiones existentes en ASP.NET MVC, me ha dado una idea que quiero compartir con vosotros… El tema consiste en que si el usuario se equivoca y entra una URL errónea como /Home/Jindex (en lugar de /Home/Index) le podemos sugerir que quizá quería ir a /Home/Index. Vamos a ver como podríamos hacerlo…

La idea es que cuando recibamos una petición errónea en el HandleUnknownAction miremos cuales son las acciones del controlador y miremos cual es la acción que más se aproxima a la acción que el usuario ha entrado.

1. Obteniendo las acciones del controlador actual

Si usamos el ActionInvoker por defecto de ASP.NET MVC, las acciones están mapeadas a métodos públicos del controlador. El nombre del método define el nombre de la acción, excepto si el método está decorado con el atributo ActionNameAttribute que especifica un nombre de acción distinto.

Así pues, una manera de obtener las acciones del controlador actual es recorrerse sus métodos públicos y obtener su nombre o bien el nombre del atributo ActionNameAttribute que tuviese asociado:

namespace System.Web.Mvc
{
public static class ControllerExtensions
{
public static IEnumerable<string> GetAllActions(this Controller self)
{
var methods = self.GetType().GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public);
return methods.Select(x =>
x.GetCustomAttributes(typeof(ActionNameAttribute), true).Length == 1 ?
((ActionNameAttribute)x.GetCustomAttributes(typeof(ActionNameAttribute), true)[0]).Name :
x.Name);
}
}
}

2. Calculando que acción es la más parecida a la que ha entrado el usuario

El siguiente paso es ver cual de todas las acciones se parece más a la acción que ha entrado el usuario. Hay varios algoritmos para calcular la distancia entre dos cadenas, uno conocido es la distancia de Levenshtein que es el que yo he usado. En el artículo de la wikipedia enlazado tenéis el pseudo-código del algoritmo… para los vagos aquí tenéis una implementación de la distancia de Levenshtein en C#.

Yo he implementado el método cómo un método de extensión de la clase string:

namespace MvcApplication1.Extension
{
public static class StringExtensions
{
public static int LevenshteinDistance(this string s, string t)
{
// Ver una implementación en http://www.merriampark.com/ldcsharp.htm
}
}
}

Finalmente en el método HandleUnknownAction sólo nos queda recorrer el enumerable de acciones devuelto por GetAllActions y para cada acción calcular la distancia de Levenshtein entre esta acción y el nombre que ha entrado el usuario… y cojer la menor:

protected override void HandleUnknownAction(string actionName)
{
var actions = this.GetAllActions();
int min = int.MaxValue;
string newAction = null;
foreach (var action in actions)
{
int ld = action.LevenshteinDistance(actionName);
if (ld < min)
{
min = ld;
newAction = action;
}
}
if (min < int.MaxValue)
{
View("RedirectView", new RedirectModel(newAction, "Home", actionName)).
ExecuteResult(this.ControllerContext);
}
else
{
base.HandleUnknownAction(actionName);
}
}

La clase RedirectModel es una clase que tiene tres propiedades: Acción a donde pensamos que el usuario quería ir, controlador de dicha acción, y acción tecleada por el usuario:

public class RedirectModel
{
public RedirectModel(string action, string controller, string originalAction)
{
this.Action = action;
this.Controller = controller;
this.OriginalAction = originalAction;
}

public string Action { get; private set; }
public string Controller { get; private set; }
public string OriginalAction { get; private set; }
}

Finalmente, sólo nos queda la vista “RedirectView”, que yo he puesto en Shared (para que pueda ser reutilizada por varios controladores):

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

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

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

<h2>Pos por <%= Model.OriginalAction%> no me viene nada...</h2>

OOppps... esta página no existe... ¿seguro que no querías ir a
<a href="<%= Url.Action(Model.Action, Model.Controller)%>"><%= Model.Action %></a>?
</asp:Content>

Y este es el resultado, si el usuario teclea /Home/Jindex esto es lo que se le muestra:

image

Ya véis, qué fácil :) Un saludo!!!

PD: Os dejo un zip con la solución completa!!!

con 3 comment(s)
Archivado en:

Este post va a ser cortito… En los dos primeros posts de esta serie hemos visto como podemos autenticar (logon) a un usuario de facebook en nuestra web y como podemos desautenticarlo (logoff).

Yo uso ASP.NET MVC para mis desarrollos web, no voy a enumerar ahora las ventajas que en mi opinión tiene MVC sobre Webforms, sinó comentaros como podemos evitar el acceso de usuarios que no estén autenticados en facebook a ciertas regiones de nuestra web.

En ASP.NET MVC tenemos lo que se llaman filtros. A grandes rasgos un filtro es código que se ejecuta antes y/o después de cada petición y que puede alterar el comportamiento por defecto de la petición. Digamos que es un modo rápido y sencillo de aplicar técnicas AOP en aplicaciones MVC. Los filtros se enganchan a las peticiones a las que afectan mediante el uso de atributos (que se aplican a las acciones de los controladores, en MVC cada petición es atendida por una accion _método_ de un controlador).

Qué cosas podemos hacer con filtros? Pues imaginad: hacer log de las peticiones, tratar los errores que se den de una forma coherente, comprimir el resultado de la petición, o porque no, validar que el usuario esté autenticado.

Ya hay en ASP.NET MVC, un filtro que comprueba si el usuario está autenticado:

[Authorize]
public ActionResult ChangePassword()
{
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
return View();
}

El filtro Authorize comprueba que el usuario está autenticado, y si no es el caso lo redirige a la página de login. De esta manera la acción ChangePassword sólo puede ser ejecutada por usuarios autenticados.

En mi caso, los usuarios pueden entrar en mi web usando una cuenta de facebook o bien una cuenta propia de mi web (autorización estándard de ASP.NET). Pero hay ciertas acciones que sólo están disponibles si el usuario se autentica via facebook. Así que me puse manos a la obra para crear un filtro parecido a [Authorize] pero que validase si el usuario está autenticado en facebook.

Creando el filtro…

Crear un filtro es realmente sencillo… el único punto delicado es saber que tipo de filtro queremos crear, ya que existen cuatro tipos:

  1. Authorization filters: Para implementar autorizaciones
  2. Action filters: Para implementar acciones a realizar antes o después de una acción de un controlador.
  3. Result filters: Para implementar acciones a realizar antes o después de que se haya ejecutado la vista asociada a una acción.
  4. Exception filters: Para gestionar errores producidos durante el tratamiento de una petición.

Para crearnos nuestro propio filtro derivamos de la clase FilterAttribute e implementamos una interfaz u otra. En mi caso, dado que quiero implementar un authorization filter debo implementar la interfaz IAuthorizationFilter, así que manos a la obra!

La interfaz es muy sencilla: tiene un sólo método:

void OnAuthorization(AuthorizationContext filterContext)

En este método es donde comprobaremos que el usuario está autorizado. Si no lo está usaremos las propiedades del objeto filterContext para modificar la respuesta y generar una respuesta nueva (p.ej. una redirección). En caso que el usuario esté autorizado no hacemos nada y dejamos que se procese la acción del controlador.

El código para saber si un usuario está autenticado en facebook, es muy simple:

ConnectSession cs = new ConnectSession(appKey, secretKey);
if (!cs.IsConnected())
{
// No está conectado via facebook
}

Lo que yo quiero es redirigir los usuarios no autenticados via facebook a otra acción. En lugar de redirigirlos a la acción de login (como hace [Authorize]) yo quiero poder decidir en cada caso a que acción redirigir el usuario. Es decir, poder tener algo como:

[FacebookAuthorize("Index", "Home")]
public ActionResult LinkFacebookUser()
{
return View();
}

Si el usuario no está autorizado en facebook, quiero redirigir la petición a la acción Index del controlador Home.

El código base del método OnAuthorization queda tal y como sigue:

public void OnAuthorization(AuthorizationContext filterContext)
{
string appKey = this.AppKey ?? ConfigurationManager.AppSettings["ApiKey"];
string secretKey = this.SecretKey ?? ConfigurationManager.AppSettings["Secret"];

ConnectSession cs = new ConnectSession(appKey, secretKey);
if (!cs.IsConnected())
{
// Redirigir usuario
}
}

Sólo nos queda lo más “fácil”: redirigir el usuario en caso de éste no se haya autenticado via facebook!

Redirigiendo el usuario

Para redirigir el usuario debemos crear un nuevo resultado y asignarlo a la propiedad Result del objeto filterContext recibido como parámetro. No hay ningún resultado de tipo “RedirectToActionResult”, en su lugar tenemos que usar el tipo RedirectToRouteResult, de la siguiente manera:

var rvalues = new RouteValueDictionary();
rvalues["action"] = this.Action;
if (!string.IsNullOrEmpty(this.Controller))
{
rvalues["controller"] = this.Controller;
}

filterContext.Result = new RedirectToRouteResult(rvalues);

Creamos un RouteValueDictionary y asignamos las entradas “action” y “controller” a la acción y controlador donde queremos redirigir el usuario, y a partir de este RouteValueDictionary creamos el RedirectToRouteResult que asignamos a la propiedad Result.

Y listos! Con esto ya tenemos nuestro propio filtro creado!

Os dejo aquí el código fuente completo, para que lo podáis examinar libremente!

/// <summary>
/// ActionFilter de MVC per redirigir la petició a un altre vista si l'usuari
/// no està autenticat a Facebook
/// </summary>
public class FacebookAuthorize : FilterAttribute, IAuthorizationFilter
{

/// <summary>
/// Clau pública de l'API
/// </summary>
public string AppKey { get; set; }

/// <summary>
/// Clau privada de l'API
/// </summary>
public string SecretKey { get; set; }

/// <summary>
/// Controlador al qual es transfereix la petició (<c>null</c> significa
/// el controlador actual).
/// </summary>
public string Controller { get; private set; }

/// <summary>
/// Acció a la que es transfereix la petició
/// </summary>
public string Action { get; private set; }

/// <summary>
/// Construeix un objecte <c>FacebookAuthorize</c> que redirigirà la petició
/// a l'acció <paramref name="action"/> del controlador actual si l'usuari no
/// està autenticat a facebook.
/// </summary>
/// <param name="action">Acció a la qual cal redirigir.</param>
public FacebookAuthorize(string action)
{
this.Action = action;
}

/// <summary>
/// Construeix un objecte <c>FacebookAuthorize</c> que redirigirà la petició
/// a l'acció <paramref name="action"/> del controlador <paramref name="controller" />
/// si l'usuari no està autenticat a facebook.</param>
/// </summary>
/// <param name="action">Acció a la qual cal redirigir.</param>
/// <param name="controller">Controlador al qual cal redirigir.</param>
public FacebookAuthorize(string action, string controller)
{
this.Action = action;
this.Controller = controller;
}

/// <summary>
/// Valida la petició
/// </summary>
/// <param name="filterContext">Contexte de ASP.NET MVC</param>
public void OnAuthorization(AuthorizationContext filterContext)
{
string appKey = this.AppKey ?? ConfigurationManager.AppSettings["ApiKey"];
string secretKey = this.SecretKey ?? ConfigurationManager.AppSettings["Secret"];

ConnectSession cs = new ConnectSession(appKey, secretKey);
if (!cs.IsConnected())
{
var rvalues = new RouteValueDictionary();
rvalues["action"] = this.Action;
if (!string.IsNullOrEmpty(this.Controller))
{
rvalues["controller"] = this.Controller;
}

filterContext.Result = new RedirectToRouteResult(rvalues);
}
}
}

Un saludo!!!!

PD: Al final el post no ha sido tan cortito… aiinsss! Si es que cuando empiezo a escribir no paro!! :P

con 4 comment(s)
Archivado en: ,

Hola a todos! Este es el segundo post de una serie que iré haciendo contando mis experiencias con Facebook Connect. En el primer post vimos como usar facebook connect para implementar un single sign on en nuestra web (o sea que los usuarios puedan entrar en nuestra web usando el login y password de facebook).

Ahora viene la segunda parte… da igual lo buena que sea tu web, llegará un momento en que el usuario querrá irse y no creo que le guste mucho que dejemos su sesión abierta :p. Tened presente que cuando usamos connect, cuando el usuario abre la sesión en nuestra web, también la abre en facebook y viceversa: cuando cerramos la sesión en nuestra web también cerramos su sesión de facebook.

Método 1 (que no funciona)

En el post anterior, introduje FDT (Facebook Developer Toolkit), un conjunto de clases que permiten llamar a la API REST de facebook desde código C# (en lugar de usar la API javascript del propio facebook). Una de las clases que incorpora FDT es la ConnectSession, que encapsula una conexión de Facebook Connect.

Pues bien: ConnectSession tiene un método Logout, así que lo más sencillo es hacer lo siguiente:

var fbc = this.GetConnectSession();
if (fbc.IsConnected())
{
fbc.Logout();
}

¡Y listos! ¿Listos? Pues no… esto no funciona. Lo podeis comprobar fácilmente: si sin cerrar el navegador abrís otra pestaña y os vais a facebook, veréis que entráis directamente sin que os pida las credenciales. Todavía estáis autenticados en facebook.

Después de hacer algunas pruebas lo siguiente que hice, fue navegar por el código fuente de FDC y mirar que hacia el método Logout de la clase ConnectSession, y eso es lo que me encontré:

/// <summary>
/// Logs out user
/// </summary>
public override void Logout()
{
}

Vaya… pues claro que no me funcionaba! ¿Porque está vacío este método? Bueno, pues FDT proporciona distintos tipos de sesiones para dar soporte a varias funcionalidades (p.ej. aquí nos centramos en Connect, pero se puede usar FDT para desarrollar aplicaciones que funcionen dentro de facebook). Según sea el uso que le demos a FDT usaremos una clase de sesión u otra, pero todas derivan de FacebookSesion, clase abstracta que define todos los métodos que todas las sesiones deben implementar. El problema está en que algunos métodos no pueden ser implementados en según que clases derivadas, debido a que Facebook no ofrece las mismas funcionalidades según el tipo de aplicación que estemos desarrollando…

… no se si me he explicado bien! Resumiendo: Facebook no ofrece API REST para cerrar una sesión de Connect. Por eso la clase ConnectSession no implementa el método Logout. Quizá un throw new NotImplementedException hubiese sido mejor, ya que almenos daría una pista visual y rápida de lo que realmente ocurre, en lugar de tener un método vacío que no hace nada…

Método 2 (el que sí funciona)

Para hacer el logout del usuario, debemos usar el API Javascript de Facebook. De hecho, básicamente, debemos llamar al método FB.Connect.logout, sólo que hay que tener presentes un par de puntos:

  1. Antes de llamar a FB.Connect.logout debemos llamar a FB.Init para inicializar el API de Facebook
  2. Antes de llamar a FB.Init debemos llamar a FB_RequireFeatures para decidir que parte del API de facebook queremos inicializar.

FB_RequireFeatures es una función asíncrona, pero por suerte le podemos pasar el método a ejecutar cuando la ejecución haya tenido lugar, así que lo más normal es tener algo como:

function PerformFBLogout() {
FB_RequireFeatures(["XFBML"], function() {
FB.init("TU_CLAVE_DEL_API", "/xd_receiver.htm");
FB.Connect.logout(function() {
window.location="<%= Url.Action("LogOff","Account") %>";
});
});
}

Este código llama a FB_RequireFeatures, y le pasa la función a ejecutar cuando FB_RequireFeatures se haya ejecutado asíncronamente. Entonces podemos llamar a FB.Init y posteriormente a FB.Connect.logout. A FB.Connect.logout le pasamos una nueva función con el código a ejecutar cuando el usuario se haya desconectado. En mi caso cuando el usuario se haya desconectado realizo una redirección a la acción “LogOff” del controlador “Account” (sí, yo uso ASP.NET MVC). En la acción LogOff del controlador Account desautentico el usuario.de ASP.NET y muestro la vista de inicio.

Para que este código funcione, debe estar en una página que tenga el siguiente tag script en el body (no en el head):

<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript"></script>

(Yo este tag script lo tengo colocado en mi página .master)

Bueno… pues eso es todo por el momento! Ya os iré contando más batallitas con Connect!!! ;-)

con 5 comment(s)
Archivado en: ,
Más artículos Página siguiente >