Guías de estilo C#

Configuración de proyecto, cultura, valores centinela en vez de nulables y las extensiones de conversión y validación del SDK.

Las convenciones de C# que esperan el SDK Dinaup y sus extensiones. Aplícalas en tu app para que el código encaje con el SDK y con DinaZen.

Configuración del proyecto

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <Nullable>disable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
  <Using Include="Dinaup.extensions" Static="true" />
  <Using Include="DemoUp.MyDinaup" />
</ItemGroup>

Con Nullable deshabilitado el compilador no pide ? y los valores centinela son la opción por defecto. El Using estático de Dinaup.extensions deja .STR(), .IsEmpty() o .GetM() disponibles en todos los ficheros sin using por archivo.

Cultura y fechas

Las extensiones de conversión del SDK no leen la cultura del hilo: .STR() escribe los decimales con punto (12.5m.STR() da "12.5") y .DEC() lee el punto como separador decimal, salvo que le pases una CultureInfo. Un decimal viaja igual entre un servidor español y uno alemán.

Fija la cultura de la interfaz al arrancar la app en vez de heredar la del servidor, en Program.cs:

var cultureInfo = new CultureInfo("es-ES");
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.CurrentCulture = cultureInfo;

Las fechas son UTC. Los campos de fecha de MyDinaup llevan el sufijo _UTC (FechaAlta_UTC) y los informes convierten con .ToDateTime_UTC(). Sin horas locales ni conversiones de zona en el modelo; la zona del usuario se aplica al mostrar los datos.

Prácticas

Sin tipos nulables

Evita DateTime?, decimal? o Guid? salvo casos contados. Cada tipo tiene su valor vacío:

TipoVacío
DateTimeDateTime.MinValue
decimal y numéricos0 ("Máximo 0" significa sin máximo)
GuidGuid.Empty (.STR() lo convierte en "")
string""

Sin objetos anónimos

No devuelvas new { ... } desde controladores ni servicios: cada rama devuelve un tipo distinto y rompe el contrato de la API. Usa una clase con sufijo DTO.

// Tipos distintos en cada rama
if (ok)
    return Ok(new { version = "2", revision = "2" });
else
    return Ok(new { version = 2, revision = "2" }); // int frente a string
public class AppInfoDTO
{
    public string Version { get; set; } = "";
    public string Revision { get; set; } = "";
    public string Status { get; set; } = "";
}

return Ok(new AppInfoDTO
{
    Version = "2",
    Revision = "2",
    Status = "OK"
});

Colecciones y Guid con extensiones

// Correcto
if (_items.IsNotEmpty())
    ProcessItems();

if (f.NextRowId.IsEmpty())
    return;

// Incorrecto
if (_items?.Count > 0)
    ProcessItems();

if (f.NextRowId == Guid.Empty)
    return;

.IsEmpty() e .IsNotEmpty() son un par: nunca niegues uno teniendo el otro. lista.IsEmpty(), no lista.IsNotEmpty() == false.

Decimal para importes

float y double introducen errores de redondeo binario. Los importes y los cálculos de negocio van en decimal.

// Correcto
decimal price = 19.99m;
decimal total = price * quantity;

// Incorrecto
float price = 19.99f;
double total = price * quantity;

Extensiones de conversión

ExtensiónQué hace
.STR()Texto con mejores valores por defecto que .ToString(): decimal con punto y sin ceros sobrantes, Guid.Empty y nulos a "", bool a "1" o "0", DateTime a yyyy-MM-dd HH:mm:ss, DateOnly a yyyy-MM-dd.
.INT()string a int. Sin valor por defecto lanza excepción si no es un número.
.INT(defecto)Devuelve defecto si no puede convertir.
.DEC()string a decimal con punto decimal; quita € y $. Lanza Exception("Invalid cast") si no puede.
.DEC(defecto)Devuelve defecto si no puede convertir.
.BOOL()"1", "si", "sí", "on", "yes" y "true" son true (sin distinguir mayúsculas). Cualquier otro valor es false.
12.5m.STR()              // "12.5"
Guid.Empty.STR()         // ""
true.STR()               // "1"
DateTime.Now.STR()       // "2026-09-05 14:30:00"

"123".INT()              // 123
"abc".INT()              // excepción
"abc".INT(99)            // 99

"12.5".DEC()             // 12.5
"abc".DEC(9.9m)          // 9.9

"si".BOOL()              // true
"0".BOOL()               // false

Extensiones de validación

ExtensiónQué comprueba
.IsEmpty()"" o null en string; Guid.Empty en Guid y Guid?; nulo o MinValue en DateTime?, DateOnly? y TimeOnly?; sin elementos en List, IEnumerable, arrays, Dictionary, IList e IDictionary; Id vacío en DinaupBasicInformation e IDinaupRow.
.IsNotEmpty()Lo contrario. Para colecciones, al menos un elemento.
.IsNull() / .IsNotNull()Solo la referencia.
.EqualsIgnoreCase(otro)Igualdad de string sin distinguir mayúsculas.
.LikeM(a, b, c)true si el valor es igual a alguna de las opciones. Para string distingue mayúsculas; también existe para int, decimal, Guid y char.
.LikeMIgnoreCase(a, b, c)Igual que LikeM para string, sin distinguir mayúsculas.

.IsEmpty() no existe para int ni decimal: compara con 0.

if (estado.LikeM("Abierta", "Pagada"))
    Process();

if (codigo.LikeM(200, 201, 204))
    return;

Diccionarios

.GetM(clave) devuelve null (o el valor por defecto del tipo) si la clave no está; .GetM(clave, defecto) devuelve defecto. Ninguno lanza.

dic.GetM("key")              // null si no está
dic.GetM("key", "default")   // "default" si no está

Usa .GetM() siempre que ahorre líneas.

Blazor

En los componentes, las comillas son solo para literales string. Las expresiones van con @, sin comillas, para que el compilador compruebe el tipo.

@* Correcto *@
<DnzReportView Client=@Client ReportId="83fd57a3-893c-4651-adf9-d5d980af2c85" />

@* Incorrecto *@
<DnzReportView Client="@Client" />

Las convenciones para consumir MyDinaup están en C# con Dinaup y los componentes, en DinaZen.

En esta página