ASP.NET 4, más orientado al SEO

La nueva versión de ASP.NET viene cargadita de novedades orientadas a la optimización en motores de búsqueda. Si sabemos aprovecharlas, nuestros sitios webs serán mejor indexados en los buscadores, seremos más fácilmente localizables desde estas herramientas y, consecuentemente, podremos aspirar a tener mayor número de visitantes.

En este post resumiré las mejoras introducidas en  ASP.NET 4 que pueden ayudarnos en nuestra relación con los motores de búsqueda. No son novedades revolucionarias, puesto que todo lo que nos ofrecen podíamos conseguirlo con las versiones anteriores de ASP.NET de alguna u otra manera, aunque sin duda que nos facilitarán algo las cosas. 🙂

Page.MetaDescription y Page.MetaKeywords

Descripción y tags en ASP.NET ASP.NET 4 incorpora a la clase Page las propiedades MetaDescription y MetaKeywords para facilitarnos el acceso a la metainformación de la página.

Con la primera de ellas, MetaDescription , podemos especificar en cada página una descripción individualizada, que será utilizada por los buscadores como un resumen de su contenido. Por ejemplo, Google, la incluye como parte de los resultados de las búsquedas.

La segunda, MetaKeywords, permite añadir tags o palabras clave que puedan ser útiles para clasificar su contenido. Aunque la mayoría de los buscadores no utilizan esta información a efectos de indexado, al parecer algunos sí lo hacen, por lo que sigue siendo costumbre incorporar esta información.

En ambos casos podemos establecer su contenido de forma declarativa, en las directivas de la página, así:

<%@ Page Title="Variable not found" Language="C#" 
         MasterPageFile="~/Blog.master" AutoEventWireup="true"
         CodeBehind="Default.aspx.cs" Inherits="Variable.Blog" 
         MetaDescription = "Variable not found. Artículos, noticias, curiosidades, 
                            reflexiones... sobre el mundo del desarrollo de software, 
                            internet, u otros temas relacionados con la tecnología"
         MetaKeywords = "desarrollo,programación,.net,c#,asp.net,asp.net mvc,software"   
%>

Pero también, y esta posibilidad es bastante más interesante, podemos establecerlas mediante código, por ejemplo en el Page_Load(), lo que puede ser útil si por ejemplo tenemos que obtener valores desde una base de datos o similar:

protected void Page_Load(object sender, EventArgs e)
{
   Product prod = ObtenerProducto(); // Carga el producto 
 
   this.Title = prod.Denominacion;
   this.MetaDescription = prod.DescripcionBreve;
   this.MetaKeywords = prod.Tags;
}

En cualquiera de los casos, el resultado es el mismo. ASP.NET, a la hora de generar la página, añadirá en la sección <head> de la página una etiqueta <meta> con la descripción y etiquetas proporcionadas:

<title>NETBOOK ACER ASPIRE ONE D250-0BK</title>
<meta name="description" content="Netbook con procesador Intel® Atom N270 1.6 GHz, 
                                  1GB memoria, 160GB disco duro, t. gráfica Intel® GMA 950,
                                  pantalla 10.1' LED, webcam y Windows XP." />
<meta name="keywords" content="Netbook, ultraportátil, acer, aspire" />

Ni que decir tiene que la etiqueta <head> de la página (o la master que utilice) debe tener el correspondiente runat="server" para que pueda ser modificada en tiempo de servidor.

Response.RedirectPermanent()

Redirecciones 301 en ASP.NET 4 Los más viejos del lugar recordarán que Response.Redirect() está con nosotros desde los tiempos del ASP tradicional, y desde entonces ha mantenido su función: enviar al agente de usuario un resultado HTTP 302, apuntándole una nueva dirección URL en la que localizar el recurso solicitado. Vamos, lo que toda la vida hemos llamado redirección. 🙂

Sin embargo, el código de estado 302 definido por el protocolo indica, literalmente, que el recurso solicitado ha sido localizado pero que se encuentra en otra dirección (retornada como información adicional a la respuesta) aunque sólo temporalmente. Por ello se dice que estas redirecciones son transitorias; el navegador (o los robots de búsqueda, en el caso que nos ocupa) no pueden asegurar que la URL indicada sea la ubicación correcta y permanente del recurso.

