Sap Business One Service Layer

Después de meses de retrasos y problemas (que si falla la controladora RAID del nuevo servidor, que si hay que esperar a un parche de SAP que se retrasa, que si pruebas por aquí y por allá), en noviembre por fin actualizamos a SAP 10. De lo más interesante de esta versión es que por fin tenemos Service Layer en la versión para SQL Server (hasta entonces, estaba sólo para Hana).

Service Layer es una nueva api de datos para que nuestras aplicaciones puedan trabajar con SAP Business One. Es una api web moderna, Restful, con protocolo OData. Por supuesto, con algunas cosillas particulares, que para algo hablamos de SAP.

Yo esperaba con muchas ganas el poder meterle mano a Service Layer. El venerable Di Server se nos ha quedado pequeño y nos da mil y un extraños problemas. No cuento con que Service Layer sea la panacea, pero se supone que debe funcionar mejor que algo marcado como obsoleto años antes de que yo empezara a usarlo. El problema, como siempre, es la formación y la documentación. O la falta de. Como es habitual, me toca buscarme las habichuelas.

Por fortuna, SAP nos ofrece un tutorial para crearnos un cliente OData para .Net. El tutorial está hecho para .Net Core 2.1 y nos servirá para crearnos nuestra propia biblioteca para nuestros proyectos. Los pasos básicos se explican en el apartado Environment Setup y que vendrían a ser los siguientes:

  1. Usamos Postman para conectarnos a Service Layer y descargarnos los metadata.
  2. Instalamos en nuestro Visual Studio una extensión para trabajar con servicios OData. Puede ser OData Connected Service o Unchase OData Connected Service, que trae más opciones que la primera.
  3. Mapeamos Service Layer usando el archivo de metadata generado antes. Esto es necesario porque el login de Service Layer no es estándar. Como suele ser normal en SAP Business One, además de usuario y contraseña, necesitamos la empresa a la que nos conectamos, y eso no hay forma de hacerlo ni con OData Connected Service ni con Unchase. Se supone que podemos elegir qué queremos mapear, para obtener algo más ligero. Con la cantidad de interrelaciones que tiene SAP, creo que requiere un esfuerzo exagerado hacerlo y es mejor mapearlo todo y seguir.
  4. Personalizamos la clase parcial del context con el código del tutorial.

Con esto podemos empezar. Yo he optado por hacer una biblioteca de clases en .Net Standard 2.0, para poder usarla tanto para proyectos de .Net Framework como de .Net Core. El archivo generado con las clases es enorme (se puede decirle al asistente que cree cada clase en un archivo, pero es un error que no hay que cometer o, en mi caso, repetir). Recalco lo de enorme. Con un i5-8400 y 16GB de RAM, Visual Studio empezó a darme errores raros de sintaxis, hasta que me di cuenta de que era el Intellisense, que tardaba varios minutos en enterarse de qué iba todo.

Visto lo visto, decidí dejar la biblioteca como proyecto independiente (con un par de aplicaciones, una en .Net Framework y la otra en .Net Core para pruebas) y ofrecerla como paquete NuGet ya compilado, antes que liarla en mi proyecto principal.

Pero antes me encontré con un problema: en .Net Framework no funcionaba. Al intentar conectar, me devolvía siempre un server error, sin más explicaciones. Con .Net Core, sin embargo, fue perfecto a la primera. Me llevó un rato encontrar la solución:

Dim mRuta As New Uri(Configuracion.UrlServiceLayer)
Dim servicePoint = ServicePointManager.FindServicePoint(mRuta)
servicePoint.Expect100Continue = False
Context = New ServiceLayer(mRuta)

Con esto, ya tengo mi biblioteca Service Layer operativa y puedo empezar con lo serio: crear y modificar objetos. Pero de eso, hablaré otro día.

3 comentarios en “Sap Business One Service Layer

  1. Hola amigo buen día, llevo días intentando conectarme al service layer, con .net y c# pero no tengo respuesta.

    podrías mostrar como hacer una conexión por favor.

    ¡Saludos!

  2. Hola, Antonio,

    Yo utilizo el DataServiceContext creado y personalizado con el tutorial de SAP que menciono en la entrada (tengo código de ejemplo que me dejó el formador con HttpWebRequest/Response, pero me da muchos problemas).

    Tiene un método Login que recibe la compañía, usuario, contraseña y código del idioma. Por ejemplo, su uso en una aplicación de Windows Forms con varias cajas de texto y un botón para el login quedaría:

    Private Async Sub btnLogin_Click(sender As Object, e As EventArgs) Handles btnLogin.Click
        Try
            Dim session = Await context.Login(CompanyDB, UserName, Password).GetValueAsync
            TextBox1.Text = session.SessionId
            TextBox2.Text = session.Version
        Catch ex As Exception
            TextBox3.Text = ex.ToString
        End Try
    End Sub

    Y el Logout:

    Private Sub btnLogout_Click(sender As Object, e As EventArgs) Handles btnLogout.Click
        Dim response = context.Logout.Execute
        TextBox3.Text = response.StatusCode.ToString
    End Sub

    Si la aplicación es en .Net Framework, hará falta lo del Expect100Continue = False.

  3. Un uso cualquiera de esto podría ser una consulta sencilla sobre artículos como ésta:

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Try
            Dim yoQueSe = context.Items.Where(Function(x) x.ItemCode.StartsWith("PT")).
                                        Select(Function(x) New With {x.ItemCode, x.ItemName}).
                                        Take(100).ToList
            Dim sstring As New StringBuilder
            For Each value In yoQueSe
                sstring.AppendLine(value.ItemCode & " - " & value.ItemName)
            Next
            TextBox3.Text = sstring.ToString
        Catch ex As DataServiceRequestException
            If TypeOf ex.InnerException?.InnerException Is ODataErrorException Then
                Dim innerException = TryCast(ex.InnerException.InnerException, ODataErrorException)
                TextBox3.Text = innerException.Error.ErrorCode & vbCrLf & innerException.Error.Message
            Else
                TextBox3.Text = $"{ex.InnerException.GetType}"
            End If
        Catch ex As Exception
            TextBox3.Text = ex.ToString
        End Try
    End Sub

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.