Hace algunos años, cuando recién empezaba a trabajar en migraciones a ASP.NET Core, tomé un proyecto que parecía sencillo: mover un puñado de páginas WebForms a la nueva plataforma, una por una, sin apuro. Abrí el code-behind de la primera página, vi una grilla de datos, un par de botones, algunos desplegables, y pensé: esto es un fin de semana, dos como mucho. Me equivoqué por casi tres semanas, y toda esa diferencia tuvo una sola causa: ViewState.
Había calculado tiempo para reconstruir el markup, aplicar nuevos estilos, quizás destrabar algún event handler viejo. No había calculado que el comportamiento real de la página —qué pasaba con todos esos datos entre un clic y el siguiente— dependía de un mecanismo que simplemente no existe en ninguna parte del .NET moderno. Cada supuesto que tenía sobre "portar" la página se derrumbó en cuanto miré más allá del HTML y entendí cómo se comportaba la página entre solicitudes.
Esta es la parte que casi nadie tiene en cuenta cuando pide un presupuesto rápido para actualizar WebForms. Vale la pena explicarla bien, porque una vez que se entiende, se entiende también por qué estas migraciones son reconstrucciones, no conversiones.
Qué hacía realmente ViewState
En el WebForms clásico, cada control de una página —una caja de texto, un desplegable, una grilla con su orden y su fila seleccionada— llevaba su propio estado de forma silenciosa a través de los postbacks. No escribías código para recordar que un usuario había tecleado algo en un campo, o que una grilla estaba ordenada por fecha y no por nombre. El framework lo hacía por vos: serializaba el estado de todo el árbol de controles en un campo oculto, __VIEWSTATE, lo enviaba junto con la página, y lo volvía a leer en la siguiente solicitud para restaurar todo exactamente como estaba.
En su momento parecía magia, y para cierto tipo de aplicación intranet cargada de formularios, lo era de verdad: productiva. Armaba una página, colocaba controles, conectaba un par de manejadores de eventos, y el framework se encargaba de la plomería de qué había hecho ya el usuario.
Por qué ese modelo no tiene lugar en ASP.NET Core
ASP.NET Core —ya sea en MVC, Razor Pages o Blazor Server— no tiene un ciclo de vida de página en el sentido de WebForms. No hay una secuencia de Init, Load, PreRender corriendo detrás de escena. No hay controles de servidor que lleven su propio estado entre solicitudes. Cada solicitud se maneja por sí misma: el framework no recuerda qué tenía seleccionado un desplegable a menos que algo se lo indique explícitamente.
Esto no es una funcionalidad faltante que haya que agregar de nuevo. Es un modelo completamente distinto, y deliberado: ASP.NET Core está construido para que las solicitudes sean, por defecto, sin estado, porque eso es lo que escala, lo que se puede testear y lo que funciona de forma limpia en una granja de servidores con balanceo de carga, sin necesidad de sesiones pegajosas. Microsoft no ofrece un equivalente de ViewState en el .NET moderno porque toda la arquitectura está pensada para no necesitarlo.
Qué lo reemplaza, en concepto
El reemplazo no es una sola funcionalidad: es una forma de pensar distinta, manejo de estado explícito en lugar de implícito. Vos decidís, para cada porción de estado, dónde tiene que vivir realmente:
- Los view models llevan lo que la solicitud actual necesita para renderizar la página, armados de cero cada vez.
- Los campos de formulario y query strings llevan lo que necesita sobrevivir exactamente un viaje de ida y vuelta, el mismo trabajo que hacían los campos ocultos en WebForms, solo que ahora declarado a propósito en vez de generado automáticamente.
- La sesión o una base de datos lleva lo que necesita sobrevivir entre varias páginas o solicitudes: un carrito de compras, un asistente de varios pasos, los filtros que un usuario fue armando.
- El estado del lado del cliente (algo de JavaScript, almacenamiento local) lleva lo que es puramente una comodidad de interfaz y nunca necesitó tocar el servidor.
Nada de esto es más difícil que ViewState, una vez que se lo ve con claridad. De hecho es más simple de razonar, porque no pasa nada de forma invisible. Pero sí implica que alguien tiene que mirar la página vieja y decidir, de forma deliberada, qué era en realidad cada comportamiento "recordado", y dónde vive ahora.
Por qué esta es la razón real por la que los presupuestos rápidos se complican
Desde afuera, una página WebForms parece portable: una tabla acá, un par de botones allá, un filtro desplegable. Es fácil presupuestar reconstruir esta interfaz y seguir adelante. La sorpresa aparece cuando se abre el code-behind y se descubre que buena parte de lo que parece lógica de negocio es en realidad lógica de plomería de estado: código que solo existe porque ViewState hacía posible cierto tipo de interacción. Una grilla que recuerda su fila seleccionada después de un postback, un asistente que mantiene su paso actual entre cargas de página, un panel de filtros que funciona cuando se vuelve a la página: todo eso dependía de ViewState, de forma invisible, y todo eso necesita un nuevo hogar, explícito.
Esa es la diferencia entre "convertir" y "reconstruir". No se puede traducir línea por línea un comportamiento que depende de ViewState a ASP.NET Core, porque no hay destino para una traducción literal. La interfaz tiene que reconstruirse alrededor de un modelo de estado distinto, incluso cuando el resultado final se vea y se comporte de forma idéntica para el usuario.
Una forma práctica de abordarlo
Cuando audito una página WebForms antes de una migración, no empiezo preguntando cómo se ve la página. Pregunto qué recuerda entre solicitudes, y por qué. Para cada control que parece retener algo —una fila seleccionada, un valor tecleado, un filtro, un paso dentro de un flujo— anoto su tiempo de vida (¿una sola solicitud? ¿toda la sesión? ¿para siempre, en una base de datos?) y si es estado esencial del negocio o solo una comodidad de interfaz. Solo una vez que existe esa lista empiezo a decidir el equivalente moderno de cada elemento.
Ese solo ejercicio convierte un presupuesto basado en adivinar en uno basado en un plan. Y suele ser también el momento en que un presupuesto de "migración de WebForms" deja de ser un número sacado del aire y pasa a ser un número que alguien puede defender.
Qué significa esto para su cronograma
Nada de esto quiere decir que actualizar WebForms sea una mala idea o algo imposible: quiere decir que es un tipo de proyecto distinto al de apuntar el framework nuevo a las páginas viejas. La lógica de negocio de abajo suele ser perfectamente recuperable; lo que tiene que cambiar es el modelo de estado que la envuelve. Saberlo desde el principio es la diferencia entre tener un plan y encontrarse con una sorpresa a las tres semanas de trabajo.