El código HTTP 301, sin embargo, indica que el recurso solicitado ha sido movido de forma permanente a la dirección que se retorna al agente de usuario. Esto podría ser útil, por ejemplo, si modificamos los dominios sobre los que corre un sitio web, o cambiamos el esquema de URLs que estamos utilizando y, entre otras cosas, quisiéramos aprovechar la información la información que ya tienen los buscadores sobre nuestro sitio.

Pues con este propósito ha sido incluido el método Response.RedirectPermanent()cuya forma de utilización es idéntica a las redirecciones habituales:

 
public partial class About: System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
   {
      // Redirigimos About.aspx a AcercaDe.aspx
      Response.RedirectPermanent("AcercaDe.aspx"); // 301 al canto!
}
}

¡Direcciones amigables!

Routing en ASP.NET 4 ¡Ya era hora de que ASP.NET ofreciera un mecanismo integrado para generar URLs amigables! El motor de routing introducido con ASP.NET 3.5  SP1 tan magníficamente aprovechado en el framework MVC, toma ahora su protagonismo también en el mundo de Webforms. Y la forma de usarlo, igual de sencilla. 🙂

Para que nuestra aplicación soporte friendly urls, tenemos que registrar durante la inicialización del sistema las rutas, o mejor dicho, el patrón del URLs, que vamos a entender. Por ejemplo, imaginemos que nuestra aplicación, un catálogo on-line, debe atender peticiones a direcciones como las siguientes para mostrar la página de detalle de un producto:

Esto podríamos conseguirlo durante la inicialización del sistema, registrando un patrón de ruta de la siguiente forma, en el global.asax:

void Application_Start(object sender, EventArgs e)
{
   // Code that runs on application startup
   RegistrarRutas();
}
 
private void RegistrarRutas()
{
   RouteTable.Routes.MapPageRoute(
      "DetalleProducto",       // Nombre de la ruta
      "product/{productCode}"// patrón de URL
      "~/product.aspx"         // Página que procesará la petición
);
}

Lo que indica la ruta es que las direcciones de tipo product/{productCode} serán enviadas a la página física ~/product.aspx. La porción variable de la URL, el parámetro {productCode} podremos obtenerlo desde el código de la página para cargar los datos desde el almacén correspondiente, por ejemplo así (en product.aspx.cs):

public partial class Product : System.Web.UI.Page
{
   protected void Page_Load(object sender, EventArgs e)
   {
      string productCode = RouteData.Values["productCode"] as string;
      if (!cargaProducto(productCode))
      {
         Response.Redirect("ProductNotFound.aspx");
      }
   }
}

Como puede intuirse, a través de la colección RouteData.Values podremos acceder a los parámetros suministrados en el interior de la ruta, es decir, los que forman parte de la URL de la página que introduce el usuario.

Al igual que ocurre en ASP.NET MVC, el sistema de rutas es bidireccional, por lo que podremos también generar links ‘amistosos’ hacia recursos del sitio web, como en el siguiente ejemplo:

HyperLinkToAcerAspire.NavigateUrl = 
      GetRouteUrl("DetalleProducto", 
                   new { productCode = "ACER-ASPIRE-ONE-D250-0BK" }
);

El método GetRouteUrl() genera la URL del producto utilizando el propio motor de routing, para lo que le pasamos el identificador de la ruta y, en un objeto anónimo, el valor para el parámetro requerido (productCode). El resultado para el caso anterior será la dirección: /product/ACER-ASPIRE-ONE-D250-0BK.

Mejoras en el marcado

Aunque no vamos a detallarlas, o al menos en este post ;-), aparecen también en ASP.NET 4 un buen conjunto de novedades dirigidas a mejorar la calidad del marcado que enviamos a cliente. Y dado que siempre se ha dicho que un marcado respetuoso con los estándares es bueno para el SEO, estas nuevas capacidades del framework nos ayudarán también a mejorar nuestro posicionamiento.

