Un cigarrillo electrónico para developers o el Guille fuma pendrives

Pues eso… que hoy me he/han regalado un cigarrillo electrónico, ya que como ahora me cuesta más escribir el código y terminarlo rapidito, porque estoy usando más código con puntos y comas, así que… como el cigarrillo es casi obligatorio cuando estoy developeando, pues… (Con VB no me es tan necesario el cigarrillo :-P).
No, en serio… que yo fumo más cuando estoy más dedicado al desarrollo que otras cosas, así que… cigarrillo electrónico al canto, aparte de por temas de salud, es que dejo la casa empestada a tabaco y no es plan… si hasta el bigote se me está poniendo amarillo, así que… Y como soy developer, pues resulta que el cigarrillo ese electrónico (recargable por USB) es como un pendrive… pero más grande… si no te lo crees… mira la foto.

El Guille fuma pendrive, y pa muestra tengo uno en la mano izquierda (a la derecha según se mira la foto), que no que el cigarrillo-pendrivado es el que tengo en la mano derecha…

Y ya de paso, puedes localizarme, que en la foto está la ubicación de #folleskilandia nº7 😉

Cuando me trajeron el cigarrillo, yo lo esperaba normal, pero no así… jajaj… además la cápsula que lleva (con un 0,8% de nicotina) tiene sabor a arándanos… y a mí me gustan los arándanos… así que… en el día de hoy (bueno, de ayer por la tarde hasta hace un rato) me he fumado el equivalente a un paquete de 20 cigarrillos… pero, afortunadamente, no de nicotina.
Y es que la estanquera dice que cada cápsula equivale a un paquete de tabaco, así que… como ya he tenido que cambiarla, pues… a por el segundo paquete 😉
Y, como te decía, estos que tengo saben a fruta roja, y como me gustan, lo mismo por eso me lo he fumado entero… en fin…

