Qué es lo que no hay que hacer en ASP.NET, y qué hacer en su lugar

Vamos a ver varios errores que se suelen cometer en proyectos ASP.NET. Veremos algunas recomendaciones de qué deberíamos hacer para no caer en esos errores. Está basado en una presentación de Damian Edwards en el Norwegian Developers Conference.

Disclaimer

Este post no pretende ser una guía completa para asegurar que tu aplicación sea segura y eficiente. Es necesario seguir más buenas prácticas para la seguridad y el rendimiento que no se tratan en este post. Sólo se sugieren formas de evitar errores comunes relacionados con las clases y procesos de .NET.

Cumple los estándares

Control Adapters

Recomendación: deja de usar los control adapters para renderizado adaptativo, y usa CSS con media queries y el estandar de HTML.

Los Controls Adapters se introdujeron en .NET 2.0 para renderizar código de presentación personalizado para diferentes dispositivos y entornos. A día de hoy, este tipo de renderizado se puede conseguir con CSS y HTML. Deberías dejar de usar los Control Adapters y convertir cualquier adapter que tengas a Css y HTML.

Para más información, pásate por Media Queries y How To: Add Mobile Pages to Your ASP.NET Web Forms / MVC Application.

Propiedades de estilo y Controles

Recomendación: Deja de usar los estilos (style) en el markup de los controles, y pon el formato a través de plantillas CSS.

Los controles de servidor contienen docenas de propiedades que se pueden usar para setear el estilo in-line. Por ejemplo, la propiedad ForeColor establece el color del texto de un control. Se puede conseguir el mismo efecto de manera más eficiente con hojas de estilo CSS. Las hojas de estilo nos permiten centralizar los valores de estilos y nos evitan tener que establecer estos valores por toda la aplicación.

El siguiente ejemplo muestra una clase CSS que establece el color del texto a Rojo:

.CautionRow {
      color: red;
}

En el siguiente ejemplo vemos cómo podemos aplicar de manera dinámica la clase CSS:

protected void CustomersGridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
 if (e.Row.Cells[2].Text == "Unconfirmed")
 {
   e.Row.CssClass = "CautionRow";
 }
}

Callbacks de páginas y controles

Recomendación: Deja de usar los callbacks de los controles y páginas, y en lugar de eso usa: AJAX, UpdatePanel, métodos de acción de MVC, Web API o SignalR.

En las primeras versiones de ASP.NET, los métodos de callback de las páginas y controles nos permitían actualizar parte de la página sin tener que actualizarla entera. Ahora podemos conseguir que sólo se actualice una parte de la página con AJAX, UpdatePanel, MVC, Web API o SignalR. Deberías dejar de usar los métodos de callback ya que pueden causar incidencias con las URLs amigables y el enrutado. Por defecto, los controles no habilitan los métodos de callback, pero si lo habilitaste en algún control, deberías desactivarlo.

Detección de capacidades del navegador

Recomendación: Deja de usar la detección estática de capacidades del navegador, y en su lugar usa la detección dinámica de características.

En las primeras versiones de ASP.NET, las características que soportaba cada navegador se guardaban en un archivo XML. Detectar las características que se soportan a través de una búsqueda estática no es la mejor aproximación. Ahora, podemos detectar dinámicamente las características que un navegador soporta gracias a motores de detección de características como Modernizr. La detección de capacidades determina el soporte para poder usar un método o una propiedad y comprobar si el navegador produjo el resultado deseado. Por defecto, Modernizr está incluido en las plantillas de aplicaciones Web.

Seguridad

Request Validation

Recomendación: valida las entradas de usuario, y codifica la salida para los usuarios.

La validación de peticiones es una característica de ASP.NET que comprueba cada petición y la detiene si se detecta algún peligro. Pero no te bases en la validación de peticiones para securizar tu aplicación contra ataques de cross-site scripting. En lugar de eso, valida todas las entradas de usuario y codifica las salidas. En algunos casos, puedes usar expresiones regulares para validar las entradas, pero en casos más complicados deberías validar las entradas de usuarios usando clases de .NET que determinen si los valores introducidos son permitidos.

El siguiente ejemplo muestra cómo podemos usar la clase Uri para ver si la Uri indicada por un usuario es válida:


var isValidUri = Uri.IsWellFormedUriString(passedUri, UriKind.Absolute);

Ántes de renderizar la entrada de usuario como HTML o incluirlo como entrada en una consulta SQL, codifica los valores para asegurarte de que no se ha incluido ningún código malicioso.

Puedes codificar el valor en HTML con las marcas <%: %>, de la siguiente forma:

<span><%: userInput %></span>

O, con la sintáxis de Razor, podemos codificar en HTML con @, de la siguiente forma:

<span>@userInput</span>

El siguiente ejemplo muestra cómo podemos codificar en HTML un valor en el code-behind:

var encodedInput = Server.HtmlEncode(userInput);

