Microsoft y el mundo

De un tiempo a esta parte, me da la impresión de que los desarrolladores de Visual Studio (y .Net en general) tienen una visión del mundo bastante peculiar que se deja fuera a muchos de sus clientes. Algo que no veía en los tiempos de .Net Framework y la década del 2000 y primera mitad del 2010. La última vez que he pensado esto ha sido al empezar a usar Visual Studio 2022. Lo primero que me saltó fue esta advertencia:

NU1803    You are running the ‘restore’ operation with an ‘HTTP’ source. Non-HTTPS access will be removed in a future version. Consider migrating to an ‘HTTPS’ source.

Siguiendo el enlace de la advertencia, me encuentro con lo siguiente:

«El acceso no HTTPS se quitará en una versión futura.»

Toma ya. ¿En qué mundo viven estos? Busqué en el GitHub de NuGet el tema y lo encontré rápido, junto con las mismas quejas que podía escribir yo. Que, básicamente es:

Pero, alma de cántaro, ¿no te has parado a pensar que los que usamos servidores NuGet con http lo hacemos porque son servidores locales, montados para el grupo de desarrollo de la empresa? Yo lo hago con NuGet Server y ahora lo tengo en un servidor virtualizado que se utiliza para otras cosas, con los archivos alojados en un servidor de archivos aparte. Es muy cómodo para compartir nuestras bibliotecas comunes entre los distintos proyectos. Y esta decisión nos va a complicar la vida una barbaridad. ¿Qué hacemos? ¿Https con certificado autofirmado? Esto dará problemas por otro lado, seguro. ¿Pagar un certificado para un ordenador que ahora está (física o virtualmente) en un rincón, sin acceso a internet? ¿Y el tiempo de investigación y configuración de todo el tinglado?

Me queda bastante claro que a los que se les ha ocurrido la maravillosa idea no han trabajado mucho en pequeñas y medianas empresas.

Raw SQL Query en EF Core

Cuando, en 2016, empecé a trastear con Entity Framework, tuve que elegir entre la versión clásica o la nueva versión «Core». En la documentación te indicaba que, para proyectos nuevos, lo mejor era elegir EF Core, que era lo que se iba a desarrollar a futuro. Pero, ¡ay!, la versión entonces disponible no soportaba vistas y yo las necesitaba como el comer. No me quedó otra que empezar con EF clásico y dejar EF Core para más adelante.

El año pasado, aprovechando que ya teníamos una versión para .Net Standard de EF clásico, modifiqué mis proyectos con EF para pasarlos del tipo .Net Framework al tipo actual .Net Core, en donde se puede tener varios frameworks de destino. Así, los compilé para .Net Framework y .Net Core.

Este otoño, preparando mi salto a .Net Core del año que viene (posiblemente con Blazor), se me ha ocurrido también compilar mis proyectos de EF tanto para la versión clásica como para la nueva EF 7.0, recién salida del horno. En parte también porque en la documentación decía más o menos que los desarrolladores de EF clásico ya no teníamos excusa. Bien, me dije, veamos si es así. La madre del cordero es el mapeo parcial de la base de datos de SAP Business One, que es sólo relacional en parte. Más de 150 entidades (hasta la fecha), entre tablas y vistas.

Lo primero fue revisar qué usaba en EF clásico y cómo se hacía en EF Core, así que monté un prototipo y mapeé un par de tablas y vistas, con sus relaciones. Sin problemas.

Luego llegó el turno de las consultas SQL a pelo y ahí, ¡ah, amigo!, ahí se complicó todo.

En EF clásico teníamos una opción maravillosa que era:

public DbRawSqlQuery<TElement> SqlQuery<TElement>(string sql, params object[] parameters)

Sigue leyendo

Cliente WCF asíncrono

Volviendo con lo del otro día, se me ocurrió ampliar los contratos de servicio para definir métodos asíncronos, de cara a trabajar con .Net Core este invierno. Quería hacerlo sólo en los clientes, para no tocar toda la parte de los servicios, que ya está creada, así que jugué con las compilaciones condicionales:

<ServiceContract([Namespace]:="http://CdA.Cromo.Services", SessionMode:=SessionMode.NotAllowed)>
Public Interface ILotesService

    <OperationContract()>
    <FaultContract(GetType(ServiceFault))>
    Function Modificar(Lote As LoteSAP, InfoConfig As InfoConfiguracion) As Mensaje

