Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 14 de enero de 2020
ASP.NET Core Las Razor Class Libraries (RCL) constituyen una interesante fórmula para crear componentes redistribuibles de interfaz de usuario para aplicaciones basadas en ASP.NET Core MVC o Razor Pages. En las bibliotecas de clases de este tipo podemos incluir controladores, view components, tag helpers o vistas y páginas Razor, elementos que estarán disponibles en las aplicaciones que las referencien, bien directamente o bien a través del paquete NuGet en el que las distribuyamos.

Sin embargo, es menos conocido el hecho de que estas bibliotecas pueden incluir también recursos estáticos como imágenes, hojas de estilo o scripts, lo que resulta bastante interesante a la hora de crear componentes totalmente autosuficientes y muy reutilizables.

En este post vamos a ver cómo crear una RCL redistribuible que definirá el tag helper <mario>, cuya inclusión en una página hará que ésta muestre el conocido personaje correteando por la pantalla, como se muestra en la siguiente captura:

Mario corriendo por la pantalla

1. Lo básico: creación y consumo de la Razor Class Library

Como sabemos, podemos crear RCLs utilizando Visual Studio o bien desde la línea de comandos:
dotnet new razorclasslib --support-pages-and-views
Importante: en ambos casos es fundamental añadir el soporte para páginas y vistas a la hora de crear el proyecto.
También podemos crear una biblioteca de clases tradicional, y modificar su archivo .csproj para dejarlo como el mostrado a continuación:
<Project Sdk="Microsoft.NET.Sdk.Razor">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
  </PropertyGroup>
  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>
</Project>
Una vez creado el proyecto, ya podemos introducir en él los componentes que queramos distribuir en la biblioteca. En nuestro caso, iremos preparando el esqueleto del tag helper <mario>, que podría ser algo así:
public class MarioTagHelper: TagHelper
{
    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        output.SuppressOutput();
        output.Content.SetHtmlContent("<p align=center>Mario will be here!</p>");
        return base.ProcessAsync(context, output);
    }
}
Para utilizar este componente desde una aplicación ASP.NET Core MVC, sólo tenemos que referenciar el proyecto RCL (o el paquete NuGet, si hubiéramos decidido distribuir así nuestra biblioteca) y, dado que se trata de un tag helper, registrarlo en el archivo _ViewImports.cshtml. Suponiendo que la biblioteca Razor se denomina MarioRcl, sería algo como:
...
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, MarioRcl
De esta forma, ya podríamos utilizar en nuestras páginas la etiqueta que hemos definido, por ejemplo en la vista Index.cshtml creada por defecto en proyectos MVC:
...
<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web 
       apps with ASP.NET Core</a>.</p>
</div>
<mario></mario>
El resultado en ejecución sería el siguiente:

Tag Helper mario -solo texto- en ejecución

2. Incluir recursos estáticos

Por simplificar, en el código anterior solo hemos mostrado un texto en el lugar donde se introdujo la etiqueta <mario>, aunque en realidad lo que nos interesará es insertar un código script que vaya superponiendo distintas imágenes para crear la ilusión de animación.


Mario corriendo, frame 1    Mario corriendo, frame 2    Mario corriendo, frame 3

Dado que vamos a utilizar estas tres imágenes, debemos añadirlas a la carpeta wwwroot del proyecto RCL, que sabemos que es donde se almacenan por convención los recursos estáticos; un buen lugar para hacerlo, sin duda, sería en una subcarpeta llamada images en su interior.

Carpeta wwwroot del proyecto RCL Por defecto, todos los recursos presentes en la carpeta wwwroot de la RCL serán distribuidos junto con la biblioteca de clases Razor. De hecho, si probáis a publicar el proyecto ASP.NET Core MVC que referencia a la RCL, podréis observar que los recursos estáticos se encuentran en la carpeta de salida, en un directorio llamado por convención wwwroot\_content\[Ensamblado RCL].

Por ejemplo, si nuestro proyecto RCL se denomina MarioRcl, los recursos estáticos introducidos en la carpeta wwwroot de la Razor Class Library se introducirán automáticamente en la carpeta de salida wwwroot\_content\MarioRcl del proyecto web que lo utilice, como se muestra en la siguiente captura de pantalla del explorador de archivos de Windows:

Recursos estáticos de la RCL
Si hay recursos estáticos que no queremos incluir al empaquetar la RCL, podemos excluirlos explícitamente utilizando el elemento <DefaultItemExcludes> del archivo .csproj.
Y con esto, podríamos decir que la magia está hecha. El único requisito adicional a tener en cuenta es que el proyecto web deberá incluir el middleware Static Files en el pipeline, pues es la forma de asegurar el proceso de peticiones a archivos estáticos.

En este momento, si modificamos el tag helper de la siguiente forma, ya deberíamos poder ver a Mario, aún quietecito, en las páginas que utilicen el tag <mario>.
public class MarioTagHelper : TagHelper
{
    private static readonly string Root =
        $"/_content/{typeof(MarioTagHelper).Assembly.GetName().Name}";

    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        output.SuppressOutput();
        output.Content.SetHtmlContent(GetHtml());
        return base.ProcessAsync(context, output);
    }

    private string GetHtml() =>
        $"<p align=center><img src='{Root}/images/1.png'>";
}
Tag Helper Mario en ejecución mostrando una imagen

Sólo un detalle adicional: esto os funcionará sin problema cuando publiquéis el proyecto o si lo ejecutáis desde la carpeta de salida o el IDE en el entorno Development, pero no si modificáis el entorno a Production. En este caso tendréis que añadir una configuración extra al Program.cs:
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStaticWebAssets(); <-- ¡Añadir esta línea!
            webBuilder.UseStartup<Startup>();
        });

Punto extra: démosle vida al muñeco

Ya sabemos todo lo necesario para incluir recursos estáticos en una RCL y distribuirlos con ella, así que podríamos considerar que este último punto sobra en el post. Pero como habíamos establecido al principio, el objetivo era ver corretear a Mario por pantalla, así que démosle un último empujón.

Para conseguirlo, sólo tendremos que introducir algo de Javascript en el tag helper para que mueva el sprite y vaya alternando sus frames al mismo tiempo. Una posible implementación podría ser la siguiente:
public class MarioTagHelper : TagHelper
{
    private static readonly string Root = 
        $"/_content/{typeof(MarioTagHelper).Assembly.GetName().Name}";

    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        output.SuppressOutput();
        var id = context.UniqueId; // ID único del tag <mario> en proceso,
                                   // generado por Razor

        output.Content.SetHtmlContent(GetHtml(id));
        return base.ProcessAsync(context, output);
    }

    private string GetHtml(string id) =>
$@"<div style='width=100%;' id='parent-{id}'>
<img id='mario-{id}' style='position: absolute; left: 0;'>
</div>
<script>
(function () {{
    let frame = 0;
    let parent = document.getElementById('parent-{id}');
    let element = document.getElementById('mario-{id}');
    let left = parseInt(element.style.left);
    setInterval(function() {{
        element.src = '{Root}/images/' + frame + '.png';
        element.style.left = left+'px';
        left=(left+10)%(parent.offsetWidth-element.offsetWidth);
        frame = (frame+1) % 3;
    }}, 80);
}})();
</script>";

}

¡Y eso es todo! Con esto ya tendremos a Mario correteando por la pantalla. Pero lo importante no es eso, sino que por el camino hemos aprendido a crear una RCL reutilizable con contenidos estáticos :)

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!