Marzo de 2019

Volumen 34, número 3

[Tecnología de vanguardia]

Componentes jerárquicos de Blazor

Por Dino Esposito

Dino EspositoComo último marco en unirse al grupo de aplicaciones de página única (SPA), Blazor ha tenido la oportunidad de aprovechar las mejores características de otros marcos, como Angular y React. Aunque el concepto fundamental detrás de Blazor es aprovechar C# y Razor para crear aplicaciones de SPA, un aspecto claramente inspirado en otros marcos de trabajo es el uso de componentes.

Los componentes de Blazor se escriben en lenguaje Razor, de un modo muy similar al que se crean las vistas de MVC. Y aquí es donde la cosa se pone interesante para los desarrolladores. En ASP.NET Core, se pueden alcanzar niveles sin precedentes de expresividad a través de nuevos artefactos de lenguaje llamados asistentes de etiquetas. Un asistente de etiquetas es una clase C# instruida para analizar un árbol de marcado concreto a fin de convertirlo en HTML5 válido. Todas las ramas que puede encontrar al crear un fragmento complejo hecho a medida de HTML se controlan desde el código. Y lo que escriben los desarrolladores en archivos de texto es marcado sin formato. Con los asistentes de etiquetas, la cantidad de fragmentos de código se reduce significativamente. Los asistentes de etiquetas son excelentes, pero algunos aún presentan ciertas imperfecciones de programación que los componentes de Blazor eliminan de forma brillante. En este artículo, crearé un nuevo componente de Blazor que presenta un cuadro de diálogo modal a través de los servicios del marco Bootstrap 4. Al hacerlo, trataré componentes con plantilla de Blazor y parámetros en cascada.

Imperfecciones de los asistentes de etiquetas

En mi libro "Programming ASP.NET Core" (Microsoft Press, 2018), presento un asistente de etiquetas de muestra que hace prácticamente el mismo trabajo que hemos comentado antes. Convierte el marcado que no es HTML en marcado específico de Bootstrap para cuadros de diálogo modales (consulte bit.ly/2RxmWJS).

Cualquier transformación entre la marca de entrada y el resultado deseado se realiza a través de código C#. Un asistente de etiquetas, de hecho, es una clase C# sencilla que se hereda de la clase base TagHelper y reemplaza un método único. El problema es que la composición del marcado y la transformación se debe expresar en código. Aunque esto aporta mucha flexibilidad, cualquier cambio también conlleva un paso de compilación. En concreto, es necesario usar código C# para describir un árbol DIV con todos sus conjuntos de atributos y elementos secundarios.

En Blazor, las cosas son mucho más fáciles, ya que no necesita recurrir a los asistentes de etiquetas para crear una sintaxis de marcado más sencilla para elementos sofisticados, como un cuadro de diálogo modal de Bootstrap. Veamos cómo crear un componente modal en Blazor.

Cuadros de diálogo modales

La idea es configurar un componente de Blazor reutilizable que encapsule el componente del cuadro de diálogo modal de Bootstrap. En la figura 1 se presenta el árbol de marcado HTML5 conocido necesario para que Bootstrap (versiones 3.x y 4.x) funcione.

Figura 1 Marcado de Bootstrap para cuadros de diálogo modales

<button type="button" class="btn btn-primary"
        data-toggle="modal"
        data-target="#exampleModal">
  Open modal
</button>
<div class="modal">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Modal title</h5>
        <button type="button" class="close" data-dismiss="modal">
          <span>&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <p>Modal body text goes here.</p>
      </div>
      <div class="modal-footer">
        <button type="button"
                class="btn btn-secondary"
                data-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>

A ningún desarrollador web le hace gracia reiterar un fragmento de marcado una y otra vez en distintas vistas y páginas. La mayor parte del marcado es diseño puro y la única información variable es el texto para mostrar y, quizás, algunos estilos y botones. A continuación, se incluye un marcado más expresivo que resulta más fácil de recordar:

<Modal>
  <Toggle class="btn"> Open </Toggle>
  <Content>
    <HeaderTemplate> ... </HeaderTemplate>
    <BodyTemplate> ... </BodyTemplate>
    <FooterTemplate> ... </FooterTemplate>
  </Content>
</Modal>

Los elementos constituyentes de un componente modal son inmediatamente visibles en el código de marcado más expresivo. El marcado incluye un elemento Modal contenedor con dos árboles secundarios: uno para el botón de alternancia y otro para el contenido real.

Según la sintaxis de Bootstrap de los componentes modales, todo cuadro de diálogo necesita un desencadenador para mostrarse. Normalmente, el desencadenador es un elemento de botón decorado con un par de atributos data-toggle y data-target. Sin embargo, el componente modal también se puede desencadenar mediante JavaScript. El subcomponente Toggle, simplemente, actúa como contenedor para el marcado de desencadenador. En cambio, el subcomponente Content encapsula todo el contenido del cuadro de diálogo y se divide en tres segmentos: encabezado, cuerpo y pie de página.

