Ready To Blazor

Bienvenido a Ready To Blazor, un starter-kit basado en Blazor Server que acelera la creación de aplicaciones empresariales conectadas a Dinaup. En este artículo encontrarás una introducción gradual—desde la pila de componentes hasta ejemplos de código listos para copiar—para que puedas publicar tu primera pantalla en minutos.
Conceptos
Ready To Blazor usa Radzen
Ready To Blazor adopta la colección de componentes Radzen Blazor para proporcionar :
DataGrid, Tabs, DialogService, notificaciones y decenas de controles preparados para producción.
Un look & feel consistente y personalizable mediante los temas de Radzen.
Integración directa con código C# (sin generadores externos).
DinaZen → puente entre Radzen y Dinaup
Para simplificar la comunicación con la API de Dinaup hemos publicado DinaZen: un paquete NuGet que añade helpers de extensión y componentes visuales que encajan como un guante con Radzen.
Basta con añadir la referencia y un @using DinaZen
para disponer de:
LoaderU: indicador de carga reactivo.
Extensiones como
Extensions.LoadReportDataAsync
para rellenar DataGrids en una línea.Conversores y validadores listos para usar.
Autenticación integrada (login / alta / recuperación)
Ready To Blazor trae el flujo de identidad completo:
Inicio de sesión
Pages/Sesion/LoginForm.razor
Inicia sesión y recuerda al usuario.
Alta de cuenta
LoginForm.razor
(pestaña Crear Cuenta)
Valida email, fuerza política de contraseña y envía correo de activación.
Recuperar contraseña
LoginForm.razor
(pestaña Recuperar Contraseña)
Envía enlace de recuperación y permite cambiarla con código.
Todo el flujo se apoya en los servicios CurrentUserService
y SMTPService
, comentados más abajo.
Configura tus credenciales SMTP
El envío de correos (activación y recuperación) requiere un servidor SMTP válido; esto se configura desde appsettings.json
.
Conecta tu instancia MyDinaup
Ready To Blazor asume que tu organización dispone de un entorno MyDinaup. Solo necesitas:
URL base de tu API Dinaup.
Un API Key / Secret con permisos de lectura y escritura.
Ejemplos
Archivo de Configuración
Ejemplo de archivo appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Dinaup": {
"APIBaseUrl": "https://api.dinaup.com/v2/****",
"APIKey": "****",
"APISecret": "********"
},
"Smtp": {
"Host": "smtp.resend.com",
"Port": 2587,
"EnableSsl": true,
"UserName": "resend",
"SenderName": "****",
"SenderEmail": "****",
"Password": "****"
}
}
Cargar Informe Manual
Antes de entrar en herramientas de más alto nivel, en Dinaup casi todo se basa en "Reports" o "Informes" APIPaisesC
es una clase de MyDinaup , es decir, no tendrás que programarla simplemente instanciarla.
Ejemplo de como cargar 100 países.
var rpt = new APIPaisesC();
await rpt.ExecuteQueryAsync(DinaupClient, 1, 100);
foreach (var row in rpt.Rows)
Console.WriteLine($"{row.TextoPrincipal} – {row.CodAlfabetico2}");
Ejemplo 1 – Carga directa en memoria
Este ejemplo muestra cómo cargar todos los registros de un informe de Dinaup de una sola vez al iniciar la página. Es una opción sencilla y rápida para conjuntos de datos pequeños o medianos que se consulten con poca frecuencia.
@page "/Ej1"
@if (Paises.IsNull())
{
<LoaderU />
}else{
<RadzenDataGrid PageSize="10" AllowPaging="true" [email protected]() Data=@Paises>
<Columns>
<RadzenDataGridColumn Property=@(nameof(APIPaisesC.APIPaises_RowC.TextoPrincipal)) Title="Nombre Pais" Width="200px" />
<RadzenDataGridColumn Property=@(nameof(APIPaisesC.APIPaises_RowC.CodAlfabetico2)) Title="Código Pais" Width="100px" />
</Columns>
</RadzenDataGrid>
}
@code {
private IEnumerable<APIPaisesC.APIPaises_RowC> Paises;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
var rpt = new APIPaisesC();
await rpt.ExecuteQueryAsync(DinaupClient, 1, 1000);
Paises = rpt.Rows;
}
}
Ejemplo 2 – Carga bajo demanda
En este caso, el RadzenDataGrid
se conecta al informe Dinaup usando el evento LoadData
. Solo se recuperan los datos necesarios para la página actual, y se aplican filtros y ordenaciones desde el servidor. Ideal para trabajar con grandes volúmenes de datos de forma eficiente.
@page "/Ej2"
@using Dinaup
@using static DemoUp.MyDinaup.Reports.FuncionalidadD.APISeccionDePruebasAPIC
<RadzenDataGrid Data=@ReportData?.Rows
Count=@(ReportData?.TotalResults ?? 0)
PageSize=@PageSize
AllowSorting=true
AllowPaging="true"
AllowFiltering="true"
[email protected]()
LoadData="@LoadData">
<Columns>
<RadzenDataGridColumn Property="@(nameof(APISeccionDePruebasAPI_RowC.TextoPrincipal))" Title="Nombre País" Width="200px" />
<RadzenDataGridColumn Property="@(nameof(APISeccionDePruebasAPI_RowC.FechaIA))" Title="Fecha" Width="100px" />
<RadzenDataGridColumn Property="@(nameof(APISeccionDePruebasAPI_RowC.Valorentero))" Title="Valorentero" Width="100px" />
<RadzenDataGridColumn Property="@(nameof(APISeccionDePruebasAPI_RowC.Textodeprueba))" Title="Textodeprueba" Width="100px" />
</Columns>
</RadzenDataGrid>
@code {
private int PageSize = 10;
private DemoUp.MyDinaup.Reports.FuncionalidadD.APISeccionDePruebasAPIC ReportData = new();
private RadzenDataGrid<APIPaisesC.APIPaises_RowC> grid;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
ReportData.RequestParams.ResultsPerPage = PageSize;
ReportData.RequestParams.CurrentPage = 1;
await LoadData(null);
}
private async Task LoadData(LoadDataArgs args)
{
await Extensions.LoadReportDataAsync(DinaupClient, CurrentUserService.User, ReportData, args);
}
}
Operaciones de Escritura
Las operaciones de escritura se realiza utilizando WriteOperation. Aquí un ejemplo mínimo.
// Crear una nueva operación de escritura (nuevo registro, por eso Guid.Empty)
var wOp = new WriteOperation(Guid.Empty);
// Agregar los datos al registro. Se puede añadir cualquier campo definido en la sección.
wOp.DataMainRow.Add(SeccionDePruebasAPIES.TextoPrincipal, "Prueba");
// Ejecutar la operación de escritura contra la sección deseada
var result = await DinaupClient.RunWriteOperationAsync(
CurrentUserService.User, // Sesión del usuario actual
SeccionDePruebasAPID._SectionIDGUID, // GUID de la sección
wOp,
false // false = escritura directa (sin ejecutar scripts ni recalcular)
);
// Validar que la operación se haya realizado correctamente
result.EnsureSuccess();
// Obtener el ID del nuevo registro insertado
var resultId = wOp.WriteOperationResult.RowID;
Console.WriteLine($"Registro creado con éxito. ID: {resultId}");
Ejemplo 3 - Agregado rápido
Aquí tienes un ejemplo mínimo y claro para ejecutar una WriteOperation
<RadzenTextBox @bind-Value=@editingText Style="width: 100%;" />
<RadzenButton Icon="add" Text="Guardar" Click=@AddTestData IsBusy=@addTestDataIsBusy ></RadzenButton>
@code {
string editingText;
private bool addTestDataIsBusy { get; set; }
private async Task AddTestData()
{
addTestDataIsBusy = true;
try
{
var wOp = new WriteOperation(Guid.Empty);
wOp.DataMainRow.Add(SeccionDePruebasAPIES.TextoPrincipal, editingText);
var result = await DinaupClient.RunWriteOperationAsync(CurrentUserService.User, SeccionDePruebasAPID._SectionIDGUID, wOp, false);
result.EnsureSuccess();
editingText = "";
} catch (Exception ex) {
NotificationService.Notify(NotificationSeverity.Error, "Ups.", ex.Message);
}
addTestDataIsBusy = false;
}
}
Anotaciones Internas [Subir archivo + añadir comentario]
Este ejemplo permite escribir un comentario al usuario, adjuntar una imagen desde cámara o galería, y vincular ambos a una fila (rowId
) y sección (sectionId
). Al guardar, se ejecuta Annotation_PutAsync
, y se notifica al componente padre con OnAnnotationSended
.
El tipo de anotación se define con AnnotationTypeE
, que puede ser:
Files
: para documentación interna, como fichas técnicas o manuales.Comments
: para notas internas o interacción entre miembros del equipo.PublicGallery
: para archivos públicos como fotos de un producto. Cuidado: pueden ser accesibles públicamente e indexados por buscadores.
Ideal para añadir contexto o evidencias visuales en cualquier flujo de trabajo.
<RadzenCard class="d-flex justify-content-between gap-1 align-items-center px-3 align-items-center align-content-center">
<div class="d-flex flex-grow-1 flex-column">
<span class="rz-text-overline">Nuevo comentario</span>
<RadzenTextArea Style="width: 100%" @bind-Value=@message />
@if (selectedFileName.IsNotEmpty())
{
<div class="d-flex">
<FileNameU FileName=@selectedFileName></FileNameU>
<RadzenButton Variant="Variant.Text" ButtonStyle="ButtonStyle.Danger" Icon="close" Click=@RemoveFile />
</div>
}
else
{
<RadzenUpload ChooseText="Adjuntar archivo..." Multiple="false" Accept="image/*;capture=camera" Complete=@OnComplete Url="file/upload/single" />
}
</div>
<div>
<RadzenButton Icon="send" Click=@SaveAnnotation />
</div>
</RadzenCard>
@code {
[Parameter] public Action OnAnnotationSended { get; set; }
[Parameter] public System.Guid rowId { get; set; }
[Parameter] public System.Guid sectionId { get; set; }
[Parameter] public AnnotationTypeE Type { get; set; }
public string message;
public System.Guid selectedFileID;
public string selectedFileName;
async Task OnComplete(UploadCompleteEventArgs args)
{
var jsonData = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(args.RawResponse);
var id = jsonData.GetM("id");
var filename = jsonData.GetM("filename");
selectedFileID = id.ToGUID();
selectedFileName = filename;
InvokeAsync(this.StateHasChanged);
}
async Task RemoveFile()
{
selectedFileID = Guid.Empty;
selectedFileName = "";
InvokeAsync(this.StateHasChanged);
}
async Task SaveAnnotation()
{
try
{
await SessionPlay.Annotation_PutAsync(SessionPlay, sectionId, rowId.STR(), selectedFileID, message, Type);
message = "";
selectedFileID = Guid.Empty;
selectedFileName = "";
try
{
OnAnnotationSended.Invoke();
}
catch (Exception e2x)
{
}
}
catch (Exception ex)
{
ex.Notify(NotificationService);
}
}
}
Convención
✅ Uso de
nameof
Siempre que se indique una propiedad por su nombre (por ejemplo, en columnas, filtros o bindings), debe usarsenameof
para mantener el código seguro ante refactors y facilitar la navegación.✅ Botones asíncronos con
IsBusy
Todos los botones que ejecuten acciones asíncronas mediante el eventoClick
deben incluir el indicadorIsBusy
para proporcionar feedback visual al usuario mientras se procesa la acción.✅ Nombres de secciones según licencia del usuario Se deben conservar los nombres de las secciones tal como aparecen en la licencia del usuario. Por ejemplo:
GetPaises
es correcto y no se considera una mezcla de idiomas, ya que"Paises"
es el identificador personalizado del usuario en su entorno MyDinaup.✅ Fechas y Horas siempre UTC
✅ Fechas sin Horas Siempre DateOnly
Última actualización