#If NET5_0_OR_GREATER Then
    <OperationContract()>
    Function ModificarAsync(Lote As LoteSAP, InfoConfig As InfoConfiguracion) As Task(Of Mensaje)
#End If

End Interface

Sigue leyendo

Vi la luz

El otro día modifiqué un servicio WCF y me tocó pegarme, a continuación, con la aplicación cliente. Siguiendo el manual, en su día utilicé «Agregar referencia de servicio» de Visual Studio. Actualizar luego es siempre un dolor de cabeza. Las clases de datos y las enumeraciones utilizadas por el servicio las comparto vía NuGet y las tiene el cliente. En teoría, le puedes indicar al asistente que utilice esos ensamblados en lugar de generar de nuevo todo, pero al actualizar… raro es que funcione a la primera.

Entonces encontré este viejo artículo y, aún mejor, esta explicación con ejemplos de ese artículo. Cómo crear a mano los proxy de cliente. Vaya cosa simple. Vaya forma de hacer el canelo todos estos años.

Así que paré todo y dediqué dos días a llevarme las interfaces de los contratos de servicio a otro proyecto, crear un tercero con los proxy de cliente, empaquetarlos y distribuirlos por NuGet. Los agregué al cliente, eliminé el código generado por Visual Studio y lo sustituí por las nuevas clases y listo.

De paso, compilé los nuevos proyectos para .Net Core y lo probé con una aplicación Visual Basic. Iba genial, lo que es un alivio porque el Agregar referencia de servicio en proyectos de .Net Core sólo aparece para C#.

Me gusta cuando aprendo algo nuevo de una tecnología que llevo usando años. Por una parte me da rabia (es algo que debería haber sabido antes, pienso), por otra me muestra todo lo que me queda por mejorar en las áreas que controlo.

SAP Business One B1WS: editar oferta de ventas II

Hace poco tuve que modificar unos documentos de márketing (ofertas y pedidos de ventas) vía B1WS/Di Server. Hasta el momento, sólo había creado documentos de SAP y supuse que la edición seguiría el mismo proceso que con los UDO, con los que tengo bastante más manejo. Básicamente, el proceso con un UDO es:

  • Obtengo el objeto sin modificar vía B1WS.
  • Modifico los campos de la cabecera con los datos nuevos.
  • Con el array de líneas, agrego las nuevas, elimino las que hay que borrar y modifico los campos de las que hay que modificar.
  • Le vuelvo a enviar todo a SAP.

Con las líneas, que es lo que me importa aquí, el proceso es como éste, siendo Origen el objeto con los cambios que envía la capa de lógica de negocio y Destino, el objeto sin modificar recuperado de SAP. Como pueden observar, hay una propiedad en las filas origen que me indica qué debo hacer con ellas:

Sigue leyendo

Microsoft da la espalda a VB.Net

El 11 de marzo se publicó una entrada en el blog de Visual Basic de Microsoft, la primera entrada desde noviembre de 2018 en un blog que había estado bastante surtido hasta octubre de 2017. El título de la entrada es Visual Basic support planned for .NET 5.0. Es una entrada sin firma (aparece .NET Team como autor, mientras que las anteriores, salvo pocas excepciones, iban firmadas por sus autores) y viene a ser la sentencia de muerte de Visual Basic por parte de Microsoft. La entrada dice que van a hacer Visual Basic compatible con .NET 5.0 (la siguiente versión de la rama de .NET Core que va ahora por la 3.1). Ésa es la zanahoria que nos dan, después de meses (por no decir años) de sentirnos ninguneados y olvidados frente a C#.

El palo viene luego: Going forward, we do not plan to evolve Visual Basic as a language. No van a seguir desarrollando Visual Basic. No van a incluir las futuras mejoras de .NET Core. No es que me sorprenda. Llevábamos bastante tiempo con Visual Basic desaparecido del mapa y no tenemos la suerte de que haya un pequeño grupo de desarrollo que pueda ir a lo suyo, como pasa con F#. Tampoco es novedad que Microsoft deje en la estacada a sus clientes. Pasó con VisualFox Pro, por ejemplo. O con Windows Phone. O con las aplicaciones de tienda de Windows 8. Otrora una de las joyas de la corona de Microsoft, hoy Visual Basic es considerado aburrido, viejo y obsoleto por el equipo de desarrollo de .NET y el mensaje es claro: migrad a C#, dinosaurios.