Convenientemente crossposteado desde: ASP.NET 4, más orientado al SEO  @ Variable not found.

Controladores diminutos

 

Controladores en ASP.NET MVCCuando desarrollamos sobre el framework MVC, estamos acostumbrados a crear nuestros controladores partiendo de la clase base Controller, que nos proporciona métodos, propiedades y mecanismos que nos ahorran mucho trabajo en su implementación. Por ejemplo, toda la lógica de localización e invocación de las acciones está definida en esta clase, así como métodos de creación de los tipos más utilizados de ActionResult, las llamadas al model binder, o propiedades de acceso rápido a datos del contexto.

Sin embargo, y es una prueba más de la flexibilidad de diseño del framework, esto no es ni mucho menos obligatorio. La factoría de controladores por defecto no es tan exigente a la hora de localizar estas clases como primer paso durante el proceso de una petición.

Si observamos el código del marco de trabajo, concretamente la clase ControllerTypeCache, nos encontramos con este método estático, responsable de determinar si un tipo dado cumple las condiciones necesarias para ser considerado un controlador válido:

internal static bool IsControllerType(Type t)

{

    return ((((t != null)

             && t.IsPublic) 

             && (t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) 

             && !t.IsAbstract)) 

             && typeof(IController).IsAssignableFrom(t));

}

 

Como podemos observar, en primer lugar se comprueba que la clase sea pública; es lógico, pues el framework debe instanciarla. A continuación, comprueba que su nombre atienda a la convención de acabar en “Controller” (sí, la convención que aprendimos a desmontar hace algún tiempo ;-)). La siguiente es que no se trate de una clase abstracta, algo básico si pretendemos instanciarla. Por último, se exige que implemente el interfaz IController cuya definición es la siguiente:

public interface IController

{

    void Execute(RequestContext requestContext);

}

Es decir, en ningún caso aparece la clase Controller, ni siquiera la clase ControllerBase, antecesora de la primera. Basta con implementar el método Execute() definido en el interfaz.

Por tanto, cumpliendo estos requisitos podríamos escribir un controlador tan reducido como el siguiente:

using System.Web.Routing;

using System.Web.Mvc;

 

namespace MasterViewModel.Controllers

{

    public class PersonaController : IController

    {

        public void Execute(RequestContext contexto)

        {

            contexto.HttpContext.Response.Write(

                    "Ejecutando la acción " + contexto.RouteData.Values["action"] +

                    " con parámetro " + contexto.RouteData.Values["id"]

            );

        }

    }

}

 

 

Incluyendo dicho controlador en un proyecto en el que mantengamos las rutas por defecto, si realizamos una petición del tipo GET /Persona/Beber/Cerveza, obtendremos en el navegador un mensaje como el siguiente:

Resultado de ejecución

Sin embargo, aunque el desarrollo de controladores así de ligeros pueda resultar a priori una idea atractiva  pensando sobre todo en optimizar aún más la velocidad de respuesta, en la práctica no tiene demasiado sentido hacerlo. Sería prácticamente igual de eficiente sobrescribir el método ExecuteCore de la clase Controller, y nos beneficiaríamos de los métodos y propiedades implementados en la misma.

Diminutamente crossposteado desde: Controladores diminutos @ Variable not found.

Server.Transfer en ASP.NET MVC

ASP.NET MVC ofrece “de serie” mecanismos para transferir el control desde una acción a otra utilizando para ello redirecciones HTTP. Esto significa que cuando un cliente realiza una petición y el método de acción desea que ésta sea procesada desde otra acción, el navegador será informado de la URL a la que debe dirigirse mediante una respuesta de tipo 302 para que, tras recibirla, realice una nueva solicitud que finalmente ejecutará la lógica apropiada.

En el diagrama se muestra el proceso completo de transferencia desde una petición a /home/mail hacia /mail/index utilizando la ruta por defecto, y muestro una posible implementación en ASP.NET MVC a continuación:

Redirección basada en retornar un HTTP 302

Y el código:

public class HomeController : Controller

{

    [...]

    // GET /Home/Mail

    public ActionResult Mail()

    {

        return RedirectToAction("index", "mail");

    }

 

}

 