En resumen, según el fragmento de código anterior, la UI resultante está compuesta de un botón principal denominado "Open". Al hacer clic, el botón abre un elemento DIV con tres capas: encabezado, cuerpo y pie de página.

Para crear los componentes anidados necesarios para el cuadro de diálogo modal, debe usar componentes con plantilla y parámetros en cascada. Tenga en cuenta que los parámetros en cascada requieren que ejecute Blazor 0.7.0 o una versión más reciente.

El componente modal

Echemos un vistazo al código de la figura 2. El marcado es bastante mínimo e incluye un elemento DIV en torno a un fragmento de marcado con plantilla. El archivo modal.cshtml de la figura 2 declara una propiedad de plantilla denominada ChildContent que recopila (obviamente) cualquier contenido secundario. El resultado del marcado es extraer un elemento DIV circundante que recopila tanto el marcado de alternancia como el contenido real que se muestra en el cuadro de diálogo.

Figura 2 Código fuente del componente modal

<CascadingValue Value="@Context">
  <div>
    @ChildContent
  </div>
</CascadingValue>
@functions
{
  protected override void OnInit()
  {
    Context = new ModalContext
    {
      Id = Id,
      AutoClose = AutoClose
    };
  }
  ModalContext Context { get; set; }
  [Parameter] private string Id { get; set; }
  [Parameter] private bool AutoClose { get; set; }
  [Parameter] RenderFragment ChildContent { get; set; }
}

Aparentemente, este componente de contenedor no es muy útil. Sin embargo, desempeña un papel fundamental dada la estructura del marcado necesaria para los cuadros de diálogo de Bootstrap. Los componentes Toggle y Content comparten el mismo identificador que identifica el cuadro de diálogo modal de forma única. Mediante el uso de un componente de contenedor, puede capturar el valor de identificador en un solo lugar y reproducirlo en cascada en el árbol. Sin embargo, en este caso concreto, el identificador ni siquiera es el único parámetro que hay que reproducir en cascada en las capas de marcado más internas. Opcionalmente, un cuadro de diálogo modal puede tener un botón Cerrar en el encabezado, así como otros atributos relacionados con el tamaño de la animación o el cuadro de diálogo. Toda esta información se puede agrupar en un objeto de transferencia de datos personalizado y reproducir en cascada en el árbol.

La clase ModalContext se usa para recopilar el identificador y el valor booleano del botón de cierre, como se muestra en este código:

public class ModalContext
{
  public string Id { get; set; }
  public bool AutoClose { get; set; }
}

El elemento CascadingValue captura la expresión proporcionada y la comparte automáticamente con todos los componentes más internos explícitamente enlazados con él. Sin la característica de parámetros en cascada, cualquier valor compartido en componentes complejos y jerárquicos debe insertarse explícitamente donde sea necesario. Sin esta característica, tendría que indicar el mismo identificador dos veces, tal como se muestra en este código:

<Modal>
  <Toggle id="myModal" class="btn btn-primary btn-lg">
    ...
  </Toggle>
  <Content id="myModal">
    ...
  </Content>
</Modal>

Los valores en cascada son útiles en situaciones en que se debe pasar el mismo conjunto de valores a lo largo de la jerarquía de un componente complejo formado por varios subcomponentes. Tenga en cuenta que los valores en cascada deben agruparse en un único contenedor. Por lo tanto, si tiene que pasar varios valores escalares, primero debe definir un objeto contenedor. En la figura 3 se ilustra cómo fluyen los parámetros a través de la jerarquía de componentes modales.

Valores en cascada en componentes jerárquicos
Figura 3 Valores en cascada en componentes jerárquicos

Dentro del componente modal

El contenido interno del componente modal se analiza de forma recursiva y los componentes Toggle y Content se encargan de eso. A continuación se incluye el código fuente del componente Toggle.cshtml:

<button class="@Class"
        data-toggle="modal"
        data-target="#@OutermostEnv.Id">
  @ChildContent
</button>
@functions
{
  [CascadingParameter] protected ModalContext OutermostEnv { get; set; }
  [Parameter] string Class { get; set; }
  [Parameter] RenderFragment ChildContent { get; set; }
}

En esta implementación, se aplica un estilo al elemento de alternancia mediante una propiedad pública denominada Class. El contenido del botón se captura a través de una propiedad de plantilla denominada ChildContent. Tenga en cuenta que, en Blazor, una propiedad de plantilla denominada ChildContent captura automáticamente todo el marcado secundario del elemento principal. Además, una propiedad de plantilla en Blazor es una propiedad de tipo RenderFragment.

Lo interesante del código fuente anterior es el enlace a los valores en cascada. Utilice el atributo CascadingParameter para decorar una propiedad de componente, como OutermostEnv. A continuación, la propiedad se rellena con los valores en cascada del nivel más interno. Como resultado, OutermostEnv toma el valor asignado a la instancia de ModalContext recién creada en el método Init del componente raíz (consulte la figura 2).

