viernes, 18 de diciembre de 2009

Canviar el nombre de un cliente de un Workspace de TFS

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??? ;-)



PD: Evidentemente… crosspost desde mi blog en geeks.ms

jueves, 17 de diciembre de 2009

Ooops.. esta página no la tengo, pero tengo otra parecida para tí…

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



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

martes, 15 de diciembre de 2009

Facebook Connect (iii): Eh! Que esta parte de mi web sólo es para usuarios de facebook! (ASP.NET MVC)

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



PD2:  Este post es crossposting de mi blog en geeks.ms!!! ;-)

lunes, 14 de diciembre de 2009

Facebook Connect (ii): Adiós, amigo adiós (o como hacer el logout).

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

jueves, 10 de diciembre de 2009

Facebook Connect: Si estás en facebook bienvenido a mi web…

Antón Molleda comentaba en un post de su blog ([WLMT] Socializándonos), las ventajas que ofrece integrar nuestras aplicaciones an alguna de las redes sociales existentes. Él comentará sus experiencias con el Windows Live Messenger Toolkit en su blog, así que yo voy a comentaros cuatro cosillas sobre Facebook Connect, el mecanismo de integración que nos ofrece Facebook.

Con Facebook Connect lo que obtenemos es la posibilidad de que los usuarios registrados en Facebook puedan entrar con su login en nuestra web (o aplicación, aunque yo me cocentraré en web), y no sólo eso, sinó que (previa aceptación del usuario) nuestra aplicación pueda publicar contenido en facebook.

En el caso que me ocupa estoy desarrollando una web y me interesa que los usuarios que están en facebook puedan entrar en ella sin necesidad de registrarse en mi web. Mi web seguirá manteniendo su propio sistema de registro de usuarios, puesto que no es obligatorio tener cuenta en facebook para entrar en ella. La página de login tendría un aspecto tal como (queda prohibido cualquier comentario sobre la estética :p):

image

1. Crear una aplicación en Facebook para Connect

El primer paso para poder usar Facebook Connect es crear la aplicación facebook que nos va a dar acceso a facebook. Para ello, entramos en facebook y seguimos los siguientes pasos:

  • Nos dirigimos a http://www.facebook.com/developers/createapp.php para crear una nueva aplicación facebook.
  • En el apartado Basic entramos el nombre de la aplicación. El resto de valores los podemos dejar por defecto (aunque si soys detallistas siempre podéis añadir un icono a vuestra aplicación :p). Ahora viene lo importante…
  • En el apartado Connect debéis entrar el dominio donde está vuestra web… Si estais desarrollando seguramente no tendréis vuestra web colgada. Según tengo entendido localhost no és un dominio válido, así que os “inventais” un nombre y entrais en el archivo hosts este nombre con la ip 127.0.0.1. Además si vuestro servidor web no escucha por el puerto 80 (lo normal cuando estamos con VS) debemos entrar el número de puerto que vamos a usar y configurar VS para que arranque cassini siempre con el mismo puerto. P.ej. en el apartado Connect de mi aplicación facebook yo tengo entrado http://wofserver:12345:

image

Y luego tengo el VS configurado para que me arranque cassini siempre por el puerto 12345:

image

Y recordad la entrada en el fichero hosts para mapear vuestro “dominio” al 127.0.0.1

  • En el apartado Basic de vuestra aplicación hay dos identificadores que vamos a necesitar:
    • API Key: Es la clave pública de la aplicación y que debe usarse en la gran mayoría de las llamadas al API de facebook
    • Secret: La clave secreta. Debe usarse en algunas llamadas al API, y como su nombre indica no debemos compartirla con nadie ;)
  • Una vez creada la aplicación, estamos listos para interactuar con facebook.

2. Preparando nuestra aplicación web para hablar con facebook

La clave para habilitar la comunicación con facebook está en un archivo html llamado xd_receiver.htm. El código de dicho archivo es tal y como sigue (siempre es igual):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>xd</title>
</head>
<body>

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

</body>
</html>





Podemos poner el archivo donde queramos, pero yo os recomiendo que lo pongáis en el directorio raíz de vuestra web. Es decir en mi caso http://wofserver:12345/xd_receiver.htm













3. Preparando el entorno para renderizar XFBML



Para ayudarnos a integrarnos con facebook, éste incorpora una API de tags conocida como XFBML (XHtml FaceBook Markup Language). Usando estos tags podemos p.ej. renderizar el botón de login, o el nombre del usuario conectado.



Para ello debemos indicar que nuestras páginas son XHTML e importar el espacio de nombres fb que es el que usan los tags XFBML. Para ello modificamos las etiquetas <html> y <!DOCTYPE>:




<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:fb="http://www.facebook.com/2008/fbml">





Ya casi estamos… sólo nos queda referenciar el uso del fichero javascript que habilita el API de facebook (así como XFBML), para ello debemos añadir el siguiente tag <script> justo después de abrir el tag <body>:




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





Evidentemente os recomiendo utilizar una master page para tener todos estos cambios en todas vuestras páginas (estos tags deben estar en cualquier página que quiera usar el API de facebook).



4. Usando XFBML



