Una de las razones del éxito de ASP.NET es que reduce la barrera de entrada para los desarrolladores web. No es necesario tener un doctorado en informática para escribir código ASP.NET. Muchos desarrolladores de ASP.NET que conozco en el trabajo son autodidactas y escribían hojas de cálculo de Microsoft® Excel® antes de escribir C# o Visual Basic®. Ahora están escribiendo aplicaciones web y, en general, merecen elogios por el trabajo que están haciendo.
Pero el poder conlleva responsabilidad, e incluso los desarrolladores experimentados de ASP.NET pueden cometer errores. Durante muchos años de consultoría sobre proyectos ASP.NET, descubrí que ciertos errores son particularmente propensos a provocar defectos recurrentes. Algunos de estos errores pueden afectar el rendimiento. Otros errores pueden inhibir la escalabilidad. Algunos errores también pueden costar a los equipos de desarrollo un tiempo valioso para localizar errores y comportamientos inesperados.
Aquí hay 10 errores que pueden causar problemas durante el lanzamiento de aplicaciones de producción ASP.NET y formas de evitarlos. Todos los ejemplos provienen de mi propia experiencia en la creación de aplicaciones web reales en empresas reales y, en algunos casos, proporciono contexto al describir algunos de los problemas que encontró el equipo de desarrollo de ASP.NET durante el proceso de desarrollo.
LoadControl y almacenamiento en caché de resultados Hay muy pocas aplicaciones ASP.NET que no utilizan controles de usuario. Antes de las páginas maestras, los desarrolladores utilizaban controles de usuario para extraer contenido común, como encabezados y pies de página. Incluso en ASP.NET 2.0, los controles de usuario proporcionan una forma eficaz de encapsular contenido y comportamiento y de dividir la página en regiones cuya capacidad de almacenamiento en caché se puede controlar independientemente de la página en su conjunto (un proceso llamado segmentos. Una forma especial de almacenamiento en caché de resultados). ).
Los controles de usuario se pueden cargar de forma declarativa o forzada. La carga forzada se basa en Page.LoadControl, que crea una instancia de un control de usuario y devuelve una referencia de control. Si el control de usuario contiene miembros de un tipo personalizado (por ejemplo, una propiedad pública), puede convertir la referencia y acceder al miembro personalizado desde su código. El control de usuario de la Figura 1 implementa una propiedad denominada BackColor. El siguiente código carga el control de usuario y asigna un valor a BackColor:
protected void Page_Load(object sender, EventArgs e){// Carga el control de usuario y agrégalo a la página Control control = LoadControl("~/MyUserControl.ascx") ;PlaceHolder1 .Controls.Add(control);//Establece su color de fondo ((MyUserControl)control).BackColor = Color.Yellow;}
El código anterior es en realidad muy simple, pero es una trampa esperando a que caiga el desarrollador desprevenido. ¿Puedes encontrar el defecto?
Si supuso que el problema está relacionado con el almacenamiento en caché de resultados, está en lo cierto. Como puede ver, el ejemplo de código anterior se compila y funciona bien, pero si intenta agregar la siguiente declaración (que es perfectamente legal) a MyUserControl.ascx:
<%@ OutputCache Duration="5" VaryByParam="None" %>
Luego, la próxima vez que ejecute la página, verá una InvalidCastException (¡qué alegría!) y el siguiente mensaje de error:
"No se puede convertir un objeto de tipo 'System.Web.UI.PartialCachingControl' para escribir 'MyUserControl'".
Por lo tanto, este código funciona bien sin la directiva OutputCache, pero falla si se agrega la directiva OutputCache. Se supone que ASP.NET no debe comportarse de esta manera. Las páginas (y los controles) deben ser independientes del almacenamiento en caché de resultados. Entonces, ¿qué significa esto?
El problema es que cuando el almacenamiento en caché de resultados está habilitado para un control de usuario, LoadControl ya no devuelve una referencia a la instancia de control, sino que devuelve una referencia a una instancia de PartialCachingControl, que puede ajustar o no la instancia de control, dependiendo de si la instancia de control está incluida o no; La salida del control es caché. Por lo tanto, si un desarrollador llama a LoadControl para cargar dinámicamente un control de usuario y convierte la referencia del control para acceder a métodos y propiedades específicos del control, debe prestar atención a cómo lo hace para que el código se ejecute independientemente de si hay un Directiva OutputCache.
La Figura 2 ilustra la forma correcta de cargar dinámicamente un control de usuario y convertir la referencia de control devuelta. Aquí hay un resumen de cómo funciona:
• Si al archivo ASCX le falta una directiva OutputCache, LoadControl devuelve una referencia MyUserControl. Page_Load convierte la referencia a MyUserControl y establece la propiedad BackColor del control.
• Si el archivo ASCX incluye una directiva OutputCache y la salida del control no está almacenada en caché, LoadControl devuelve una referencia al PartialCachingControl cuya propiedad CachedControl contiene una referencia al MyUserControl subyacente. Page_Load convierte PartialCachingControl.CachedControl en MyUserControl y establece la propiedad BackColor del control.
• Si el archivo ASCX incluye una directiva OutputCache y la salida del control está almacenada en caché, LoadControl devuelve una referencia al PartialCachingControl cuya propiedad CachedControl está vacía. Tenga en cuenta que Page_Load ya no continúa. La propiedad BackColor del control no se puede establecer porque la salida del control proviene del caché de salida. En otras palabras, no existe ningún MyUserControl para establecer propiedades.
El código de la Figura 2 se ejecutará independientemente de si hay una directiva OutputCache en el archivo .ascx. Aunque parezca un poco más complicado, evitará errores molestos. Simple no siempre significa fácil de mantener.
Volver al principio Sesiones y almacenamiento en caché de resultados Hablando de almacenamiento en caché de resultados, tanto ASP.NET 1.1 como ASP.NET 2.0 tienen un problema potencial que afecta las páginas de caché de resultados en servidores que ejecutan Windows Server™ 2003 e IIS 6.0. Personalmente, he visto que este problema ocurre dos veces en un servidor de producción ASP.NET, y en ambas ocasiones se resolvió desactivando el almacenamiento en búfer de salida. Más tarde supe que existe una solución mejor que deshabilitar el almacenamiento en caché de resultados. Así es como se veía cuando encontré este problema por primera vez.
Lo que sucedió fue que un sitio web (llamémoslo aquí Contoso.com, que ejecuta una aplicación de comercio electrónico público en un pequeño dominio web ASP.NET) se comunicó con mi equipo quejándose de que estaban experimentando un error de "proceso cruzado". Los clientes que utilizan el sitio web Contoso.com a menudo pierden repentinamente los datos que ingresaron, pero en su lugar ven datos relacionados con otro usuario. Después de un pequeño análisis, descubrimos que la descripción del subproceso cruzado no es precisa; el error de "sesión cruzada" es más apropiado. Parece que Contoso.com almacena datos en estado de sesión y, por alguna razón, los usuarios se conectan ocasional y aleatoriamente a las sesiones de otros usuarios.
Uno de los miembros de mi equipo escribió una herramienta de diagnóstico que registra elementos clave de cada solicitud y respuesta HTTP, incluido el encabezado de la cookie. Luego instaló la herramienta en el servidor web de Contoso.com y la dejó funcionar durante unos días. Los resultados son muy obvios. Aproximadamente una vez cada 100.000 solicitudes, ASP.NET asigna correctamente un ID de sesión a una sesión completamente nueva y devuelve el ID de sesión en el encabezado Set-Cookie. Luego devuelve el mismo ID de sesión (es decir, el mismo encabezado Set-Cookie) en la siguiente solicitud inmediatamente adyacente, aunque la solicitud ya estaba asociada con una sesión válida y el ID de sesión en la cookie se envió correctamente. En efecto, ASP.NET saca aleatoriamente a los usuarios de sus propias sesiones y los conecta a otras sesiones.
Nos sorprendimos y nos propusimos descubrir por qué. Primero verificamos el código fuente de Contoso.com y, para nuestro alivio, el problema no estaba ahí. A continuación, para asegurarnos de que el problema no estuviera relacionado con el host de la aplicación en el ámbito web, dejamos solo un servidor en ejecución y apagamos todos los demás. El problema persiste, lo cual no es sorprendente ya que nuestros registros muestran que los encabezados Set-Cookie coincidentes nunca provienen de dos servidores diferentes. ASP.NET genera accidentalmente ID de sesión duplicados, lo cual es increíble porque utiliza la clase RNGCryptoServiceProvider de .NET Framework para generar estos ID, y los ID de sesión son lo suficientemente largos como para garantizar que el mismo ID nunca se genere dos veces (al menos en la siguiente). no se generará dos veces en billones de años). Más allá de eso, incluso si RNGCryptoServiceProvider genera por error números aleatorios repetidos, no explica por qué ASP.NET reemplaza misteriosamente el ID de sesión válido por uno nuevo (que no es único).
Por una corazonada, decidimos echar un vistazo al almacenamiento en caché de resultados. Cuando OutputCacheModule almacena en caché una respuesta HTTP, debe tener cuidado de no almacenar en caché el encabezado Set-Cookie; de lo contrario, una respuesta en caché que contenga una nueva ID de sesión conectará a todos los destinatarios de la respuesta en caché (y al usuario cuya solicitud generó la respuesta en caché). a la misma sesión. Verificamos el código fuente; Contoso.com tiene habilitado el almacenamiento en caché de resultados en ambas páginas. Desactivamos el almacenamiento en caché de resultados. Como resultado, la aplicación funcionó durante varios días sin ningún problema entre sesiones. Después de eso, funcionó sin errores durante más de dos años. En otra empresa con una aplicación diferente y un conjunto diferente de servidores web, vimos desaparecer exactamente el mismo problema. Al igual que en Contoso.com, eliminar la caché de resultados resuelve el problema.
Microsoft confirmó más tarde que este comportamiento se debe a un problema en OutputCacheModule. (Es posible que se haya publicado una actualización cuando lea este artículo). Cuando se utiliza ASP.NET con IIS 6.0 y el almacenamiento en caché en modo kernel está habilitado, OutputCacheModule a veces no elimina el encabezado Set-Cookie de las respuestas almacenadas en caché que pasa. a Http.sys. La siguiente es la secuencia específica de eventos que resulta en el error:
• Un usuario que no ha visitado recientemente el sitio (y por lo tanto no tiene una sesión correspondiente) solicita una página que tiene el almacenamiento en caché de resultados habilitado, pero cuyo resultado no está disponible actualmente. en el caché.
• La solicitud ejecuta código que accede a la sesión creada más recientemente por el usuario, lo que provoca que la cookie de ID de sesión se devuelva en el encabezado Set-Cookie de la respuesta.
• OutputCacheModule proporciona resultados a Http.sys, pero no puede eliminar el encabezado Set-Cookie de la respuesta.
• Http.sys devuelve respuestas almacenadas en caché en solicitudes posteriores, conectando por error a otros usuarios a la sesión.
¿La moraleja de la historia? El estado de sesión y el almacenamiento en caché de resultados en modo kernel no se mezclan. Si utiliza el estado de sesión en una página con el almacenamiento en caché de resultados habilitado y la aplicación se ejecuta en IIS 6.0, debe desactivar el almacenamiento en caché de resultados en modo kernel. Aún se beneficiará del almacenamiento en caché de resultados, pero debido a que el almacenamiento en caché de resultados en modo kernel es mucho más rápido que el almacenamiento en caché de resultados normal, el almacenamiento en caché no será tan eficiente. Para obtener más información sobre este problema, consulte support.microsoft.com/kb/917072.
Puede desactivar el almacenamiento en caché de salida en modo kernel para una página individual incluyendo el atributo VaryByParam="*" en la directiva OutputCache de la página, aunque hacerlo puede resultar en un aumento repentino en los requisitos de memoria. Otro enfoque más seguro es desactivar el almacenamiento en caché en modo kernel para toda la aplicación incluyendo el siguiente elemento en web.config:
<httpRuntime enableKernelOutputCache="false" />
También puede utilizar una configuración de registro para deshabilitar el almacenamiento en caché de salida en modo kernel globalmente, es decir, deshabilitar el almacenamiento en caché de salida en modo kernel para todos los servidores. Consulte support.microsoft.com/kb/820129 para obtener más detalles.
Cada vez que escucho a un cliente informar problemas desconcertantes en la sesión, le pregunto si está utilizando el almacenamiento en caché de resultados en alguna página. Si utilizan el almacenamiento en caché de resultados y el sistema operativo host es Windows Server 2003, recomendaría que deshabiliten el almacenamiento en caché de resultados en modo kernel. El problema suele solucionarse. Si el problema no se resuelve, el error existe en el código. ¡Esté alerta!
Volver arriba
Vida útil del ticket de autenticación de formularios ¿Puedes identificar el problema con el siguiente código?
FormsAuthentication.RedirectFromLoginPage(nombre de usuario, verdadero);
Este código puede parecer correcto, pero nunca debe usarse en una aplicación ASP.NET 1.x a menos que el código de otra parte de la aplicación compense los efectos negativos de esta declaración. Si no estás seguro de por qué, sigue leyendo.
FormsAuthentication.RedirectFromLoginPage realiza dos tareas. Primero, cuando FormsAuthenticationModule redirige al usuario a la página de inicio de sesión, FormsAuthentication.RedirectFromLoginPage redirige al usuario a la página que solicitó originalmente. En segundo lugar, emite un ticket de autenticación (generalmente incluido en una cookie y siempre incluido en una cookie en ASP.NET 1.x) que permite al usuario permanecer autenticado durante un período de tiempo predeterminado.
El problema radica en este período de tiempo. En ASP.NET 1.x, pasar otro parámetro que sea falso a RedirectFromLoginPage genera un ticket de autenticación temporal que caduca después de 30 minutos de forma predeterminada. (Puede cambiar el período de tiempo de espera usando el atributo Timeout en el elemento web.config.) Sin embargo, al pasar otro parámetro verdadero se emitirá un ticket de autenticación permanente que es válido por 50 años. ¡Esto crea un problema porque si alguien roba esa autenticación! ticket, pueden utilizar la identidad de la víctima para acceder al sitio web mientras dure el ticket. Hay muchas formas de robar tickets de autenticación (probar tráfico no cifrado en puntos de acceso inalámbricos públicos, crear secuencias de comandos en sitios web, obtener acceso físico a la computadora de la víctima, etc.), por lo que pasar verdadero a RedirectFromLoginPage es más seguro que deshabilitar su sitio web. No es mucho mejor. Afortunadamente, este problema se resolvió en ASP.NET 2.0. RedirectFromLoginPage ahora acepta los tiempos de espera especificados en web.config para tickets de autenticación temporales y permanentes de la misma manera.
Una solución es no pasar nunca verdadero en el segundo parámetro de RedirectFromLoginPage en aplicaciones ASP.NET 1.x. Pero esto no es práctico porque las páginas de inicio de sesión a menudo cuentan con una casilla "Mantenerme conectado" que el usuario puede marcar para recibir una cookie de autenticación permanente en lugar de temporal. Otra solución es utilizar el fragmento de código en Global.asax (o el módulo HTTP si lo prefiere), que modifica la cookie que contiene el ticket de autenticación permanente antes de devolverla al navegador.
La Figura 3 contiene uno de esos fragmentos de código. Si este fragmento de código está en Global.asax, modifica la propiedad Expires de la cookie de autenticación de formularios permanente saliente para que la cookie caduque después de 24 horas. Puede establecer el tiempo de espera en cualquier fecha que desee modificando la línea comentada "Nueva fecha de vencimiento".
Puede que le resulte extraño que el método Application_EndRequest llame a un método auxiliar local (GetCookieFromResponse) para comprobar la cookie de autenticación de la respuesta saliente. El método Helper es una solución para otro error en ASP.NET 1.1 que causaba que se agregaran cookies falsas a la respuesta si usaba el generador de índice de cadenas de HttpCookieCollection para verificar si hay cookies inexistentes. Usar un generador de índices enteros como GetCookieFromResponse resuelve el problema.
Volver al principio Estado de vista: el asesino silencioso del rendimiento En cierto sentido, el estado de vista es lo mejor que existe. Después de todo, el estado de visualización permite que las páginas y los controles mantengan el estado entre devoluciones de datos. Por lo tanto, no es necesario escribir código para evitar que el texto de un cuadro de texto desaparezca cuando se hace clic en un botón, o para volver a consultar la base de datos y volver a vincular el DataGrid después de una devolución de datos, como lo haría en ASP tradicional.
Pero el estado de vista tiene una desventaja: cuando crece demasiado, se convierte en un asesino silencioso del rendimiento. Algunos controles, como los cuadros de texto, toman decisiones según el estado de la vista. Otros controles (en particular, DataGrid y GridView) determinan su estado de visualización en función de la cantidad de información mostrada. Me sentiría intimidado si un GridView mostrara 200 o 300 filas de datos. Aunque el estado de vista de ASP.NET 2.0 es aproximadamente la mitad del tamaño del estado de vista de ASP.NET 1.x, un GridView incorrecto puede reducir fácilmente el ancho de banda efectivo de la conexión entre el navegador y el servidor web en un 50 % o más.
Puede desactivar el estado de vista para controles individuales configurando EnableViewState en falso, pero algunos controles (especialmente DataGrid) pierden algunas funciones cuando no pueden usar el estado de vista. Una mejor solución para controlar el estado de la vista es mantenerla en el servidor. En ASP.NET 1.x, puede anular los métodos LoadPageStateFromPersistenceMedium y SavePageStateToPersistenceMedium de la página y manejar el estado de vista como desee. El código de la Figura 4 muestra una anulación que evita que el estado de la vista se conserve en campos ocultos y, en cambio, lo conserva en el estado de sesión. Almacenar el estado de la vista en el estado de la sesión es particularmente efectivo cuando se usa con el modelo de proceso del estado de la sesión predeterminado (es decir, cuando el estado de la sesión se almacena en un proceso de trabajo ASP.NET en la memoria). Por el contrario, si el estado de la sesión se almacena en la base de datos, sólo las pruebas pueden mostrar si mantener el estado de la vista en el estado de la sesión mejora o disminuye el rendimiento.
El mismo enfoque se utiliza en ASP.NET 2.0, pero ASP.NET 2.0 proporciona una forma más sencilla de conservar el estado de vista en el estado de sesión. Primero, defina un adaptador de página personalizado cuyo método GetStatePersister devuelva una instancia de la clase SessionPageStatePersister de .NET Framework:
clase pública SessionPageStateAdapter :System.Web.UI.Adapters.PageAdapter{anulación pública PageStatePersister GetStatePersister () {return new SessionPageStatePersister(this.Page ) ; }}
Luego, registre el adaptador de página personalizado como adaptador de página predeterminado colocando el archivo App.browsers en la carpeta App_Browsers de su aplicación de la siguiente manera:
<browsers><browser refID="Default"><controlAdapters><adapter controlType=" System.Web. UI.Page"adapterType="SessionPageStateAdapter" /></controlAdapters></browser></browsers>
(Puede nombrar el archivo como desee, siempre que tenga una extensión .browsers). Luego, ASP.NET carga el adaptador de página y utiliza el SessionPageStatePersister devuelto para preservar todo el estado de la página, incluido el estado de la vista.
Una desventaja de utilizar un adaptador de página personalizado es que se aplica globalmente a cada página de la aplicación. Si prefiere mantener el estado de visualización de algunas páginas en estado de sesión pero no de otras, utilice el método que se muestra en la Figura 4. Además, puede tener problemas al utilizar este método si el usuario crea varias ventanas del navegador en la misma sesión.
Volver arriba
Estado de la sesión de SQL Server: otro factor determinante del rendimiento
ASP.NET facilita el almacenamiento del estado de la sesión en la base de datos: simplemente active un interruptor en web.config y el estado de la sesión se moverá fácilmente a la base de datos backend. Esta es una característica importante para las aplicaciones que se ejecutan en el ámbito web porque permite que todos los servidores del ámbito compartan un repositorio común del estado de la sesión. La actividad adicional de la base de datos reduce el rendimiento de las solicitudes individuales, pero la mayor escalabilidad compensa la pérdida de rendimiento.
Todo esto suena bien, pero las cosas cambian cuando consideras algunos puntos:
• Incluso en aplicaciones que usan el estado de sesión, la mayoría de las páginas no usan el estado de sesión.
• De forma predeterminada, el administrador de estado de sesión de ASP.NET realiza dos accesos (un acceso de lectura y otro de escritura) al almacén de datos de la sesión en cada solicitud, independientemente de si la página solicitada utiliza el estado de la sesión.
En otras palabras, cuando utiliza la opción de estado de sesión de SQL Server™, paga un precio (dos accesos a la base de datos) por cada solicitud, incluso en solicitudes de páginas que no tienen nada que ver con el estado de la sesión. Esto tiene un impacto negativo directo en el rendimiento de todo el sitio web.
Figura 5 Elimine el acceso innecesario a la base de datos del estado de la sesión.
Entonces, ¿qué debe hacer? Es simple: deshabilite el estado de sesión en páginas que no usan el estado de sesión. Esto siempre es una buena idea, pero es especialmente importante cuando el estado de la sesión se almacena en una base de datos. La Figura 5 muestra cómo deshabilitar el estado de la sesión. Si la página no utiliza el estado de sesión en absoluto, incluya EnableSessionState="false" en su directiva de página, así:
<%@ Page EnableSessionState="false" ... %>
Esta directiva evita que el administrador del estado de la sesión lea y escriba en la base de datos del estado de la sesión en cada solicitud. Si la página lee datos del estado de la sesión pero no escribe datos (es decir, no modifica el contenido de la sesión del usuario), configure EnableSessionState en ReadOnly de la siguiente manera:
<%@ Page EnableSessionState="ReadOnly" ... %>
Finalmente, si la página requiere acceso de lectura/escritura al estado de la sesión, omita la propiedad EnableSessionState o configúrela en verdadero:
<%@ Page EnableSessionState="true" ... %>
Al controlar el estado de la sesión de esta manera, se asegura de que ASP.NET sólo acceda a la base de datos del estado de la sesión cuando sea realmente necesario. Eliminar el acceso innecesario a la base de datos es el primer paso para crear aplicaciones de alto rendimiento.
Por cierto, la propiedad EnableSessionState es pública. Esta propiedad ha sido documentada desde ASP.NET 1.0, pero todavía rara vez veo que los desarrolladores la aprovechen. Quizás porque no es muy importante para el modelo de estado de sesión predeterminado en la memoria. Pero es importante para el modelo de SQL Server.
Volver al principio Funciones sin caché La siguiente declaración aparece con frecuencia en el archivo web.config de una aplicación ASP.NET 2.0 y en los ejemplos que presentan el administrador de funciones de ASP.NET 2.0:
<roleManager enable="true" />
Pero como se muestra arriba, esta afirmación tiene un impacto negativo significativo en el rendimiento. ¿Sabes por qué?
De forma predeterminada, el administrador de funciones de ASP.NET 2.0 no almacena en caché los datos de las funciones. En cambio, consulta el almacén de datos de roles cada vez que necesita determinar a qué rol, si corresponde, pertenece el usuario. Esto significa que una vez que un usuario se autentica, cualquier página que aproveche los datos de la función (por ejemplo, páginas que utilicen mapas de sitio con configuraciones de recorte de seguridad habilitadas y páginas que tengan acceso restringido utilizando directivas URL basadas en funciones en web.config) causará la función. manager para consultar el almacén de datos de roles. Si los roles se almacenan en una base de datos, puede prescindir fácilmente del acceso a varias bases de datos para cada solicitud. La solución es configurar el administrador de funciones para almacenar en caché los datos de las funciones en cookies:
<roleManager enable="true" cacheRolesInCookie="true" />
Puede utilizar otros atributos <roleManager> para controlar las características de la cookie de función; por ejemplo, durante cuánto tiempo la cookie debe permanecer válida (y, por lo tanto, con qué frecuencia el administrador de funciones regresa a la base de datos de funciones). Las cookies de funciones están firmadas y cifradas de forma predeterminada, por lo que el riesgo de seguridad, aunque no es cero, se mitiga.
Volver al principioSerialización de propiedades del archivo de configuración
El servicio de perfiles ASP.NET 2.0 proporciona una solución preparada para el problema de mantener el estado por usuario, como las preferencias de personalización y de idioma. Para utilizar el servicio de perfiles, debe definir un perfil XML que contenga los atributos que desea conservar en nombre de un usuario individual. Luego, ASP.NET compila una clase que contiene las mismas propiedades y proporciona acceso fuertemente tipado a instancias de clase a través de propiedades del archivo de configuración agregadas a la página.
La flexibilidad del perfil es tan grande que incluso permite utilizar tipos de datos personalizados como propiedades del perfil. Sin embargo, hay un problema que personalmente he visto que hace que los desarrolladores cometan errores. La Figura 6 contiene una clase simple denominada Publicaciones y una definición de perfil que utiliza Publicaciones como atributo de perfil. Sin embargo, esta clase y este archivo de configuración producen un comportamiento inesperado en tiempo de ejecución. ¿Puedes descubrir por qué?
El problema es que Posts contiene un campo privado llamado _count, que debe serializarse y deserializarse para congelar y volver a congelar completamente la instancia de clase. Sin embargo, _count no se serializa ni deserializa porque es privado y ASP.NET Profile Manager usa la serialización XML de forma predeterminada para serializar y deserializar tipos personalizados. El serializador XML ignora los miembros no públicos. Por lo tanto, las instancias de Publicaciones se serializan y deserializan, pero cada vez que se deserializa una instancia de clase, _count se restablece a 0.
Una solución es hacer que _count sea un campo público en lugar de un campo privado. Otra solución es encapsular _count con una propiedad pública de lectura/escritura. La mejor solución es marcar las publicaciones como serializables (usando SerializableAttribute) y configurar el administrador de perfiles para usar el serializador binario de .NET Framework para serializar y deserializar instancias de clase. Esta solución mantiene el diseño de la clase misma. A diferencia de los serializadores XML, los serializadores binarios serializan campos independientemente de si son accesibles. La Figura 7 muestra la versión fija de la clase Publicaciones y resalta la definición modificada del perfil que la acompaña.
Una cosa que debe tener en cuenta es que si está utilizando un tipo de datos personalizado como propiedad de perfil y ese tipo de datos tiene miembros de datos no públicos que deben serializarse para poder serializar completamente una instancia del tipo, use serializeAs=" Binary" en las propiedades de declaración de propiedad y asegúrese de que el tipo en sí sea serializable. De lo contrario, no se producirá la serialización completa y perderá tiempo intentando determinar por qué el perfil no funciona.
Volver al principio Saturación del grupo de subprocesos A menudo me sorprende mucho el número real de páginas ASP.NET que veo cuando ejecuto una consulta de base de datos y espero 15 segundos o más para que se devuelvan los resultados de la consulta. (¡También esperé 15 minutos antes de ver los resultados de mi consulta!) A veces, el retraso es una consecuencia inevitable de la gran cantidad de datos que se devuelven; otras veces, el retraso se debe a un diseño deficiente de la base de datos. Pero independientemente del motivo, las consultas largas a la base de datos o cualquier tipo de operaciones de E/S prolongadas harán que el rendimiento disminuya en las aplicaciones ASP.NET.
Ya describí este problema en detalle antes, por lo que no entraré en demasiados detalles aquí. Baste decir que ASP.NET se basa en un grupo de subprocesos limitado para manejar solicitudes. Si todos los subprocesos están ocupados esperando a que se complete una consulta de base de datos, una llamada a un servicio web u otra operación de E/S, se liberarán cuando se complete una operación. completado antes de que se emita un hilo, otras solicitudes deben estar en cola y esperando. Cuando las solicitudes se ponen en cola, el rendimiento cae drásticamente. Si la cola está llena, ASP.NET provoca que las solicitudes posteriores fallen con un error HTTP 503. Ésta no es una situación que nos gustaría ver en una aplicación de producción en un servidor web de producción.
La solución son las páginas asincrónicas, una de las mejores características aunque poco conocidas de ASP.NET 2.0. Una solicitud de una página asincrónica comienza en un subproceso, pero cuando inicia una operación de E/S, regresa a ese subproceso y a la interfaz IAsyncResult de ASP.NET. Cuando se completa la operación, la solicitud notifica a ASP.NET a través de IAsyncResult y ASP.NET extrae otro subproceso del grupo y completa el procesamiento de la solicitud. Vale la pena señalar que cuando ocurren operaciones de E/S, no se ocupa ningún subproceso del grupo de subprocesos. Esto puede mejorar significativamente el rendimiento al evitar que las solicitudes de otras páginas (páginas que no realizan operaciones de E/S prolongadas) esperen en la cola.
Puede leer todo sobre las páginas asincrónicas en la edición de octubre de 2005 de MSDN® Magazine. Cualquier página que esté vinculada a E/S en lugar de a máquina y que tarde mucho tiempo en ejecutarse tiene buenas posibilidades de convertirse en una página asincrónica.
Cuando les hablo a los desarrolladores sobre páginas asincrónicas, a menudo responden "Eso es genial, pero no las necesito en mi aplicación". A lo que respondo "¿Alguna de sus páginas necesita consultar la base de datos?" servicios web? ¿Ha revisado los contadores de rendimiento de ASP.NET para obtener estadísticas sobre solicitudes en cola y tiempos de espera promedio? Aunque su aplicación está funcionando bien hasta ahora, a medida que crece el tamaño de su cliente, la carga puede aumentar "
. de las aplicaciones ASP.NET del mundo real requieren páginas asincrónicas. ¡Por favor recuerda esto!
Volver arriba Suplantación y autorización ACL La siguiente es una directiva de configuración simple, pero hace que mis ojos se iluminen cada vez que la veo en web.config:
<identity impersonate="true" />
Esta directiva permite la suplantación del lado del cliente en aplicaciones ASP.NET. Adjunta un token de acceso que representa al cliente al subproceso que maneja la solicitud para que las comprobaciones de seguridad realizadas por el sistema operativo se realicen contra la identidad del cliente en lugar de la identidad del proceso de trabajo. Las aplicaciones ASP.NET rara vez requieren burlas; mi experiencia me dice que los desarrolladores a menudo permiten las burlas por motivos equivocados. He aquí por qué.
Los desarrolladores suelen habilitar la suplantación en aplicaciones ASP.NET para que los permisos del sistema de archivos puedan usarse para restringir el acceso a las páginas. Si Bob no tiene permiso para ver Salaries.aspx, el desarrollador habilitará la suplantación para que se pueda evitar que Bob vea Salaries.aspx configurando la lista de control de acceso (ACL) para negarle el permiso de lectura a Bob. Pero existe el siguiente peligro oculto: la suplantación no es necesaria para la autorización de ACL. Cuando habilita la autenticación de Windows en una aplicación ASP.NET, ASP.NET verifica automáticamente la ACL para cada página .aspx solicitada y rechaza las solicitudes de las personas que llaman que no tienen permiso para leer el archivo. Todavía se comporta así incluso si la simulación está desactivada.
En ocasiones es necesario justificar la simulación. Pero normalmente puedes evitarlo con un buen diseño. Por ejemplo, supongamos que Salaries.aspx consulta una base de datos en busca de información salarial que solo los gerentes conocen. Con la suplantación, puede utilizar permisos de base de datos para negar al personal no directivo la capacidad de consultar datos de nómina. O puede ignorar la suplantación y limitar el acceso a los datos de nómina configurando una ACL para Salaries.aspx para que los no administradores no tengan acceso de lectura. El último enfoque proporciona un mejor rendimiento porque evita por completo la burla. También elimina el acceso innecesario a la base de datos. ¿Por qué se deniega la consulta de la base de datos solo por razones de seguridad?
Por cierto, una vez ayudé a solucionar problemas de una aplicación ASP heredada que se reiniciaba periódicamente debido a una huella de memoria sin restricciones. Un desarrollador sin experiencia convirtió la instrucción SELECT de destino en SELECT * sin considerar que la tabla consultada contenía imágenes, que eran grandes y numerosas. El problema se ve agravado por una pérdida de memoria no detectada. (¡Mi área de código administrado!) Una aplicación que había estado funcionando bien durante años de repente dejó de funcionar porque las declaraciones SELECT que solían devolver uno o dos kilobytes de datos ahora devolvían varios megabytes. Si a esto le sumamos el problema del control de versiones inadecuado, la vida de un equipo de desarrollo tendrá que ser “hiperactiva”; y por “hiperactiva” es como tener que ver a tus hijos jugar un juego molesto mientras tú juegas. Acostarse por la noche. Aburrido partido de fútbol.
En teoría, las pérdidas de memoria tradicionales no pueden ocurrir en aplicaciones ASP.NET compuestas enteramente de código administrado. Pero el uso insuficiente de memoria puede afectar el rendimiento al obligar a que la recolección de basura se realice con mayor frecuencia. Incluso en aplicaciones ASP.NET, tenga cuidado con SELECT *.
Volver arriba No confíe completamente en él: ¡configure el archivo de configuración de la base de datos!
Como consultor, a menudo me preguntan por qué las aplicaciones no funcionan como se esperaba. Recientemente, alguien preguntó a mi equipo por qué una aplicación ASP.NET solo completaba aproximadamente 1/100 del rendimiento (solicitudes por segundo) requerido para solicitar un documento. Los problemas que hemos descubierto antes son exclusivos de los problemas que hemos visto en aplicaciones web que no funcionaron correctamente y son lecciones que todos deberíamos tomar en serio.
Ejecutamos SQL Server Profiler y monitoreamos la interacción entre esta aplicación y la base de datos back-end. En un caso más extremo, un solo clic en un botón provocó que se produjeran más de 1.500 errores en la base de datos. No puede crear aplicaciones de alto rendimiento de esa manera. Una buena arquitectura siempre comienza con un buen diseño de base de datos. No importa cuán eficiente sea su código, no funcionará si es pesado por una base de datos mal escrita.
La arquitectura de acceso de datos deficiente generalmente resulta de uno o más de los siguientes:
• Diseño de base de datos deficiente (generalmente diseñado por desarrolladores, no administradores de bases de datos).
• Uso de conjuntos de datos y equipos de datos, especialmente DataAdapter.update, que funciona bien para aplicaciones de formularios de Windows y otros clientes ricos, pero generalmente no es ideal para aplicaciones web.
• Una capa de acceso de datos mal diseñada (DAL) que tiene cálculos mal programados y consume muchos ciclos de CPU para realizar operaciones relativamente simples.
El problema debe identificarse antes de que pueda tratarse. La forma de identificar problemas de acceso a datos es ejecutar SQL Server Profiler o una herramienta equivalente para ver lo que está sucediendo detrás de escena. El ajuste de rendimiento se completa después de verificar la comunicación entre la aplicación y la base de datos. Pruébalo, te sorprenderá lo que encuentras.
Volver a la conclusión superior Ahora conoce algunos de los problemas y sus soluciones que puede encontrar al construir una aplicación de producción ASP.NET. El siguiente paso es echar un vistazo más de cerca a su propio código e intentar evitar algunos de los problemas que he esbozado aquí. ASP.NET puede haber reducido la barrera de entrada para los desarrolladores web, pero sus aplicaciones tienen todas las razones para ser flexibles, estables y eficientes. Considere esto cuidadosamente para evitar errores para principiantes.
La Figura 8 proporciona una breve lista de verificación que puede usar para evitar las dificultades descritas en este artículo. Puede crear una lista de verificación de defectos de seguridad similar. Por ejemplo:
• ¿Ha cifrado secciones de configuración que contienen datos confidenciales?
• ¿Está comprobando y validando la entrada utilizada en las operaciones de la base de datos y está utilizando la entrada codificada HTML como salida?
• ¿Su directorio virtual contiene archivos con extensiones desprotegidas?
Estas preguntas son importantes si valoran la integridad de su sitio web, los servidores que lo alojan y los recursos de back -end en los que confían.
Jeff Prosise es editor colaborador de la revista MSDN y autor de varios libros, incluida la programación de Microsoft .NET (Microsoft Press, 2002). También es cofundador de Wintellect, una empresa de consultoría y educación de software.
De la edición de julio de 2006 de la revista MSDN.