public class MailController : Controller

{

    [...]

    // GET /Mail

    public ActionResult Index()

    {

        return View();

    }

 

}

Aunque es la forma habitual de realizar redirecciones en ASP.NET MVC, hace unos días estaba pensando que hay ocasiones en las tanta petición puede resultar pesada y vendría bien disponer de algo parecido al Server.Transfer, presente desde que peleábamos con ASP clásico, que era capaz de transferir la ejecución a otro punto sin necesidad de que el cliente replanteara la petición.

Y no es que me parezca una práctica especialmente recomendable, puesto que hay muchas cuestiones de ese tipo que pueden resolverse en MVC a nivel configuración de rutas o simplemente replanteando la estructura de direcciones del sitio, pero la verdad es que cuando te pica el gusanillo…

Intento erróneo #1: invocar directamente una acción desde otra

Bueno, en realidad este intento no llegué a hacerlo, pero se trata de un error bastante frecuente cuando se empieza con ASP.NET MVC framework, y me ha parecido interesante reflejarlo aquí como aviso a navegantes:

public class HomeController : Controller

{

    // GET /Home/Mail

    public ActionResult Mail()

    {

        return new MailController().Index(); // <- Mal

    }

 

}

 

Aunque a primera vista parece tener sentido, y de hecho funcionará muchas veces, se trata de una mala práctica y puede causarnos muchos problemas, y sobre todo, como en el ejemplo anterior, si se trata de llamadas de acciones de distintos controladores. Hay que tener en cuenta que en el contexto de petición estará la información de la petición inicial (/home/mail), no el que podría esperarse desde el método de acción new MailController().Index().

También es peligroso porque al invocarlo directamente estaríamos saltándonos todos los filtros que hubiéramos podido establecer mediante atributos en la acción final, MailController.Index(). Imaginad, por ejemplo, que la acción está protegida por un [Authorize]… :-O

Intento erróneo #2: invocar a Server.Transfer en el controlador

El segundo intento fue, pues eso, invocar directamente a Server.Transfer() desde el cuerpo de una acción. Pensaba que en cualquier caso no era una buena idea puesto que el método de acción que lo invocase sería difícilmente comprobable usando pruebas unitarias, pero bueno, estaba dispuesto a sacrificar esta ventaja:

public class HomeController : Controller

{

    // GET /Home/Mail

    public ActionResult Mail()

    {

        Server.Transfer(Url.Action("Index", "Mail")); // Mal

        return new EmptyResult();                     // En realidad podría devolver

    }                                                 // un nulo, o cualquier otra cosa

}

Al ejecutar el proyecto, el resultado obtenido es una bonita excepción “Error al ejecutar la solicitud secundaria” al intentar procesar la transferencia hacia la dirección “/Mail” (generada por el helper Url.Action()).

Primer acercamiento a la solución correcta: default.aspx

Aparentemente, la solución para transferir el control hacia otra acción pasa por recorrer el ciclo de vida completo de la petición, pero sin necesidad de que el navegador vuelva a hacerla. Ahora la cuestión es cómo conseguir esto armando el menor estropicio posible.

Si os fijáis, al crear el proyecto ASP.NET MVC, en la plantilla se habrá incluido un archivo en la carpeta raíz del mismo, llamado default.aspx. Su objetivo es transferir la ejecución al framework MVC si un usuario realiza una petición al raíz del sitio, siendo esta página el documento por defecto.

Observemos el código por defecto del método Page_Load que encontramos en su code-behind:

public void Page_Load(object sender, System.EventArgs e)

{

    // Change the current path so that the Routing handler can correctly interpret

    // the request, then restore the original path so that the OutputCache module

    // can correctly process the response (if caching is enabled).

 

    string originalPath = Request.Path;

    HttpContext.Current.RewritePath(Request.ApplicationPath, false);

    IHttpHandler httpHandler = new MvcHttpHandler();

    httpHandler.ProcessRequest(HttpContext.Current);

    HttpContext.Current.RewritePath(originalPath, false);

}