Antes de poder usar XFBML hemos de incializar nuestra conexión con facebook y habilitar XFBML. Yo tengo el código puesto en la pantalla de login, pero esto ya dependerá de cada uno. Para ello debemos usar el siguiente código javascript:




<script type="text/javascript">
FB_RequireFeatures(["XFBML"], function() {
FB.Facebook.init("CLAVE_API", "/xd_receiver.htm");
});
</script>





Ahora sí: el uso de XFBML es super simple. P.ej. para renderizar el botón de login basta con usar el tag <fb:login-button>:




<fb:login-button v="2" size="medium" 
onlogin="window.location.reload(true);">
Login WoF with facebook
</fb:login-button>





En este caso se renderiza el botón de login, y cuando el usuario se haya identificado se forzará un refresco de la página. El texto que colocamos dentro del tag <fb:login-button> será el texto del botón:



image



Hay muchos tags XFBML, en esta dirección (http://wiki.developers.facebook.com/index.php/XFBML) tenéis la lista completa.





Cuando el usuario haga click en el botón de iniciar sesión en facebook pueden ocurrir dos cosas:




  1. Que el usuario ya estuviese identificado en facebook. En este caso se disparará el evento onlogin del botón.


  2. Que el usuario no estuviese identificado en facebook. En este caso le saldrá la ventana para que se identifique. Una vez identificado se disparará el evento onlogin:




image 




5. Accediendo a información del usuario



Llegados a este punto ya nos hemos integrado con el login del usuario… Pero esto no sirve de mucho si no somos capaces de extraer información del usuario (p.ej su nombre, o bien si ya había un usuario identificado en facebook). Para ello podemos usar el API de facebook (obviamente basada en javascript), pero yo voy a contaros como se puede hacerlo usando el “Facebook Developer Toolkit” que os podéis descargar de Codeplex.



Una vez descargado basta con referenciar el assembly “Facebook.dll”, y luego podemos utilizar la clase ConnectSession para averiguar si un usuario está conectado en facebook:




var fbc= new ConnectSession("<CLAVE_API>","<CLAVE_SECRETA>");
if (fbc.IsConnected())
{
// Usuaro conectado en facebook. En fbc.UserId tenemos el ID unico del
// usuario en facebook.
}
else
{
// No hay usuario conectado en facebook
}





A partir de aquí, lo que hagamos ya dependerá de nuestras necesidades. P.ej. en mi caso, pregunto al usuario se desea usar mi web con su login de facebook y si responde afirmativamente creo un usuario especial en mi tabla de usuarios, vinculado a dicho id de facebook y autentico este usuario en mi sitio web…



Espero que este post os haya servido para daros una idea de como funciona Facebook Connect. En siguientes posts espero ir contando alguna cosilla más al respecto! ;)



Saludos!!!



PD: Como siempre, esto es un crosspost desde mi blog en geeks.ms!

martes, 1 de diciembre de 2009

Evita las dependencias con tu contendor de IoC

Usar un contenedor de IoC es una práctica más que recomendable, pero al hacerlo es muy fácil caer en el anti-patrón de dependencia con el contenedor. Ese patrón se manifesta de varias formas sútiles, y aunque hay algunos casos en que pueda ser aceptable, en la gran mayoría indica una mala práctica que debemos revisar.

¿Que tiene de malo este código?

// IS1 y IS2 son dos interfaces cualesquiera
// S1 y S2 son dos clases que implementan dichas interfaces
class A
{
IS1 s1;
IS2 s2;
public A(IUnityContainer container)
{
this.s1 = container.Resolve<IS1>();
this.s2 = container.Resolve<IS2>();
}
}

class Program
{
static void Main(string[] args)
{
IUnityContainer ctr = new UnityContainer();
ctr.RegisterType<IS1, S1>();
ctr.RegisterType<IS2, S2>();
A a = ctr.Resolve<A>();
}
}





El código funciona correctamente, pero ¡ojo! Tenemos una dependencia directa de la clase A hacia IUnityContainer. Realmente la clase A depende de IUnityContainer o bien depende de IS1 y IS2? La realidad es que las dependencias de la clase A son IS1 e IS2.



1. La visión filosófica del asunto



Si yo leo el constructor de la clase A y veo que pone:




public A(IUnityContainer container)
{
// Código...
}





Debo leer todo el código del constructor para ver las dependencias reales de la clase A. Por otro lado si el constructor fuese:




public A(IS1 s1, IS2 s2)
{
// Código
}





Ahora queda mucho más claro que las dependencias reales de la clase A son IS1 y IS2.



En resumen: evitad en lo máximo de lo posible pasar el propio contenedor como parámetro de los constructores. En su lugar pasad las dependencias reales y dejad que el contenedor las inyecte.



Y obviamente evitad (casi) siempre un código como:




class A
{
IUnityContainer container;
public A(IUnityContainer container)
{
this.container = container;
}
// Código
}





¡Ahí estamos todavía más vendidos! Para averiguar las dependencias reales de la clase A, ahora debemos mirar todo el código de la clase A, puesto que en cualquier sitio alguien puede hacer un resolve (antes de que alguien salte por las paredes que eche un vistazo al punto 4 del post, por favor :p).