Y sobre lo que te he dicho al principio de que ahora estoy usando más puntos y comas (porque estoy escribiendo más cosas con C#), aunque esa no es la razón para que fume más, jejeje. Y es que como no creo que esta gente de Microsoft se retracte de la decisión de no seguir añadiendo nuevas características a VB, y como yo no me voy a quedar estancado solo con .NET Framework 4.8 ni solo con las aplicaciones de escritorio, pues… he tomado la decisión de añadir a mis aplicaciones código en C#, de esa manera cuando quiera hacer algo para .NET (Core) que no se pueda hacer con .NET Framework 4.8 ya estaré más suelto a la hora de escribirlo con C#, ya que con VB no podré. Porque creo que la petición que les he hecho para que sigan evolucionando Visual Basic no va a prosperar. Aunque si quieres apoyar la petición, puedes hacerlo (aquí te explico más detalles).

#evolveVB #evolicionarVB son los tags que he creado para la petición, por si quieres usarlos 😉

Sobre lo que te decía de C#, en la última utilidad que estoy haciendo, para comparar el contenido de dos directorios y poder manipularlos (borrar, copiar, etc.) un programilla que los más veteranos (en edad) podrán comparar con el Comandante Norton que hace años algunos usábamos con MS-DOS. Una nueva opción que he añadido (para seleccionar y modificar los colores de los temas a usar) he decidido escribirla completamente con C# y usarla desde el proyecto que ya tengo en Visual Basic. Y casi seguro que para las nuevas cosas que quiera ir añadiendo a otros proyectos o los nuevos que vengan, seguiré probando con los puntos y comas.

Este proyecto que te comento aún lo tengo en desarrollo, y si bien la parte funcional del programa ya «funciona», (es decir, ya se pueden comparar los directorios y operar con el contenido), aún no está terminado, pero si quieres echarle un vistazo, lo tengo publicado en github: Comparar directorios.

Por cierto, en la foto está el proyecto de VB con el formulario en modo diseño, el VS Code con código de C# y… una máquina virtual con el QBX (Basic Professional) que es para MS-DOS.

Pues esto es todo… otro día más…

Nos vemos.
Guillermo

¿Te gustaría que a VB.NET le añadan nuevas características en .NET 5 (.NET Core)? pues… vota esta petición

Pues eso… que eso de que a los que preferimos VB sobre C# para crear nuestras aplicaciones (y aunque no lo prefiriéramos) no nos debería parecer bien que la gente de Microsoft haya decidido no añadir nuevas características al lenguaje, dejándolo estancado… es decir, que si bien podremos seguir usándolo no le van a añadir nuevas características. Por eso he publicado una petición en la comunidad de desarrolladores de Microsoft para ver si no lo dejan de lado.

En los enlaces que hay en el párrafo anterior están los enlaces a lo que comentó la gente del team de .NET sobre que Visual Basic no evolucionará como lenguaje aunque se podrá usar para generar aplicaciones de .NET 5.0 y .NET Core o .NET Standard (además de .NET Framework), pero te los repito: Visual Basic support planned for .NET 5.0 y la petición también la repito 😉 : Will be back Visual Basic evolve with C#?

Y la petición que yo te hago a tí que estás leyendo esto es que pinches en el enlace de la petición (este) y vote por esa petición, si te parece conveniente, claro, que tampoco te voy a obligar… ¡Faltaría más!

Y es que da la impresión de que los de Microsoft se han olvidado de todo (el dinero) que les ha dado Visual Basic (tanto para .NET como las versiones anteriores llamadas de forma genérica: VB6) y que lo abandonen de esa forma, mientras que F# si que seguirá evolucionando, que C# sea la estrella es porque está ligado 1 a 1 con el avance de .NET y por tanto debe seguir avanzando, tal como te expliqué en El porqué C# siempre tendrá novedades aunque Visual Basic ya no. Precisamente ahí te comenté lo que Anthony D. Green (un Program Manager de Visual Basic) opina sobre esto de que Microsoft no le dedique, al menos, la misma cantidad de persona que le dedica a F#, no, no tengo nada contra F#, solo que me extraña que un lenguaje que no usará ni la décima parte de la gente que usa VB.NET siga «palante» y VB no. Según ese señor, no sería necesario dedicarle más de tres personas (en nómina) aparte de los desarrolladores que quieran colaborar con el desarrollo (y la evolución) de Visual Basic. Si quieres, lee el artículo de Anthony y, aunque es un poco largo, deja muy claro las cosas.

Y ya no te canso más. Solo decirte que si te animas a votar favorablemente por la petición te explico cómo hacerlo:

Ve al sitio de Developer Community y accede a mi petición y pulsa en la flecha de arriba que está en la parte superior izquierda (ver la figura) para votar favorablemente, pero, por favor, no pulses en la flecha que señala para abajo, ya que quitarías votos… y no es plan 😉

Para votar a favor, pulsa en la flecha que señala para arriba. Gracias.

Agradecerte de antemano tu apoyo… y si quieres usar el hashtag (#evolvevb) pues mejor… que actualmente solo aparece un resultado y… no tiene nada que ver con evolucionar VB (que es lo que viene a significar evolveVB) 😉
Si quieres puedes usar #evolucionarVB para que todo quede en casa.

Por cierto, la Developer Community de Visual Studio está para añadir peticiones de nuevas características y para informar de bugs/fallos en Visual Studio y .NET.

Pues nada más… ¡gracias!

Nos vemos.
Guillermo

En el roadmap de Visual Studio dicen que el diseñador de formularios de Windows de .NET 5 estará listo para diciembre

Pues eso… que le estaba echando una visual al roadmap de Visual Studio 2019 y en el apartado de .NET aparece lo que te muestro en la figura 1, es decir, que ya tienen solucionados todos los problemas que tenía… vamos que el diseñador de formularios de Windows para las aplicaciones de .NET 5.0 es/era una caca de la vaca comparado con el diseñador de .NET Framework. Pero si es verdad que todo eso ya lo tienen listo (están marcados con una estrella verde) entonces un ¡OLÉ! grande para los desarrolladores de Visual Studio. A ver si en la próxima actualización de la versión Preview está disponible.

Figura 1. Las cosas que ya tiene (cuando actualicen el VS) el diseñador de Windows Forms para .NET Core

Otra cosa que me ha llamado la atención es lo que pone en la última estrella:
Las aplicaciones de WinForms VB .NET 5 tienen experiencia de diseñador (en inglés: WinForms VB .NET 5 applications have designer experience), lo que yo entiendo de esa frase es que para Visual Basic (VB) hay un diseñador diferente que para C# y que tiene una ¿experiencia de diseño? (sigo sin enterarme qué significa eso jejeje, en fin).

En serio, supongo que se refieren a que no nos van a dejar atrás en esto también a los que usamos Visual Basic .NET.

 

Pues nada… habrá que esperar a la próxima o próximas actualizaciones de la Preview de Visual Studio 2019 v16.9 para ver qué experiencia de diseño tendremos los VBers 😉

 

Nos vemos.
Guillermo

Generar clave SHA1 con el nombre y password del usuario

Pues eso… en este post te explico cómo generar una clave (usando la clase SHA1CryptoServiceProvider) formada a partir de dos cadenas, normalmente el nombre del usuario y el password (o contraseña), de esta forma se genera una cadena única (de 40 caracteres) de forma que si alguien accede a ella no sabrá nunca cuales fueron las dos cadenas que la formaron (o eso es lo que espero que ocurra, jejeje). Además te mostraré también una función para evitar que el usuario introduzca caracteres no válidos y que pueden se usados para acceder maliciosamente a una base de datos.
Por supuesto, te mostraré el código tanto para Visual Basic como para C#.

El código que te mostraré (al menos el de generar la clave SHA1) está basado en este código publicado en mi sitio (usando .NET 1.1):
El ejemplo de Visual Basic .NET:
comprobar_usuario_usando_base_datos_vb2003
El ejemplo de C#:
comprobar_usuario_usando_base_datos_cs2003

Generar una clave SHA1 usando SHA1CryptoServiceProvider

Para acceder a esta clase necesitas una importación del espacio de nombres System.Security.Cryptography y como en el código usaremos un objeto StringBuilder y UTF8Encoding, también habrá que importar System.Text.

En el código de ejemplo para usar los métodos definidos en la clase UtilSHA1 (que será en la que defino los dos métodos usados) se hará una comprobación de si tanto en el nombre como en el password usado hay caracteres no válidos, los caracteres que compruebo en el método ValidarTextoClave son los caracteres: ?*%’_ y

De esa forma intentamos asegurarnos que no se pueda hacer un SQL injection, es decir, intentar acceder maliciosamente a la base de datos a la que presumiblemente se quiere acceder.

Veamos primero el código que usa los dos métodos, el de comprobar la validez del texto introducido (ValidarTextoClave) y el de generar la clave SHA1 (GenerarClaveSHA1).

Método Main con el código de prueba para usar los métodos de la clase

Como te dije antes, se pide el nombre del usuario y el password a usar para generar la cadena con la clave SHA1 (que será de 40 caracteres convertidos a mayúsculas).

Este sería el código del método Main para Visual Basic, con las importaciones de los espacios de nombres necesarios en todo el código de ejemplo:

Option Strict On
Option Infer On

Imports System

Imports System.Text
Imports System.Security.Cryptography

Module Program
    Sub Main(args As String())
        
        dim valido As Boolean
        dim usuario As String
        dim passw as String

        Do
            Console.Write("Escribe el nombre del usuario: ")
            usuario = Console.ReadLine()
            ' si el nombre del usuario tiene caracteres no permitidos, preguntar de nuevo
            valido = UtilSHA1.ValidarTextoClave(usuario)
            if not valido
                Console.WriteLine("Nombre de usuario NO VÁLIDO.")
            end if
        Loop While Not valido

        Do
            Console.Write("Escribe la clave: ")
            passw = Console.ReadLine()
            ' si la clave tiene caracteres no permitidos, preguntar de nuevo
            valido = UtilSHA1.ValidarTextoClave(passw)
            if not valido
                Console.WriteLine("La clave NO ES VÁLIDA.")
            end if
        Loop While Not valido

        ' generar la clave SHA1 y mostrarla
        dim claveSHA1 = UtilSHA1.GenerarClaveSHA1(usuario, passw)
        Console.WriteLine($"La clave SHA1 es: '{claveSHA1}'.")
            
    End Sub
End Module

 

Este sería el código del método Main para C#, con las importaciones (using) de los espacios de nombres usados en el código:

using System;

using System.Text;
using System.Security.Cryptography;

class Program
{
    static void Main(string[] args)
    {
        bool valido;
        string usuario;
        string passw;

        do
        {
            Console.Write("Escribe el nombre del usuario: ");
            usuario = Console.ReadLine();
            // si el nombre del usuario tiene caracteres no permitidos, preguntar de nuevo
            valido = UtilSHA1.ValidarTextoClave(usuario);
            if (!valido)
                Console.WriteLine("Nombre de usuario NO VÁLIDO.");
        }
        while (!valido);

        do
        {
            Console.Write("Escribe la clave: ");
            passw = Console.ReadLine();
            // si la clave tiene caracteres no permitidos, preguntar de nuevo
            valido = UtilSHA1.ValidarTextoClave(passw);
            if (!valido)
                Console.WriteLine("La clave NO ES VÁLIDA.");
        }
        while (!valido);

        // generar la clave SHA1 y mostrarla
        var claveSHA1 = UtilSHA1.GenerarClaveSHA1(usuario, passw);
        Console.WriteLine($"La clave SHA1 es: '{claveSHA1}'.");
    }
}

 

La clase UtilSHA1 con los métodos para comprobar la validez del texto y generar la clave

A continuación te muestro el código del método para validar el texto del nombre del usuario y el password o contraseña para que no contenga caracteres no deseados.

Para Visual Basic:

''' <summary>
''' Validar caracteres en la clave.
''' No se aceptan ?*%'_ ni --
''' </summary>
Public Shared Function ValidarTextoClave(laClave As String) As Boolean
    Dim sNoVale As String = "?*%'_"

    laClave = laClave.Trim()

    If laClave.IndexOf("--") > -1 Then
        Return False
    End If
    If laClave.IndexOfAny(sNoVale.ToCharArray) > -1 Then
        Return False
    End If

    Return True
End Function

 

Para C#:

/// <summary>
/// Validar caracteres en la clave.
/// No se aceptan ?*%'_ ni --
/// </summary>
public static bool ValidarTextoClave(string laClave)
{
    string sNoVale = "?*%'_";

    laClave = laClave.Trim();

    if (laClave.IndexOf("--") > -1)
        return false;
    if (laClave.IndexOfAny(sNoVale.ToCharArray()) > -1)
        return false;

    return true;
}

 

Y ahora el código con la definición del método GenerarClaveSHA1 en el que indicaremos dos cadenas: el nombre del usuario y el password y a partir de la concatenación de ambas generar el valor SHA1 producido por el método ComputeHash que en realidad devuelve un array de tipo Byte, el cual convertimos en valores hexadecimales (con dos cifras por valor) con idea de que se genere la cadena deseada de 40 caracteres en total, que finalmente convertimos en mayúsculas, pero que bien puedes dejarlo en minúsculas si así te parece mejor.

El código para Visual Basic:

''' <summary>
''' Generar una clave SHA1 para guardarla en lugar del password,
''' de esa forma no se podrá saber la clave.
''' La longitud es de 40 caracteres.
''' </summary>
''' <remarks>
''' Crear una clave SHA1 como la generada por:
''' FormsAuthentication.HashPasswordForStoringInConfigFile
''' Basado en el ejemplo de mi sitio:
''' http://www.elguille.info/NET/dotnet/comprobar_usuario_usando_base_datos_vb2003.htm
''' </remarks>
Public Shared Function GenerarClaveSHA1(nick As String, clave As String) As String
    ' Crear una clave SHA1 como la generada por 
    ' FormsAuthentication.HashPasswordForStoringInConfigFile
    ' Adaptada del ejemplo de la ayuda en la descripción de SHA1 (Clase)
    Dim enc As New UTF8Encoding
    ' Por si el usuario (nick) es nulo
    If String.IsNullOrWhiteSpace(nick) Then
        nick = ""
    Else
        nick = nick.ToLower
    End If
    Dim data() As Byte = enc.GetBytes(nick & clave)
    Dim result() As Byte

    Dim sha As New SHA1CryptoServiceProvider
    ' This is one implementation of the abstract class SHA1.
    result = sha.ComputeHash(data)

    ' Convertir los valores en hexadecimal
    ' cuando tiene una cifra hay que rellenarlo con cero
    ' para que siempre ocupen dos dígitos.
    Dim sb As New StringBuilder
    For i As Integer = 0 To result.Length - 1
        If result(i) < 16 Then
            sb.Append("0")
        End If
        sb.Append(result(i).ToString("x"))
    Next

    Return sb.ToString.ToUpper
End Function

 

El código para C#:

/// <summary>
/// Generar una clave SHA1 para guardarla en lugar del password,
/// de esa forma no se podrá saber la clave.
/// La longitud es de 40 caracteres.
/// </summary>
/// <remarks>
/// Crear una clave SHA1 como la generada por:
/// FormsAuthentication.HashPasswordForStoringInConfigFile
/// Basado en el ejemplo de mi sitio:
/// http://www.elguille.info/NET/dotnet/comprobar_usuario_usando_base_datos_cs2003.htm
/// </remarks>
public static string GenerarClaveSHA1(string nick, string clave)
{
    // Crear una clave SHA1 como la generada por 
    // FormsAuthentication.HashPasswordForStoringInConfigFile
    // Adaptada del ejemplo de la ayuda en la descripción de SHA1 (Clase)
    UTF8Encoding enc = new UTF8Encoding();
    // Por si el usuario (nick) es nulo
    if (string.IsNullOrWhiteSpace(nick))
        nick = "";
    else
        nick = nick.ToLower();
    byte[] data = enc.GetBytes(nick + clave);
    byte[] result;

    SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider();
    // This is one implementation of the abstract class SHA1.
    result = sha.ComputeHash(data);

    // Convertir los valores en hexadecimal
    // cuando tiene una cifra hay que rellenarlo con cero
    // para que siempre ocupen dos dígitos.
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < result.Length; i++)
    {
        if (result[i] < 16)
            sb.Append("0");
        sb.Append(result[i].ToString("x"));
    }

    return sb.ToString().ToUpper();
}

 

Nota:
El código final del método GenerarClaveSHA1 se puede simplificar para que use dos caracteres hexadecimales sin necesidad de la comparación de si el valor es menor de 16.
El código te lo muestro en el repositorio de github por si quieres intentarlo por tu cuenta 😉

 

Nota importante:
Comentarte que la generación de la clave SHA1 distingue entre mayúsculas y minúsculas, es decir, si al generar la clave SHA1 usaste Guillermo como usuario (o nick) si vuelves a generarla con el nombre en minúsculas (guillermo) el valor generado será diferente.
Esto mismo es aplicable a la contraseña o password.

 

El código completo con los proyectos para usar con .NET 5.0 (tanto con dotnet como con Visual Studio Code o con Visual Studio 2019 v16.8) está publicado en github:
Generar clave SHA1 con el nombre y password del usuario.

 

Y esto es todo… espero que te sea de utilidad… ya sabes que esa es la idea… y si te parece bien (y puedes) no estaría de más que dejaras una propina usando el enlace de PayPal 😉
Gracias.

 

Nos vemos.
Guillermo

La evolución de .Net, una plática de Héctor de León con «El Guille»

Pues eso… hoy he tenido una charla en directo con Héctor de León (del canal hdeleon.net) sobre la evolución de .NET y sobre (la evolución de) un servidor de ustedes… Eso fue a las 19:00 hora de España (12:00 pm hora de México), pero si te lo perdiste o lo quieres volver a ver, aquí te dejo el enlace (y el video insertado), espero que te guste y te sea de utilidad y de paso sabrás un poco más de qué hace ahora el Guille y todas esas cosas… 😉

Aquí te dejo el enlace al susodicho video:

El enlace: La evolución de .Net, una plática con «El Guille» | Invitado Guillermo Som de elguille.info

El video insertado en un iframe:

 

Espero que te sirva, al menos para conocer al que escribe estas cosas tan raras de programación en este blog (www.elguillemola.com) y en el «clásico» de www.elGuille.info

Nos vemos.
Guillermo

Érase una vez hace 24 años…

Pues eso… en un mes como este de noviembre pero hace 24 años nació el «prototipo» de lo que ahora es el sitio del guille: http://www.elguille.info, pero aquella primera vez, gracias a mi amigo Manolo Franco que me dio la posibilidad de alojar el sitio en sus servidores para que yo fuese practicando con todo esto de tener algo en la red de redes) era otra cosa (en realidad otro nombre), y según te comenté hace 6 años, era www.costasol.es/guiller, que después quedó durante un tiempo como guille.costasol.net, hasta que a finales de 2005 se quedó como está ahora: www.elguille.info.

Aunque después, creo que el 2009 creé este blog (www.elguillemola.com) para que me resultara más fácil crear entradas (o artículos, como prefieras llamarlo) y desde entonces hasta hoy, así se ha quedado.

Solo aclarar que durante unos años (desde 2003) también estaba www.mundoprogramacion.com, pero que desde el año pasado, ese sitio ya solo es una redirección de www.elguille.info, ya que antes estaba hospedado «gratuitamente» en domitienda (businet, etc.) gracias a mi amigo Adolfo Molto, pero éste vendió la empresa y los nuevos dueños ya no quisieron alojar ese sitio ni el resto de mis sitios (elguille.info y elguillemola.com), así que… tuve que crear un alojamiento (hosting) de pago en el que ya no tenía tanto sitio para mis sitios (no es un juego de palabras, jejeje) y así estamos actualmente… y mientras pueda costearlo, ahí seguirán… 😉

 

Aquí tienes una captura de como era el sitio del guille por el año 1997 o así:

Figura 1. El sitio del guille en abril de 1997

 

Y así en febrero de 2002:

Figura 2. El sitio del guille en febrero de 2002

 

Solo escribo esto para recordar este cumple, que aunque en realidad no fuese el día 15 de noviembre de 1996 cuando nació elguille en la web (según mi amigo Daniel Seara fue el día 20 de noviembre), la cuestión es que fue en noviembre de 1996, y en eso nos quedamos… 😉

Pues nada más… ¡Felicidades al sitio de elGuille! y a ver si sigue activo o al menos disponible unos años más…

Nos vemos.
Guillermo

sharplab.io una utilidad online para ver el código tal como lo compila .NET

Pues eso… viendo el video de Filip Ekberg sobre los record en C# 9.0 (siempre hay que intentar aprender más) me gustó ver que mientras explicaba el código se veía el código desemsamblado (como lo genera el compilador de .NET) y es usando una herramienta online accesible mediante el enlace a https://sharplab.io/.

Si pones la dirección https://sharplab.io/ en el navegador, te mostrará una ventana con dos paneles.

Con las siguientes capturas te voy explicando un poco cómo configurar el «entorno» de trabajo.

En la figura 1 vemos el entorno ya configurado para usar los compiladores de Roslyn, para eso indicamos master (11 nov 2020) (es la que hay a día de hoy 13 de noviembre) que como ves con más detalle en la figura 2 nos permite seleccionar el «framework» a usar para compilar el código que pongamos en el panel izquierdo.

En la figura 3 puedes ver que se pueden usar los tres lenguajes que se incluyen en .NET, es decir: C#, Visual Basic y F#.

En el panel derecho se muestra el código decompilado, por defecto es en C# (sí, aunque en la izquierda lo escribas con otro lenguaje diferente a C#).

Pero como puedes ver en la figura 4 se puede mostrar de otras formas, eso sí, si quieres ver el código tal como lo trata el compilador, éste será en C#.
Las otras opciones son, entre otras,. el código ensamblado IL o incluso en ejecución (Run).
Ya es cuestión de ir probando cada una de esas opciones de la figura 4 para ir viendo cómo nos muestra el código.

En la figura 5 puedes ver código de Visual Basic (en el panel izquierdo) y el resultado de compilarlo y mostrarlo en C#.

En la figura 6 te muestro un trozo de código de C# 9.0 usando las instrucciones de nivel superior (top-level statements) y los tipos de registro (record).

Pero fíjate que he señalado una clase que (al menos así parece ser) es requerida para poder usar el código de tipo nivel superior, es decir, sin necesidad de crear el método Main, etc.), ya que si quitas ese código te dará error, tal como puedes ver en la figura 7.

Y si te gusta usar el tema oscuro, en la parte inferior derecha (junto a Theme) puedes seleccionar entre Auto, Light y Dark. A mí me ha mostrado el modio claro (Light) cuando lo inicié y ahí abajo me indicaba Auto tal como puedes ver en la figura 1.

Y esto es todo… es cuestión de ir practicando y probar más cosas de esta útil herramienta online.

Figura 1.

Figura 2.

Figura 3.

Figura 4.

Figura 5.

Figura 6.

Figura 7.

Nos vemos.
Guillermo

Novedades de C# 9.0

Pues eso… tal como te dije hace unos días, ya ha llegado el momento de explicarte algunas de las novedades de C# 9.0; concretamente aquí te voy explicar de forma concisa (sin enrollarme mucho) en qué consisten tres de las novedades que trae la versión 9.0 de C#, la que se incluye en .NET 5.0 que en este mes de noviembre estará en modo release o, dicho de otro modo, en versión final.

NOTA:
En realidad hoy día 10 de noviembre lo han publicado, mientras escribía este artículo se hacía la presentación oficial de .NET 5.0 en el .NET Conf 2020 (que puedes ver la grabación usando el enlace).

Estas novedades que te voy a explicar aquí están basadas en la documentación de .NET, concretamente en: Novedades de C# 9.0.
Y las tres novedades que he elegido son las siguientes:
1- Instrucciones de nivel superior (Top-level statements)
2- Registros (Records)
3- Establecedores de solo inicialización (Init only setters)

NOTA:
No me voy a explayar (alargar demasiado) en las explicaciones, si quieres detalles, por favor mira el enlace que te he puesto antes y en la documentación te lo explican con gran lujo de detalles.

Para usar el compilador de C# 9.0 necesitarás tener instalado .NET 5.0 y el código ejecutarlo con Visual Studio 2019 Preview o usando dotnet desde la línea de comandos o con Visual Studio Code.

Instrucciones de nivel superior (top-level statements)

Esta novedad es solo aplicable a un fichero en cada proyecto de C# y viene a ser una forma más simple de escribir el punto de entrada de una aplicación que normalmente se define con el método estático Main. De hecho, si se utiliza esta forma de escribir el código no podrá existir otro punto de entrada diferente, es decir, no podrá haber otro método estático Main que indique por dónde empezar a ejecutar el código.

En cualquier programa de C# nos encontraremos con un código parecido a este (por ejemplo el clásico Hola Mundo:

using System;

namespace novedadescs9_01
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Pero ahora con C# 9.0 lo podemos simplificar de esta forma:

using System;

Console.WriteLine("Hello World!");

Es decir, nos quedamos con el código realmente operativo. En este caso la importación de System para poder acceder al método WriteLine de la clase Console.

Como te comenté antes, esta forma de escribir el código solo se puede hacer en un fichero del proyecto y equivale al método de entrada de dicho proyecto.

Si ese método debe manipular los argumentos de la línea de comando, estos se pueden seguir gestionando con args (aunque no estén indicados en ningún sitio).

Por ejemplo, supongamos que queremos tomar el primer argumento como el nombre al que saludar, lo haríamos de esta forma:

using System;

Console.WriteLine("¡Hola {0}!", args[0]);

De la misma forma, si nuestro método Main (aunque no haya aparentemente ninguno) tiene que devolver un valor, por ejemplo que devuelva 1 si no se ha indicado nada en la línea de comandos o lo indicado está vacío, lo haríamos de la siguiente forma:

using System;

if (args.Length == 0 || string.IsNullOrEmpty(args[0]) )
    return 1;

Console.WriteLine("¡Hola {0}!", args[0]);

return 0;

Si ejecutas el código anterior sin indicar nada (ningún argumento) no hará (aparentemente) nada, pero en realidad si hace, ya que devuelve 1 como resultado de la ejecución. Si se indica algo mostrará el mensaje de saludo y devolverá cero.

Para hacerlo más evidente, podemos cambiar el if de la siguiente forma:

using System;

if (args.Length == 0 || string.IsNullOrEmpty(args[0]))
{
    Console.WriteLine("Debes escribir un nombre.");
    return 1;
}

Console.WriteLine("¡Hola {0}!", args[0]);

return 0;

Ahora estará más claro que no hemos escrito nada como argumento al ejecutar el programa.

NOTA:
Si has elegido usar dotnet puedes hacer las pruebas de la siguientes formas:
(se supone que estás usando la terminal de Visual Studio Code o el símbolo del sistema con el directorio activo donde esté el código.

Si indicas un argumento, será así:
dotnet run Guille
y el resultado será:
¡Hola Guille!

Si no indicas argumentos escribe esto:
dotnet run
y el resultado será:
Debes escribir un nombre.

En la figura 1 puedes ver la salida de las dos formas de usarlo (con o sin argumento).

Figura 1.

 

Registros (Records)

Para escribir el código de la segunda novedad de C# 9.0 vamos a crear un nuevo proyecto, si quieres hacerlo con dotnet desde la línea de comandos, sitúate en la carpeta donde dotnet creará una carpeta con el código necesario para este ejemplo el cual se llamará novedadescs9_02 y escribe lo siguiente:

dotnet new console -o novedadescs9_02

Cambia al directorio recién creado con cd novedadescs9_02 y edita el fichero Program.cs para que tenga el código que te mostraré a continuación, aunque antes un poco de explicación sobre de qué va esto de los registros o records.

Los tipos de registro (record types) es un tipo por referencia que son inmutables de forma predeterminada. Inmutable significa que una vez creados no se pueden modificar.

Hasta ahora los tipos por referencia (clases y tipos anónimos) no contenían los datos que manipulaban si no una referencia a esos datos, mientras que los tipos por valor (entre ellos las estructuras y tuplas) contienen los valores, es decir, si pasamos un tipo por valor a un método se pasa una copia de los datos, a diferencia de los tipos por referencia que pasan una referencia al objeto en memoria que contiene los valores.
Y esto es principalmente lo que hacen los tipos de registro cuando se pasan a un método, no se pasa la referencia, si no una copia de los datos.

De hecho este es uno de los puntos importantes de los tipos record, que al ser inmutables se pueden comparar de la misma forma que comparamos, por ejemplo un tipo entero, y devolverá un valor de igualdad si todos los campos/propiedades que contiene son iguales. Algo que con las clases no ocurre.

Veamos una declaración de un tipo record de la forma más simple (ahora veremos cómo definirlo de una forma parecida a una clase.

NOTA:
Para simplificar voy a usar el código tal y como te he explicado en el primer punto de este artículo como instrucciones de nivel superior (top-level statements).

public record Persona(string Nombre, int Edad);

Esta sería la forma simplificada de definir un registro.
En este caso usamos la palabra clave record y en este caso define un tipo de registro con dos propiedades: Nombre de tipo cadena y Edad de tipo entero.

Para usarla lo haremos de la misma forma que con otros tipos:

var persona1 = new Persona("Guillermo", 63);
Console.WriteLine(persona1);

Fíjate que al mostrar el contenido de la variable persona1 se muestra de una forma ya predefinida, pero como siempre, al forma de actuar de ToString (que es el que se encarga de mostrarlo de esa forma), lo puedes definir a tu antojo.
En ese ejemplo la salida será la siguiente:

Persona { Nombre = Guillermo, Edad = 63 }

Si queremos definir un método ToString personalizado lo haremos de la forma tradicional (ahora tenemos que usar llaves para definir el método):

public record Persona2(string Nombre, int Edad)
{
    public override string ToString()
    {
        return $"{Nombre} nació en {DateTime.Now.Year - Edad}";
    }
}

Por supuesto también podemos usar la herencia con los tipos record, imagina que el tipo Persona2 lo quieres derivar de Persona y añadir la definición de ToString, simplemente lo haríamos así:

public record Persona2(string Nombre, int Edad) : Persona (Nombre, Edad)
{
    public override string ToString()
    {
        return $"{Nombre} nació en {DateTime.Now.Year - Edad}";
    }
}

Aunque aquí no ganamos mucho, pero al menos definimos Persona2 a partir del tipo Persona.

Para ver una clase derivada que añada alguna funcionalidad extra, por ejemplo que tenga otra propiedad más podemos definir el tipo Personaje a partir del tipo Persona, en este caso, el tipo Personaje define una propiedad de tipo entero llamada Desde (además de las dos propiedades que expone el tipo Persona).

public record Personaje(string Nombre, int Edad, int Desde) : Persona(Nombre, Edad)
{
    public override string ToString()
    {
        return $"{Nombre} nació en {DateTime.Now.Year - Edad} y está activo desde {Desde}.";
    }
}

Aquí también definimos el método personalizado ToString, pero si no queremos definirlo, simplemente haríamos lo siguiente:

public record Personaje(string Nombre, int Edad, int Desde) 
                        : Persona(Nombre, Edad);

Al mostrar el contenido del personaje1 el resultado en este caso sería (obviamente) el predeterminado del método ToString.

En la figura 2 tienes la salida del código con la definición propia del método ToString y en la figura 3 la salida sin definir un método ToString para el tipo de registro Personaje.

 

Así que, ya es cosa tuya cómo quieres que se comporte el método ToString.

Por supuesto, puedes definir otros métodos que necesites. Y ya sabes cómo 😉

¿Qué significa que los tipos record son inmutables?

Como te comenté antes, las propiedades definidas en un tipo de registro no se pueden cambiar, son inmutables (como las cadenas de .NET), por tanto si queremos cambiar el valor de una propiedad de un tipo ya en memoria, simplemente NO PODEMOS HACERLO. De hecho, de hacerlo lo que conseguiríamos es crear un nuevo registro con los nuevos valores. Pero vayamos por pasos.
Si tienes la definición de Persona que hemos visto anteriormente y decides cambiar la edad de la persona1 simplemente no podrás, ya que te indicará que Edad es de solo lectura.

Escribe lo siguiente (pongo el código completo para que te resulte más fácil hacer la prueba):

using System;

var persona1 = new Persona("Guillermo", 63);
Console.WriteLine(persona1);
persona1.Edad = 64;
Console.WriteLine(persona1);

public record Persona(string Nombre, int Edad);

Si intentas ejecutar esto desde la línea de comandos con dotnet te mostrará el siguiente error (ver la figura 4):

Figura 4. Error de compilación usando dotnet.

Si ese código lo escribes en Visual Studio 2019 Preview, tal como puedes ver en la figura 5, te mostrará la asignación (lo que te muestro en rojo en el código anterior) como un error que indica que:

Error CS8852: Init-only property or indexer 'Persona.Edad' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.
Figura 5. El error tal como lo muestra Visual Studio 2019 Preview.

Ese mensaje nos lo aclarará la siguiente y última sección de este artículo sobre las novedades de C# 9.0. Pero eso dentro de un momento.

var persona2 = persona1 with { Edad = 64 };

Console.WriteLine("La persona2: {0}.",persona2);

Aquí lo que hemos hecho es crear una copia de persona1 con un nuevo valor en la edad, pero realmente son dos objetos en memoria diferentes: Uno con la edad de 63 y el otro con la edad de 64, es decir: ¡hemos clonado al guille con un año más! (de seguir con otro ejemplo más, me jubilaré pronto jejeje).

Fíjate que no hemos usado el tipo Persona para crear el nuevo objeto, simplemente lo hemos creado a partir del que ya estaba definido en persona1, pero con algunos datos diferentes, en este ejemplo simplemente hemos cambiado el valor de Edad.

Ahora veremos lo que te comentaba al principio de la forma de comparar objetos creados a partir de tipos de registro.

Veamos el siguiente código en el que definimos dos objetos del tipo Persona, en los que los hemos inicializado con los mismos datos y después asignamos a la variable iguales el resultado de comparar con == ambos objetos.
como veremos al ejecutar el código es que ambos son iguales, ¡aunque en realidad sean dos objetos diferentes!

var persona1 = new Persona("Guillermo", 63);
Console.WriteLine("La persona1: {0}.", persona1);

var persona2 = new Persona("Guillermo", 63);
Console.WriteLine("La persona2: {0}.", persona2);

var iguales = persona1 == persona2;
Console.WriteLine("persona1 == persona2 es {0}", iguales);


public record Persona(string Nombre, int Edad);

El resultado de ejecutar ese código será el siguiente:

La persona1: Persona { Nombre = Guillermo, Edad = 63 }.
La persona2: Persona { Nombre = Guillermo, Edad = 63 }.
persona1 == persona2 es True

Esa comparación de igualdad la podemos escribir de la siguiente forma:

var iguales = persona1.Equals(persona2);
Console.WriteLine("persona1.Equals(persona2) es {0}", iguales);

El resultado será el mismo que usando el operador de igualdad.

Y para finalizar este apartado de los tipos de registro veamos qué ocurre si tenemos el siguiente código:

var persona2 = persona1 with { Edad = 64 };
Console.WriteLine("La persona2: {0}.", persona2);

var iguales = persona1.Equals(persona2);
Console.WriteLine("persona1.Equals(persona2) es {0}", iguales);

¿Cuál crees que será el valor de iguales?

La respuesta: tendrás que ejecutar el código para comprobarlo 😉

 

Deconstruir tipos de registro

Una cosa interesante con los tipos de registro (record) es lo que se conoce como de-constructor (Deconstruct) (mira la documentación para una explicación detallada) ya que aquí solo te pondré un ejemplo para que sepas que se puede hacer algo como esto:

var persona = new Persona("Guillermo", 63);
Console.WriteLine("La persona: {0}.", persona);

var (n, e) = persona;
Console.WriteLine($"{n} {e}");


public record Persona(string Nombre, int Edad);

Es decir, podemos asignar a una tupla el contenido de un registro, en este ejemplo (tal como puedes comprobar al ejecutar el código) es que se asignan los valores del Nombre y la Edad a la tupla.

 

 

Establecedores de solo inicialización (Init only setters)

Tal como vimos en el penúltimo ejemplo, o mejor dicho en el mensaje de error del penúltimo ejemplo (al que asignamos un valor a la propiedad Edad de un tipo de registro), el mensaje de error indica que las propiedades de solo inicialización solo se pueden asignar en un inicializador de objeto.
Es decir, la propiedad Edad es de solo lectura, pero en este caso, el error nos indica que la propiedad de solo inicialización (init-only property) solo se puede usar en un inicializador de objetos. Y aquí es donde entra esta tercera novedad de C# 9.0 que te quiero comentar, pero veamos primero un código de ejemplo de cómo usar esta nueva instrucción: init.

class Persona
{ 
    public string Nombre { get; init; }
    public int Edad { get; init; }
}

Como ves en el código anterior, en la definición de las propiedades de la clase Persona están los modificadores get e init. Get es, como bien supondrás, la parte que devuelve el valor de la propiedad (lo que hasta ahora hemos tenido) y en este caso init sustituye al modificador set de la propiedad; pero en este caso concreto le indicamos que esa asignación se hará solo y exclusivamente al iniciar una instancia de la clase Persona.

Una forma de definir un objeto persona1 a partir de la clase Persona sería la siguiente:

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };

Aquí estamos usando una clase en lugar de un tipo de registro. Por eso esa forma de crear el nuevo objeto. Lo he hecho así, para que no te olvides de cómo crear nuevos objetos a partir de una clase 😉

Como ves el funcionamiento es parecido a lo que hemos visto en la sección anterior, pero el definir una clase es porque los tipos de registro de forma predeterminada tienen el inicializador definido en las propiedades.

Pero el que los tipos de registro sean inmutables no quiere decir que no podamos definir propiedades de lectura/escritura, de hecho podemos hacer algo como esto:

record Persona
{
    public string Nombre { get; set; }
    public int Edad { get; init; }
}

Con el código anterior creamos un record que permite cambiar el valor de la propiedad Nombre, por tanto podemos hacer algo como lo siguiente sin que nos de error:

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };
Console.WriteLine(persona1);

persona1.Nombre = "Guille";
Console.WriteLine(persona1);

Y podemos hacerlo porque la propiedad Nombre ya no está definida como solo de inicialización.

Pero aún así, si definimos otro objeto del tipo (record) Persona con los mismos datos, la comparación de igualdad seguirá funcionando como vimos anteriormente.

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };
persona1.Nombre = "Guille";

Console.WriteLine(persona1);

var persona2 = new Persona { Nombre = "Guille", Edad = 63 };

var iguales = persona1 == persona2;
Console.WriteLine("persona1 == persona2 es {0}", iguales);

En este caso, el segundo objeto (persona2) lo definimos con «Guille» como nombre, si no, no sería igual que el objeto persona1.

Y seguramente pensarás que ¿Para qué quiero el tipo record, si parece que funciona igual que si lo defino como class?

Bien pensado, pero no, los tipos de registro (record) no funcionan igual que los tipos definidos a partir de una definición de una clase (class), y para muestra un botón.

Veamos el ejemplo anterior usando una clase en lugar de un registro.

using System;

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };
persona1.Nombre = "Guille";

Console.WriteLine(persona1);

var persona2 = new Persona { Nombre = "Guille", Edad = 63 };

var iguales = persona1 == persona2;
Console.WriteLine("persona1 == persona2 es {0}", iguales);

class Persona
{
    public string Nombre { get; set; }
    public int Edad { get; init; }
}

Si ejecutas ese código (te recuerdo que Persona está declarado con class en lugar de record) el valor asignado a iguales será false.

Y es False porque en realidad son dos objetos por referencia que no usan las nuevas características de los tipos de registro, en el que la comparación de igualdad se hace comprobando los valores campo a campo (o propiedad a propiedad), mientras que en los tipos «clasicos» por referencia se comprueba si el objeto es el mismo, y en este caso, no son el mismo, cada variable hace referencia a un valor diferente en la memoria.

Otra cosa es que escribamos el siguiente código:

using System;

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };
persona1.Nombre = "Guille";

Console.WriteLine(persona1);

var persona2 = persona1;

var iguales = persona1 == persona2;
Console.WriteLine("persona1 == persona2 es {0}", iguales);

var iguales2 = persona1.Equals(persona2);
Console.WriteLine("persona1.Equals(persona2) es {0}", iguales2);

class Persona
{
    public string Nombre { get; set; }
    public int Edad { get; init; }
}

En este caso, tanto el valor de iguales como el de iguales2 será True, ya que solo tenemos un objeto en la memoria, pero dos variables que hacen referencia al mismo objeto y por tanto, si cambiamos el valor de la propiedad Nombre en una de las variables ese cambio se hará efectivo en los dos objetos.

Si no me crees añade el siguiente código antes de la definición de la clase Persona y verás lo que muestra ahora.

Console.WriteLine("Antes de la nueva asignación");
Console.WriteLine(persona1.Nombre);
Console.WriteLine(persona2.Nombre);

persona1.Nombre = "Guillermo";

Console.WriteLine("Después de la nueva asignación");
Console.WriteLine(persona1.Nombre);
Console.WriteLine(persona2.Nombre);

Si ejecutas nuevamente el código con estos cambios, la salida será la siguiente:

Antes de la nueva asignación
Guille
Guille
Después de la nueva asignación
Guillermo
Guillermo

Esto demuestra que las dos variables están apuntando al mismo objeto en memoria.

Para terminar, veamos qué ocurre si en lugar de una clase usáramos un tipo record.

¿Te atreves a dar la respuesta a qué mostraría ese código?
No… o sí… bueno veamos el código y el resultado y así salimos de dudas 😉

using System;

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };
persona1.Nombre = "Guille";

Console.WriteLine(persona1);

var persona2 = persona1;

var iguales = persona1 == persona2;
Console.WriteLine("persona1 == persona2 es {0}", iguales);

var iguales2 = persona1.Equals(persona2);
Console.WriteLine("persona1.Equals(persona2) es {0}", iguales2);

Console.WriteLine("Antes de la nueva asignación");
Console.WriteLine(persona1.Nombre);
Console.WriteLine(persona2.Nombre);

persona1.Nombre = "Guillermo";

Console.WriteLine("Después de la nueva asignación");
Console.WriteLine(persona1.Nombre);
Console.WriteLine(persona2.Nombre);

record Persona
{
    public string Nombre { get; set; }
    public int Edad { get; init; }
}

Efectivamente, el resultado es como el anterior.

Ya que al hacer la asignación directamente:

var persona2 = persona1;

Estamos compartiendo la misma dirección de memoria en las dos variables, como puedes comprobar, esto no cambia con los tipos por referencia, sean clases o registros.

Y para terminar, solo comentarte que una asignación como esta:

var persona2 = persona1 with { Nombre = "Guille" };

Solo la podemos hacer con los tipos de registro (record) no con las clases (class).

Si lo intentamos con un tipo Persona definida como class el error sería:

Error CS8858: The receiver type 'Persona' is not a valid record type.

Y con esto te dejo por hoy… espero que te hayas aclarado un poco y si no es así… lo siento, pero no sé cómo explicarlo mejor… y si me surge cómo explicarlo mejor, no dudes que te lo explicaré… todo será cuestión de ir practicando con el nuevo tipo de C# 9.0.

En cualquier caso, como siempre, ¡Espero que te haya sido de utilidad! esa es siempre la intención 😉

Nos vemos.
Guillermo

El código fuente con todo el código usado en el artículo

Lo he publicado en github: Novedades de C# 9.0

Los bloques reutilizables (Reusable Block) del editor Gutenberg de WordPress

Pues eso… una de las características de el editor Gutenberg para WordPress son los bloques y particularmente los bloques reutilizables (Reusable Block).
Aquí no te voy a explicar qué son esos bloques reutilizables o Reusable Block en inglés. En el enlace anterior tienes información sobre esto de los bloques reusables o reutilizables.

Lo que te voy a explicar es dónde se guardan y cómo localizarlos todos, ya que, al menos a mí se me ha dado el caso, que teniendo unos 15 bloques reutilizables, en el editor (algo escondido por cierto) del editor de bloques, solo me muestra 9. El resto, como si hubiesen desaparecido.

Y la verdad es que he estado buscando en la web a ver qué dicen de dónde están, pero en todos los casos que me he encontrado ha sido para decir lo escondidos que están y mostrar la ventana o página de edición de bloques reusables.

La página en concreto para editar esos bloques personalizados es:
<URL del blog>/wp-admin/edit.php?post_type=wp_block

La forma de acceder a esa página (aparte de escribirla en el navegador) es la siguiente:

Pulsa en editar (o crear) una entrada o página, en el editor (de Gutenberg o de bloques, no en el editor clásico), arriba a la izquierda hay una crucecita azul que al pulsarla te permite añadir uno de los bloques predefinidos de WordPress, pero además hay un enlace (o ficha) para los reutilizables (ver figuras 1 y 2).

Figura 1. Acceder a los bloques reutilizables de WordPress

 

Figura 2. Gestionar los bloques reutilizables.

Al seleccionar Reutilizable te mostrará los bloques que tienes definidos y debajo un enlace para poder editarlos (Gestionar todos los bloques reutilizables).

Al pulsa en ese enlace te mostrará la página con los bloques reutilizables (ver figura 3).

Figura 3. Editando los bloques reutilizables (del blog)

Tal como te mostré antes, la URL sería: <url del sitio>/wp-admin/edit.php?post_type=wp_block. Donde debes cambiar <url del sitio> por la url de tu blog.

En mi caso (ver figura 3) solo tengo 5 bloques reutilizables, pero en otros sitios, tengo unos 15 definidos, pero solo me muestra 9, y no de forma correlativa (según la creación).

Pero mirando la base de datos del sitio, concretamente en la tabal «posts» están todos los bloques (así como páginas, entradas del blog, etc.).

Y la forma de dar con todos ellos es haciendo una búsqueda (select) con estos datos:

SELECT * FROM 'PREFIJO_posts' WHERE post_type = 'wp_block' ORDER BY ID

Debes cambiar PREFIJO por el prefijo que tenga tu tabla.

Esto te mostrará todos los bloques reutilizables que hayas creado y podrás editarlos o al menos averiguar el ID para poder editarlo en el WordPress. Y si así te parece después poder exportarlo como un fichero .JSON.

Para editar un bloque sabiendo el ID o el nombre interno (campo post_name).

Es decir, para editar el bloque con ID 1900 tendrías que escribir algo como esto en la URL: <URL DEL SITIO>/wp-admin/post.php?post=1900&action=edit

Y esto es todo… solo quería dejar constancia por si alguien se siente un poco perdido (como yo estaba hace un par de días) por no saber dar con los bloques reutilizables. Al menos si intentaba buscarlos de forma «normal», sin tener que entrar en la base de datos 😉

Espero que te sea de utilidad.

Nos vemos.
Guillermo

Actualizando el contenido de elGuille.info para adaptarlo a todos los dispositivos

Pues eso… que el Google Search Console me ha avisado que tengo más de 1200 páginas en www.elguille.info que no están adaptadas a las aplicaciones móviles y similares… En realidad el aviso es: La ventana gráfica no está definida.

Así que… me he puesto a buscar los ficheros que no tienen asignado este «meta»:

<meta name="viewport" content="width=device-width, initial-scale=1">

Y después de buscarlos, a editarlos y añadirle esa línea en la cabecera (<head>).

Como yo sabía que las páginas ASPX que usan páginas maestras sí tienen definida la ventana gráfica, al buscar he tenido que eliminar de la búsqueda todos los ficheros aspx que tuviesen la página maestra (en realidad los ficheros que contengan <asp:Content), y como también sé que hay páginas que no deben indexarse con los buscadores, he buscado también las páginas que no tengan content=»noindex» ni content=»none», que es lo que tengo puesto en las páginas que no deben estar indexadas, en realidad el meta completo es algo así:
<meta name=»robots» content=»noindex»> (o «none», que para el caso es lo mismo que «noindex»).

Para hacer la búsqueda he usado una utilidad que tengo de hace unos años (gsBuscarTexto) que he modificado entre otras cosas para poder indicar varias cadenas de búsqueda, tanto para las que estén en el fichero, por ejemplo si contiene <body es que no están usando páginas maestras, salvo la página maestra. Pero también tenía que filtra que no tuviese ciertas cadenas, las que te he indicado antes, es decir, que no tenga ninguna de estas subcadenas:
<meta name=»viewport»; <asp:Content; content=»noindex»; content=»none»

Y teniendo en cuenta solo algunas directorios del sitio, han aparecido más de 600 coincidencias, pero ya me quedan menos (ver la figura 1)

Figura 1. gsBuscarTexto ayudando a buscar los ficheros que no tienen <meta name=»viewport»

Y aún me queda examinar todo el resto del sitio.

Y no solo encontrar los ficheros afectados, si no que hay que abrirlos, añadir el meta «viewport», guardarlo y después subirlos al sitio…

En fin… un buen trabajillo para que todo el que quiera ver el contenido de elGuille.info lo vea lo mejor posible… 😉

 

Espero que así te resulte más fácil consultar el contenido desde el dispositivo que sea…

De nada 😉

 

Nos vemos.
Guillermo