En el componente Toggle, el valor de identificador en cascada se usa para establecer el valor del atributo data-target. En la jerga de Bootstrap, el atributo data-target de un botón de alternancia de cuadro de diálogo identifica el identificador del elemento DIV que aparecerá al hacer clic en el botón de alternancia.

Contenido del cuadro de diálogo modal

Un cuadro de diálogo de Bootstrap está formado por un máximo de tres bloques DIV distribuidos verticalmente: encabezado, cuerpo y pie de página. Todos son opcionales, pero le recomendamos tener al menos uno definido con el fin de proporcionar algunos comentarios mínimos a los usuarios. Un componente con plantilla es la solución perfecta, en este caso. Esta es la interfaz pública del componente Content resultante del archivo Content.cshtml:

@functions
{
  [CascadingParameter] ModalContext OutermostEnv { get; set; }
  [Parameter] RenderFragment HeaderTemplate { get; set; }
  [Parameter] RenderFragment BodyTemplate { get; set; }
  [Parameter] RenderFragment FooterTemplate { get; set; }
}

El parámetro OutermostEnv en cascada llevará los datos definidos fuera del dominio kerberos del componente Content. En este caso, se usan las propiedades ID y AutoClose. El valor de identificador se usa para identificar el contenedor más externo del cuadro de diálogo. Se abrirá el elemento DIV firmado con el identificador cuando se desencadene el componente modal. En cambio, el valor AutoClose se usa para controlar una instrucción IF que decide si la barra de encabezado debe incluir un botón Descartar.

Por último, tres propiedades de plantilla RenderFragment definen el contenido real de las áreas personalizables: encabezado, pie de página y cuerpo.

Como puede ver en la figura 4, el componente Content realiza la mayor parte del trabajo para representar el marcado de Bootstrap que se espera para los cuadros de diálogo modales. Define el diseño HTML general y usa propiedades de plantilla para importar los detalles del marcado que hacen que un cuadro de diálogo determinado sea único: encabezado, pie de página y cuerpo. Gracias a las plantillas Blazor, se puede especificar cualquier marcado real como contenido alineado en la página del autor de llamada. Tenga en cuenta que el código fuente de la página del autor de llamada (denominado Cascade en la aplicación de ejemplo) está representado en la figura 3.

Figura 4 Marcado del componente Content

<div class="modal" id="@OutermostEnv.Id">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">
          @HeaderTemplate
        </h5>
        @if (OutermostEnv.AutoClose)
        {
          <button type="button" class="close"
                  data-dismiss="modal">
            <span>&times;</span>
          </button>
        }
      </div>
      <div class="modal-body">
        @BodyTemplate
      </div>
      <div class="modal-footer">
        @FooterTemplate
      </div>
    </div>
  </div>
</div>

Más información sobre los parámetros y valores en cascada

Los valores en cascada solucionan el problema del flujo eficaz de valores en la pila de subcomponentes. Los valores en cascada se pueden definir en distintos niveles de una jerarquía compleja y van de un elemento antecesor a todos sus descendientes. Cada elemento antecesor puede definir un único valor en cascada, posiblemente un objeto complejo que reúne varios valores escalares.

Para usar los valores en cascada, los componentes descendientes declaran parámetros en cascada. Un parámetro en cascada es una propiedad pública o protegida decorada con el atributo CascadingParameter. Los valores en cascada pueden asociarse con una propiedad Name, como se indica a continuación:

<CascadingValue Value=@Context Name="ModalDialogGlobals">
  ...
</CascadingValue>

En este caso, los elementos descendientes usarán la propiedad Name para recuperar el valor en cascada, como se puede ver a continuación:

[CascadingParameter(Name = "ModalDialogGlobals")]
ModalContext OutermostEnv { get; set; }

Si no se especifica ningún nombre, los valores en cascada se enlazan con los parámetros en cascada por tipo.

Resumen

Los valores en cascada están diseñados específicamente para componentes jerárquicos, pero, al mismo tiempo, los componentes jerárquicos (y con plantilla) realmente son el tipo más común de componente de Blazor que los desarrolladores pueden escribir. En este artículo se muestran los parámetros en cascada y los componentes con plantillas y jerárquicos, pero también se muestra la eficacia que puede suponer el uso de componentes de Razor para expresar partes concretas del marcado mediante una sintaxis de nivel superior. En concreto, he elaborado una sintaxis de marcado personalizada para representar un cuadro de diálogo modal de Bootstrap. Tenga en cuenta que puede conseguir lo mismo con ASP.NET Core sin formato mediante asistentes de etiquetas o HTML en el modelo MVC de ASP.NET clásico.

El código fuente del artículo está disponible en bit.ly/2FdGZat.


Dino Esposito ha escrito más de 20 libros y más de 1000 artículos en su carrera de 25 años. Autor de “The Sabbatical Break”, un espectáculo de estilo teatral, Esposito se dedica a escribir software para un mundo más verde como estratega digital de BaxEnergy. Puede seguirle en Twitter: @despos.

Gracias al siguiente experto técnico de Microsoft por revisar este artículo: Daniel Roth