La visión “filosófica” me indica que si la clase A debería depender solo de IS1 e IS2 no es posible que me aparezca una dependencia sobre IUnityContainer. Necesita la clase A a IUnityContainer para hacer su trabajo? No, verdad? Pues eso.



Incluso aunque tengas claro, clarísimo que nunca vas a abandonar Unity (él no lo haría! :p) este código no huele nada bien.



2. La visión práctica



Algún dia quizá te canses e Unity y te decidas a usar por ejemplo, Windsor Container… Este cambio debería ser un cambio sencillo: un cambio en el bootstrapper de tu aplicación, y donde instanciabas Unity ahora instancias Windsor, lo configuras y listo!



Listo? Listo sólo si tus clases no dependen de IUnityContainer, porque en caso contrario… bueno, puedes tener un buen problemilla ;)



3. Algunos detalles...



Antes he comentado que este anti-patrón puede aparecer de formas realmente sutiles:




class A
{
public A()
{
//...
}
[Dependency()]
public IS1 S1 { get; set; }
// Más código
}





¿Es correcto este código? Mejor que el código anterior donde recibíamos IUnityContainer como parámetro si que es, porque no tenemos ninguna dependencia directa contra Unity… Pero realmente si que estamos dependiendo de Unity: Sólo Unity entenderá el atributo [Dependency()] para inyectarnos la propiedad S1. Así que a la práctica estamos como en el caso anterior.



Ahora bien, la diferencia fundamental es que, en mi opinión, que la clase A reciba IUnityContainer como parámetro rebela un mal diseño, mientras que en este caso la dependencia nos aparece porque no existe ningún mecanismo estándar para especificar que queremos que una propiedad sea inyectada (por lo que cada contenedor usa su propio mecanismo).



Si tienes claro, clarísimo que nunca abandonarás Unity, entonces no hay problema alguno en este código. Por otro lado si no quieres atarte al contenedor entonces este código no te sirve (echa un vistazo a mi post Unity? Sí gracias, pero no me abraces demasiado para ver más detalles al respecto).



4. Ya, pero yo uso el patrón Service Locator



Todo lo que hemos hablado hasta ahora afecta sobre todo en aquellos casos en que usábamos inyección de dependencias, pero existe otro patrón íntimamente relacionado: el service locator. En este patrón tenemos un objeto (el propio service locator) que se encarga de devolvernos referencias a servicios.



Es común que el propio contenedor de IoC se use como service locator, porque ofrece soporte directo para ello. Sin embargo no es una buena opción… porque conduce inevitablemente a situaciones como las que hemos visto, en concreto a situaciones como esta:




class A
{
private IUnityContainer serviceLocator;
public A(IUnityContainer serviceLocator)
{
this.serviceLocator = serviceLocator;
}
void foo()
{
// obtengo los servicios...
var logSvc = serviceLocator.Resolve<ILogService>();
var locSvc = serviceLocator.Resolve<ILocalizationService>();
// hago cosas con mis servicios
}
}





Este código es prácticamente igual al que os decía que debéis evitar a toda costa. Podríamos pasar ILogService e ILocalizationService en el constructor, pero ahora imaginad que tenemos muchos servicios y nuestras clases los usan todos (en un proyecto en el que estoy trabajando manejamos decenas de servicios, y además es común que las clases usen muchos de los servicios sólo en un método).



El error aquí, está en usar el propio contenedor como Service Locator: lo hacemos porque es rápido y cómodo ya que el contenedor nos ofrece soporte para ello, pero a cambio nos estamos atando al contenedor… Nosotros no queremos una dependencia contra IUnityContainer, sinó una dependencia contra el service locator. Y qué es el service locator? Pues algo distinto al propio contenedor. Por ejemplo, eso:




interface IServiceLocator
{
T GetService<T>() where T : class;
}
class ServiceLocator : IServiceLocator
{
private IUnityContainer container;
public ServiceLocator(IUnityContainer container)
{
this.container = container;
}

public T GetService<T>() where T : class
{
return this.container.Resolve<T>();
}
}





La clase ServiceLocator si que depende de Unity (ahí si que es inevitable la dependencia). Ahora la clase A la podemos reescribir como:




class A
{
private IServiceLocator serviceLocator;
public A(IServiceLocator serviceLocator)
{
this.serviceLocator = serviceLocator;
}
void foo()
{
// obtengo los servicios...
var logSvc = serviceLocator.GetService<ILogService>();
var locSvc = serviceLocator.GetService<ILocalizationService>();
// hago cosas con mis servicios
}
}







Y la clase A ya no depende de IUnityContainer: lo hace de IServiceLocator, lo que es aceptable y totalmente lógico.



Además, tener nuestra propia implementación del service locator nos permite adaptarlo a nuestras necesidades (p. ej. ¿qué hacer si nos piden un servicio que no está registrado?).



Así pues, usar el patrón service locator no es excusa para tener nuestro código lleno de dependencias contra el contenedor de IoC.



¿Opiniones? ;-)



Un saludo a todos!



pd: Este post es un crosspost desde mi blog en geeks.ms