Anthem.Net, abandonado a su suerte
IMPORTANTE: Este artículo fue actualizado el 18 de noviembre de 2009. Ver nota al final del mismo. Gracias.
Los desarrolladores en la plataforma web Asp.Net conocemos las debilidades de la aproximación de Microsoft al mundo Ajax. Funcionamiento regular tirando a malo y, sobre todo, archivos Javascript enormes a descargar por el cliente, son un serio problema para la usabilidad de nuestras páginas, donde, a pesar de la extensión de las conexiones de alta velocidad, el tamaño y tiempo de la carga sigue siendo un factor muy importante.
Por fortuna, los admiradores de .Net no teníamos por qué conformarnos con el añadido oficial de Microsoft para tecnología Ajax, llamado con el original nombre (¿a que nunca lo habríais adivinado?
) ¡Asp.Net Ajax! Como digo, por suerte existían varias alternativas, de las cuáles tal vez una de las mejores fuese Anthem.Net. Para mi gusto, la mejor virtud de esta sencilla plataforma era el relativamente pequeño tamaño de los archivos Javascript, los cuáles proporcionaban una experiencia aceptable al usuario. Por esa razón los empleé para algunas partes del CMS propio que tengo desarrollado.
Pero se acabó lo que se daba. Desde hace más de un año, los desarrolladores de Anthem.Net no dan señales de vida. El proyecto ha sido abandonado a su suerte. Una empresa privada ha cogido el testigo pero no parecen avanzar nada con el código y la sección de descarga no funciona nunca. Los foros oficiales ya no existen y el dominio oficial ha expirado, mostrando una "bonita" página de parking. Sólo quedan en pie las páginas de proyecto y descarga en SourceForge así como los foros de este último sitio, donde nadie parece saber nada y donde los desarrolladores hace muchos meses que no contestan.Por fortuna, logré parchear yo mismo un error que daba con las url amigables y el módulo Rewrite.
En este momento estoy usando compresión Gzip de servirdor en mis páginas para mejorar los tiempos de carga (maravilla casi imprescindible). Pues bien, Anthem no se lleva muy bien con esta compresión. Tras varias horas tratando de parchear el código (ventajas del código abierto), me rindo. En un negocio como la web donde cada minuto cuenta, no puedes estar perdiendo días en reinventar la rueda. Aparte, honestamente, aun dedicando horas, no tengo garantizado que consiguiera parchear ese código que no comencé y que tendría que analizar para ver cómo funciona. Solución práctica, abandonar el barco como hicieran sus capitanes antes de que se hunda. A la velocidad que avanza la web, no es prudente mantener un componente que sabes que muy probablemente nunca más actualizarán sus autores.
Una de dos, o tendré que dedicar varias horas (o días
) a probar otros marcos de trabajo Ajax para .Net o tendré que usar el marco oficial. Quien sabe, lo mismo en todo este tiempo, conlas últimas actualizaciones de .Net ha mejorado algo...
En fin, una lástima. Descanse en paz, Anthem.Net.
Actualización 18 de noviembre de 2009. Tras unas semanas de pruebas con el marco de trabajo oficial Ajax para Asp.Net de Microsoft como sustituto de Anthem, vuelvo a llegar a las mismas conclusiones. Sigue siendo inviable para cualquier web que pretenda ser ligera, un requisito prácticamente imprescindible si pretendemos destacar sobre nuestra competencia y, sobre todo, dar el mejor servicio posible a nuestro usuario. Por todo ello, y para no renunciar a la maravillosa compresión gZip, estuve un par de días tratando de parchear el problema. Al final lo conseguí tras no poco esfuerzo. El beneficio obtenido no puede ser mayor. He logrado poder utilizar Anthem y beneficiarme (y mis usuarios) de su velocidad y el tamaño diminuto de su Javascript. A continuación publico la función parcheada para quien le pueda ser de utilidad. Importante: el parche está aplicado a la versión 1.4.0 ya que la 1.5.2, que es la última que se publicó hasta la fecha no me funcionaba bien para mis propósitos. Por otro lado, este parche invalida la compatibilidad con Asp.Net 1.1 ya que utilicé librerías que aún no existían en esta versión. Esto no creo que sea muy problemático ya que las versiones 2.0 y posteriores son mucho más completas y casi compatibles con 1.1, por lo que se puede migrar cualquier aplicación antigua sin modificar (casi) nada y es muy recomendable. El archivo es Manager.cs.
internal void WriteResult(Stream stream, MemoryStream htmlBuffer)
{
// Gzip compression fix (sep 28, 2009)
HttpContext.Current.Response.Headers.Remove("Content-Encoding");
HttpContext.Current.Response.Headers.Remove("Vary");
HttpContext.Current.Response.Headers.Remove("Connection");
string viewState = null;
string viewStateEncrypted = null;
string eventValidation = null;
Hashtable controls = null;
string[] scripts = null;
if (_updatePage)
{
string html = HttpContext.Current.Response.ContentEncoding.GetString(htmlBuffer.GetBuffer());
byte[] html2 = (htmlBuffer.GetBuffer());
MemoryStream miStream = new MemoryStream(html2);
bool isGzippedPage = false;
// The header of a Gzip file or stream (its "magic numbers") is allways hexadecimal ox1F ox8B
if (html2[0].ToString("X") == "1F" && html2[1].ToString("X") == "8B")
isGzippedPage = true;
/* If the page is gZipped, we must decompress it. Othewise, the program will
* noy be able to recoved de data sent during the callback
* */
if (isGzippedPage)
{
string htmlGzipped = "";
const int bufferSize = 4096;
byte[] gzipBuffer = new byte[bufferSize];
int count = 0;
GZipStream gzippedPage = new GZipStream(miStream, CompressionMode.Decompress, true);
while (true)
{
count = gzippedPage.Read(gzipBuffer, 0, bufferSize);
if (count != 0)
{
foreach (byte gzipBufferElement in gzipBuffer)
{
htmlGzipped += Convert.ToChar(gzipBufferElement);
}
}
if (count != bufferSize)
{
// have reached the end
break;
}
}
gzippedPage.Close();
System.Text.Encoding.ASCII.GetBytes(htmlGzipped);
html = htmlGzipped;
}
viewState = GetViewState(html);
//viewState = html;
//viewState = htmlGzipped;
//viewState = htmlNoUnicode;
#if V2
viewStateEncrypted = GetViewStateEncrypted(html);
eventValidation = GetEventValidation(html);
#endif
controls = GetControls(html);
foreach (object o in _targets.Values)
{
Control c = o as Control;
if (c != null && !c.Visible)
{
if (c.ID != null && controls.ContainsKey(c.ID))
controls[c.ID] = "";
}
}
scripts = GetScripts(html);
}
StringBuilder sb = new StringBuilder();
try
{
WriteValueAndError(sb, _value, _error, viewState, viewStateEncrypted, eventValidation, controls, scripts);
}
catch (Exception ex)
{
// If an exception was thrown while formatting the
// result value, we need to discard whatever was
// written and start over with nothing but the error
// message.
sb.Length = 0;
WriteValueAndError(sb, null, ex.Message, null, null, null, null, null);
}
byte[] buffer = HttpContext.Current.Response.ContentEncoding.GetBytes(sb.ToString());
stream.Write(buffer, 0, buffer.Length);
}