Como indican los comentarios, lo que se hace es cambiar la ruta original de la petición, instanciar un nuevo manejador MVC (MvcHttpHandler) y hacer que éste se encargue de procesarla… o en otras palabras, estamos transfiriendo la ejecución, justo lo que queríamos conseguir. Después, para evitar daños colaterales, se vuelve a dejar la ruta de la petición como estaba.

Por tanto, podríamos utilizar este mismo código y crear un método de extensión sobre la clase Controller:

public static class ControllerExtensions

{

    public static void Transfer(this Controller controller, string url)

    {

        string originalPath = controller.Request.Path;

        HttpContext.Current.RewritePath(url, false);

        IHttpHandler httpHandler = new MvcHttpHandler();

        httpHandler.ProcessRequest(HttpContext.Current);

        HttpContext.Current.RewritePath(originalPath, false);

    }

}

Así, podríamos utilizarlo desde nuestras acciones de la siguiente forma:

public class HomeController : Controller

{

    [...]

 

    // GET /Home/Mail

    public ActionResult Mail()

    {

        this.Transfer(Url.Action("index", "mail"));

        return null;

    }

}

Con esto ya tenemos una primera solución a nuestro problema. Sin embargo, aunque puede parecer muy limpio, y de hecho yo estaba bastante conforme con esta solución, este código tiene varios problemas.

En primer lugar, realizar pruebas unitarias sobre la acción Mail sería realmente complicado. Además, la invocación a Transfer() ejecutaría la acción asociada a la ruta de la URL indicada, pero al finalizar continuaría ejecutándose el método Mail(), desde donde la hemos invocado. Esto puede provocar efectos curiosos, si por ejemplo retornásemos un ViewResult o cualquier otro tipo de contenido desde la misma (serían enviados de forma consecutiva el cliente).

Solución final: crear un ActionResult personalizado

Aunque el enfoque anterior es correcto y podría ser válido a pesar de sus inconvenientes, encuentro una solución más fina en una pregunta de StackOverflow, How to simulate Server.Transfer in ASP.NET MVC: crear un resultado de acción (ActionResult) personalizado.

/// <summary>

/// Transfers execution to the supplied url.

/// </summary>

public class TransferResult : RedirectResult

{    

    public TransferResult(string url): base(url)    {    }

   

    public override void ExecuteResult(ControllerContext context)    

    {        

        var httpContext = HttpContext.Current;        

        httpContext.RewritePath(Url, false);        

        IHttpHandler httpHandler = new MvcHttpHandler();        

        httpHandler.ProcessRequest(HttpContext.Current);    

    }

}

De esta forma, evitamos los dos problemas de la anterior aproximación. El método de acción seguiría siendo fácilmente testeable, y evitaríamos resultados indeseados al tratarse de un tipo de respuesta, que evita la posibilidad de introducir código adicional.

Alegremente crossposteado desde: Server.Transfer en ASP.NET MVC@Variable not found.

Top posts 2009 en Variable Not Found

Ya iba siendo hora de estrenarme este 2010 y,image como ordena la tradición, el primer post del nuevo año lo reservo para comentar las entradas de Variable not found que han sido más populares en a lo largo del año que acabamos de cerrar.

Esta vez voy a introducir una novedad en el análisis, separando los posts que han sido escritos durante este año de otros, creados años anteriores y que todavía gozan de gran popularidad en la red.

Cosecha de 2009

Encabezando el top ten aparece “101 formas de saber que tu proyecto está condenado al fracaso”, una traducción autorizada del original 101 Ways To Know Your Software Project Is Doomed de Max Pool, que enumeraba, en clave de humor, una serie de pistas para saber si un proyecto se estaba precipitando hacia un estrepitoso fracaso.

Seguido de cerca tenemos “ASP.NET MVC: trece preguntas básicas”, un pequeño resumen de cuestiones realizadas muy frecuentemente tras el primer encuentro con el framework, como qué es MVC, qué ventajas tiene sobre Webforms, o si vale la pena pasarse a esta nueva forma de desarrollar aplicaciones web.

En tercer lugar, “Y todavía otras 101 citas célebres del mundo de la informática”, la tercera entrega de un clásico de este blog, con otras ciento una frases recogidas sobre este mundillo. En total, 303 frases, no está mal.

