ViewState: Cómo cambiar donde almacenarlo

Creado el día 18/01/2008 11:26 por zorry

Como bien sabéis, el ViewState se almacena en un campo hidden en las páginas, de manera que el navegador cliente es el que recibe y envía todos los datos del ViewState, persistiendo el estado de la página entre PostBacks.

Este escenario es el más aceptable en líneas generales. Pero si tenemos poco ancho de banda, podríamos cambiar el lugar donde almacenar el ViewState y almacenarlo en la sesión. Para ello, en la página que queramos configurar su ViewState sólo tenemos que implementar el siguiente código:

   1: protected override PageStatePersister PageStatePersister
   2: {
   3:     get 
   4:     {
   5:          return new SessionPageStatePersister(this);
   6:     }
   7: }

De todos modos, es necesario tener cuidado y valorar bien cuántos usuarios ejecutarán nuestra aplicación y cuantos datos se almacenan en sesión, puesto que es posible que si nuestra aplicación albergará muchos usuarios y se guardan muchos datos en sesión, se sobrecargue en exceso el servidor web.


Realizando pruebas de carga con Ajax

Creado el día 05/01/2008 06:55 por zorry

En el proyecto en el que trabajo actualmente, tenemos que probar cómo se comporta la aplicación web bajo carga de muchos usuarios. Para realizar la carga, empleo Visual Studio 2005 Team Suite.

En una primera aproximación para realizar las pruebas de carga, realicé una captura web mediante la herramienta nativa de Visual Studio. En principio funciona bien, captura las peticiones http a la aplicación, pero como nuestra aplicación funciona con Ajax, las peticiones que realiza la aplicación de manera asíncrona mediante Javascript no son capturadas, con lo que las pruebas no son completas.

La solución que he encontrado ha sido emplear Fiddler para capturar el tráfico. Se arranca esta herramienta de captura antes de arrancar la aplicación web, se realiza la prueba de navegación desde un navegador, y posteriormente, Fiddler permite salvar todo el tráfico entre nuestro navegador y la aplicación web (incluido Ajax) como prueba de Visual Studio.

Posteriormente, este archivo webtest puede importarse en un proyecto de pruebas de Visual Studio 2005 Team suite para realizar las pruebas de carga.


Reescribir la URL para soportar una aplicación Cookieless

Creado el día 05/12/2007 08:12 por zorry

Al hilo del post anterior, la aplicación Windows que trabaja con nuestra aplicación web, es una aplicación con un Internet Explorer embebido. Como el manejo de las Url lo realiza por debajo, no soporta llamar a una Url con un SessionId específico de la siguiente forma:

http://servidor/Aplicacion/(S(i1hxwon1me1aazix1w1jnd55))/default.aspx

Esto es porque sólo soporta enviar parámetros mediante querystring, de la siguiente manera:

http://servidor/Aplicacion/default.aspx?SessionId=(S(i1hxwon1me1aazix1w1jnd55))/

Con lo que necesitamos realizar un reescrito de la Url para que el servidor coja correctamente el Id de sesión que queremos que emplee. Lo conseguimos mediante un modulo Http.

Lo primero es crearnos una clase dentro de nuestra aplicación web:

   1: namespace MiAplicacion.Web.Modulo
   2: {
   3:     public class RewriteModule : IHttpModule
   4:     {
   5:         public RewriteModule() { }
   6:  
   7:         public void Dispose() { }
   8:  
   9:         public void Init(HttpApplication application)
  10:         {
  11:             application.BeginRequest += new EventHandler(application_BeginRequest);
  12:         }
  13:  
  14:         void application_BeginRequest(object source, EventArgs e)
  15:         {
  16:             HttpApplication app = (HttpApplication)source;
  17:  
  18:             //Control de sesión cookieless
  19:             if (getIsCookielessSessionState())
  20:                 processCookielessSessionId(app);
  21:         }
  22:     }
  23: }

En este código nos creamos un manejador para el evento BeginRequest. En este manejador comprobamos que la aplicación tiene configurada la sesión cookieless (línea 19), y en el caso de que así sea, reescribir la url (línea 20).

Seguidamente, hay que modificar el web.config, para activar el módulo http:

   1: <httpModules>
   2:  <add name="AuthHTTPModule" type="MiAplicacon.Web.Modulo.RewriteModule"/>
   3: </httpModules>

