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
ModuleProgram'''<summary>''' El objeto HttpClient se recomiendo instanciarlo solo 1 vez en la aplicación.'''</summary>PrivateReadOnly ClienteHttp AsNew System.Net.Http.HttpClient()
Sub Main(args AsString())
'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()
EndSubPrivateAsyncSub 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 ThenConsole.WriteLine("Descarga completada.")
' Mostrar el contenido del fichero local.Process.Start("notepad", ficLocal)
EndIfConsole.WriteLine()
Console.WriteLine("Pulsa INTRO para finalizar.")
EndSub'''<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>PublicAsyncFunction DownloadFileAsync(ficWeb AsString, ficDest AsString) AsTask(OfBoolean)
Try' Simplificando la descarga.Dim contenido = Await ClienteHttp.GetByteArrayAsync(ficWeb)
' Si se ha podido descargar.If contenido IsNotNothingAndAlso contenido.Length > 0 Then' Guardarlo en el fichero de destino.' Si el fichero destino existe, se sobreescribe.Using fs AsNew System.IO.FileStream(ficDest, System.IO.FileMode.Create,
System.IO.FileAccess.Write,
System.IO.FileShare.None)
Await fs.WriteAsync(contenido.AsMemory(0, contenido.Length))
EndUsingElseConsole.WriteLine("No se ha podido descargar.")
ReturnFalseEndIfCatch ex AsException' Se ha producido un error al descargar o guardar.Console.WriteLine("Error: {0}", ex.Message)
ReturnFalseEndTryReturnTrueEndFunctionEndModule
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
{
classProgram
{
///<summary>/// El objeto HttpClient se recomiendo instanciarlo solo 1 vez en la aplicación.///</summary>privatereadonlystatic System.Net.Http.HttpClient ClienteHttp = new();
// En C# 7.1 y superior se puede usar Main como Task y async.staticasyncTask 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>///<paramname="ficWeb">El fichero a descargar (de una dirección URL).</param>///<paramname="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>publicasyncstaticTask<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.");
returnfalse;
}
}
catch (Exception ex)
{
// Se ha producido un error al descargar o guardar.Console.WriteLine("Error: {0}", ex.Message);
returnfalse;
}
returntrue;
}
}
}
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.
Pues eso… aunque he leído por ahí que no es conveniente detectar si se está navegando en un dispositivo móvil… eso era porque lo hacía con código duro (hard-code) es decir, detectar según el valor devuelto por userAgent de window.navigator, que sí, que puede producir resultados no deseados… pero… si usas Request.Browser.IsMobileDevice la cosa cambia.
Y hacer esa comprobación es bien simple. ¿Dónde hacerla? En cualquier parte del código «script» de tu página ASP.NET (de .NET Framework), ya sea que estés usando Visual Basic o C# (o incluso otros lenguajes que soporten las páginas web asp.net de .net framework).
Un par de ejemplos, por favor
Esta es una página .aspx con código para C#:
<%@PageLanguage="C#"AutoEventWireup="true" %>
<scriptrunat="server">protectedvoid Page_Load(object sender, EventArgs e)
{
if (Request.Browser.IsMobileDevice)
{
LabelDesktop.Visible = false;
LabelMobile.Visible = true;
}
else
{
LabelDesktop.Visible = true;
LabelMobile.Visible = false;
}
}
</script><!DOCTYPEhtml><htmlxmlns="http://www.w3.org/1999/xhtml"><headrunat="server"><title></title></head><bodystyle="font-family:Consolas"><formid="form1"runat="server"><div><asp:Labelrunat="server"ID="LabelMobile"Visible="false"Font-Size="xx-Large"Font-Bold="true"Text="Estas viendo esto en el navegador de un dispositivo móvil."/><asp:Labelrunat="server"ID="LabelDesktop"Visible="false"Font-Size="xx-Large"Font-Bold="true"Text="Estas viendo esto en el navegador de escritorio."/></div></form></body></html>
Esta es una página .aspx con código para Visual Basic:
<%@PageLanguage="VB"AutoEventWireup="true" %>
<scriptrunat="server">ProtectedSub Page_Load(sender AsObject, e AsEventArgs)
If Request.Browser.IsMobileDevice Then
LabelDesktop.Visible = False
LabelMobile.Visible = TrueElse
LabelDesktop.Visible = True
LabelMobile.Visible = FalseEndIfEndSub</script><!DOCTYPEhtml><htmlxmlns="http://www.w3.org/1999/xhtml"><headrunat="server"><title></title></head><bodystyle="font-family:Consolas"><formid="form1"runat="server"><div><asp:Labelrunat="server"ID="LabelMobile"Visible="false"Font-Size="xx-Large"Font-Bold="true"Text="Estas viendo esto en el navegador de un dispositivo móvil."/><asp:Labelrunat="server"ID="LabelDesktop"Visible="false"Font-Size="xx-Large"Font-Bold="true"Text="Estas viendo esto en el navegador de escritorio."/></div></form></body></html>
Nota: Puedes tener las dos páginas en una misma aplicación o proyecto WEB de Visual Studio sin necesidad de hacer nada especial, solo tener las páginas en un sitio que acepte .NET Framework.
A tener en cuenta
Si muestras la páginas en un dispositivo móvil, es posible que el navegador te permita ver la página como «escritorio», en ese caso, el valor que devuelve es escritorio, no que es móvil. En las siguientes capturas tienes la demostración. En esas capturas estoy usando el Edge para Android en un Google Pixel 4a.
En la figura 1 estoy mostrando la página versión de Visual Basic en el móvil, que si quieres la puedes probar usando el enlace mostrado (está alojada en mi sitio: elguille.info/WebFormVB.aspx).
Figura 1.
En la figura 2 te muestro la versión para C# antes de cambiarla a modo escritorio. También puedes probarla usando este enlace en mi sitio: elguille.info/WebFormCS.aspx.
Figura 2. Mostrando la página como móvil y queriendo cambiar a versión para ordenador
Figura 3. Mostrando la página como si estuviese en escritorio y queriendo cambiar a versión apra sitio móvil.
Nota: También puedes probarlo en el navegador de escritorio usando las herramientas de desarrollador, que en Edge y Chrome se pueden acceder usando Ctrl+Shift+I. Y desde esas herramientas puedes indicar que se muestre como si fuese en un móvil (ver la figura 4).
Figura 4. Usando las herramientas de desarrollador puedes ver en el escritorio cómo sería usarlo en un móvil… o casi.
Pues eso… esto es de hace unos años, del 4 de junio de 2015 concretamente (según me ha dicho Luis, porque yo ya no lo recordaba), y aunque la entrevista/charla se hizo de una vez, él las publicó (en modo podcast, audio) en dos partes.
Pero como las he buscado y no las he encontrado ni en mi sitio (elguille.info) ni en este blog, lo publico ahora aunque sea después de tanto tiempo ya que considero que deben estar «localizables» porque fueron las primeras :-). Así podrás saber más cosas sobre mí… aunque a fecha de hoy ya hay 4 charlas en YouTube, es conveniente que estos dos podcasts también estén visibles al buscar las charlas o entrevistas que me han hecho.
Así que… aquí tienes los enlaces a estos dos podcasts con la entrevista de Luis del Valle Hernández que me hizo el 4 de junio de 2015:
Y ya que estamos con los enlaces, te pongo también los 4 de YouTube que me han hecho entre junio de 2020 y abril de 2021 (en plena pandemia del COVID-19):
Pues eso… Marcelo de SevillaDotNet me ha invitado a un café virtual el próximo miércoles día 7 de abril a las 18:30 hora de la España peninsular (una hora menos en Canarias), así que… si nos quieres acompañar regístrate y asiste siguiendo este enlace: Café Virtual con Guillermo Som.
Nota del martes 6 de abril Si quieres, también puedes verlo en directo por YouTube o bien usar este mismo enlace para después de mañana 7 de abril de 2021:
Si quieres ver qué hora es ahora en España (Madrid) sigue el enlace y así sabrás qué hora será en tu país 😉 Me comentan que al pulsar el enlace te muestra la hora en tu ciudad.
Este es el resumen de la charla cafetera 🙂
En este café virtual vamos a charlar con Guillermo Som. Tras tantos años participando y ayudándonos a todos con su sitio elguille.info, probablemente te haya ayudado en algún momento. Vamos a charlar sobre sus inicios en la informática, sobre el camino que él ha tenido cómo programador, de .NET y su evolución y mucho más.
En esa ocasión me daba error la aplicación de Xamarin.Forms y sin saber porqué… Pero la he vuelto a probar y por ahora, al menos en el IDE de Visual Studio 2019 va bien. De todas formas, ya sabes si usas lo que te voy a explicar aquí, y ves que la aplicación casca, ya sabes porqué es.
La documentación (en inglés) dice esto:
For performance reasons, you should call this method only when you do not know at design time what assembly is currently executing. The recommended way to retrieve an Assembly object that represents the current assembly is to use the Type.Assembly property of a type found in the assembly.
Que en el idioma actualizado de Cervantes viene a decir esto (según Google Translator):
Por motivos de rendimiento, debe llamar a este método solo cuando no sepa en tiempo de diseño qué ensamblado se está ejecutando actualmente. La forma recomendada de recuperar un objeto Ensamblado que representa el ensamblado actual es usar la propiedad Type.Assembly de un tipo que se encuentra en el ensamblado.
Aquí te voy a poner el código (tanto de Visual Basic como de C#) para usar esta forma recomendada de asignar un ensamblado (de la clase System.Reflection.Assembly).
Este código está en bibliotecas (proyectos del tipo Class Library) para usar con .NET Standard 2.0 (de esta forma los ensamblados se podrán usar tanto en .NET Core como en .NET Framework).
Ejemplo para C#
publicstaticstring VersionDLL()
{
var ensamblado = typeof(AboutViewModel).Assembly;
var fvi = FileVersionInfo.GetVersionInfo(ensamblado.Location);
// FileDescription en realidad muestra (o eso parece) lo mismo de ProductName
var s = $"{fvi.ProductName} v{fvi.ProductVersion} ({fvi.FileVersion})" +
$"\r\n{fvi.Comments}";
return s;
}
Ejemplo para Visual Basic
PublicFunction VersionDLL() AsString
Dim ensamblado = GetType(DatosMostrar).Assembly
Dim fvi = FileVersionInfo.GetVersionInfo(ensamblado.Location)
' FileDescription en realidad muestra (o eso parece) lo mismo de ProductName
Dim s = $"{fvi.ProductName} v{fvi.ProductVersion} ({fvi.FileVersion})" &
$"{vbCrLf}{fvi.Comments}"
Return s
EndFunction
Ese código mostrará la versión del ensamblado, la versión del fichero y la descripción, aunque en realidad a la propiedad a la que tienes que acceder es a Comments, ya que FileDesciption muestra lo mismo que ProductName.
Y aquí tienes una captura para la aplicación de Android con los comentarios de las 3 bibliotecas que estoy usando, 2 de ellas escritas con Visual Basic y la tercera (la que le da funcionalidad visual a la aplicación está escrita en C#).
Figura 1. Captura en el emulador de Android.
Y esta otra captura es de la aplicación en el emulador (local) de UWP (Universal Windows Platform) pero con la aplicación real. Aunque en modo depuración.
Figura 2. Captura en el (emulador) de UWP.
Espero que te sea de utilidad… Esa es siempre la idea…
Nos vemos. Guillermo
P.S. El puñetero Jetpack agrega código (de más) a las imágenes y cuando estas se muestran desde www.elguille.info, simplemente no se ven… pero… puedes pulsar en el sitio (en blanco) en el que está la imagen y te la mostrará… Lo mismo quito el Jetpack, que, aparte del «bloque» Markdown y el poder publicar en twitter y mi sitio de facebook, no me sirve de mucho…
Pues eso… aquí tienes una utilidad para cambiar los nombres de los ficheros. No solo cambiar un texto existente en el nombre, si no que también se puede añadir (o quitar) un texto al principio o al final del nombre del fichero.
Los ficheros a procesar los puedes indicar según la extensión de los mismos (o usando un filtro con * y/o ?. En total se pueden procesar 4 directorios/filtros.
En la figura puedes ver la aplicación en modo de ejecución.
Funcionamiento de la aplicación
Como puedes ver en la figura, tiene 4 grupos de directorios/ficheros con idea de que puedas indicar varias extensiones o bien 4 directorios diferentes.
Cada directorio se puede o no procesar (el primero siempre se tendrá en cuenta), por supuesto, si no indicas el path ni la extensión no se procesarán (o así debería ser, pero en realidad no hago ningana comprobación al respecto, así que… para salir de dudas… ¡pruébalo!).
En la parte inferior tienes las opciones de qué hará la aplciación, teniendo estas opciones:
Añadir el texto indicado en Text 1 al principio.
Añadir el texto indicado en Text 1 al final.
Quitar el texto indicado en Text 1 del principio.
Quitar el texto indicado en Text 1 del final.
Cambiar lo que haya en Text 1 por lo que hay en Texto 2
Y para las dos opciones de añadir texto puedes indicar si no se hace el cambio en el caso de que dicho texto (el de Texto 1) ya está.
Cuando se selecciona alguna de las dos opciones de añadir, se habilita ese control. En el resto de opciones está deshabilitado.
Lo mismo ocurre con el texto de Texto 2, que se habilitará solo si se selecciona la opción de cambiar, en las otras 4 opciones estará deshabilitado, y por tanto no se tendrá en cuenta lo que se escriba.
En las cajas de textos (Texto 1 y Texto 2) se pueden indicar espacios al final (y se tendrán en cuenta en la búsqueda y/o reeemplazo) y como visiblemente es difícil de ver si hay uno o más espacios, en caso de que haya más de un espacio al final, el programa los quitará y dejará solo uno. Si no hay espacios al final del texto no se añadirá ninguno.
Nota:
Hay que tener en cuenta, sobre todo al reemplazar texto (opción Cambiar) que el texto indicado en Texto 1 se reemplazará por el que haya en Texto 2 en todo el nombre del fichero (sin tener en cuenta ni el path ni la extensión).
Por tanto, si buscas el y el nombre original es Inclemencias del tiempo y quieres cambiar ese el por la el rtexto resultandte será (en mayúsculas los cambios realizados): IncLAmencias dLA tiempo.
El que avisa…
Controles usados en la utilidad
Esta utilidad (o aplciación) usa 4 controles del tipo BackgroundWorker con idea de usar de forma fácil la operación asíncrona.
El que está enlazado con el primer grupo (el que siempre está activo) siempre se ejecuta. Pero los otros 3 solo si se marca la opción que está arriba de cada grupo.
La barra de progreso tendrá en cuenta todos los ficheros que se vayan a procesar.
Y para saber el total de ficheros a procesar, en cada evento DoWork de cada control BackgroundWorker se añade a una colección definida a nivel del formulario en la que se almacenará los nombres de los ficheros a procesar (teniendo en cuenta el directorio en el que se buscarán los ficheros con el filtro indicado.
Ese filtro se hace usando lo indicado en cada caja de texto etiquetada tras Filtro. Como se pueden usar los comodines * y ? podrás usar cosas como estas:
*.txt para indicar todos los ficheros con la extensión .txt
algo?.xls* esto buscará los ficheros que empiecen con algo y cualquier carácter a continuación y que tengan una extensión que empiece por .xls
Como el código está compilado con .NET Framewwork 4.8 en realidad esto se puede hacer sin indicar el asterisco final, es decir, se puede indicar de esta forma: algo?.xls para que acepte todas las extensiones que empiecen por xls (.xls y .xlsx).
En .NET 5.0 habría que hacerlo como he comentado en el segundo punto, pero no es este el caso de esta aplicación.
Aquí te muestro el código del evento Click del botón Procesar:
PrivateSub btnProcesar_Click(sender AsObject, e AsEventArgs) Handles btnProcesar.Click
ficheros.Clear()
' Si al final del texto a buscar/poner
' hay más de un espacio, cambiarlo por solo 1
If txtTexto1.Text.EndsWith(" ") Then
txtTexto1.Text = txtTexto1.Text.TrimEnd(" "c) & " "
EndIf
If txtTexto2.Text.EndsWith(" ") Then
txtTexto2.Text = txtTexto2.Text.TrimEnd(" "c) & " "
EndIf
If chkProces1.Checked Then
bgwProc1.RunWorkerAsync()
EndIf
If chkProces2.Checked Then
bgwProc2.RunWorkerAsync()
EndIf
If chkProces3.Checked Then
bgwProc3.RunWorkerAsync()
EndIf
' Este siempre se procesa
' Hacer esta llamada al final para que se restauren los valores
' al terminar el trabajo.
bgwProc.RunWorkerAsync()
EndSub
En los eventos DoWork de cada BackgroundWorker se hace la llamada al método … con el siguiente código:
Donde txtDir y txtExtensiones se usarán los nombres correwspondientes a cada grupo, por ejemplo, en el grupo 1 (el cero es el que siempre se ejecuta) se hará la siguiente llamada:
El código de backgroundWorker_DoWork es el siguiente, en donde solo se obtienen los ficheros que corresponden a cada directorio y con el filtro indicado.
'''<summary>
''' Método usado por el método DoWork de los BackgroundWorkers.
'''</summary>
'''<param name="txtDir">El directorio a usar.</param>
'''<param name="filtro">El filtro de los ficheros a usar.</param>
PrivateSub backgroundWorker_DoWork(txtDir AsTextBox,
filtro AsString)
' Aquí se acumulan los ficheros en la colección
' y se procesarán todos al final.
' De esta forma sabemos cuántos ficheros se procesan en total.
Dim fileEnum = System.IO.Directory.EnumerateFiles(txtDir.Text, filtro)
ficheros.AddRange(fileEnum)
EndSub
Una vez que finaliza el trabajo de bgwProc se procesan los ficheros.
Esto último se hace en el evento RunWorkerCompleted de ese control BackgroundWorker (en un momento verás el código).
Ahí se harán los cambios de todos los ficheros (que estarán en la colección ficheros), llamando al método cambiarNombres al que le pasamos como argumento el nombre del fichero a procesar.
Una vez finalizado los cambios, (y para que de tiempo a ver el mensaje), lanzo un temporizador después de tres segundos en el que se limpiarán los mensajes y se ocultará la barra de progreso.
Cabe decir que si alguno de los otros procesos dura má que este último puede que no se procesen bien todos los ficheros, pero… eso es algo que podemos arreglar en una futura revisión de este programa.
Y que a mí se me ocurre que se puede hacer comprobando cuando terminan todos los procesos asíncronos (comprobando cada uno de los eventos RunWorkerCompleted de cada tarea).
Pero por ahora te lo dejo como tarea que debes hacer y que podrás comprobar el día que yo lo publique.
Este es el código del método que hace todo esto (y como tip decirte que ese código tendrá que estar en otro sitio diferente si quieres tener en cuenta lo que he comentado en la nota anterior).
PrivateSub bgwProc_RunWorkerCompleted(sender AsObject,
e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles bgwProc.RunWorkerCompleted
' Procesar los ficheros acumulados en la colección
ProgressBar1.Visible = True
ProgressBar1.Maximum = ficheros.Count
ProgressBar1.Value = 0
For i = 0 To ficheros.Count - 1
cambiarNombres(ficheros(i))
ProgressBar1.Value = i + 1
Next
Dim s AsString
If ficheros.Count = 1 Then
s = "el nombre al fichero."
Else
s = $"los {ficheros.Count} nombres."
EndIf
LabelStatus.Text = $"Finalizado el proceso de cambiar {s}"
Application.DoEvents()
' Mostrar ese mensaje por 3 segundos
Timer1.Interval = 3000
Timer1.Enabled = True
EndSub
PrivateSub Timer1_Tick(sender AsObject, e AsEventArgs) Handles Timer1.Tick
Timer1.Enabled = False
ProgressBar1.Visible = False
LabelStatus.Text = LabelStatus.Tag.ToString()
Application.DoEvents()
EndSub
Por último te muestro el código del método cambiarNombres.
PrivateSub cambiarNombres(file AsString)
Dim fi AsNew System.IO.FileInfo(file)
Dim s AsString = file ' fi.FullName
LabelStatus.Text = $"{fi.Name}"
Application.DoEvents()
If optCambiar.Checked Then
s = fi.Name.Replace(txtTexto1.Text, txtTexto2.Text)
If s <> fi.Name Then
My.Computer.FileSystem.RenameFile(file, s)
EndIf
ElseIf optAñadirPrincipio.Checked Then
If chkNoDuplicar.Checked Then
If fi.Name.StartsWith(txtTexto1.Text) = FalseThen
s = txtTexto1.Text & fi.Name
My.Computer.FileSystem.RenameFile(file, s)
EndIf
Else
s = txtTexto1.Text & fi.Name
My.Computer.FileSystem.RenameFile(file, s)
EndIf
ElseIf optAñadirFinal.Checked Then
If chkNoDuplicar.Checked Then
If fi.Name.EndsWith(txtTexto1.Text) = FalseThen
s = fi.Name & txtTexto1.Text
My.Computer.FileSystem.RenameFile(file, s)
EndIf
Else
s = fi.Name & txtTexto1.Text
My.Computer.FileSystem.RenameFile(file, s)
EndIf
ElseIf optQuitarPrincipio.Checked Then
If fi.Name.StartsWith(txtTexto1.Text) Then
s = fi.Name.Replace(txtTexto1.Text, "")
My.Computer.FileSystem.RenameFile(file, s)
EndIf
ElseIf optQuitarFinal.Checked Then
If fi.Name.EndsWith(txtTexto1.Text) Then
s = fi.Name.Replace(txtTexto1.Text, "")
My.Computer.FileSystem.RenameFile(file, s)
EndIf
EndIf
LabelStatus.Text &= $" --> {s}"
Application.DoEvents()
EndSub
Y esto es todo por ahora… espero que te sea de utilidad, y si quieres indicar alguna mejora o algún fallo, eres libre de hacerlo en los comentarios de este post.
Muchas gracias.
Nos vemos. Guillermo
P.S.
Crearé la versión de C# de esta utilidad (por ahora solo la tengo en Visual Basic) y cuando la tenga finalizada, la publicaré también en GitHub.
Aunque esa versión de CSharp la haré con los cambios que antes te he indicado: para asegurar que todos los procesos asíncronos han finalizado.
P.S.2 (28-feb-2021 02:21)
Ya está el código modificado teniendo en cuenta que se finalicen todos los procesos de los BackgroundWorker y además con el código para C#.
Puedes ver el código fuente en GitHub.
Para hacer lo que comentaba más arriba: comprobar que todos los BackgroundWorkers han finalizado he modificado el código de forma que se hacen estas comprobaciones:
Defino dos variables a nivel de formulario para saber cuántos grupos se van a procesar (cada grupo se corresponde con un BackgroundWorker) y cuántos han finalizado.
En el evento RunWorkerCompleted se usa el siguiente código (te muestro el de C#):
privatevoid bgwProc_RunWorkerCompleted(object sender,
System.ComponentModel.RunWorkerCompletedEventArgs e)
{
cuantosFinalizados += 1;
// Cuando estén todos los procesos finalizados, hacer los cambios.
if (cuantosFinalizados == cuantosProcesos)
finalizarCopia();
}
El código que había antes en ese método de evento ahora está en el método finalizaCopia, tal como puedes ver en el siguiente código que es para C#.
privatevoid finalizarCopia()
{
// Procesar los ficheros acumulados en la colección
ProgressBar1.Visible = true;
ProgressBar1.Maximum = ficheros.Count;
ProgressBar1.Value = 0;
for (var i = 0; i <= ficheros.Count - 1; i++)
{
cambiarNombres(ficheros[i]);
ProgressBar1.Value = i + 1;
}
string s;
if (ficheros.Count == 1)
s = "el nombre al fichero.";
else
s = $"los {ficheros.Count} nombres.";
LabelStatus.Text = $"Finalizado el proceso de cambiar {s}";
Application.DoEvents();
// Mostrar ese mensaje por 3 segundos
timer1.Interval = 3000;
timer1.Enabled = true;
}
En el método de evento Click del botón Procesar ahora se tiene en cuenta las dos nuevas variables para saber cuántos grupos hay que tener en cuenta.
privatevoid btnProcesar_Click(object sender, EventArgs e)
{
ficheros.Clear();
cuantosFinalizados = 0;
cuantosProcesos = 1;
// Si al final del texto a buscar/poner
// hay más de un espacio, cambiarlo por solo 1
if (txtTexto1.Text.EndsWith(" "))
txtTexto1.Text = txtTexto1.Text.TrimEnd(' ') + " ";
if (txtTexto2.Text.EndsWith(" "))
txtTexto2.Text = txtTexto2.Text.TrimEnd(' ') + " ";
if (chkProces1.Checked)
{
cuantosProcesos += 1;
bgwProc1.RunWorkerAsync();
}
if (chkProces2.Checked)
{
cuantosProcesos += 1;
bgwProc2.RunWorkerAsync();
}
if (chkProces3.Checked)
{
cuantosProcesos += 1;
bgwProc3.RunWorkerAsync();
}
// Este siempre se procesa
// Hacer esta llamada al final para que se restauren los valores
// al terminar el trabajo.
bgwProc.RunWorkerAsync();
}
Y esto es todo…
En GitHub está el nuevo código para Visual Basic y el proyecto de C#.
P.S. 3 (01-mar-2021) Actualizado el código de VB y C# para que tenga en cuenta un par de fallillos que había: – al añadir/quitar de los nombres el texto indicado, ya que se usba fi.Name y ahí se incluye la extensión, y la extensión no se debe tener en cuenta al cambiar el nombre. – al marcar/desmarcar las opciones de los grupos, que se deshabilitaba todo, ahora solo se habilitan/deshabilitan los controles que contiene, salvo el propio CheckBox.
Pues eso… el otro día estaba escribiendo código para un nuevo sitio web de un colega (ConservasYoga.com.es) y me decidí a hacerlo en C# , por aquello de que creía que ya no existen plantillas (o eso creo ) en Visual Studio 2019 para ASP.NET con Visual Basic, pero sí existen, de las que no existen es para usar ASP.NET Core.
La cuestión es que quería acceder a ciertas propiedades (y/o métodos) de la master page y lo hice (o lo intenté) tal como lo hago con Visual Basic, es decir, usando Master.Propiedad, pero nada… daba error… después de muchas pruebas lo conseguí… algo rebuscado, pero… probando, probando… lo pude encontrar, y es que en C# para acceder a las cosas definidas en una página maestra hay que usar el nombre de la página maestra (en minúsculas) seguida de un guón bajo y la palabra master (también en minúsculas), es decir, si la página maestra se llama Site.master para acceder al código desde C# hay que usarlo de esta forma: site_master.
Un ejemplo de sitio usando Master Page en VB y C#
Para este ejemplo he optado por seleccionar un sitio en blanco: ASP.NET Empty Web Site (ver figura 1), ya que si se elige el tipo ASP.NET Web Forms Site te añade un montón de código y página, etc., que… en los hosting de ASP.NET que hay por esta zona no funcionan… y si intento que sea de ASP.NET Core ya ni te digo, ninguna de las empresas de hosting con las que he probado (Axarnet, IONOS, acens) lo soportan, incluso una de ellas me dijo que como es código abierto por eso no lo soportan… pero sí venden servidores con Linux, WordPress, PHP… que… ¡lo mismo no son de código abierto! 😉
En fin…
Figura 1. Crear un nuevo proyecto de C# (Empty Web Site)
Cuando trabajo con sitios de ASP.NET no me gusta usar el code behind, si no que prefiero que cada página tenga su código, de esa forma no es necesario compilar la aplicación, si no que se usa el código directamente en el sitio hospedado y ya está… el ASP.NET de IIS se encarga de compilar las páginas y el código a usar. Y lo mejor es que si haces cambios, solo tienes que subir la página o el fichero de código modificado y ya está… ¡a compilarlo tocan! pero… ¡que lo compile otro! 😉
En el segundo tipo de proyecto todo lo que añade el Visual Studio usa el code behind, mientras que en el proyecto vacío, cuando añadas una nueva página (maestra o normal) puedes indicar que no se incluya el código de forma separada (que es lo que viene a significar el code behind o separación entre el diseño y el código).
Si elegimos añadir una nueva página con el código incrustado en la propia página tendremos que hacer algo como lo mostrado en la figura 2.
Figura 2. Añadir nueva página con el código separado de la página aspx
En ese caso, se indica también seleccionar una página maestra para esa página aspx.
Y si decidimos que el código esté separado lo haremos como se muestra en la figura 3.
Figura 3. Nueva página con el código en la propia página.
Es decir, quitamos la marca de la casilla Place code in separate file.
Al añadir una página de esa forma tendremos esto en la página:
Donde CodeFile indica qué página es la que tiene el código y el Inherits es el nombre de la clase.
Si esto lo has hecho por error… puedes arreglarlo. ¿Cómo? Simplemente quitando todo lo que se indica en CodeFile y en Inherits y poniendo el código aparte, tal como te muestro a continuación:
Nota: Por cierto, esa página la he añadido al proyecto de C#, pero está usando el código de VB, y es porque yo, por error, he seleccionado una página de Visual Basic (tal como ves en la figura 3). Pero en el código mostrado lo he cambiado a C#.
Decir o aclarar que en un sitio web hecho con ASP.NET para .NET Framework podemos usar tanto código de VB como de C#, aunque no revueltos.
En este sitio que he creado, en el proyecto de C# uso una página con código de VB y otras dos con el código de C#.
En la página maestre he definido una propiedad con el título de la aplicación. En C# quedaría de esta forma:
En la de Visual Basic, esa misma propiedad la definimos como te muestro a continuación:
<scriptrunat="server">PublicSharedProperty AppName AsString = "Web Site Master VB"</script>
Como es una propiedad compartida, en C# se utiliza static y en VB se usa Shared.
Y para usarla desde C# lo haríamos de esta forma, por ejemplo para poner el título de la página 2:
<asp:ContentID="Content2"ContentPlaceHolderID="ContentPlaceHolder1"Runat="Server"><h2>Prueba2 en C# para <%=masterpage_master.AppName %> </h2></asp:Content>
Como dije al principio, en VB podemos usar Master para acceder al código de la página maestra, en C# no sepuede. Lo que también se puede en VB es usar la clase de la página maestra, es decir, tal como se hace en C#. Decirte que esto último hará que en VB no te muestre un warning que sí muestra cuando se accede a la página maestra usando Master (ver la figura 4).
Nota: No sé porque ahora me muestra ese warning, ya que siempre lo he usado así (con Master.Propiedad) y nunca había salido esa advertencia, pero bueno… si queremos compatibilidad entre los dos lenguajes, podemos hacerlo usando el nombre de la clase.
Figura 4. Desde C# no se puede usar Master si no el nombre de la clase de la página maestra
Resumiendo el acceso a las páginas maestras desde código
Mejor usar el nombre de la clase, tanto en C# (que es la única forma de ahcerlo, al menos que yo sepa) como en VB.
Te iba a comentar que, aparte de lo que ya hemos visto, desde un sitio web de asp.net con .NET Framework se puede usar tanto código de VB y de C# en conjunto, solo hay que poner dicho código en carpetas diferentes e indicarlo en el fichero Web.Config.
Usar código de VB y C# en un mismo sitio de ASP.NET Framework
Crea la carpeta de código App_Code, decide qué lenguaje será el que use las clases puestas en esa carpeta (normalmente el lenguaje con el que has creado el proyecto), crea una nueva carpeta (dentro de App_Code) para poner las clases del otro lenguaje.
Por ejemplo, si queremos que en la carpeta App_Code estén las clases de VB y en la carpeta c-sharp (App_Code\c-sharp), pondremos esto en el fichero web.config:
Este código estará dentro de la rama: <configuration><system.web>.
Para acceder a las clases o el código se hace de la forma habitual, en este ejemplo, he definido una propiedad estática/compartida para que se pueda acceder desde el código ASP.
Hay que tener en cuenta que C# distingue entre mayúsculas y minúculas, mientras que a VB le da igual como la escribamos.
<p>Usando el código definido en la carpeta <b>App_Code</b></p>
<p>Acceso al código de C# (class1.Nombre): <% =Class1.Nombre %> </p>
<p>Acceso al código de VB (class2.Nombre): <% =Class2.Nombre %> </p>
<p>En C# hay que usar correctamente el nombre: Class1 y Class2 (no class1/class2 como en VB).</p>
Y esto es todo… este es el código en GitHub por si le quieres echar un vistazo.
Cambios en el código de C# para que compile con C# 5.0
Al limpiar el proyecto de los «packages» que añade el Visual Studio, empiezan los errores, uno de ellos es que C# 5.0 no permite asignar valores a las auto-propiedades, por tanto, el código mostrado antes hay que sustituirlo por este otro:
El de la clase Class1.cs: (tanto en el proyecto de VB como en el de C#)
publicstaticstring Nombre
{
get { return"¡Hola Mundo de C#!"; }
}
El de la página maestra:
<scriptrunat="server">publicstaticstring AppName
{
get {return"Web Site Master C#"; }
}
</script>
El código de VB no hay que modificarlo, se ve que el compilador usado reconoce la autodefinición de propiedades con asignación de valores.
Ya estás en la segunda entrega de este Tutorial sobre el uso de las APIs de Google. Aquí te mostraré el código de Visual Basic y C# para acceder a los contactos de Google usando People API. Como te comenté en la priumera entrega sobre cómo crear un proyecto en Google Cloud, el API que usaremos es People API, no te confundas con Contacts API (como a mí me ocurrió) ya que parece que será la más adecuada, pero además de que Google no recomienda que se use Contacts API, la he probado y no he conseguido sacar prácticamente nada de información, y al que he sacado es de unos pocos contactos (que ni recordaba que los tenía creados).
Nota: Está uno acostumbrado a que te marquen los errores horrográficos que esto de que no te los marque es un Orror! 😛
Bueno vamos empezar con el código de C# que es lo que la gente suele ver en internet (de VB no he visto ni un ejemplo, así que… ya mismo lo verás en primicia).
Después de escribir el párrafo anterior he cambiado de opinión y te voy a enseñar conjuntamente el código de los dos lenguajes, para que vayas comparando.
Código de C# y VB para mostrar los contactos de una cuenta de Google usando People API
Nota: Debo decir que parte de este código lo he visto en algunos ejemplosencontrados tanto en la documentación de Google como en foros de C#. Pero principalmente de la documentación (escasa) de Google y en agunos casos, los ejemplos eran de java, ni siquiera de C#.
Como te comenté en el preámbulo del tutorial, debes añadir las referencias a los paquetes de NuGet que instalarán las APIs en tu proyecto. Yo he añadido todas las que te indiqué en esa primera página del tutorial. Por supuesto si quieres acceder, por ejemplo al calendario o a GMail, tendrás que añadir referencias a esas APIs, además de añadirlas al proyecto creado en Google Cloud Platform.
Veamos el código inicial (con las importaciones de los espacios de nombres) que será básicamente el mismo en todas las apliocaciones que hagas para acceder a las APIs de Google.
// Genéricasusing Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Util.Store;
// People APIusing Google.Apis.People.v1;
using Google.Apis.People.v1.Data;
// Docs APIusing Google.Apis.Docs.v1;
using Google.Apis.Docs.v1.Data;
// Drive APIusing Google.Apis.Drive.v3;
using Google.Apis.Drive.v3.Data; // Para el tipo Fileusing System;
using System.Collections.Generic;
//using System.IO;using System.Threading;
using System.Text;
using System.Linq;
'// GenéricasImports Google.Apis.Auth.OAuth2
Imports Google.Apis.Services
Imports Google.Apis.Util.Store
'// People APIImports Google.Apis.People.v1
Imports Google.Apis.People.v1.Data
'// Docs APIImports Google.Apis.Docs.v1
Imports Google.Apis.Docs.v1.Data
'// Drive APIImports Google.Apis.Drive.v3
Imports Google.Apis.Drive.v3.Data '// Para el tipo FileImports System
Imports System.Collections.Generic
Imports System.Threading
Imports System.Text
Imports System.Linq
Ahora vamos a crear una serie de campos estáticos/compartidos (static en C#, shared en VB) que usaremos también de forma genérica en todos los proyectos de acceso a Google APIs.
classProgram
{
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/docs.googleapis.com-dotnet-quickstart.json
staticstring[] Scopes = { DocsService.Scope.Documents,
DocsService.Scope.DriveFile,
PeopleService.Scope.ContactsReadonly };
staticstring ApplicationName = "Tutorial Google APIs VB";
// Los datos del proyecto creado para VB
staticClientSecrets secrets = newClientSecrets()
{
ClientId = "430211665266-t68vl99t2q40v3lbctgbph23j2644bpj.apps.googleusercontent.com",
ClientSecret = "Xqexl0FMPedNc1KYs0iJt22A"
};
staticDocsService docService;
staticDriveService driveService;
staticPeopleService peopleService;
staticUserCredential Credential;
Lo que ese primer comentario indica (es de un quickstart de Google) es que si cambias los valores del array Scopes debes eliminar el directorio creado en Documentos\.credentials (ahora verás porqué).
El array Scopes contiene los ámbitos de la aplicación, es a qué estamos pidiendo permiso para acceder. Ahí he añadido los tres ámbitos que usaré en este tutorial, pero si solo quieres acceder a los contactos con People API, añadiendo PeopleService.Scope.ContactsReadonly sería suficiente. Si te fijas, estás pidiendo autorización (a OAuth) para acceder de forma de solo lectura a los contactos. En realidad estás pidiendo permisos para acceder a los Documentos y al Drive sin restricciones, pero a los contactos solo para leerlos. Estos dos últimos necesitan más permisos porque queremos crear ficheros, eliminarlos, lo mismo con los documentos.
Lo importante (también) es la asignación de ClientSecrets, ahí tendás que poner los datos que te haya generado al configurar el OAuth de la aplicación. Eso que te muestro son los valores que en mi proyecto para este tutorial he pedido, el que se indique que la aplicación se llama Tutorial Google APis VB no significa que no se pueda usar para C#, ese título o nombre de aplicación no es restrictivo para nada.
Nota: Normalmente esos valores se recomienda que no se hagan públicos… ¡vaya el caso que hago! jejejeje… pero para este caso, me fio de ti, y sé que no se lo dirás a nadie y así lo mantenemos en secreto… 😉
Las tres declaraciones después de asignar el valor a secrets son para acceder a los servicios de las tres APIs que usaré en estos tutoriales, las defino a nivel de clase para que se puedan usar en todos los métodos que sean necesarios y no tener que pasarlas como argumento de llamada al método que lo necesite.
La última declaración (Credential) es para crear las credenciales del usuario. La asignación la haré en el método Main, y como es un valor compartido, también se podrá usar en cualquier parte de la clase.
El siguiente código es el método Main, en el que llamaremos al método que accede a los contactos y los muestra.
Y como verás en las capturas el correo que he usado no tenía ningún contacto y he tenido que crear uno para comprobar que funciona… las cosas del Guille…
staticvoid Main(string[] args)
{
Console.WriteLine("Tutorial Google APIs con C#");
string credPath = System.Environment.GetFolderPath(
Environment.SpecialFolder.Personal);
// Directorio donde se guardarán las credenciales
credPath = System.IO.Path.Combine(credPath, ".credentials/Tutorial-Google-APIs-VB");
Credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
secrets,
Scopes,
"user",
CancellationToken.None,
newFileDataStore(credPath, true)).Result;
//Console.WriteLine("Credential file saved to: " + credPath);
// Mostrar los contactos
MostrarContactos();
Console.WriteLine();
Console.WriteLine("Pulsa una tecla.");
Console.Read();
}
PublicSharedSub Main(ByVal args AsString())
Console.WriteLine("Tutorial Google APIs con Visual Basic")
Dim credPath AsString = System.Environment.GetFolderPath(
Environment.SpecialFolder.Personal)
'// Directorio donde se guardarán las credenciales
credPath = System.IO.Path.Combine(credPath, ".credentials/Tutorial-Google-APIs-VB")
Credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
secrets,
Scopes,
"user",
CancellationToken.None,
NewFileDataStore(credPath, True)).Result
'// Mostrar los contactos
MostrarContactos()
Console.WriteLine()
Console.WriteLine("Pulsa una tecla.")
Console.Read()
EndSub
El valor de credPath es el direcorio donde se crearán las credenciales que vamos a usar en este programa. Se guardará en los documentos del usuario en una carpeta llamada Tutorial-Google-APIs-VB que estará incluida en .credentials.
Si haces cambios en los permisos o te han faltado permisos en la configuración de OAuth y los modificas, debes eliminar la carpeta del tutorial, a de .credentials la puedes dejar, por si la usas para almacenar las credenciales de otros programas que use el usuario. También tendrás que borrarla si decides usar otra cuenta de Google (GMail) para acceder a esos contactos. Si solo tienes una cuenta de GMail, no hará falta que la borres salvo para los dos primeros casos.
Cuando ejecutemos la aplicación verás qué es lo que se guarda en esa carpeta.
A Credentials le asignamos las credenciales que queremos usar.
Aquí tengo que hacer una puntuialización. El valor de secrets se asigna con los valores que hemos indicado (de foirma llana y clara) antes, pero también se puede hacer accediendo a un fichero que contiene esos valores, al menos así no se mostrarán directamente en el código los valores secretos. Aunque seguirán estando al alcance del usuario de la aplicación.
La forma de hacerlo lo pondré en otro ejemplo de este tutorial.
Ahora solo nos falta ver qué código contiene elmétodo MostrarContactos.
¡Vamos a ello!
privatestaticvoid MostrarContactos()
{
// Create Drive API service.
peopleService = newPeopleService(newBaseClientService.Initializer()
{
HttpClientInitializer = Credential,
ApplicationName = ApplicationName,
});
// Lista de los contactos (People)
Console.WriteLine("Contactos:");
// Este muestra todos los contactos
GetPeople(peopleService, null);
Console.WriteLine();
Console.WriteLine($"Hay {total} contactos / People");
Console.WriteLine();
}
Le asignamos el valor a peopleService para que pida esos permisos a Google, como ves se usa el protocolo HTTP. Eso lo que hará es abrir una página en el navegador pidiendo permiso para acceder a los datos de una cuenta de GMail (ver la figura 1)
Figura 1. Seleccionar la cunta que queremos usar para acceder a sus contactos.
Esa primera pantalla nos pide que indiquemos a qué contactos queremos acceder, es decir, solo accederá a los contactos de la cuenta que indiques, y para indicar esa cuenta debes tener el password, si no… no hay nada que hacer. Por tanto, esto que vamos a hacer es seguro, en el sentido de que yo no voy a acceder (ni puedo) a tus contactos… otra cosa es que mi aplicación se comunicara conmigo o guardara esa información en algún sitio… pero no te preocupes, no lo hace, además lo puedes comprobar por ti mismo viendo el código.
Una vez elegida la cuenta, te mostrará un aviso de que la aplicación no es segura o, en realidad, que no se ha verificado (ver la figura 2). Tendrás que autorizarla para que pueda seguir.
Figura 2. DEbes mostrar la información oculta para permitir ir a la aplicación
Una vez hechoi esto, Google te pedirá confirmación para dejar que la aplicación haga lo que se ha indicado en Scopes. Así que… otras tres pantallitas más de autorización (ver figuras 3 a 5).
Figura 3. Permiso para ver los contactos.Figura 4. Acceder al Drive.Figura 5. Acceder a los documentos.
Y una vez permitido estas cosas, debes terminar de aceptarlo (ver la figura 6 en la está el resumen de los permisos que le darás a la aplicación).
Figura 6. Confirmación final de los permisos.
Después de pulsar en Permitir, el navegador te mostrará un mensaje de que puedes cerrar la página.
Estos permisos no te los volverá a pedir para usar esta aplicación. Y por tanto, siempre accederá a la cuenta de GMail que has indicado.
Si quieres usar otra cuenta diferente, borra la carpeta esa que te comenté antes (o el fichero que contiene) y eso hará que te vuelva a mostrar todas estas pantallas.
Antes de seguir, espera un poco que me estoy meando de la risa… ¡ay!
Es que te dije que definí la variable peopleService (y las otras dos) a nivel de clase para no tener que usarla como argumento a un método y voy y pongo como argumento el valor de esa variable compartida… en fin… la cabeza del Guille…
Bueno, ya.
Sigamos con el código que accede a los contactos.
privatestaticint total = 0;
staticvoid GetPeople(PeopleService service, string pageToken)
{
// Define parameters of request.
PeopleResource.ConnectionsResource.ListRequest peopleRequest =
service.People.Connections.List("people/me");
//
// Lista de campos a usar en RequestMaskIncludeField:
// https://developers.google.com/people/api/rest/v1/people/get
//
peopleRequest.RequestMaskIncludeField = newList<string>()
{"person.names", "person.phoneNumbers", "person.emailAddresses",
"person.birthdays", "person.Addresses"
};
if (pageToken != null)
{
peopleRequest.PageToken = pageToken;
}
ListConnectionsResponse people = peopleRequest.Execute();
if (people != null && people.Connections != null && people.Connections.Count > 0)
{
total += people.Connections.Count;
foreach (var person in people.Connections)
{
Console.Write(person.Names != null ? ($"{person.Names.FirstOrDefault().DisplayName} - ") : "");
Console.Write(person.PhoneNumbers != null ? ($"{person.PhoneNumbers.FirstOrDefault().Value} - ") : "");
Console.Write(person.EmailAddresses != null ? ($"{person.EmailAddresses.FirstOrDefault().Value} - ") : "");
Console.Write(person.Addresses != null ? ($"{person.Addresses.FirstOrDefault()?.City} - ") : "");
if (person.Birthdays != null)
{
var fecha = "";
var b = person.Birthdays.FirstOrDefault()?.Date;
if (b != null)
fecha = $"{b.Day}/{b.Month}/{b.Year}";
Console.Write($"{fecha}");
}
Console.WriteLine();
}
if (people.NextPageToken != null)
{
Console.WriteLine();
Console.WriteLine($"{total} contactos mostrados hasta ahora. Pulsa una tecla para seguir mostrando contactos.");
Console.WriteLine();
Console.ReadKey();
GetPeople(service, people.NextPageToken);
}
}
else
{
Console.WriteLine("No se han encontrado contactos.");
return;
}
}
PrivateShared total AsInteger = 0
PrivateSharedSub GetPeople(service AsPeopleService, pageToken AsString)
Dim peopleRequest AsPeopleResource.ConnectionsResource.ListRequest =
service.People.Connections.List("people/me")
peopleRequest.RequestMaskIncludeField = NewList(OfString)() From {
"person.names",
"person.phoneNumbers",
"person.emailAddresses",
"person.birthdays",
"person.Addresses"
}
If pageToken IsNotNothingThen
peopleRequest.PageToken = pageToken
EndIf
Dim people AsListConnectionsResponse = peopleRequest.Execute()
If people IsNotNothingAndAlso
people.Connections IsNotNothingAndAlso
people.Connections.Count > 0 Then
total += people.Connections.Count
ForEach person In people.Connections
Console.Write(If(person.Names IsNotNothing,
($"{person.Names.FirstOrDefault().DisplayName} - "), ""))
Console.Write(If(person.PhoneNumbers IsNotNothing,
($"{person.PhoneNumbers.FirstOrDefault().Value} - "), ""))
Console.Write(If(person.EmailAddresses IsNotNothing,
($"{person.EmailAddresses.FirstOrDefault().Value} - "), ""))
Console.Write(If(person.Addresses IsNotNothing,
($"{person.Addresses.FirstOrDefault()?.City} - "), ""))
If person.Birthdays IsNotNothingThen
Dim fecha = ""
Dim b = person.Birthdays.FirstOrDefault()?.Date
If b IsNotNothingThen fecha = $"{b.Day}/{b.Month}/{b.Year}"
Console.Write($"{fecha}")
EndIf
Console.WriteLine()
Next
If people.NextPageToken IsNotNothingThen
Console.WriteLine()
Console.WriteLine($"{total} contactos mostrados hasta ahora. " &
"Pulsa una tecla para seguir mostrando contactos.")
Console.WriteLine()
Console.ReadKey()
GetPeople(service, people.NextPageToken)
EndIf
Else
Console.WriteLine("No se han encontrado contactos.")
Return
EndIf
EndSub
El valor de peopleRequest debe ser siempre people/me o bien si te sabes el ID de otro usuario lo podrías indicar en lugar de «me«. Ese me significa que accedes a tus contactos. He buscado por todas partes, y no he averiguado como saber ese famoso ID, ni siquiera de una de mis cuentas para poder saber si se puede acceder a la cuenta de alguien que no seas tú. Cuando lo averigüe te lo cuento.
Los valores asigndos a la lista RequestMaskIncludeField serán a los datos que queremos acceder y siempre deben empezar con person. seguido del campo al que queremos acceder. En este ejemplo estamos indicando que queremos acceder a: los nombres, teléfonos, emails, cumpleaños y domicilios.
Y como esos valores pueden ser nulos (y normalmente son una colección de valores), comprobamos si es nulo y en el caso de las colecciones accedemos al primero o al predeterminado, si hay algo lo mostramos y si no, se usa una cadena vacía.
Con el valor de Birtdays debemos hacer una comprobación más elaborada e incluso te recomendaría que usaras un try/catch si quieres acceder a los valores de la fecha, porque de las pruebas que he heco, en un caso me dio error al querer acceder al año… se ve que el contacto ese no quería dar su año de nacimiento y dio error al querer convertirlo a entero… en fin…
El NextPageToken nos indica que hay más págins de datos, así que… si las hay volvemos a llamar a este método y seguimos mostrando los contactos que falten por acceder.
Y esto es todo lo que hay que hacer para mostrar los contactos usando People API. Otra cosa es que quieras crear contactos o modificarlos, en esos casos tendrás que modificar el valor del Scope de los contatos. Si hago pruebas de crear o modificar te los pondré por aquí.
Para terminar, la captura del programa en funcionamiento. Ver la figura 7.
Figura 7. La cuenta que he usado para acceder a los contactos, ¡no tiene contactos!
Pues la sorpresa que me llevé… pensé que lo mismo fallaba algo, así que… me fui a google, creé un contacto y volvía a probar… ¡Esta vez sí que sí! (ver la figira 8).
Figura 8. Un contacto tengo en la cuenta que he usado para esta prueba.
Si quieres crer una cuenta y un proyecto para poder acceder a esos APIs de Google, lo primero es acceder a Google Cloud Platform y crear una cuenta, o mejor dicho, activarla con Google Cloud.
Una vez que tengas la cuenta, tendrás que crear un proyecto y configurarlo para que pueda acceder a esas APIs.
En este tutorial te explico paso a paso cómo hacerlo, y con un montón de capturas para que no te líes y yo no me olvide de contarte los paso, que todo hay que decirlo. 🙂
Nota: Por ahora te voy dejar las capturas y en otra ocasión, si hace falta, te explico lo que cada captura significa. Es que quiero pasar a mostrarte el código para acceder al API de People (contactos) que ya lo tengo hecho tanto con C# como con Visual Basic.
Figura 1. Crear un nuevo proyecto
Figura 2.
Figura 3.
Figura 4.
Figura 5.
Figura 6. En la ficha G Suite están las de Docs API y drive API
Figura 7. Tienes que elegir y habilitar una por una.
Figura 8.
Figura 9. Añadir más librerías (bibliotecas)
Figura 10. Elige People API. No elijas Contacts API, que aparte de estar obsoleta, no vale para nada…
Figura 11. Esta es la parte más importnte, sin la autorización OAuth no podrás acceder a nada.
Figura 12. Siempre debe ser External
Figura 13.
Figura 14.
Figura 15. Añadir las características que tu aplicación usará de cada API que has añadido.
Figura 16.
Figura 17.
Nota: Los permisos debes ir probándolos a ver qué error te da, según lo que quieras hacer. Pero básicamente necesitas acceso de lectura, salvo que quieras modificar un contacto, un documento o algún fichero del Drive.
Figura 18. No te olvides de darle a UPDATE ya que al estar tan abajo en la pantalla emergente ni se ve, y so no le das a Update, no se guardan los cambios.
Figura 19.
Figura 20.
Figura 21. Aquí tienes que añadir los usuarios autorizados para que usen la aplicación. El máximo es 100 y una vez que aades uno, ya no lo puedes eliminar.
Figura 22.
Figura 23. Los tipos de credenciales que crearemos
Figura 24.
Figura 25. Estas so las APIs que usaremos con este proyecto.
Figura 26. El tipo de aplicación que vamos a crear.
Figura 27.
Figura 28. Estas son las dos claves que necesitarás en tu código
Figura 29.
Figura 30.
Figura 31. Si no hs copiado las credenciales de acceso, descarga el fichero .json y ahí las tienes.
Pues esto es todo lo que debes saber para crear un proyecto. En la siguiente entrega voy a mostrarte el código de una aplicación de consola para acceder a los contactos (People API). Sí, en C# y en VB.
Pues eso… que como yo me he encontrado con problemas a la hora de implementar las APIs de Google y de encontrar ejemplos (que de haberlos solo eran para C#), me he decido a escribir esta especie de tutorial para poder acceder a las siguientes APIs de Google: Drive, Docs y People. Es decir, poder manipular el contenido de Google Drive, los documentos y los contactos de Google que están asociados tu cuenta (o la cuenta que decidas usar) de GMail.
Lo primero que necesitas (salvo que uses las que yo voy a crear para este tutorial) es crearte una cuenta en Google Cloud Platform, es gratuito al menos sino usas recursos que son de pago. La cuenta a usar será la que tengas ya de GMail o bien puedes crear una nueva cuenta y asociarla con Google Cloud.
Cuandoi asocias una cuenta por primera vez, te darán una oferta de 300$ (US $) para que pruebes de forma gratuita los recursos de pago (siempre que no te pases de esos 300 dólares). Pero como te digo, para las cosas que te voy a explicar no te hace falta.
Por ahora me voy a saltar el paso de crear tus propios poyectos en Google Cloud, eso lo dejaré para el final (salvo que cambie de opinión), lo que sí te digo es que si quieres utilizar los ejemplos que te voy a mostrar usando mis claves o las que yo indique en el código) antes debes decírmelo para poder añadir tu correo de GMail a los usuarios que pueden usar esas claves.
Esto ultimo lo puedes hacer dejando un comentario en este mismo post y yo te agregaré, esto puede que no lo haga pasado unos meses, ya que solo quiero que la gente lo pruebe en este tiempo reciente, mientras publico los tutoriales. Y dejando, como mucho, unos tres meses de desde la publicación original, así que… si estás leyendo este tutorial después del 15 de marzo de 2021, es posible que o bien no admita más usuarios o simplemente que no lo siga «soportando», además de que hay un límite de 100 usuarios por proyecto, y si ya están esos usuarios registrados con la aplciación, es posible que, incluso antes del 15 de marzo del 2021 no pueda admitir más usuarios.
Dicho esto, empecemos.
Nota: Los ejemplos los voy a hacer con .NET 5.0.1 pero son válidos para .NET Framework v4.5 y superiores, .NET Standard y .NET Core.
Crear un proyecto en C# o Visual Basic
Crea un proyecto de consola para C# o VB y ve a Tools>NuGet Package Manager>Manage NuGet Packages for Solution… (en español es: Herramientas>Administrador de paquetes NuGet>Administrar paquetes NuGet para la solución…)
Busca y añade los siguientes paquetes de NuGet (entre paréntesis te indico las versiones que estoy usando al escribir este tutorial):
Nota: Los enlaces son por si quieres ir a la página de NuGet y descargarlas desde allí o ver cómo instalarlas según estés usando Visual Studio 2019 o Visual Studio Code o simplemente creando los proyectos desde la consola con dotnet. Eso sí, necesitarás tener instlado el SDK de dotnet 5 para poder compilar el código.
Entrega 3: Código para acceder a los documentos con Drive API y Docs API
Entrega 4: Código para crear un documento con Docs API
Nota: Voy a publicar ahora este post mientras creo el protyecto de prueba, escribo el código, lo convierto de C# a VB (ya tengo código escrito en C#) y lo publico, así podrás solicitar tu «permiso» para usar estos ejemplos con mi ID y clave secreta (que después de publicarla no será tan secreta 😉 )