Leí la noticia con resignación. Ya bastante me cuesta tener que dar explicaciones por usar Visual Basic cada vez que lo menciono. Hasta mis compañeros de departamento se han pasado a C# en el último proyecto. El día que tenga que pasarme a C# para ganarme los garbanzos, pues lo haré. O a lo que sea. Programar es mi trabajo. No tiene por qué ser mi afición.

Sin embargo, ayer leí también la contestación de Anthony D. Green, 5 entradas donde se explaya a gusto que ustedes pueden leer aquí: la primera, la segunda, la tercera, la cuarta y la quinta, y que agradezco de corazón. Me ha recordado que Visual Basic es un lenguaje de código abierto desde 2014 que irá donde su comunidad decida que vaya. Me ha explicado algo que debía ser totalmente evidente, que desarrolladores ajenos a un lenguaje de programación no deberían condicionar el futuro de ese lenguaje; que no tengo por qué dar explicaciones ni disculparme por usar las herramientas que uso y porque me guste el lenguaje de programación que me gusta. Cuenta anécdotas y experiencias vividas en su tiempo trabajando en Microsoft y termina con una carta abierta a Nadella.

La lectura ha cambiado mi resignación por cabreo y rebeldía. Quiero seguir programando en VB.Net. Quiero que mi trabajo siga siendo una afición. Quiero disfrutar mucho más de su sintaxis, de imprimirme el código y leerlo en voz alta y del placer de programar, en definitiva. Y si tengo que dejarlo, quizás pueda aprovechar y quitarme esa espinita clavada que es mi otro amor, Pascal, y aprender de una vez Delphi.

EF: usando campos no mapeados en consultas

Tengo un vicio confesable (creo) en programación: las enumeraciones. Siempre que puedo, las uso. Cuando, aprovechando unas semanas de verano con poca carga de trabajo, intenté mapear parte de la base de datos de SAP Business One con Entity Framework (lo que tuvo su complicación, que no es lo que se dice muy relacional), me pregunté si podía utilizar enumeraciones para los campos de estado de documentos, que tan pronto pueden ser O/C como P/R/L/C o cualquier otra forma super-intuitiva.

No me refiero sólo a tener una propiedad de sólo lectura no mapeada para obtener un valor comprensible con los datos ya cargados, o sea, esto:

    <Column("Status")>
    Public Property EstadoSAP As String
    <NotMapped>
    Public ReadOnly Property EstadoEnumerado As EWORSAPStatus
        Get
            Return Convierteme(Me.EstadoSAP)
        End Get
    End Property

Sigue leyendo

ASP MVC: autenticación por formularios

Este año pasado he dado mis primeros pasitos con ASP MVC, siendo también mi primera incursión en programación web. Lo primero que a lo que tuve que enfrentarme (tras pasar por varios tutoriales y leerme un par de libros) fue la gestión del usuario. La aplicación MVC compartía componentes con otras (WCF y Windows Forms) y la gestión del usuario y sus permisos se hacía ya con una implementación de IPrincipal. Una búsqueda por Internet me llevó a que debía usar para esto la vieja autenticación por formularios.

Para ello, primero debía crear mi nuevo proyecto MVC sin autenticación. Creé, a continuación, otro con autenticación para coger todo lo que pudiera del código y ahorrarme trabajo: casi todo el diseño e la vista de login, la vista parcial de _LoginPartial, su implementación en la cabecera… Todo lo que me pudiera suponer un ahorro de trabajo.

Sigue leyendo

Operador Update

El otro día necesitaba actualizar valores en una colección de DataRows tipadas y echaba de menos no poder hacerlo con LINQ, así que me puse a buscar. Y buscando, buscando, encontré el código para un método de extensión Update que soluciona la papeleta. El código está aquí en C# y en Visual Basic quedaría así:

Public Delegate Sub Func(Of TArg0)(element As TArg0)