Para codificar de manera segura valores para un comando SQL, usa los parámetros de comandos como SQLParameter

Autenticación de formularios y sesiones sin cookies

Recomendación: Requiere cookies

Pasar información de autenticación en el query string no es seguro. Por lo tanto, requiere cookies cuando tu aplicación incluya autenticación. Si tu cookie guarda infórmación sensible, considera usar SSL para ello.

El siguiente ejemplo muestra cómo podemos indicar en el Web.config que la autenticación por formulario requiere una cookie que se transmite sobre ssl:

<authentication mode="Forms">
<forms loginUrl="member_login.aspx"
cookieless="UseCookies"
requireSSL="true"
path="/MyApplication" />
</authentication>

EnableViewStateMac

Recomendación: Nunca la pongas a false

Por defecto, EnableViewStateMac se configura a true. Aún cuando tu aplicación no esté usando view states, no configures la propiedad EnableviewStateMac a false. Poner este valor a false hará que tu aplicación sea vulnerable a ataques de cross-site scripting.

El siguiente ejemplo muestra cómo configurar la propiedad EnableViewStateMac a true. A día de hoy no necesitas ponerlo a true ya que está así configurado por defecto. Sin embargo, si lo has puesto a false en alguna página de tu aplicación, deberías corregirlo inmediatamente:

<%@ Page language="C#" EnableViewStateMac="true" %>

Medium Truts

Recomendación: No dependas en Medium Trust (o en cualquier otro nivel de confianza) como parte de la seguridad.

Partial trust no proteje tu aplicación y no debería ser usado. En lugar de ello, usa Full Trust, y aisla las aplicaciones "untrusted" en applications pools separados. Además, ejecuta cada aplication pool con una identificación única. Para más información ve a ASP.NET Partial Trust does not guarantee application isolation.

<appSettings>

Recomendation: No desactives las opciones de seguridad en el elemento <appSettings>

El elemento appSettings contiene varios valores necesarios para actualizaciones de seguridad. No deberías cambiar o desactivar esos valores. Si los desactivas cuando despliegas una actualizción, reactívalos inmediatamente después de desplegar.

Para más detalles, lee ASP.NET appSettings Element.

UrlPathEncode

Recomendación: En su lugar usa UrlEncode

El método UrlPathEncode se añadió a .NET para corregir un problema muy concreto de compatibilidad con los navegadores. No codifica de manera adecuada una URL, y no protege a tu aplicación de cross-site scripting. No deberías usarlo nunca en tu applicación. En su lugar, usa UrlEncode.

El siguiente ejemplo muestra cómo podemos pasar una url codificada como un parámetro del query string a un control hyperlink:

string destinationURL = "http://www.contoso.com/default.aspx?user=test";
NextPage.NavigateUrl = "~/Finish?url=" + Server.UrlEncode(destinationURL);

Rendimiento y fiabilidad

PreSendRequestHeaders y PreSendRequestContext

Recomendación: No uses estos eventos con modulos manejados.

Podemos usar los eventos PreSendRequestHeaders y PreSendRequestContext con módulos nativos de IIS, pero no los uses con módulos manejados que implementen IHttpModule. Te causarán problemas con peticiones asíncronas.

Eventos asíncronos con Web Forms

Recomendación: En Web Forms, evita escribir metodos asíncronos que devuelven void para eventos del ciclo de vida de la página, en su lugar usa Page.RegisterAsyncTask para código asíncrono.

Cuando marcamos un evento de página con async y void, no se puede determinar cúando ha terminado el código asíncrono. En su lugar, usa Page.RegisterAsyncTask para ejectuar el código asíncrono de manera que podrás saber cuándo ha terminado.

El siguiente ejemplo muestra un manejador del click de un bottón que contiene código asíncrono. Este ejemplo incluye leer un valor string asíncronamente, y es sólo un ejemplo simplificado de una tarea asíncrona y que no es una prácitca recomendada:

protected void StartAsync_Click(object sender, EventArgs e)
{
 Page.RegisterAsyncTask(new PageAsyncTask(async() =>
  {
    string stringToRead = "Long text value";
    using (StringReader reader = new StringReader(stringToRead))
    {
      string readText = await reader.ReadToEndAsync();
      Result.Text = readText;
    }
  }));
}

Si estás usando tareas asíncronas, configura como la versión 4.5 como TargetFramework en el Web.config. con esto se activa el nuevo contexto de sincronización que se introdujo en .NET 4.5. Este valor está configurado por defecto en los proyectos que creemos con Visual Studio 2012, pero no se configura si estas trabajando con proyectos existentes:

<system.web>
<httpRuntime TargetFramework="4.5" />
</system.web>

Trabajos Fire-and-Forget

Recomendación: Cuando trabajamos con peticiones en ASP.NET, evita lanzar trabajos de fire-and-forget (como el método ThreadPool.QueueUserWorkItem o crear un timer que llama continuamente a un delegado).

