Rewriting en .NET (III)
Tercera entrega del tema, dedicada esta vez a las expresiones regulares y a los problemas más frecuentes con los que os podéis encontrar durante el desarrollo.
El extraño mundo de las expresiones regulares
Las clases regex y Match son el corazón del rewriting. Ambas se encuentran en el NameSpace “System.Text.RegularExpressions”.
A grandes rasgos, la idea general de las expresiones regulares es lograr encontrar patrones en cadenas, es decir, dentro de una cadena, la que sea, poder encontrar la coincidencia en la misma de otra subcadena, sin que tengamos que escribir literalmente esa subcadena, sino dar un formato genérico, que cualquier subcadena que lo cumpla, sea reconocida.
Podéis encontrar un buen manual sobre expresiones regulares donde la primera parte es general y la segunda más orientada a otros entornos, no solo a .NET, en :
http://bulma.net/body.phtml?nIdNoticia=770&nIdPage=2
El punto más importante es tener en cuenta que en caso de encontrar el patrón de la expresión, en caso de substitución, ésta se realizará de TODA la cadena que coincida con el mismo.
Vamos a ver las expresiones básicas sobre una regla de reescritura de nuestro ejemplo.
Regla:
Url de entrada:
(ESesFRfrcatCAT)\S+(ProductoLocal/)([0-9]+)/+\w+\.\w+$
Url de salida, que reescribiremos
Productos.aspx?IdProducto=$3
Si descomponemos la expresión en partes tendremos lo siguiente:
(ESesFRfrcatCAT)
Agrupación () paréntesis:
(ESesFRfrcatCAT) Utilizado para formar grupo, que se comportan como tal, tanto a efectos de comprobación, como para ser sustituidos por un parámetro. A efectos de parámetros, siempre manda el orden en que se formen los grupos; en este caso, si usásemos en la reescritura el parámetro $1, correspondería a este grupo.
barra vertical:
Componente OR; una opción u otra.
En nuestro ejemplo, equivaldría a una expresión lógica como à ES OR es OR FR …
Cadenas
\S+ Cualquier cadena una o más veces.
Literales
(ProductoLocal/) Dentro de nuestra expresión, podemos escribir literales exactos. En nuestro ejemplo, forzamos a que dentro del patrón de búsqueda, exista exactamente “ProductoLocal/”
Rangos [] Corchetes
([0-9]+)/ Para establecer, rangos de números o letras. En este caso, cualquier número entre 0 y 9 repetido una o más veces.
+\w+\.\w+ cualquier carácter alfanumérico, seguido de un “.”, seguido de cualquier carácter alfanumérico.
Coincidencia desde el final de la cadena
$ Forzamos a que la coincidencia se busque desde el final de la cadena a la hora de compararlo con el patrón.
Por último la reescritura que se hará en caso de coincidencia (“match”) en la expresión, será:
Productos.aspx?IdProducto=$3, donde $3 corresponde al parámetro 3, como podéis comprobar el orden corresponde al grupo ([0-9]+), que será el Id del producto en nuestro caso.
Por lo tanto, una petición de página como ésta:
http://www.midominio.com/ES/ProductoLocal/1/script.html
Crearía la reescritura siguiente: http://www.midominio.com/Productos.aspx?IdProducto=1
Donde la cadena coincidente es: “ES/ProductoLocal/1/script.html”
Como resumen de caracteres especiales en la generación de expresiones regulares, tenéis:
[ ] à CorchetesàPara rangos
() àParéntesisàPara formar grupos
+ à Aparición de la subcadena 1 o más veces
* à Asteriscoà Aparición de la subcadena 0 o más veces
\ àHace que no se considere un comando lo que le sigue.
^ à Indica que la coincidencia debe ser desde el inicio de la cadena (línea)
$ à Indica que la coincidencia debe darse desde el final de la cadena (línea)
à Barra verticalàOperador OR
\w à Indica palabra (alfanuméricos y _)
\W à Opuesto al anterior
\s à Coincidencia con los caracteres los espacios y otros caracteres en blanco
\S à Opuesto al anterior
2. Problemas durante el desarrollo
El famoso problema del PostBack
Básicamente el problema consiste que cuando hacemos un postback en la página a la que hemos aplicado el Rewriting, al hacer el render del objeto Htmlform, en la misma etiqueta form aparece el atributo action, con la página real, física, a la que llamamos, no con la página virtual que simulamos abrir.
Por ejemplo si nuestra página con rewrite es http://dominio/producto_x.html y nuestra página real a la que redirigimos es http://dominio/productos.aspx?IdProducto=x al hacer el render en el servidor de la página, el tag form sería algo como <form method=”post” action= productos.aspx>. Esto conlleva problemas con la captura de eventos de los controles, también que en la barra de navegación del navegador, aparezca la url real a la que hemos accedido.
El problema es que no se puede acceder a este atributo en tiempo de ejecución ya que es de solo lectura lo que hace que haya que buscar otro tipo de solución.
Hay diversos planteamientos para solucionarlo, entre ellos el uso de script sobrescribiendo el atributo en cuestión. En mis proyectos yo he optado, después de sudar mucho y cruzar los dedos, con buenos resultados hasta el momento, por crear una clase que herede de HtmlForm y en ella modificamos el atributo Action. La clase en cuestión con el método que nos interesa para modificarlo quedaría así:
<toolboxdata("<{0}:frm runat="server">")> _
Public Class FormRewrite
Inherits System.Web.UI.HtmlControls.HtmlForm
Implements System.Web.UI.Design.IControlDesignerTag
'Protected Overrides Sub Render(ByVal output As System.Web.UI.HtmlTextWriter)
' If Not Me.DesignMode Then
' MyBase.Render(output)
' End If
'End Sub
Protected Overrides Sub RenderAttributes(ByVal writer As System.Web.UI.HtmlTextWriter)
Attributes.Add("enctype", Enctype)
Attributes.Add("id", ClientID)
Attributes.Add("method", Method)
Attributes.Add("name", Name)
Attributes.Add("target", Target)
Attributes.Render(writer)
MyBase.Attributes.Remove("action")
End Sub
A partir de este momento, en los formularios que usaremos, sustituiremos el tag form de los mismos por nuestra clase
<formrewrite:formrewrite id="form1" method="post" runat="server">
<form id="form1" method="post" runat="server">
El registro del mismo lo podemos hacer en la misma página en la que lo usaremos o bien en el fichero Web.config con efectos para todas las páginas
En el primer caso
<%@ Register TagPrefix ="FormRewrite" Assembly ="App_Code" Namespace="BBWebforms"%>
En el segundo:
<!--<registerTagPrefixes>
<add tagPrefix="FormRewrite"
namespace="BBWebForms"
assembly="App_Code" />
</registerTagPrefixes>-->
Con esto tendremos solventado el problema del postBack en el rewrite, pero aún queda un escollo más por salvar; actualemente en la versión del VS 2005 parece existir un bug que no permite tags forms personalizados.
El error que nos ocupa es el siguiente cuando intentéis entrar en el diseñador con un tag Form propio heredado de HtmlForm:
Unable to cast object of type 'System.Web.UI.Design.HtmlIntrinsicControlDesigner' to type 'System.Web.UI.Design.ControlDesigner'
Y habréis perdido la visión de todos los controles de la página bajo este feo error.
Habitualmente trabajo con un diseñador, este bug, me obliga a cambiar el tag al original <form> cuando quiero dejar que yo mismo o el diseñador accedamos al html para retocar algo. Demos las gracias por las masterpages que hacen que esto sólo lo tenga que hacer para unas pocas páginas, si no, lo hubiera desechado. En cualquier caso, considero lo comentado un buen y eficiente sistema para solventar el problema del postback y un bug del diseñador de VS no debería hacer que lo rechazarmos sin darle antes unas cuantas vueltas a los pros y contras.
Peticiones de ficheros desde las páginas (css, js, gif, jpg, etc.)
Algo a tener en cuanta es que nuestra tarea de reescritura no se acaba con las peticiones a páginas. Hay que tener en cuenta cada petición que nos llega al servidor de los distintos tipos de ficheros habituales en cada una de nuestras páginas; desde las hojas de estilo, hasta los ficheros flash, pasando por scripts, hojas de estilo, etc. Todos y cada uno de ellos deben pasar por todas las fases que hemos comentado. Desde añadir las extensiones al servidor p ara su paso al motor de ASP para su proceso, hasta escribir las expresiones regulares para la captura y reescritura de las mismas.
Normalmente me basta añadir una expresión regular a mi Web.config para esos ficheros específicos por cada grupo de páginas de las que tengo ya preparada una regla. Por ejemplo:
Dos reglas para distintos de pagina, en el caso, dinamicas1 y dinamicas2
<regla>
<url>(ESesFRfrcatCAT)\S+(dinamica1/)([0-9]+)/+\w+\.\w+$</url>
<rewrite>productos.aspx?IdProducto=$3</rewrite>
</regla>
<regla>
<url>(ESesFRfrcatCAT)\S+(dinamica2/)([0-9]+)/+\w+\.\w+$</url>
<rewrite>categorias.aspx?IdCategoria=$3</rewrite>
</regla>
Mi regla para todas las peticiones de ficheros para esas mismas paginas
<regla>
<url>(ESesFRfrcatCAT)\S+(dinamica[0-9]+/)+([0-9]+)/(\S+\.(jsJSswfSWFjpgJPGgifGIFcssCSSSKINskin))</url>
<rewrite>$4</rewrite>
</regla>


0 comentarios:
Publicar un comentario en la entrada