En primer lugar deberíamos definir formal y brevemente qué es y para que sirven los proveedores de perfiles en ASP.NET y como están realizados por dentro.
Los proveedores de perfiles, sin querer entrar en mucho detalle, engloban una serie de funcionalidad específica, para guardar datos de un posible perfil de un usuario de un sitio Web. Entre estos datos, los más comunes son por ejemplo: el tema de la página que tiene seleccionado, el nombre, apellidos, edad, y todas las preferencias que se nos ocurran.
Este proveedor de perfiles, por defecto, está implementado para que guarde los datos en la base de datos por defecto de ASP.NET, la base de datos de SQL Server ASPNETDB, y de esta limitación, nos viene al caso este post.
Existen muchas ocasiones en las que no usamos sql Server por especificaciones del diseño y tenemos que usar otro motor de base de datos distinto, o queremos ampliar esa funcionalidad que ya trae para que además de guardar en la bbdd por defecto de sql Server, guarde otros datos en otras tablas de nuestra base de datos propia. En fin, se me ocurren multitud de escenarios en los que sería beneficioso poder agregar funcionalidad o cambiarla por completo para adecuar el adaptador de perfiles a nuestras necesidades.
Gracias a que el proveedor de perfiles por defecto de Microsoft ASP.NET está basado en el patrón “Strategy”, que a groso modo dice que para resolver un problema determinado (en nuestro caso guardar preferencias de nuestros usuarios para mas tarde aplicarlas cuando vuelva al sitio) se le puede conectar diferentes algoritmos , según nos convenga. Cualquier programa que ofrezca un servicio o función determinada, que pueda ser realizada de varias maneras, es candidato a utilizar el patrón Strategy. Puede haber cualquier número de estrategias y cualquiera de ellas podrá ser intercambiada por otra en cualquier momento, incluso en tiempo de ejecución.
Después de esta pequeña introducción me voy a referir al motivo de este post, que es crear un proveedor de perfiles que en vez de guardar los datos en una base de datos, pueda guardarlos en una cookie, para luego volver a recuperarlos en posteriores ocasiones siempre que la cooki siga existiendo.
1º En primer lugar, lo que vamos a hacer es crear un proyecto classlibrary con Visual Studio.
Borramos la clase que nos viene por defecto y creamos una que se llame ProfileCookieProvider y que herede de ProfileProvider.
class ProfileCookie:ProfileProvider
2º Luego sobrescribimos todos los métodos de la clase de la que heredamos.
public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
{
throw new NotImplementedException();
}
public override int DeleteProfiles(string[] usernames)
{
throw new NotImplementedException();
}
public override int DeleteProfiles(ProfileInfoCollection profiles)
{
throw new NotImplementedException();
}
public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
{
throw new NotImplementedException();
}
public override string ApplicationName
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override System.Configuration.SettingsPropertyValueCollection GetPropertyValues(System.Configuration.SettingsContext context, System.Configuration.SettingsPropertyCollection collection)
{
throw new NotImplementedException();
}
public override void SetPropertyValues(System.Configuration.SettingsContext context, System.Configuration.SettingsPropertyValueCollection collection)
{
throw new NotImplementedException();
}
3º Declaramos las variables y propiedades de la clase
class ProfileCookie:ProfileProvider
{
// Declaramos las variables
private string _Nombrecookie;
public string Nombrecookie
{
get { return _Nombrecookie; }
set { _Nombrecookie = value; }
}
private int _ExpiracionCookie;
public int ExpiracionCookie
{
get { return _ExpiracionCookie; }
set { _ExpiracionCookie = value; }
}
private string _appNombre;
public string AppNombre
{
get { return _appNombre; }
set { _appNombre = value; }
}
4º Creamos el procedimiento de inicialización del proveedor, donde se le pasará un parámetro name con el nombre del proveedor y un parámetro namevaluedcollection “config” que contendrá todos los atributos del proveedor que se encuentran en el archovo de configuración Web.config de la aplicación que usa el proveedor.
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
// Comprobar que el proveedor tiene un nombre
if(string.IsNullOrEmpty(name))
{
name= “ProfileCookieProvider”;
}
// Inicializar el método de la clase base
base.Initialize(name, config);
// Leemos el nombre de la aplicacion
_appNombre = config["aplicationName"];
if (string.IsNullOrEmpty(_appNombre))
{
_appNombre = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
}
//Leemos el nombre de la cookie
_Nombrecookie = config["cookieName"];
if (string.IsNullOrEmpty(_Nombrecookie))
{
_Nombrecookie = “.Perfil”;
}
//Eliminamos el nombre de la cookie
config.Remove(“cookieName”);
// Leemos la duración de la cookie
bool sucess = Int32.TryParse(config["cookieName"], out _ExpiracionCookie);
if (!sucess)
{
_ExpiracionCookie = 10;
}
config.Remove(“cookieExpires”);
// Lanzamos una excepcion si hay algún atributo desconocido
if (config.Count > 0)
{
string atributo = config.GetKey(0);
if (!string.IsNullOrEmpty(atributo))
{
throw new ProviderException(“Atributo no reconocido : “ + atributo);
}
}
}
5º No vamos a implementar todos los métodos heredados de la clase base, lanzando un error en todas las que no implementemos.
throw new NotImplementedException();
6º Implementamos el método GetPropertyValues que devolverá el contenido del perfil del usuario. Se leerán los datos desde el almacén de datos, en este caso desde la cookie, y se devuelve en una colección de valores.
public override System.Configuration.SettingsPropertyValueCollection GetPropertyValues(System.Configuration.SettingsContext context, System.Configuration.SettingsPropertyCollection collection)
{
System.Configuration.SettingsPropertyValueCollection settings;
settings = new System.Configuration.SettingsPropertyValueCollection();
if (collection.Count == 0)
return settings;
// Añadimos todas las porpiedades a la colección de salida,
// Garantizando que se van a crear dichas entradas en el objeto profil
foreach (System.Configuration.SettingsProperty property in collection)
{
System.Configuration.SettingsPropertyValue pv = new System.Configuration.SettingsPropertyValue(property);
settings.Add(pv);
}
// Leemos la cookie si existe
string username = (string)context["UserName"];
string cookie = Nombrecookie + ” “ + username;
System.Web.HttpCookie cookieprofile = System.Web.HttpContext.Current.Request.Cookies[cookie];
if (cookieprofile == null)
{
return settings;
}
// Descodificación en Base64 y deserialización binaria
string data = cookieprofile["SerializedData"];
byte[] bits = Convert.FromBase64String(data);
System.Collections.Hashtable table = null;
using (System.IO.MemoryStream mem = new System.IO.MemoryStream(bits))
{
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bin;
bin = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
table = (System.Collections.Hashtable)bin.Deserialize(mem);
mem.Close();
}
// Añadimos los datos a las propiedades
foreach (System.Configuration.SettingsPropertyValue spv in settings)
{
spv.Deserialized = true;
spv.PropertyValue = table[spv.Name];
}
return settings;
}
7º Implementamos el método SetPropertyValues, que guardará el contenido de perfil del usuario al final de cada solicitud.
/// <summary>
/// Guarda el contenido de collection en una cookie
/// </summary>
/// <param name=”context”>Información contextual</param>
/// <param name=”collection”>Colección de propiedades a guardar en la cookie</param>
public override void SetPropertyValues(System.Configuration.SettingsContext context, System.Configuration.SettingsPropertyValueCollection collection)
{
// Obtengo información sobre el usuario
string username = (string)context["UserName"];
bool autenticado = (bool)context["IsAuthenticated"];
// Si no hay propiedades terminamos
if (String.IsNullOrEmpty(username) || collection.Count == 0)
{ return; }
// Preparo la Cookie
string cookie = Nombrecookie + “_” + username;
System.Web.HttpCookie cookiePerfil = System.Web.HttpContext.Current.Request.Cookies[cookie];
if (cookiePerfil == null)
{
cookiePerfil = new System.Web.HttpCookie(cookie);
}
cookiePerfil.Expires = DateTime.Now.AddMinutes(ExpiracionCookie);
// Preparo los datos para guardar en la cookie
System.Collections.Hashtable tabla = new System.Collections.Hashtable();
foreach (System.Configuration.SettingsPropertyValue pp in collection)
{
if (!autenticado && !(bool)pp.Property.Attributes["AllowAnonymous"])
{
continue;
}
tabla.Add(pp.Name, pp.PropertyValue);
}
// Codificar y escribir la tabla hash para la cookie
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bin;
bin = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
System.IO.MemoryStream mem = new System.IO.MemoryStream();
bin.Serialize(mem, tabla);
string data = Convert.ToBase64String(mem.GetBuffer(), 0, (int)mem.Length);
cookiePerfil["SerializedData"] = data;
// Guardar en la cookie
System.Web.HttpContext.Current.Response.AppendCookie(cookiePerfil);
}
Ahora generamos el proyecto y nos crea una dll que es la que tendremos que usar en nuestro proyecto ASP.NET, que junto con unas configuraciones en el Web.config de nuestra aplicación harán posible el uso de perfiles de usuarios almacenados en cookies.
Esto lo explicaré en el siguiente artículo ya que éste se ha extendido demasiado.
Espero que os haya sido útil esta información y que le saquéis provecho.
Hasta pronto.