Apariencia
Formularios, concurrencia y opciones dependientes
Índice · Motivación · Anterior: diagnósticos · Siguiente: ejemplo completo
Un plan contiene las reglas reutilizables. Una sesión representa la actividad de un formulario: qué edición está vigente, si hay una operación pendiente y qué estado puede publicarse. Crea una sesión por formulario y dispónla cuando termine su vida útil.
Problema que resuelve
Mientras una persona escribe pueden coexistir varias evaluaciones. Una consulta antigua puede terminar después de una nueva y reemplazar un resultado vigente; además, consultar en cada pulsación desperdicia trabajo. La sesión incorpora debounce, cancelación y versiones sin convertir el plan reutilizable en un objeto con estado de UI.
Usa una sesión durante una interacción prolongada. Para validar una petición única o autorizar el guardado definitivo, ejecuta el plan directamente sobre la instantánea que realmente se enviará.
Edición y envío
csharp
using var session = plan.CreateSession(debounce: TimeSpan.FromMilliseconds(250));
session.Changed += state =>
{
if (state.Report is { } report)
foreach (var error in report.ForField("Email", DiagnosticView.Summary))
Console.WriteLine(error.Message);
};
var editingResult = await session.UpdateWithResultAsync(candidateSnapshot, context);
var finalReport = await plan.ValidateAsync(candidateSnapshot, context);El fragmento supone un plan contextual y una instantánea estable del candidato. UpdateWithResultAsync aplica el debounce de la sesión: si llega una nueva edición durante la espera, la operación anterior se cancela. EvaluateAsync evalúa inmediatamente salvo que reciba un retraso explícito.
Para enviar, valida la instantánea que se va a guardar y comprueba finalReport.IsValid. Un estado visual anterior puede corresponder a otros datos. Tampoco una consulta de unicidad evita por sí sola carreras entre validación y persistencia; la escritura debe aplicar las garantías correspondientes del almacenamiento.
Estados
| Estado de formulario | Significado |
|---|---|
Idle | Todavía no se ha publicado una evaluación |
Pending | La operación está esperando el retraso o ejecutándose |
Valid | La evaluación terminó sin incumplimientos aplicables |
Invalid | Hay incumplimientos en el reporte |
Failed | La operación tuvo un fallo técnico; consultar Error |
Canceled | La operación fue cancelada |
Durante Pending se publica una instantánea sin reporte anterior. Un fallo técnico tampoco entrega un reporte satisfactorio.
Versiones y publicación
La sesión utiliza AsyncDataSession<ValidationReport>. Cada petición incrementa una versión y cancela la anterior. Antes de publicar, vuelve a comprobar que la versión siga vigente. Así una consulta lenta de una edición antigua no reemplaza el estado de una edición posterior.
La cancelación es cooperativa: el servicio debe atender el token para detener su trabajo. Aunque no lo haga inmediatamente, la comprobación de versión evita publicar resultados antiguos. Los callbacks de una versión obsoleta ya encolados en el dispatcher también se descartan.
La sesión captura SynchronizationContext.Current al crearse. Si el entorno no dispone de uno apropiado, proporciona dispatch: action => ... para ejecutar la acción en el hilo de UI. Sin dispatcher ni contexto, las notificaciones son directas. Los manejadores de Changed deben ser breves y síncronos.
Con un dispatcher, terminar el await significa que la evaluación acabó, pero la actualización visual puede seguir encolada. No presupongas que session.Snapshot ya contiene el resultado recién calculado.
Resultado propio de la operación
UpdateWithResultAsync y EvaluateAsync devuelven FormValidationOperationResult, con el mismo vocabulario de estados que el formulario.
csharp
var operation = await session.EvaluateAsync(candidateSnapshot, context);
if (operation.State == FormValidationState.Valid)
{
var report = operation.Report!;
Console.WriteLine(report.IsValid);
}IsCurrentAtCompletion indica si la operación seguía vigente en el instante de terminar. Es una observación histórica: puede comenzar otra edición justo después. Para actualizar controles, utiliza Changed, cuya publicación verifica nuevamente la versión.
Para formularios, EvaluateAsync y UpdateWithResultAsync devuelven FormValidationOperationResult. Este resultado usa FormValidationState y ofrece directamente Version, State, Report, Error e IsCurrentAtCompletion:
csharp
var operation = await session.UpdateWithResultAsync(candidateSnapshot, context);
if (operation.State == FormValidationState.Invalid)
foreach (var error in operation.Report!.ForField("Email", DiagnosticView.Summary))
Console.WriteLine(error.Message);ValidateWithResultAsync y UpdateAsync se mantienen para compatibilidad y continúan devolviendo DataOperationResult<ValidationReport>.
LatestEvaluation<T> es un coordinador de menor nivel que devuelve una versión y permite consultar IsCurrent(version). No proporciona el ciclo completo de estados y publicación de la sesión de formulario.
Opciones dependientes
Options.For<T, C>() define una fuente de opciones para un campo. La carga es independiente de las reglas que comprueban si la selección es válida.
csharp
var cities = Options.For<Address, AddressContext>()
.Field(x => x.CityId)
.Load<CityOption>((address, context, token) => context.LoadCitiesAsync(address.CountryId, token))
.DependsOn(x => x.CountryId);
using var optionsSession = cities.CreateSession();
var operation = await optionsSession.EnsureLoadedAsync(addressSnapshot, context);
if (operation.Snapshot.State == DataState.Ready)
Console.WriteLine(operation.Snapshot.Value!.Count);En el fragmento, AddressContext.LoadCitiesAsync devuelve ValueTask<IReadOnlyList<CityOption>>. El cambio de país cambia la entrada declarada, inicia una nueva carga y descarta la respuesta anterior si llega tarde.
DependsOn declara qué valores comparar; no se suscribe a eventos del modelo. La UI llama a la sesión cuando cambia el candidato. Declara todas las entradas que usa el loader y que pueden alterar su resultado. La comparación utiliza igualdad de valores para dependencias y EqualityComparer<C>.Default para contexto; modificar internamente el mismo objeto de contexto puede no detectarse.
Reutilización y recarga
| Método | Comportamiento |
|---|---|
EnsureLoadedAsync | Reutiliza una operación pendiente o terminada si entradas y contexto coinciden; también permite reutilización sin dependencias |
ReloadAsync | Fuerza una nueva consulta |
RefreshAsync | API de compatibilidad; sin dependencias recarga en cada llamada |
Sin dependencias, EnsureLoadedAsync trata el origen como un catálogo independiente del candidato. Si el loader usa CountryId, omitir DependsOn puede dejar datos obsoletos. Si cambian datos remotos sin cambiar entradas, usa ReloadAsync.
Los fallos y cancelaciones permiten reintentar. La lista recibida se copia a una colección de solo lectura, sin clonar cada opción. Una operación reutilizada devuelve el resultado de la operación original, incluida su marca histórica de vigencia.
Durante Pending no se conservan opciones anteriores. La sesión tampoco borra la selección, enlaza controles ni verifica que el elemento elegido exista. La UI decide cuándo reiniciar la selección, y las reglas validan esa selección por separado.
Fuentes: sesión de formulario, coordinación de datos y opciones.