Si tu aplicación tiene un trabajo de fire-and-forget que se ejecuta en ASP.NET, la aplicación se puede desincronizar. En cualquier momento, se puede destruir el app domain con lo que el processo que hayas lanzado no se correspondera con el estado actual de tu aplicación.

Deberías mover este tipo de tareas fuera de ASP.NET. Puedes usar un Servicio de Windows o on Worker role en Windows Azure para realizar el trabajo, y ejectuar ese código desde otro proceso.

Si tienes que hacer esto en ASP.NET, deberías usar el paquete de Nuget llamado WebBackgrounder para ejectuar ese código.

Petición de la entidad Body

Recomendación: Evita leer Request.Form o Request.InputStream ántes de que se ejecute el evento.

Lo más pronto que deberías leer de Request.Form o Request.InputStream es durante la ejecución del evento. En MVC, el Controlador es el que ejectua el evento cuando se ejecuta el método de acción. En Web Forms, la Page es el que ejecuta el evento cuando se lanza el evento Page.Init. Si lees la entidad del body ántes de que se ejecute el evento, interferirás el proceso de la petición.

Si necesitas leer la entidad body ántes de que se ejecute el evento, usa Request.GetbufferlessInputStream o Request.GetBufferedInputStream. Cuando usas GetBufferlessInputStream, obtienes el stream bruto de la petición, y asumes la responsabilidad de procesar la petición completa. Después de llamar a GetBufferlessInputStream, Request.Form y Request.InputStream no están disponibles ya que no se ha procesado por ASP.NET. Cuando usamos GetBufferedInputStream, tendrás una copia del stream de la petición. Request.Form y Request.InputStream siguen estando disponibles más adelante en la petición ya que ASP.NET procesa la otra copia.

Response.Redirect y Response.End

Recomendación: Presta atención en las diferencias de cómo se ejecuta el thread después de llamar a Response.Redirect(String).

El método Response.Redirect(String) llama al método Response.End. En un proceso síncrono, llamar a Response.Redirect hace que el thread actual se aborte. Sin embargo, en un proceso asíncrono, la llamada a Response.Redirect no aborta al thread actual, de manera que la ejecución continúa para la petición. En un proceso asíncrono, debes devolver la Task desde el método para parar la ejecución.

En un projecto MVC, no deberías llamar a Response.Redirect. En su lugar, usa un RedirectResult.

EnableViewState y ViewStateMode

Recomendación: Usa ViewStateMode, en lugar de EnableViewState, para tener un control granular sobre los controles que usan el view state.

Cuando configuramos el EnableViewState a false en la directiva de Page, el view state se desactiva para todos los controles de la página y no se puede habilitar. Si quieres habilitar el view state sólo para algunos controles de tu página, configura el ViewStateMode a "Disabled" para la página:

<%@ Page ViewStateMode="Disabled" . . . %>

Después, configura el ViewStateMode a "Enabled" en los controles que lo necesiten:

Habilitando el view state sólo para los controles que lo necesitan, puedes reducir el tamaño del view state de tus páginas.

SqlMembershipProvider

Recomendación: Usa providers universales.

En las plantillas de proyecto actuales, se ha reemplazado a SqlMembershipProvider por ASP.NET Universal Providers, disponible como un paquete NuGet. Si estás usando SqlMembershipProvider en un proyecto que se hizo con versiones anteriores, deberías cambiarlo a Universal Providers. Los Universal Providers funcionan con todas las bases de datos que soporta Entity Framework.

Para más información pasaros por: Introducing ASP.NET Universal Providers.

Peticiones largas (>110 segundos)

Recomendación: Usa WebSockets o SignalR para clientes conectados, y usa operaciones asíncronas de I/O.

Las peticiones largas pueden causar resultados inesperados y un bajo rendimiento de tu aplicación web. El timeout por defecto para una petición es de 110 segundos. Si estás usando session state con una petición larga, ASP.NET liberará el bloqueo del objeto de Sessión después de 110 segundos. Sin embargo, puede que tu aplicación esté en mitad de una operación en el objeto de Session cuando se libere el bloqueo, y la operación puede que no se complete de manera adecuada. Si llega una segunda petición del usuario se bloquea mientras la primera se esta ejecutando, la segunda petición podría acceder al objeto Session en un estado inconsistente.

Si tu aplicación incluye operaciones bloqueantes de I/O o síncronas, la aplicación se bloqueará.

Para mejorar el rendimiento, usa las operaciones asíncronas de I/O de .NET. Además, usa WebSockets o SignalR para conectar clientes al servidor. Estas características están diseñadas para trabajar con peticiones largas.

Artículo original: http://www.asp.net/aspnet/overview/web-development-best-practices/what-not-to-do-in-aspnet,-and-what-to-do-instead

Traducido por: Juan María Laó Ramos