martes, 21 de diciembre de 2010

Opinión: Var o no var… esa es la cuestión.

Hola a todos! Desde hace algunos días estoy usando Resharper. La verdad no era, como decirlo, muy proclive para instalármelo, ya que había tenido no muy buenas experiencas con CodeRush. Seguramente no eran culpa de CodeRush sinó mías, pero bueno… Al final me lo instalé y debo decir que estoy gratamente sorprendido: Es una auténtica maravilla.

Una cosa interesante de Resharper es que te hace sugerencias (que puedes desactivar si quieres, por supuesto) sobre como codificar mejor. Y una de las sugerencias es usar var siempre que se pueda:

image

Fijaos que incluso en un caso trivial como int i=0; nos recomienda que usemos var.

Nota: Primero una pequeña aclaración sobre var: Por si acaso… Recordad que var no es tipado dinámico, ni late-binding ni nada parecido a esto. Var simplemente le indica al compilador que infiera él el tipo de la variable. Pero la variable tiene un tipo concreto y lo tiene en tiempo de compilación. Por lo tanto olvidad todos vuestros prejuicios (si los tenéis) sobre tipos dinámicos.

Hay tres corrientes de opinión al respecto de cuando usar var: Hay gente que opina que debe usarse sólo cuando es necesario (cuando se trabaja con objetos anónimos).

Otros opinan que cuando el tipo ya aparece en la lína de código puede usarse var. Es decir, admiten esto:

var dict = new Dictionary<int, string>();





Porque el tipo de la variable ya aparece en el new. Pero no admiten lo siguiente:




var result = stockManager.GetStocks();





Porque, viendo el código: como se puede saber el tipo de result? (Debes irte a ver que devuelve el método GetStocks).



Por último el tercer grupo de opinión está a favor de usar var siempre. Incluso en los casos más triviales.



Por curiosidad:




  1. La msdn se situa en el primer grupo de opinión (literalmente dice “the use of var does have at least the potential to make your code more difficult to understand for other developers. For that reason, the C# documentation generally uses var only when it is required” - http://msdn.microsoft.com/en-us/library/bb384061.aspx).


  2. Resharper se sitúa en el tercer grupo, como hemos visto


  3. Yo me situaba en el segundo grupo de opinión.



¿Que aporta usar siempre var?



Que Resharper me estuviese continuamente insistiendo en usar var me hizo pensar en el por que de esa razón. Así que lo que hice fue probar y hacerle caso. Y empecé a usar var en todos los sitios. A ver que ocurría.



Y ocurrió una cosa interesante…



Al usar var continuamente pierdes el tipo de la variable de forma visual (es decir no sabes de que tipo es sólo viendo esa línea de código) y entonces te das cuenta de una cosa: que muchos nombres de variables aportan muy poca información sobre que hace realmente la variable. Los detractores de var dicen que puede complicar la lectura de código… pero que es más dificil de entender, esto:




Player plr = GetCurrentPlayer();
Location l = plr.GetCustomLocation();
// Varias líneas de código hablando de "l" y "plr"





o esto:




var player = GetCurrentPlayer();
var location = player.GetCustomLocation();
// Varias líneas de código hablando de "location" y "player"





El tema está en que la variable la declaras en una línea, y la usas en varias más. Evidentemente, si no usas var, la línea que declara la variable te da más información (exactamente el tipo de la variable), información que pierdes si usas var. Pero, como te das cuenta que estás perdiendo esta información, lo que haces es usar lo que resta de la línea para aumentar la claridad. Y que es lo que queda? El nombre de la variable.



Al usar var en todos los sitios te fuerzas a poner nombres más declarativos a tus variables, que expresen lo que la variable hace. Y todos sabemos que esa es una muy buena práctica. Y a veces no la seguimos, y una de las razones es que al tener una línea tipo:




Location l = new Location();





Mentalmente piensas: <<Ya se ve que “l” es una Location. Ya queda claro.>>



Pero no es cierto, porque al cabo de unas cuantas líneas te aparece una “l” y tienes que recordad que era una Location.



Mientras que si usas var, cuando escribes la línea tiendes a usar nombres más descriptivos, porque escribir:




var l = new Location();





Hace como daño a la vista :)



Así que la sugerencia de usar var siempre, personalmente no me parece muy desacertada, y creo que voy a tomar esa opción de ahora en adelante.



Pero… Y vosotros? ¿Que opináis al respecto?



Un saludo!



PD:  También creo que hay otra razón para que Resharper nos guie a usar siempre var y es que el código con var es más fácil de refactorizar (menos cambios) que el que usa declaraciones explícitas.



PD2: Como no… otro crosspost desde mi blog de geeks.ms!

jueves, 16 de diciembre de 2010

ASP.NET: Obtener el ID del usuario actual

Buenas!

No se vosotros, pero yo cuando desarrollo mis aplicaciones, si uso FKs de la otabla de usuarios, las hago en base al ID del usuario, nunca en base a su nombre. Así pues, saber el ID del usuario actualmente autenticado en mi aplicación es algo fundamental.

Primero, para saber el nombre del usuario autenticado podemos usar:

string userName = HttpContext.Current.User.Identity.Name;







El tema está en que las mentes pensantes que parieron el sistema de proveedores de autenticación en ASP.NET no tuvieron a bien a poner un campo para guardar el ID.