''' <summary>
''' Executes an Update statement block on all elements in an IEnumerable(T) sequence.
''' </summary>
''' <typeparam name=TSource">The source element type.</typeparam>"
''' <param name=source">The source sequence.</param>"
''' <param name=Updater">The update statement to execute for each element.</param>"
''' <returns>The numer of records affected.</returns>
<System.Runtime.CompilerServices.Extension> _
Public Function Update(Of TSource)(source As IEnumerable(Of TSource), Updater As Func(Of TSource)) As Integer
    If source Is Nothing Then
        Throw New ArgumentNullException("source")
    End If
    If Updater Is Nothing Then
        Throw New ArgumentNullException("update")
    End If
    If GetType(TSource).IsValueType Then
        Throw New NotSupportedException("value type elements are not supported by update.")
    End If

    Dim count As Integer = 0
    For Each element As TSource In source
        Updater(element)
        count += 1
    Next
    Return count
End Function

Su uso sería algo así:

TareasParaArticulo.Where(Function(x) x.IdUsuario = 0).Update(Function(x) x.IdUsuario = JefeProyecto)

Donde TareasParaArticulo es un conjunto de datarows tipadas (un list, un datatable…, tanto da). Lo que hace esta parte de la aplicación, para ponernos en situación, es crear una serie de tareas (tomadas de una plantilla de tareas) cuando un artículo es añadido a un proyecto. El uso del Update permite asignar aquellas tareas que en la plantilla tienen como IdUsuario el 0 al jefe del proyecto de una forma sencilla.

Relación maestro-detalle entre dos ComboBox

Me encontré el otro día con la necesidad de establecer una relación maestro-detalle entre dos combobox. Básicamente, tenía que mostrar en un combobox una lista de procesos y, en el otro, las tipologías de cada proceso. Ahí donde lo ven ustedes, no es algo difícil de hacer, pero sí de encontrar cómo hacerlo. Quizás la solución más elegante sea usar un dataset con las dos tablas en cuestión (Procesos y Tipologías) y la relación entre ambas. De este modo, yo puedo asignar la tabla Procesos como DataSource del combobox de la forma habitual:

            cboProceso.DataSource = dsProcesos.Procesos
            cboProceso.DisplayMember = «Descripcion»
            cboProceso.ValueMember = «Id»

Donde cboProceso es el combobox en cuestión y dsProcesos, el dataset tipado. Para la parte detalle de nuestro sistema, esto es, el combo de Tipologías, usamos el mismo DataSource que para el maestro, es decir, la tabla Procesos. La magia la hacemos en el DisplayMember (en el ValueMember no funciona), que lo expresaremos de la formarelación_entre_tablas.campo_a_mostrar. Es decir, el nombre de la DataRelation que une las tablas de nuestro dataset y el nombre del campo de la tabla detalles (Tipologías en nuestro caso) que mostrará el combobox:

cboTipologia.DataSource = dsProcesos.Procesos
‘ Nótese la forma del displaymember para que muestre sólo las tipologías
‘ del proceso seleccionado en el combo de procesos:
cboTipologia.DisplayMember = «FK_Tipologias_Procesos.Descripcion»
cboTipologia.ValueMember = «IdTipologia»

Muy intuitivo, como pueden observar.

El sistema funciona incluso si queremos hacer un filtrado del maestro. Siguiendo con el ejemplo, puedo crear un dataview de Procesos, usar ese dataview como DataSource de ambos combobox y el invento funcionará sin más.

Donde ya no he conseguido que funcione es con un filtrado en la parte detalles. Digamos que necesito mostrar las tipologías que cumplen cierto requisito. Paso 1: filtrar los procesos, sólo necesito aquellos que tengan tipologías que cumplan el requisito pedido. No es una consulta simple que se pueda hacer en el Filter del DataView, pero puedo tirar de LINQ o de métodos de extensión y hacer algo como esto:

dvProcesos = dsProcesos.Procesos.Where( _
                 Function(Proceso) Proceso.GetTipologiasRows.Any( _
                 Function (Tipologia) (Tipologia.OpcionesTipologia _
                              And EOpcionesTipologia.TareaAutomática) _
                              = EOpcionesTipologia.TareaAutomática)).AsDataView
 

Ya tenemos un dataview (dvProcesos) con los procesos que tienen alguna (any) tipología que cumple los requisitos pedidos. Si yo uso este dataview como DataSource de los combobox, obtengo los procesos que busco, pero todas las tipologías de esos procesos. ¿Cómo filtro ahora las tipologías?

Pues ni idea. El objeto DataRelation no me permite «meter» nada en la relación que no sea equivalencia entre columnas y no he encontrado ninguna forma de filtrar el lado «detalle» de nuestra relación. Al final, he tenido que recurrir a dos bindingsources auxiliares, uno ligado al dataview de procesos y el segundo al bindingsource anterior, especificando en su DataMemeber el nombre de la DataRelation:

mbsProcesos.DataSource = dvProcesos
mbsTipologias.DataSource = mbsProcesos
mbsTipologias.DataMember = «FK_Tipologias_Procesos» 

Ahora uso estos bindingsources para «alimentar» los dos combobox y listo. Aunque me hubiera gustado una solución más elegante.