Y por último, y no menos importante, el código para los métodos auxiliares:

   1: private bool getIsCookielessSessionState()
   2: {
   3:     object untypedSessionState = ConfigurationManager.GetSection("system.web/sessionState");
   4:     try
   5:     {
   6:         if (untypedSessionState != null)
   7:         {
   8:             System.Web.Configuration.SessionStateSection section =
   9:                 (System.Web.Configuration.SessionStateSection)untypedSessionState;
  10:  
  11:             return section.Cookieless == HttpCookieMode.UseUri;
  12:         }
  13:     }
  14:     catch
  15:     {
  16:         return false;
  17:     }
  18:  
  19:     return false;
  20: }
  21:  
  22: private void processCookielessSessionId(HttpApplication app)
  23: {
  24:     //Tratamiento para las sesiones cookieless
  25:     string parameterSessionId = "SessionId";
  26:     string url = app.Request.RawUrl;
  27:     int indexOfSessionId = url.IndexOf(parameterSessionId, StringComparison.CurrentCultureIgnoreCase);
  28:  
  29:     if (indexOfSessionId >= 0)
  30:     {
  31:         string sessionId = url.Substring(indexOfSessionId + parameterSessionId.Length + 1);
  32:         if (sessionId.IndexOf("&") >= 0)
  33:             sessionId = sessionId.Substring(0, sessionId.IndexOf("&"));
  34:  
  35:         if (sessionId.Length > 0)
  36:         {
  37:             url = url.Replace(app.Request.ApplicationPath, String.Empty);
  38:             url = url.Replace(parameterSessionId + "=" + sessionId, String.Empty);
  39:  
  40:             string newUrl = app.Request.ApplicationPath + "/" + sessionId + "/" + url;
  41:  
  42:             //Estandarizamos la url
  43:             newUrl = newUrl.Replace("?&", "?");
  44:             newUrl = newUrl.Replace("//", "/");
  45:  
  46:             if (newUrl[newUrl.Length - 1] == '?')
  47:                 newUrl = newUrl.Remove(newUrl.Length - 1);
  48:             if (newUrl[newUrl.Length - 1] == '&')
  49:                 newUrl = newUrl.Remove(newUrl.Length - 1);
  50:  
  51:             //Realizamos un redirect
  52:             app.Response.Redirect(newUrl);
  53:         }
  54:     }
  55: }

En el primer método leemos el web.config para obtener si está activada la sesión cookieless (en el evento BeginRequest, no se puede acceder a los objetos de HttpApplication relativos a la sesión).

En el segundo método, parseamos la querystring y en el caso de que llegue el valor SessionId, realiza un Response.Redirect a la url con el SessionId embebido correctamente.


Descifrando mediante RSA en ASP.Net

Creado el día 04/12/2007 21:12 por zorry

En el proyecto en el que estoy trabajando, hemos tenido que securizar una transacción de datos mediante RSA. Este cifrado se emplea para asegurar que nadie puede obtener un usuario y contraseña que se envía entre una aplicación Windows Forms de cliente, y nuestra aplicación ASP.Net.

Si bien el cifrado se realiza sin problemas en la aplicación Windows Forms, cifrando con la clave pública, estaba teniendo problemas en el descifrado de los datos.

El código original era el siguiente:

   1: RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(dwKeySize);
   2: rsaCryptoServiceProvider.FromXmlString(xmlString);
   3: byte[] encryptedBytes = System.Web.HttpServerUtility.UrlTokenDecode(inputString);
   4: byte[] clearBytes = rsaCryptoServiceProvider.Decrypt(encryptedBytes, false);
   5: return Encoding.UTF8.GetString(clearBytes);

Este código me estaba elevando una excepción en la línea 2, al tratar de leer la configuración de un string con el Xml y la clave privada. El error era un tanto críptico: CryptographicException: The system cannot find the file specified.

Navegando por la red, encontré un blog en el que especifica que el usuario que corre la aplicación debería poder realizar esta operación atacando el store de la máquina, con lo que me quedó este código:

   1: CspParameters csp = new CspParameters();
   2: csp.Flags = CspProviderFlags.UseMachineKeyStore;
   3: RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(dwKeySize, csp);
   4: rsaCryptoServiceProvider.FromXmlString(xmlString);
   5: byte[] encryptedBytes = System.Web.HttpServerUtility.UrlTokenDecode(inputString);
   6: byte[] clearBytes = rsaCryptoServiceProvider.Decrypt(encryptedBytes, false);
   7: return Encoding.UTF8.GetString(clearBytes);

Esto es, añadiendo las dos primeras líneas y pasando los parámetros del CSP al constructor del Provider.

No obstante, los problemas no han acabado aquí. Como el resto del proceso corre impersonado, al parecer el GC da problemas al tratar de eliminar la clave privada del store. Aproximadamente al minuto de la ejecución del descifrado, se elevaba una excepcion CryptographicException: Keyset not found y se moría el proceso w3wp... Maldita sea...

Tras más investigaciones, logré averiguar que el GC trata de eliminar la clave del almacén, pero con las credenciales de otro usuario, por lo que no encuentra la clave y el proceso de IIS se muere.

Pero al final logré arreglarlo mediante el siguiente código:

   1: CspParameters csp = new CspParameters();
   2: csp.Flags = CspProviderFlags.UseMachineKeyStore;
   3: RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(dwKeySize, csp);
   4: rsaCryptoServiceProvider.FromXmlString(xmlString);
   5: byte[] encryptedBytes = System.Web.HttpServerUtility.UrlTokenDecode(inputString);
   6: byte[] clearBytes = rsaCryptoServiceProvider.Decrypt(encryptedBytes, false);
   7: rsaCryptoServiceProvider.Clear();
   8: return Encoding.UTF8.GetString(clearBytes);

Esto es, insertando la línea 7. Esta línea elimina la clave del almacén de manera explícita, con lo que el GC no tiene que hacerlo con posterioridad y el proceso finaliza correctamente... Por fin y tras dos días pegándome con ello :-(