Etiqueta: elguille.info

  • Usar una class library desde proyecto de .NET MAUI

    Pues eso… algo tan simple como usar en .NET MAUI una DLL creada a partir de un proyecto del tipo class library, puede ser toda una odisea. Te lo explico para que te quede claro.

    Como ya sabrás, puedes crear proyectos del tipo class library para añadirlos como referencia a otros proyectos que usen esa DLL o biblioteca de clases. Algo que es bastante común en cualquier aplicación para .NET ya sea .NET Framework como para .NET a secas incluido los proyectos para aplicaciones móviles con Xamarin.Forms.
    En estes último, lo que se suele hacer es usar una DLL compilada para .NET Standard.
    Hasta aquí todo bien.

    La idea de usar una biblioteca de clases es para reutilizar el código en proyectos diferentes, es decir, creas la biblioteca de clases con cierta funcionalidad y esa misma biblioteca de clases la utilizas en proyectos diferentes. Al menos si esos tipos de proyectos son compatibles en el sentido de usar el mismo .NET.

    Y como ahora estoy haciendo pruebas de Google Cloud Natural Language, pues pensé crear algún proyecto para .NET MAUI que usara esa API. Y como ya tenía el código de ciertas clases creado como proyecto DLL (Class Library) pensé agregar la referencia al proyecto de .NET MAUI y… ¡yaumate! (una expresión de mi zona que quiere decir algo así como… ¡tararí que te vi! o… ¡que te lo has creído!)

    ¡Y así fue! ¡Me lo creí! Pensaba que en .NET MAUI las cosas seguirían siendo como en el resto de .NET, pero no…

    De hecho, hasta creé una class library usando la plantilla de MAUI, pero ni por esas… el proyecto de .NET MAUI nada más que daba errores de que no se podía tener referencia a esas clases definidas en la DLL (o class library).

    La solución que tomé fue añadir directamente el código de esas clases en el mismo proyecto de .NET MAUI y así funcionó, pero no era eso lo que yo pretendía, ya que además del proyecto para .NET MAUI tenía otros proyectos: de tipo consola de Windows Forms para C# y Visual Basic y en todos ellos pretendía usar la misma DLL o biblioteca de clases.

    Pero la solución buena ha sido creando una DLL (proyecto del tipo Class Library), crear un paquete de NuGet y usar ese paquete como referencia en lugar de una referencia al proyecto de tipo class library.

    Decirte que esa referencia, al proyecto, sí que funciona en los proyectos de tipo consola o de tipo Windows Forms, tanto para VB como para C#, pero no si el proyecto es de .NET MAUI.

    Te lo explico por si alguna vez te pasa esto… para que no te calientes la cabeza ni pierdas todo el tiempo que yo he perdido.

    Y para muestra, el proyecto ElizaNET y el correspondiente Eliza MAUI (los enlaces van al repositorio de GitHub), que ambos usan una DLL compilada para .NET 7.0 y que funcionan a la perfección (salvo los bugs que se puedan producir en esa biblioteca de clases, que algunos pueden surgir).

    Esos dos proyectos usan el código publicado en NuGet de Eliza gcnl Library que ahora va por la versión 1.0.2.

    El código fuente de esa DLL (o paquete de NuGet) está en este enlace (dentro del repositorio ElizaNET).

    Y esto es todo… espero que te sea de utilidad. Esa es la intención.

    Nos vemos.
    Guillermo

  • Ejemplos de Google Cloud Natural Language para consola y .NET MAUI

    Pues eso… aquí te dejo el código de una clase (Frases) para analizar un texto usando la API de Google Cloud Natural Language y un par de proyectos para usar esa clase. Los proyectos son para una aplicación de consola y para dispositivos usando .NET MAUI. Todo el código está para C#.

    No te voy a explicar mucho por aquí, salvo lo indicado en el siguiente párrafo, pero te dejo todo el código fuente (para C#) en este repositorio de GitHub.
    También incluyo algunas explicaciones y problemas que he tenido para usar la clase en el proyecto para .NET MAUI (no muchos, pero…)

    Algunos trucos en el código de .NET MAUI

    Un par de cosas que siempre suelo poner en los proyectos para .NET MAUI, porque no tienen la misma funcionalidad que con Xamarin.Forms, son:

    1. Definir el tamaño de la ventana para Windows, ya que en .NET MAUI la ventana se muestra enorme y no recuerda el tamaño último.
      Esto lo hago en el constructor de la clase App.
    2. Hacer que el control Frame se vea al completo (no se corte por la parte inferior).
      Esto lo consigo si en el StackLayout usado después del Frame se le deja un margin mínimo de 2.

    Además, he añadido el código para simular un Expander ver figura 1).
    Este expander lo utilizo para mostrar u ocultar la lista de textos de prueba.

    Otra cosa interesante es usar un objeto Task (usar otro proceso) cuando se pulsa en el botón de analizar, con idea que se muestre el texto mientras está analizando el texto y no se quede «congelada» la ventana.

    Este es el código del evento Clicked del botón de analizar:

    private async void BtnAnalizar_Clicked(object sender, EventArgs e)
    {
        txtResultado.Text = "";
    
        string tmp = txtTexto.Text;
        if (string.IsNullOrEmpty(tmp))
        {
            MostrarAviso("Por favor indica el texto a analizar de al menos 3 caracteres", esError: true);
            txtTexto.Focus();
            return;
        }
    
        text = tmp;
        HabilitarBotones(false);
    
        await Task.Run(() =>
        {
            MostrarAviso("Analizando el texto...", esError: false);
            frase = Frases.Add(text);
    
            BtnMostrar2.Dispatcher.Dispatch(() =>
            {
                // Inicialmente mostrar todo sin tokens
                BtnMostrar2_Clicked(null, null);
            });
            QuitarAviso();
        });
    
        HabilitarBotones(true);
    

    En los métodos llamados desde Task.Run se tienen en cuenta el Dispatcher de los controles que se modifican, con idea de que no den problemas al hacerlo entre hilos diferentes.

    Este es el código de los métodos QuitarAviso y MostrarAviso que modifican una etiqueta y un StackLayout.

    private void QuitarAviso()
    {
        LabelAviso.Dispatcher.Dispatch(() => { LabelAviso.IsVisible = false; });
        grbAviso.Dispatcher.Dispatch(() => { grbAviso.BackgroundColor = Colors.Transparent; });
    }
    
    private void MostrarAviso(string aviso, bool esError)
    {
        grbAviso.Dispatcher.Dispatch(() =>
        {
            if (esError)
            {
                grbAviso.BackgroundColor = Colors.Firebrick;
            }
            else
            {
                grbAviso.BackgroundColor = Colors.SteelBlue;
            }
        });
        LabelAviso.Dispatcher.Dispatch(() =>
        {
            LabelAviso.Text = aviso;
            LabelAviso.IsVisible = true;
        });
    

    Algunas capturas

    Aquí tienes un par de capturas de la app para .NET MAUI en funcionamiento, en la figura 1 está funcionando en Windows (usando el expander), en la figura 2 antes de poner el expander y en la figura 3 en un móvil con Android (antes de poner el expander), en iPhone no me funciona (tampoco el resto de los proyectos que tenía, así que, no he podido hacer captura).

    Figura 1. En Windows con el expander

    Figura 2. En Windows

    Figura 3. En Android

    Te recomiendo que leas el post anterior para ver cómo crear un cliente de Google Cloud Natural Language y poder usarlo en estos proyectos, en ese post indico que el código es para Visual Basic, pero los pasos a seguir son los mismos para Visual Basic que para C#.


    Espero que te sea de utilidad.

    Nos vemos.
    Guillermo

  • Google Cloud Natural Language, ejemplo en Visual Basic .NET

    Pues eso… aquí te dejo un ejemplo para usar las API de Google Cloud Natural Language, pero para Visual Basic .NET

    Con esas API podrás analizar textos (también en español) y ver las palabras que la forman (tokens), su estructura sintáctica, etc.

    Nota:
    Para usar este código tendrás que crearte una cuenta en Google Cloud, generar una «key» para usarla y poco más, todos los pasos están explicados en este enlace (el código de ejemplo es para C#, pero te servirá.

    Pasos para crear un proyecto usando dotnet (cli):

    – Abre una ventana de consola (o terminal)
    – Posicionarse en la carpeta donde crear el proyecto
    – Crear el proyecto
    dotnet new console -n <nombre-proyecto>
    dotnet new console -lang VB -n <nombre-proyecto>
    – Cambiar al directorio del proyecto
    cd <nombre-proyecto>
    – Añadir el paquete de Google Cloud Natural Language API
    dotnet add package Google.Cloud.Language.V1
    – Copiar el fichero key.json con las claves y permisos
    – Ver estos pasos para crearla:
    https://codelabs.developers.google.com/codelabs/cloud-natural-language-csharp#3
    – En IAM, añadir la cuenta creada (incluida en el fichero key.json) en +OTORGAR ACCESO
    Solo estará la principal y/o las otras añadidas
    – Si se ha usado otra cuenta, estará en IAM>Cuentas de Servicio
    – Modificar Program.cs (o Program.vb) para usar el código que accede a la API de Natural Language
    – Ejecutar el código
    dotnet run

    Notas:
    – Debes crear una variable de entorno en Windows, (lo puedes hacer desde la misma consola) indicando el path donde estará el fichero key.json.
    – Lo que yo hago es copiar ese fichero en la carpeta del ejecutable y la variable de entorno la defino de esta forma:
    set GOOGLE_APPLICATION_CREDENTIALS=key.json
    – Puedes modificar el fichero del proyecto y añadir lo siguiente:

      <ItemGroup>
        <None Update="key.json">
          <CopyToOutputDirectory>Always</CopyToOutputDirectory>
        </None>
      </ItemGroup>
    

    El código de ejemplo

    '--------------------------------------------------------------------------------
    ' Ejemplo de Google Cloud Natural Language en Visual Basic .NET (31/ene/23 18.50)
    '
    ' (c)Guillermo Som (Guille), 2023
    '--------------------------------------------------------------------------------
    
    Imports System
    Imports System.Text
    Imports gcl = Google.Cloud.Language.V1
    Imports Google.Protobuf.Collections
    Imports Google.Cloud.Language.V1.AnnotateTextRequest.Types
    
    'Namespace NaturalLanguageApiDemo
    Class Program
        Shared client As gcl.LanguageServiceClient '?
    
        Shared Sub Main(args As String())
            'Dim text = "El 8 de Febrero voy en bici al Camino de Santiago desde Sarria ¿crees que aguantaré?"
            Dim text = "Probando Google Cloud Natural Language con VB.NET ¿Funcionará esto?"
    
            Console.WriteLine("Ejemplos de Google.Cloud.Language")
            Console.WriteLine()
            Console.WriteLine("Pruebas de Google Cloud Natural Language en Visual Basic .NET")
            Console.WriteLine()
            Console.WriteLine("  Creando el cliente...")
            client = gcl.LanguageServiceClient.Create()
            Console.WriteLine()
    
            Dim repitiendo As Boolean = False
    
            Do
    
                If repitiendo Then
                    Console.WriteLine($"Última: '{text}'")
                    Console.WriteLine("Indica la frase que quieres analizar (0 salir, [la última])")
                Else
                    Console.WriteLine($"Predeterminada: '{text}'")
                    Console.WriteLine("Indica la frase que quieres analizar (0 salir, [predeterminada])")
                End If
    
                Console.Write("> ")
                Dim resText = Console.ReadLine()
    
                If Not String.IsNullOrEmpty(resText) Then
    
                    If resText = "0" Then
                        Exit Do
                    End If
    
                    text = resText
                End If
    
                Do
                    Console.WriteLine($"Analizar: '{text}'")
                    Console.Write("1- Todo con tokens, 2- Todo sin tokens, 3- Solo tokens, 0- nueva frase [2] ? ")
                    resText = Console.ReadLine()
                    Console.WriteLine()
    
                    If String.IsNullOrEmpty(resText) Then
                        resText = "2"
                    End If
    
                    If resText = "1" Then
                        Analizar(text, conTokens:=True)
                    ElseIf resText = "2" Then
                        Analizar(text, conTokens:=False)
                    ElseIf resText = "3" Then
                        AnalizarTokens(text)
                    ElseIf resText = "0" Then
                        Exit Do
                    End If
    
                    Console.WriteLine()
                Loop While True
    
                repitiendo = True
            Loop While True
        End Sub
    
        Private Shared Sub Analizar(text As String, conTokens As Boolean)
            If client Is Nothing Then
                client = gcl.LanguageServiceClient.Create()
            End If
    
            Dim document = gcl.Document.FromPlainText(text)
            Dim response As gcl.AnnotateTextResponse
    
            Try
                response = client.AnnotateText(document, New Features With {
                        .ExtractSyntax = True,
                        .ExtractEntities = True,
                        .ExtractDocumentSentiment = True,
                        .ExtractEntitySentiment = True,
                        .ClassifyText = True
                    })
            Catch
                response = client.AnnotateText(document, New Features With {
                        .ExtractSyntax = True,
                        .ExtractEntities = True,
                        .ExtractDocumentSentiment = True,
                        .ExtractEntitySentiment = True
                    })
            End Try
    
            Dim sentiment = response.DocumentSentiment
            Console.WriteLine($"Detected language: {response.Language}")
            Console.WriteLine($"Sentiment Score: {sentiment.Score}, Magnitude: {sentiment.Magnitude}")
            Console.WriteLine("***Entities:")
            Dim entity1 As gcl.Entity = Nothing
    
            For Each entity0 In response.Entities
    
                If entity1 Is Nothing Then
                    entity1 = entity0
                Else
                    If entity0.Equals(entity1) Then Continue For
                End If
    
                Console.WriteLine($"Entity: '{entity0.Name}'")
                Console.WriteLine($"  Type: {entity0.Type},  Salience: {CInt((entity0.Salience * 100))}%")
    
                If entity0.Mentions.Count > 0 Then
                    Console.WriteLine($"  Mentions: {entity0.Mentions.Count}")
    
                    For Each mention In entity0.Mentions
                        Console.Write($"    Text: '{mention.Text.Content}' (beginOffset: {mention.Text.BeginOffset}),")
                        Console.WriteLine($" Type: {mention.Type}, Sentiment: {mention.Sentiment}")
                    Next
                End If
    
                If entity0.Metadata.Count > 0 Then
                    Console.WriteLine($"  Metadata: {entity0.Metadata}")
    
                    If entity0.Metadata.ContainsKey("wikipedia_url") Then
                        Console.WriteLine($"    URL: {entity0.Metadata("wikipedia_url")}")
                    End If
                End If
            Next
    
            Console.WriteLine("***Categories:")
    
            For Each cat In response.Categories
                Console.WriteLine($"Category: '{cat.Name}' (Confidence: {cat.Confidence})")
            Next
    
            Console.WriteLine("***Sentences:")
    
            For Each sentence In response.Sentences
                Console.WriteLine($" Sentence.Text.Content: '{sentence.Text.Content}'")
                Console.WriteLine($"   Sentence.Text.BeginOffset: {sentence.Text.BeginOffset}")
                Console.WriteLine($" Sentence.Sentiment .Magnitude: {sentence.Sentiment.Magnitude}, .Score: {sentence.Sentiment.Score}")
            Next
    
            If conTokens Then
                Console.WriteLine("***Tokens:")
    
                For i As Integer = 0 To response.Tokens.Count - 1
                    MostrarToken(i, response.Tokens, conContenido:=False)
                Next
            End If
        End Sub
    
        Private Shared Sub AnalizarTokens(text As String)
            If client Is Nothing Then
                client = gcl.LanguageServiceClient.Create()
            End If
    
            Dim document = gcl.Document.FromPlainText(text)
            Dim response As gcl.AnnotateTextResponse
    
            Try
                response = client.AnnotateText(document, New Features With {
                        .ExtractSyntax = True,
                        .ExtractEntities = True,
                        .ExtractDocumentSentiment = True,
                        .ExtractEntitySentiment = True,
                        .ClassifyText = True
                    })
            Catch
                response = client.AnnotateText(document, New Features With {
                        .ExtractSyntax = True,
                        .ExtractEntities = True,
                        .ExtractDocumentSentiment = True,
                        .ExtractEntitySentiment = True
                    })
            End Try
    
            AnalizarSentecias(response)
        End Sub
    
        Private Shared Sub AnalizarSentecias(self As gcl.AnnotateTextResponse)
            Dim index As Integer = 0
    
            For Each sentence In self.Sentences
                Dim content = sentence.Text.Content
                Dim sentence_begin = sentence.Text.BeginOffset
                Dim sentence_end = sentence_begin + content.Length - 1
    
                While index < self.Tokens.Count AndAlso self.Tokens(index).Text.BeginOffset <= sentence_end
                    MostrarToken(index, self.Tokens, conContenido:=True)
                    index += 1
                End While
            Next
        End Sub
    
        Private Shared Sub MostrarToken(nToken As Integer, tokens As RepeatedField(Of gcl.Token), Optional conContenido As Boolean = True)
            Dim token As gcl.Token = tokens(nToken)
            Console.WriteLine($"{nToken}- Token: Text.Content: '{token.Text.Content}', Lemma: '{token.Lemma}'")
    
            If token.DependencyEdge.Label = gcl.DependencyEdge.Types.Label.Root Then
                Console.Write($"  **DependencyEdge Label: {token.DependencyEdge.Label}")
    
                If token.DependencyEdge.HeadTokenIndex <> nToken Then
                    Console.Write($", HeadTokenIndex: {token.DependencyEdge.HeadTokenIndex}")
                End If
    
                Console.WriteLine("**")
            Else
                Console.Write($"  DependencyEdge Label: {token.DependencyEdge.Label}, HeadTokenIndex: {token.DependencyEdge.HeadTokenIndex}")
                Dim tokenDependency = tokens(token.DependencyEdge.HeadTokenIndex)
                Console.WriteLine($" ('{tokenDependency.Text.Content}')")
            End If
    
            If conContenido Then
                Console.WriteLine($"  PartOfSpeech:")
                Console.Write($"    Tag: {token.PartOfSpeech.Tag},")
                Dim sb = New StringBuilder()
    
                If token.PartOfSpeech.Aspect <> gcl.PartOfSpeech.Types.Aspect.Unknown Then
                    sb.Append($" (Aspect: {token.PartOfSpeech.Aspect},")
    
                    If token.PartOfSpeech.[Case] <> gcl.PartOfSpeech.Types.[Case].Unknown Then
                        sb.Append($" Case: {token.PartOfSpeech.[Case]},")
                    End If
    
                    If token.PartOfSpeech.Form <> gcl.PartOfSpeech.Types.Form.Unknown Then
                        sb.Append($" Form: {token.PartOfSpeech.Form},")
                    End If
    
                    If sb.ToString().EndsWith(","c) Then
                        sb.Length -= 1
                    End If
    
                    sb.Append("),")
                End If
    
                If token.PartOfSpeech.Gender <> gcl.PartOfSpeech.Types.Gender.Unknown Then
                    sb.Append($" (Gender: {token.PartOfSpeech.Gender},")
    
                    If token.PartOfSpeech.Mood <> gcl.PartOfSpeech.Types.Mood.Unknown Then
                        sb.Append($" Mood: {token.PartOfSpeech.Mood},")
                    End If
    
                    If token.PartOfSpeech.Number <> gcl.PartOfSpeech.Types.Number.Unknown Then
                        sb.Append($" Number: {token.PartOfSpeech.Number},")
                    End If
    
                    If sb.ToString().EndsWith(","c) Then
                        sb.Length -= 1
                    End If
    
                    sb.Append("),")
                End If
    
                If token.PartOfSpeech.Proper <> gcl.PartOfSpeech.Types.Proper.Unknown Then
                    sb.Append($" Proper: {token.PartOfSpeech.Proper}")
                End If
    
                If sb.ToString().Trim().Length > 0 Then
                    Console.WriteLine(sb.ToString().TrimEnd(","c))
                End If
    
                sb.Clear()
                sb.Append("   ")
    
                If token.PartOfSpeech.Person <> gcl.PartOfSpeech.Types.Person.Unknown Then
                    sb.Append($" Person: {token.PartOfSpeech.Person},")
                End If
    
                If token.PartOfSpeech.Reciprocity <> gcl.PartOfSpeech.Types.Reciprocity.Unknown Then
                    sb.Append($" Reciprocity: {token.PartOfSpeech.Reciprocity},")
                End If
    
                If token.PartOfSpeech.Tense <> gcl.PartOfSpeech.Types.Tense.Unknown Then
                    sb.Append($" Tense: {token.PartOfSpeech.Tense},")
                End If
    
                If token.PartOfSpeech.Voice <> gcl.PartOfSpeech.Types.Voice.Unknown Then
                    sb.Append($" Voice: {token.PartOfSpeech.Voice}")
                End If
    
                If sb.ToString().Trim().Length > 0 Then
                    Console.WriteLine(sb.ToString())
                End If
            Else
                Console.WriteLine($"  PartOfSpeech Aspect: {token.PartOfSpeech.Aspect}, Case: {token.PartOfSpeech.[Case]}, Form: {token.PartOfSpeech.Form}")
                Console.WriteLine($"  PartOfSpeech Gender: {token.PartOfSpeech.Gender}, Mood: {token.PartOfSpeech.Mood}, Number: {token.PartOfSpeech.Number}")
                Console.WriteLine($"  PartOfSpeech Person: {token.PartOfSpeech.Person}, Proper: {token.PartOfSpeech.Proper}")
                Console.WriteLine($"  PartOfSpeech Reciprocity: {token.PartOfSpeech.Reciprocity}, Tag: {token.PartOfSpeech.Tag}")
                Console.WriteLine($"  PartOfSpeech Tense:: {token.PartOfSpeech.Tense}, Voice: {token.PartOfSpeech.Voice}")
            End If
        End Sub
    End Class
    'End Namespace

    Una captura

    La aplicación en funcionamiento

    Código fuente

    El código fuente del ejemplo para Visual Basic .NET, así como el de C#, los puedes ver/descargar desde este repositorio en GitHub.

    Y esto ha sido todo amigos… 😉

    Nos vemos.
    Guillermo