A continuación, el post “jqGrid: Grids espectaculares para ASP.NET MVC, paso a paso” ha demostrado el interés que despierta el uso de este plugin para mostrar y editar datos tabulados en el framework ASP.NET MVC.

En quinta posición tenemos la serie de tres posts “C#: Desmitificando las expresiones lambda” con la que pretendí quitar un poco de misterio a las expresiones lambda, esa fascinante y temida característica de la última hornada de nuestro lenguaje favorito.

En el post “Aspectos a tener en cuenta al crear sitios web públicos” recopilé las opiniones de un hilo de StackOverflow en el que se debatía sobre aquellos aspectos que teníamos que considerar, desde el punto de vista técnico, para crear webs destinadas al público en general, clasificadas por categorías como experiencia de usuario, seguridad, rendimiento, SEO, etc.

En séptima posición, “Plantillas de proyectos ASP.NET MVC 1.0 para NUnit”, donde se describían los pasos para instalar NUnit como framework de pruebas unitarias en proyecto ASP.NET MVC.

La octava posición es para “Indicios de que tu interfaz de usuario fue creado por un programador”, que comentaba y ampliaba el post de Ian Voyce “The 7 signs your UI was created by a programmer” sobre los rastros que dejamos los desarrolladores cuando queremos hacer de diseñador.

En penúltima posición, “Generación de PDF desde .NET usando formularios”, donde describía un truco muy rápido para generar documentos PDF al vuelo, utilizando formularios en el documento y la librería iTextSharp para rellenar su contenido.

En décima posición, “Programadores con producción neta negativa (NNPP)”, post donde comentaba el curioso fenómeno de la existencia de desarrolladores cuyo saldo en las aportaciones a un proyecto resultara negativo, o sea, que el valor de su producción fuera superado por el coste de los errores y defectos que introducían en las aplicaciones.

Cosechas anteriores

En este apartado encontramos los posts que podríamos considerar clásicos en este blog, pero dado que muchos de los lectores acaban de llegar, no está de más comentar su existencia.
Las dos primeras posiciones, un año más como líderes indiscutibles, citados, copiados y pegados hasta la saciedad, “Otras 101 citas célebres del mundo de la informática” y “101 citas célebres del mundo de la informática”.

En tercera posición, “Bordes redondeados en webs (sin esfuerzo) con Nifty Corners Cube”, un post de 2007 donde comentaba el uso de esta librería de scrips para redondear las esquinas de elementos de bloque en páginas web.

Le sigue otra creación de 2007, “Evitar el postback al pulsar un botón en ASP.Net”, donde comentaba distintas formas de hacer que no se lanzara un postback al pulsar un botón incluido en una página ASP.NET, utilizando, por ejemplo, para evitar envíos múltiples de un formulario.

Otro de los grandes clásicos, en “13 Consejos para comentar tu código” comentaba una serie de prácticas destinadas a facilitar la legibilidad en el código fuente.

La sexta posición la ocupan las “32 técnicas de producción de ideas”, un resumen de un post de Neuronilla sobre técnicas destinadas a favorecer la creatividad.

El post “Llamar a servicios web con ASP.NET AJAX paso a paso”, donde se describe detalladamente cómo utilizar ASP.NET AJAX para obtener datos desde un servicio web.

En octava posición, curiosamente, un post sobre el “Escaneo de puertos con idle scan”, una ingeniosa técnica para detectar puertos abiertos en máquinas remotas utilizando un equipo zombie que oculta la identidad del atacante.

“20 desastres famosos relacionados con el software” traducción autorizada por Timm Martin de su serie “20 Famous Software Disasters”, que recogía un buen número de casos en los que fallos relacionados con el software habían tenido consecuencias trágicas.

Por último, “Enviar mensajes con imágenes incrustadas desde .NET”, de la cosecha de 2008, donde se mostraba cómo incrustar archivos de imagen en un mensaje de correo electrónico generado desde una aplicación .NET.

¡¡Feliz 2010!!

Publicado en: Variable not found.