Etiqueta: c#

  • Lo que no me gusta de Visual Basic .NET (con Visual Studio)

    Pues eso… a pesar de los pesares, hay algo bueno al crear los proyectos para Visual C# desde Visual Studio que en Visual Basic (por aquello de que debemos estar protegidos de tocar donde no se debe, o eso supongo). Es algo muy simple, que introdujeron (creo) en Visual Studio 2005 y es lo de que el método Sub Main sea autogenerado por el Visual Studio y no te permita crear uno propio… o al menos eso me ha pasado con Visual Studio 2022 Version 17.2.0 Preview 1.0 y al final he tenido que crear el proyecto con C# para poder poner lo que me dé la gana en el método Main. En fin…

    Y es que, aunque haya definido mi propio método Sub Main, el Visual Studio me daba error de compilación de que ya estaba definido (o estaba definido más de una vez), no recuerdo bien qué me dijo… pero corté por lo sano, descarté el proyecto de Visual Basic y lo creé con C# (para .NET 6.0 y Windows Desktop).
    Ese proyecto solo se encarga de llamar a otro que es el principal y por tanto, lo que necesitaba hacer es poder llamar al formulario adecuado para que se iniciara ahí la aplicación (el que tengo definido en otro proyecto diferente al de inicio).
    Esto es para poder tener proyectos de inicio diferentes para cada «cliente» que quiera usar mi aplicación. Y en esos proyectos asignar los parámetros personalizados de cada cliente. Todo esto último, solo para aclarar. 😉

    Buscaré en la documentación si hay alguna forma de «pasar» de esta automatización (que, seguro que la hay, desde el propio Visual Studio, ya que creando el proyecto desde la línea de comandos con dotnet seguro que se puede). Y si la hay ya te lo contaré.

    Nota:
    Antes de publicar esto, he probado a crear un proyecto con dotnet:
    He creado el directorio con el nombre del proyecto, he cambiado a ese directorio y he escrito esto en la línea de comandos:
    dotnet new winforms -lang VB -f net6.0
    Y el proyecto se ha creado con el fichero Program.vb (con la definición de Sub Main) y un formulario Form1. Y ahora no dice nada el Visual Studio si lo cargo. Creo que el truco está en no crear los directorios ni asignaciones a My Project, etc.
    Lo probaré desde Visual Studio a ver…

    Nota 2:
    Para solucionar esto en un proyecto creado desde Visual Studio.
    He seguido estos pasos:
    He comentado (también las puedes borrar) las secciones (cada una está dentro de <ItemGroup>) :
    <Compile Update="My Project\Application.Designer.vb"> y
    <None Update="My Project\Application.myapp">.

    He comentado <MyType>WindowsForms</MyType> en el ItemGroup principal y de paso he eliminado la carpeta My Project. Y el fichero ApplicationEvents.vb.
    Esto realmente es lo que da el problema de que haya dos Sub Main.

    Y finalmente he creado un fichero Program.vb con el código «personalizado» para Sub Main.
    En fin… complicaciones por intentar facilitar las cosas a los que preferimos Visual Basic.

    Figura 1. Para quitar el error debes comentar MyType.

    Nota 3:
    Aunque los proyectos de C# creados con Visual Studio también tienen código autogenerado… por ejemplo: ApplicationConfiguration.Initialize.
    Que lo que hace es lo que siempre (o casi) han hecho los métodos Main de las aplicaciones de Windows Forms:
    public static void Initialize()
    {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.SetHighDpiMode(HighDpiMode.SystemAware);
    }

    Y ya está… tenía que decirlo y lo he dicho 😉

    Nos vemos.
    Guillermo

  • Descargar ficheros de un sitio WEB usando HttpClient (ejemplos para VB.NET y C#)

    Pues eso… para descargar ficheros de un sitio Web, hasta hoy usaba el método DownloadFile de la clase WebClient, pero ya estaba un poco harto del «warning» de que esa clase (y otras) estaban obsoletas y que era recomendable usar HttpClient, pero… no daba con un ejemplo (de código) práctico y, porque no, sencillo. Mire en varios sitios y de una forma u otra, se complicaba la cosa… hasta que me dio por mirar el contenido de la clase HttpClient en la documentación de .NET (esto me pasa por no fiarme de los ejemplos de la documentación de MS :-P).

    Y aquí te muestro lo que he hecho, creo que de forma simple.

    El código que te voy a mostrar descarga un fichero de un sitio Web y lo guarda de forma local. Como para este ejemplo he usado un fichero de texto (txt) que tengo alojado en mi sitio (www.elguille.info), en el código de ejemplo hago que se muestre con el Notepad, pero si lo que te descargas es otro tipo de fichero, ya sea una imagen, etc. tendrás que cambiar el código usado en «Process.Start«.

    Este código usa async/await para la descarga y para guardarlo localmente. Pero resulta que Visual Basic no permite usar Async en el método Main (C# tampoco, al menos en las versiones anteriores a la 7.1), por tanto, el código de VB.NET es algo diferente al de C# (aparte de los puntos y comas), básicamente porque en VB el método Main no puede ser asíncrono, así que lo he solucionado haciendo una llamada a otro método desde Main y ese otro método si es asíncrono.
    Este mismo paso intermedio tendrás que hacerlo si usas una versión de C# que no soporte que Main sea un método de tipo Task asíncrono. En realidad, tendrías que hacer otros cambios en el código de C#, ya que uso nuevas cosas que tampoco estaban en las versiones anteriores…

    Básicamente lo que hace el código es crear una instancia «estática/compartida» de un nuevo objeto del tipo HttpClient y después usarlo en el código. Esto en este ejemplo concreto no es necesario, ya que una vez que se utilice ese objeto el programa prácticamente finaliza, pero… es lo que recomiendan: que solo se use una instancia en la aplicación.

    Para la descarga, utilizo el método GetByteArrayAsync al que se le indica la dirección URL donde está el fichero en cuestión (o una página WEB si esa es la idea, la de descargar una página Web), ese método devuelve un array de tipo Byte, que usaremos para guardarlo en el fichero local, esto se consigue con WriteAsync de la clase FileStream, en cuyo constructor, entre otras cosas, indicaremos el path local.

    El que haya usado GetByteArrayAsync es porque mi código original no descarga un contenido «normal» de tipo cadena, pero para el caso, también sirve. Y de esta forma podrás usar el método que he definido para esta tarea de descargar/guardar (DownloadFileAsync) para cualquier tipo de contenido.

    Y ya, no me enrollo más y te muestro el código.

    Nota:
    Aunque sea más código, te muestro TODO el código, incluyendo las importaciones, etc.

    El código de ejemplo para Visual Basic.NET

    '--------------------------------------------------------------------------------
    ' Descargar un fichero de un sitio web usando HttpClient        (10/Feb/22 19.05)
    '
    ' Ejemplo basado en:
    ' https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient
    '
    ' (c) Guillermo Som (Guille), 2022
    '--------------------------------------------------------------------------------
    Imports System
    Imports System.Diagnostics
    Imports System.Threading.Tasks
    
    Module Program
    
        ''' <summary>
        ''' El objeto HttpClient se recomiendo instanciarlo solo 1 vez en la aplicación.
        ''' </summary>
        Private ReadOnly ClienteHttp As New System.Net.Http.HttpClient()
    
        Sub Main(args As String())
            'Console.WriteLine("Hello World!")
    
            ' Como en VB no se puede esperar en Main,
            ' hacer el trabajo asíncrono en otro método y esperar a que se termine todo...
            descargar()
    
            Console.ReadLine()
        End Sub
    
        Private Async Sub descargar()
            Dim ficWeb = "https://www.elguille.info/pruebaGuille.txt"
            Dim ficLocal = "prueba.txt"
    
            Console.WriteLine("Descargando {0}...", ficWeb)
    
            Dim res = Await DownloadFileAsync(ficWeb, ficLocal)
            If res Then
                Console.WriteLine("Descarga completada.")
    
                ' Mostrar el contenido del fichero local.
                Process.Start("notepad", ficLocal)
            End If
    
            Console.WriteLine()
            Console.WriteLine("Pulsa INTRO para finalizar.")
        End Sub
    
        ''' <summary>
        ''' Descarga el fichero indicado (url) y lo guarda en el fichero destino (usando HttpClient).
        ''' </summary>
        ''' <param name="ficWeb">El fichero a descargar (de una dirección URL).</param>
        ''' <param name="ficDest">El fichero de destino, donde se guardará el descargado.</param>
        ''' <returns>True o false según haya tenido éxito la descarga o no.</returns>
        Public Async Function DownloadFileAsync(ficWeb As String, ficDest As String) As Task(Of Boolean)
            Try
                ' Simplificando la descarga.
                Dim contenido = Await ClienteHttp.GetByteArrayAsync(ficWeb)
                ' Si se ha podido descargar.
                If contenido IsNot Nothing AndAlso contenido.Length > 0 Then
                    ' Guardarlo en el fichero de destino.
                    ' Si el fichero destino existe, se sobreescribe.
                    Using fs As New System.IO.FileStream(ficDest, System.IO.FileMode.Create,
                                                                  System.IO.FileAccess.Write,
                                                                  System.IO.FileShare.None)
                        Await fs.WriteAsync(contenido.AsMemory(0, contenido.Length))
                    End Using
                Else
                    Console.WriteLine("No se ha podido descargar.")
                    Return False
                End If
            Catch ex As Exception
                ' Se ha producido un error al descargar o guardar.
                Console.WriteLine("Error: {0}", ex.Message)
                Return False
            End Try
    
            Return True
        End Function
    
    End Module
    

    El código de ejemplo para C#

    //--------------------------------------------------------------------------------
    // Descargar un fichero de un sitio web usando HttpClient        (10/Feb/22 19.25)
    //
    // Ejemplo basado en:
    // https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient
    //
    // (c) Guillermo Som (Guille), 2022
    //--------------------------------------------------------------------------------
    
    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace Descargar_Fichero_con_HttpClient_CS
    {
        class Program
        {
            /// <summary>
            /// El objeto HttpClient se recomiendo instanciarlo solo 1 vez en la aplicación.
            /// </summary>
            private readonly static System.Net.Http.HttpClient ClienteHttp = new();
    
            // En C# 7.1 y superior se puede usar Main como Task y async.
            static async Task Main(string[] args)
            {
                //Console.WriteLine("Hello World!");
    
                var ficWeb = "https://www.elguille.info/pruebaGuille.txt";
                var ficLocal = "prueba.txt";
    
                Console.WriteLine("Descargando {0}...", ficWeb);
    
                var res = await DownloadFileAsync(ficWeb, ficLocal);
                if (res)
                {
                    Console.WriteLine("Descarga completada.");
    
                    // Mostrar el contenido del fichero local.
                    Process.Start("notepad", ficLocal);
                }
    
                Console.WriteLine();
                Console.WriteLine("Pulsa INTRO para finalizar.");
    
    
    
                // Las versiones de C# anteriores a 7.1 no pueden esperar en Main,
                // por tanto, el código anterior ponerlo en un método y llamarlo desde aquí
                // y esperar a que se termine todo...
                //descargar();
    
                Console.ReadLine();
            }
    
            /// <summary>
            /// Descarga el fichero indicado (url) y lo guarda en el fichero destino (usando HttpClient).
            /// </summary>
            /// <param name="ficWeb">El fichero a descargar (de una dirección URL).</param>
            /// <param name="ficDest">El fichero de destino, donde se guardará el descargado.</param>
            /// <returns>True o false según haya tenido éxito la descarga o no.</returns>
            public async static Task<bool> DownloadFileAsync(string ficWeb, string ficDest)
            {
                try
                {
                    // Simplificando la descarga.
                    var contenido = await ClienteHttp.GetByteArrayAsync(ficWeb);
                    // Si se ha podido descargar.
                    if (contenido != null && contenido.Length > 0)
                    {
                        // Guardarlo en el fichero de destino.
                        // Si el fichero destino existe, se sobreescribe.
                        using System.IO.FileStream fs = new(ficDest, System.IO.FileMode.Create, 
                                                                     System.IO.FileAccess.Write, 
                                                                     System.IO.FileShare.None);
                        await fs.WriteAsync(contenido.AsMemory(0, contenido.Length));
                    }
                    else
                    {
                        Console.WriteLine("No se ha podido descargar.");
                        return false;
                    }
                }
                catch (Exception ex)
                {
                    // Se ha producido un error al descargar o guardar.
                    Console.WriteLine("Error: {0}", ex.Message);
                    return false;
                }
    
                return true;
            }
        }
    }
    

    Y esto es todo… recuerda pulsar en el botoncito ese de PayPal si así lo crees conveniente 😉

    Nos vemos.
    Guillermo

    P.S.
    El código lo puedes ver/descargar del repositorio de GitHub que he creado para este caso.

  • Ahora entiendo mejor porqué ya no quieren seguir actualizando/avanzando Visual Basic

    Pues eso… viendo las novedades de C# 10.0 me he topado con los detalles de la «novedad» Global using directives, dicho de esa forma parece algo «WOW!», y… es algo que siempre he querido que quitasen de forma predeterminada al crear un nuevo proyecto de Visual Basic para .NET: que haya importaciones implícitas de espacios de nombres al crear un nuevo proyecto.

    Y eso es lo que es esa «nueva» característica de C# 10.0, crear definiciones «using» (ya que «Imports» es cosa de Visual Basic para .NET) de los espacios de nombres más habituales según el tipo de proyecto creado.

    Que sí, que está muy bien, pero a mí nunca me ha gustado, de hecho, en la mayoría de los proyectos que creaba (sobre todo si era para compartirlo como parte de algún artículo), lo quitaba, con idea de así tener que escribir las importaciones de espacios de nombres en cada fichero (de código) de ese proyecto. Porque es la única forma de saber «a ciencia cierta» qué espacios de nombres se estaban usando en ese fichero de código.

    Para que nos entendamos, a diferencia de C#, en Visual Basic el espacio de nombres System.Text nunca se importa de forma predeterminada, algo que sí ocurre en los proyectos de C#; por tanto, si quiero crear un nuevo objeto del tipo StringBuilder, o bien lo creo usando el nombre «completo» de la clase StringBuilder: System.Text.StringBuilder o bien declaro la importación del espacio de nombres System.Text y ya el compilador sabrá que StringBuilder está accesible (al revisar las clases contenidas en cada uno de los espacios de nombres que estén incluidos en las importaciones).

    ¡Eh! que no es una crítica, que me parece muy bien, y si ahora los del TEAM de C# lo están añadiendo como algo «predeterminado» será porque tampoco será tan malo, lo único que digo es que sigue siendo lo mismo que teníamos los «desarrollado-res» de Visual Basic desde hace ya casi 20 años… ¡NOVEDAD! WOW! 😉

    Total, a lo que iba, que ya no hace falta seguir dando nuevas características a Visual Basic, porque al final C# será como era Visual Basic hace 20 años… eso sí, con (entre otras cosas) acabando la línea con un punto y coma.

    Pero preferiría que Visual Basic evolucionara con las «nuevas características» realmente nuevas, para que a los que no nos gusta usar punto y coma para indicar que una instrucción ha finalizado o poner cosas entre llaves, etc., etc., y dejar de ver cómo C# evoluciona para hacer cosas que desde «siempre» ha hecho Visual Basic y, casi seguro, que era criticado por eso… Y si no me crees, échale un vistazo a las declaraciones sin tipos de variables (que, siempre al estilo de C#) en C# también lo añadieron en su día como novedad novedosa…

    Nos vemos.
    Guillermo