Por suerte obtenerlo es trivial:




int i = (int)Membership.GetUser().ProviderUserKey;





Ojo con ese código: Yo uso un membership provider propio, ya que mi base de datos usa ints para los IDs de usuarios. Si usáis el membership provider que viene por defecto en ASP.NET, el cast lo debéis hacer a Guid y no int.



Bueno todo muy bonito, pero antes que descorchéis el cava: eso hace una llamada a la base de datos. Además se trae todos los campos del registro correspondiente de la tabla de usuarios (que si usáis el membership provider que viene por defecto tiene la hostia y pico). O sea que cuidado con usar eso a mansalva… :)



Eeeerrr… ¿se puede SIN necesidad de acceder a la base de datos?



Bueno… esa es la gran pregunta, no nos vamos a engañar ;-) Hay varias maneras de poder acceder al ID del usuario sin hacer un round-trip a la base de datos, pero a si a bote pronto se me ocurren dos:




  1. Guardarlo en una variable de sesión: Podemos guardar el ID en una variable de sesión y consultarla cuando la necesitamos. Para una escalabilidad máxima podéis no usar sticky sessions y en el Session_Start guardar dicha variable con el ID. Si no usáis sticky sessions un mismo usuario puede iniciar sesión en varios IIS a la vez, pero en nuestro caso no es problemático (simplemente se consultará el ID del usuario cada vez que inicie sesión). Eso sí, estoy asumiendo que no guardáis nada más en la sesión (es decir que funcionalmente no dependéis de la sesión).


  2. Guardarlo en la cookie de autenticación del usuario. Esto no es, ni de lejos tan sencillo como el punto anterior, pero ya que hemos llegado hasta aquí…



Modificar la cookie de autenticación



Primero debemos hacer que cuando se cree la cookie de autenticación se añada el ID del usuario. En mi caso, como siempre, uso ASP.NET MVC, así que modificaré el código de AccountController (que es el que genera VS). Para aplicaciones webforms ese código debe colocarse cuando se va a hacer login del usuario.



En el código por defecto que genera VS para autenticar un usuario se usa:




public void SignIn(string userName, bool createPersistentCookie)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");

FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
}





Este código está en la clase FormsAuthenticationService (dentro de AccountsModel.cs) y no tiene ningún secreto: lo que hace es crear la cookie de autenticación de ASP.NET.



En nuestro caso vamos a modificar ese código por el siguiente:




public void SignIn(string userName, bool createPersistentCookie)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
int id = 100; // Aquí va el ID del usuario que pillaríamos de la BBDD
string userData = id.ToString();
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddMinutes(30), createPersistentCookie, userData);
string encTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
HttpContext.Current.Response.Cookies.Add(faCookie);
}





Lo que hacemos es crear una cookie, con datos adicionales (el ID del usuario).



Ahora lo que nos toca es la otra parte: reemplazar el valor de HttpContext.Current.User.Identity por uno propio que tenga el ID. Para ello usamos el evento PostAuthenticate_Request:




protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
CustomIdentity identity = new CustomIdentity(authTicket.Name, authTicket.UserData);
GenericPrincipal newUser = new GenericPrincipal(identity, new string[] {});
Context.User = newUser;
}
}





Recogemos la cookie de autenticación, desencriptamos el ticket de autenticación por forms y con los datos (el nombre y el UserData) creamos un objeto de tipo CustomIdentity, clase nuestra que nos implementa IIdentity. Luego la incrustamos dentro de un GenericPrincipal y lo establecemos a la propiedad User del HttpContext.



Nota: El segundo parámetro del constructor de GenericPrincipal es el array de roles a los que pertenece el usuario. En mi caso no uso roles, así que le asigno un array vacío.



La clase CustomIdentity es tal y como sigue:




class CustomIdentity : IIdentity
{

public CustomIdentity(string name, string id)
{
IsAuthenticated = true;
Name = name;
Id = Int32.Parse(id);
AuthenticationType = "Forms";
}

public string AuthenticationType { get; private set; }
public bool IsAuthenticated { get; private set; }
public string Name { get; private set;}
public int Id { get; private set; }
}





De esta manera, ahora podemos al Id del usuario, desde un controlador:




CustomIdentity ci = (CustomIdentity)ControllerContext.HttpContext.User.Identity;
int IdUsuario = ci.Id;





Un misterio con el que me he encontrado es que el código de PostAuthenticateRequest si se pone en AuthenticateRequest (que parece que debería funcionar igual), se queja diciendo que la clase “CustomIdentity” no es serializable. No tengo muy claro porque ocurre eso y eso si que parece ser propio de MVC. Aquí hay más información al respecto: http://stackoverflow.com/questions/1884030/implementing-a-custom-identity-and-iprincipal-in-mvc



Y Listos!



Con esto podemos acceder al ID de nuestros usuarios sin necesidad de usar para nada la base de datos. Además, dado que estamos usando el sistema de autenticación de ASP.NET (no hacemos nada raro), nos siguen funcionando los filtros de autenticación como [Authorize].



Un saludo!



Referencia: http://stackoverflow.com/questions/1064271/asp-net-mvc-set-custom-iidentity-or-iprincipal



PD: Eso es (como siempre) un crosspost desde mi blog en geeks.ms!