Git Product home page Git Product logo

b1slayer's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

b1slayer's Issues

Connecting SAP Service Layer with Blazor Using and EF Core Project

Hi @bgmulinari,

Thank you for your reply on the Stack overflow issue. As suggested that I the issue here on GitHub, I would like to point out some issues faced when working with Blazor project using your Nugget package.

  1. How can we authenticate SAP Service Layer once and the session is used in every part of the Blazor application project? For example, I have an ItemService and OrderService that retrieves data from Service Layer, how can we use the session generated from SAP Service Layer login on all the services created in Blazor.
  2. Can we also use the package as a dependency Injection with Blazor or Ef Core Web Api

Many thanks in advance.

Document Lines Patching content error/structure

First of all, thank you for the work you have done on B1SLayer. It is indeed very helpful. I have gone through your documentation both on sap forums and also here but I haven't been able to see how to handle lines or if it is even doable using B1SLayer so excuse my ignorance and for opening this as an issue.

Assuming the following scenario;

Update Production Order 3114, set warehouse to '163' for line number '2' and line number '3'.

conventionally it would be a PATCH call to /ProductionOrders(3114) with content of

var content = new StringContent("{\"ProductionOrderLines\": [{\"LineNumber\":2,\"Warehouse\":\"163\"},{\"LineNumber\":3,\"Warehouse\":\"163\"}]}", null, "text/plain");

Using

await serviceLayer.Request("ProductionOrders", "3114").PatchAsync(updatedPWInfo); returns Property LineNumber of ProductionOrder is invalid. which is true, its a property of ProductionOrderLines which is under ProductionOrders

await serviceLayer.Request("ProductionOrderLines", "3114").PatchAsync(updatedPWInfo); returns Unrecognized Resource Path which is normal and doesn't exist on the API.

I believe the issue is in updatedPWInfo what would the structure be for it to work with the list of lines just like in the productionOrder scenario above?

For UserFieldsMD object, request can't be executed

The UserFieldsMD GET for id can't be create because the key is composed by two fields

UserFieldsMD(TableName='@UDT01',FieldID=0)

The SLConnection.Request(string, object) doesn't treat multiple fields in key

Service Layer reference

UserFieldsMD
This entity enables you to manipulate 'UserFieldsMD' and manage user-defined fields in user and system tables.

GET UserFieldsMD(id)
Retrieve all or some selected properties from an instance of 'UserFieldsMD' with the given id.

Example

GET https://localhost:50000/b1s/v1/UserFieldsMD(TableName='@UDT01',FieldID=0)
GET https://localhost:50000/b1s/v1/UserFieldsMD(TableName='@UDT01',FieldID=0)?$select=Name,Type,Size

SL v2 : BatchRequest

BatchRequest not working in sl v2.

the error that appears is:
"error" : {
"code" : "244",
"message" : "Content ID should be specified in each change set request."
}

I have filled the "contentID" parameter but still not working

Thanks youu

Documentation on SLRequest.GetAllAsync<T>

Hi, congrats on the library, you've put in a good amount of work into it. Personally I use the odata client generation approach but this is more flexibly as the contracts are written by the user.

I noticed that the SLRequest.GetSync takes a parameter to unwrap value property, but the SLRequest.GetAllAsync does not, and it forces the unwrap. Could you add this to the docs ? I spent sometime trying to figure out why some response objects were coming back null then I realized this one is unwrapping no matter what (which kind of makes sense since user doesnt need to manage links or tags)

Patch Address in BusinessPartners

Good morning, I'm having trouble when I want to patch Address in BusinessParterns, here some code:

In Postman works, modified the rowNum (0) :

BusinessPartners(CardCode = 'C1801123')
{
"BPAddresses": [
{
"RowNum": 0,
"AddressName": "Bill To",
"Street": "calle 4665",
"Country": "BO",
"FederalTaxID": "34343434",
"TaxCode": "IVA_ICE",
"AddressType": "bo_BillTo",
"AddressName2": "Test",
"AddressName3": "Test",
"BPCode": "C1801123"
}
]
}

but whit this code, create another RowNum:

var Direcciones = new
{
CardName = bp.CardName,
BPAddresses = new List()
{
new {
RowNum = 0,
AddressName = "Bill To",
AddressName2 = "test",
AddressName3 = "test",
Street = "",
TaxCode = "IVA",
Country = "BO",
StreetNo = "test",
FederalTaxID = "test"
}
}
};
await _serviceLayerConn
.Request($"BusinessPartners(CardCode = '{bp.CardCode}')")
.PatchAsync(Direcciones);

Any Ideas?

Batch requests and ETAG

Hi, can ETAG for optimistic concurrency be used with SLBatchRequest? I tried with

SLBatchRequest request1 = new SLBatchRequest( HttpMethod.Patch, $"Orders({docEntry})", orderPatch1,  contentID: 1 );
SLBatchRequest request2 = new SLBatchRequest( HttpMethod.Patch, $"Orders({docEntry})", orderPatch2,  contentID: 2 );
SLBatchRequest request3 = ...;

setting

SLBatchRequest.WithHeader( "If-Match", orderEtag ); // previously read etag value

just for the 1st request or each request, but seems not working. Having not found any sample, I wonder if it is supported by Service Layers.

Thank you

Certificates

How can I set a local certificate file to the requests?

.Net 6 WebAPI Error

Olá,

Ao executar um operação de UPDATE de BusinessPartners em serie todas são efetuadas com sucesso. Porem após algum tempos recebo o seguinte erro.

Call timed out: GET https://.......:50000/b1s/v1/BusinessPartners('C114110');

em B1SLayer.SLConnection.d__451.MoveNext() em System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() em System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) em System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) em B1SLayer.SLRequest.<GetAsync>d__7.MoveNext() em System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() em System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) em System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) em System.Runtime.CompilerServices.TaskAwaiter1.GetResult()
em Rommanel.SAPB1.Data.SAPContext.d__5.MoveNext() em D:\Projetos 2020\Rommanel\Rommanel.SAPB1\Data\SAPContext.cs:linha 292

| InnerException | {"Unable to read data from the transport connection: A operação de E/S foi anulada devido a uma saída de thread ou a uma requisição de aplicativo.."}
  | Source | "System.Net.Http" | string

em System.Net.Http.HttpClient.HandleFailure(Exception e, Boolean telemetryStarted, HttpResponseMessage response, CancellationTokenSource cts, CancellationToken cancellationToken, CancellationTokenSource pendingRequestsCts)
em System.Net.Http.HttpClient.<g__Core|83_0>d.MoveNext()
em System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
em System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
em System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
em Flurl.Http.FlurlRequest.d__29.MoveNext()

Skip not functioning properly?

    var response = await _serviceLayer
        .Request("Items")
        .Select("ItemCode,ItemName,Mainsupplier,SupplierCatalogNo")
        .Skip(numberToSkip)
        .WithPageSize(2500)
        .GetAsync<IEnumerable<Item>>();

Here's my code- Not sure what the deal is here, could be an issue on my side, but when I use the skip method, everything past my page size gets truncated. For example, if numberToSkip here was equal to 2499, I would only recieve one object, and if numberToSkip was 2500+, I would recieve nothing.

For the time being I'm using this workaround:

    var response = await _serviceLayer
        .Request("Items")
        .Select("ItemCode,ItemName,Mainsupplier,SupplierCatalogNo")
        .Skip(numberToSkip)
        .WithPageSize(numberToSkip + 2500)   // 2500 here is my desired page size
        .GetAsync<IEnumerable<Item>>();

To just resize the page incrementally based on the number of objects to be skipped.

Thanks for your time, love this library

B1S-ReplaceCollectionsOnPatch in true

Good evening, how can I enable the B1S-ReplaceCollectionsOnPatch to true to execute the PatchStringAsync to be able to add banks in the business partners, in postman it works fine for me with this structure but with the ReplaceCollectionsOnPatch to true

{
"BPBankAccounts": [{
"InternalKey": 94,
"Country": "NOR",
"BankCode": "BDF",
"AccountNo": "35451111",
"IBAN": "USD"
},
{
"InternalKey": 95,
"Country": "NOR",
"BankCode": "BANPRO",
"AccountNo": "232323",
"IBAN": "USD"

         }]

}

Connect to multiple companies on the same server

Hello, first of all, thanks for this amazing job, it's very useful.

I am trying to achieve a connection to multiple companies on the same server but I'm having troubles because the post/patch methods are going to the same service layer instance.

I have the following:

List<Configuration> listConfigs;
listConfigs = new List<Configuration>();
Configuration config = new Configuration("DB1", "manager", g_Pass, serviceLayerURI);
listConfigs.Add(config);
config = new Configuration("DB2", "manager", g_Pass, serviceLayerURI);
listConfigs.Add(config);
config = new Configuration("DB3", "manager", g_Pass, serviceLayerURI);
listConfigs.Add(config);

The "Configuration" class constructor is the following:

public Configuration(string pDatabaseName, string pSAPUsername, string pSAPPassword, string pServiceLayerURI)
{
       DatabaseName = pDatabaseName;
       SAPUsername = pSAPUsername;
       SAPPassword = pSAPPassword;
       serviceLayer = new SLConnection(pServiceLayerURI, DatabaseName, SAPUsername, SAPPassword);
}

Inside the "Configuration" class I have methods that does post/patch to serviceLayer depending if the item exists or not (control is done previously). Then in the main program, I have a for each loop which use the method to post/patch items to each companies that are in the "List" with ItemMasterData objects, the problem is, it seems the post/patch is done to always the 1st company in the List which serviceLayer was connected to.

But when I declare SLConnection one by one in the code it seems to work fine and as expected (if I create SLConnection sl1, SLConnection sl2, etc.).

I've checked if the Cookies SessionId were ok using the "call.Request.Cookies" and it seems so but only the 1st database is "updated" (multiple times).

My objective is to be able to replicate an item from one "master" database (not in the List) to other "child" databases (in the List).

Do you have any idea of the problem?

How to PATCH TaxCodes on Orders?

Hi! I trying to figure out how to update TaxCodes on an order. The TaxCode is on each Document Line. How would I PATCH each Line item?

I understand updating top level:
var updateOrderComments= new { Comments = "Update my Comments" };
await serviceLayer.Request("Orders", 191).PatchAsync(updateOrderComments);

But can't get the right syntax to update the Document Lines array.

From the B1 Service Reference:
{
"CardCode": "c001",
"DocDueDate": "2014-04-04",
"DocumentLines": [
{
"ItemCode": "i001",
"Quantity": "100",
"TaxCode": "T1",
"UnitPrice": "30"
}
]
}

error handling in batch processing

Are these statements correct?

  • PostBatchAsync() respectively Flurl.Http.MultipartExtensions.PostMultipartAsync does never throw an exception like SLException or FlurlHttpException for logic errors from B1 (like '256000267 - The unit of length, width, height or weight cannot be set if the length, width, height or weight is 0.').
  • Batch processing in PostBatchAsync() respectively Flurl.Http.MultipartExtensions.PostMultipartAsync stops on request with error (for example statuscode 400), so if PostBatchAsync() is executed with a list of 3 requests and the 2nd produces an error (with for example statuscode 400), the 3rd request will not be executed and it returns a batchResult with only 2 entries (for example statuscodes 204 and 400).
  • Therefore this will be an exemple for an appropriate error handling:
List<SLBatchRequest> slBatchRequests = new List<SLBatchRequest>();
slBatchRequests.Add(new SLBatchRequest(new System.Net.Http.HttpMethod("PATCH"), "Items('123')", new {ItemName = "xxx"}));
slBatchRequests.Add(new SLBatchRequest(new System.Net.Http.HttpMethod("PATCH"), "Items('124')", new {ItemName = "xxx"})); //item with error
slBatchRequests.Add(new SLBatchRequest(new System.Net.Http.HttpMethod("PATCH"), "Items('125')", new {ItemName = "xxx"}));
const string jsonContentType = "application/json";
System.Net.Http.HttpResponseMessage[] batchResult = null;
batchResult = await serviceLayer.PostBatchAsync(slBatchRequests, false);
for (int i = 0; i < batchResult.Length; i++) {
    if (!batchResult[i].IsSuccessStatusCode) {
        if (batchResult[i].Content != null && batchResult[i].Content.Headers.ContentType.MediaType == jsonContentType) {
            string jsonString = await batchResult[i].Content.ReadAsStringAsync();
            SLResponseError error = JsonConvert.DeserializeObject<SLResponseError>(jsonString);
            Console.WriteLine($"Error in request {i + 1}: {error.Error.Code}, {error.Error.Message.Value}");
        }
        else {
            Console.WriteLine($"Error in request {i + 1}: unknown error");
        }
    }
}
if(batchResult.Count() < slBatchRequests.Count) {
    Console.WriteLine($"Due to error in request {batchResult.Count()} the following {slBatchRequests.Count - batchResult.Count()} requests have not been executed.");    
}

string-mode for batch-method

I have used your method PatchStringAsync, which takes the JSON data as direct string. But there seems to be no method like PostBatchStringAsync. Can you add somethink like that?

Just for testing I used this variant:

slBatchRequests.Add(new SLBatchRequest(new System.Net.Http.HttpMethod("PATCH"), "Items('01003')", "{\"ItemPrices\": [{ \"PriceList\": 111,\"Price\": 97.9}]}"));
slBatchRequests.Add(new SLBatchRequest(new System.Net.Http.HttpMethod("PATCH"), "Items('01003')", "{\"ItemxPrices\": [{ \"PriceList\": 111,\"Price\": 98.9}]}"));
slBatchRequests.Add(new SLBatchRequest(new System.Net.Http.HttpMethod("PATCH"), "Items('01003')", "{\"ItemPrices\": [{ \"PriceList\": 111,\"Price\": 98.9}]}"));
System.Net.Http.HttpResponseMessage[] batchResult = await serviceLayer.PostBatchAsync(slBatchRequests, false, true);
public async Task<HttpResponseMessage[]> PostBatchAsync(IEnumerable<SLBatchRequest> requests, bool singleChangeSet = true, bool contentAsString = false)
{
if (contentAsString)
{
    request.Content = new Flurl.Http.Content.CapturedStringContent((string)batchRequest.Data);
}
else
{
    request.Content = new StringContent(JsonConvert.SerializeObject(batchRequest.Data, batchRequest.JsonSerializerSettings), batchRequest.Encoding, "application/json");
}

Inconsistent Response Headers between Android and iOS in .NET MAUI with B1Slayer

Your project is great! It's saved me a ton of time. However when implementing B1Slayer in a .NET MAUI app, I've noticed an inconsistency in the response headers between Android and iOS. On Android, I receive authentication headers that are missing on iOS.

SLConnection.LoginAsync on Android gives the following output:

[DOTNET] Request URL: https://URL:50000/b1s/v1/Login
[DOTNET] HTTP Method: POST
[DOTNET] Response Status Code: OK
[DOTNET] Request Headers:
[DOTNET] Response Headers:
[DOTNET] Connection: Keep-Alive
[DOTNET] Date: Wed, 23 Aug 2023 10:05:18 GMT
[DOTNET] Keep-Alive: timeout=5, max=100
[DOTNET] Server: Apache
[DOTNET] Set-Cookie: B1SESSION=9056bec8-419c-11ee-c000-000c2980395a-3812-14596;HttpOnly;;Secure;SameSite=None, ROUTEID=.node1; path=/;Secure;SameSite=None
[DOTNET] Vary: Accept-Encoding
[DOTNET] X-Android-Received-Millis: 1692785120383
[DOTNET] X-Android-Response-Source: NETWORK 200
[DOTNET] X-Android-Selected-Protocol: http/1.1
[DOTNET] X-Android-Sent-Millis: 1692785117731
[DOTNET] Response Body: {
[DOTNET] "odata.metadata" : "https://URL:50000/b1s/v1/$metadata#B1Sessions/@Element",
[DOTNET] "SessionId" : "9056bec8-419c-11ee-c000-000c2980395a-3812-14596",
[DOTNET] "Version" : "1000210",
[DOTNET] "SessionTimeout" : 30
[DOTNET] }

The same request on iOS:
2023-08-23 11:06:57.445422+0100 SLTest[82914:1147716] Request URL: https://URL:50000/b1s/v1/Login
2023-08-23 11:06:57.445727+0100 SLTest[82914:1147716] HTTP Method: POST
2023-08-23 11:06:57.446298+0100 SLTest[82914:1147716] Response Status Code: OK
2023-08-23 11:06:57.446382+0100 SLTest[82914:1147716] Request Headers:
2023-08-23 11:06:57.446466+0100 SLTest[82914:1147716] Response Headers:
2023-08-23 11:06:57.446848+0100 SLTest[82914:1147716] Server: Apache
2023-08-23 11:06:57.446985+0100 SLTest[82914:1147716] Vary: Accept-Encoding
2023-08-23 11:06:57.448994+0100 SLTest[82914:1147716] Date: Wed, 23 Aug 2023 10:06:54 GMT
2023-08-23 11:06:57.449116+0100 SLTest[82914:1147716] Keep-Alive: timeout=5, max=100
2023-08-23 11:06:57.449216+0100 SLTest[82914:1147716] Connection: Keep-Alive
2023-08-23 11:06:57.453453+0100 SLTest[82914:1147716] Response Body: {
"odata.metadata" : "https://URL:50000/b1s/v1/$metadata#B1Sessions/@Element",
"SessionId" : "c9695270-419c-11ee-c000-000c2980395a-12492-1548",
"Version" : "1000210",
"SessionTimeout" : 30
}

Any iOS request afterwards is missing headers resulting in the following:

2023-08-23 11:27:14.436896+0100 SLTest[84984:1176478] Request URL: https://URL:50000/b1s/v1/Items?$select=ItemCode%2C ItemName
2023-08-23 11:27:14.437294+0100 SLTest[84984:1176478] HTTP Method: GET
2023-08-23 11:27:14.437526+0100 SLTest[84984:1176478] Response Status Code: Unauthorized
2023-08-23 11:27:14.437677+0100 SLTest[84984:1176478] Request Headers:
2023-08-23 11:27:14.437907+0100 SLTest[84984:1176478] Response Headers:
2023-08-23 11:27:14.438118+0100 SLTest[84984:1176478] Server: Apache
2023-08-23 11:27:14.438274+0100 SLTest[84984:1176478] Vary: Accept-Encoding
2023-08-23 11:27:14.438471+0100 SLTest[84984:1176478] Date: Wed, 23 Aug 2023 10:27:14 GMT
2023-08-23 11:27:14.438668+0100 SLTest[84984:1176478] WWW-Authenticate: Basic realm=/b1s/v1/Items
2023-08-23 11:27:14.438817+0100 SLTest[84984:1176478] Keep-Alive: timeout=5, max=100
2023-08-23 11:27:14.438967+0100 SLTest[84984:1176478] Connection: Keep-Alive
2023-08-23 11:27:14.439215+0100 SLTest[84984:1176478] Response Body: {
"error" : {
"code" : 301,
"message" : {
"lang" : "en-us",
"value" : "Invalid session or session already timeout."
}
}
}

Have tried adjusting iOS info.plist file with NSAppTransportSecurity headers.

Can reproduce by making new Maui (dotnet 7) project with B1SLayer 1.3.2, creating a new SLConnection, awaiting LoginAsync and examining the response using SLConnection.AfterCall

SLTest.zip

How to update table child?

Hi. I tried just updating or inserting the child table in User Table but it keeps giving me an error.
"One or more errors occurred. (Unrecognized resource path.)"

Thanks

Multiple filters in .Filter method

Hello,

I would like to know how add multiple filter in a single request such as startswith and eq.
For instance, .Filter("Disabled eq 'N'", "startswith(CardName, 'C')").

Thank you.

Default Classes

Hi. Thanks a lot for that helpful project. It would be helpful to have some default classes like BusinessPartner to get started.

I will implement my own but I think they are very similar for everybody and maybe you already have some defaults. If not I will create a PR as soon as I implemented some of them.

Thanks
Dominic

Expand em BusinessPartners

Caros, poderiam dar um norte estou querendo pegar os dados fiscais do PN, alguma sugestão..
GetIDBusinessParners = await SL.Connection.Request("BusinessPartners", Id)
.Select("CardCode, CardName")
.Expand("BPFiscalTaxIDCollection").Select("TaxId0,TaxId1") /// Esta Correto desta forma ? sem o expand funciona perfeitamente
.WithPageSize(10)
.WithCaseInsensitive()
.GetAsync();

queria algo do tipo

$crossjoin(Orders,Orders/DocumentLines)
?$expand=Orders($select=DocEntry, DocNum),Orders/DocumentLines($select=ItemCode,LineNum)
$filter=Orders/DocEntry eq Orders/DocumentLines/DocEntry and Orders/DocumentLines/ItemCode eq 'A00001'

De qualquer forma esta ajudando muito, obrigado!

Document Status

Hello @bgmulinari,

If there is a way to change the status of a document via B1SLayer?

Service Layer API Reference:
POST ../b1s/v1/Orders(123)/Cancel
POST ../b1s/v1/Orders(123)/Close
POST ../b1s/v1/DeliveryNotes(123)/Cancel

CrossJoin

Hi Bruno,

I've tried to get the CrossJoin function to work but it gives "Bad post content".

In Postman we do this request :

https://api.hanaserver.nl:50000/b1s/v2/QueryService_PostQuery

Body :
{
"QueryPath":"$crossjoin(Invoices,Invoices/DocumentLines)",
"QueryOption":"$expand=Invoices($select=DocEntry, DocNum),Invoices/DocumentLines($select=ItemCode,LineNum)&$filter=Invoices/DocEntry eq Invoices/DocumentLines/DocEntry and (Invoices/DocumentLines/ItemCode eq '10617' and Invoices/DocumentLines/U_StartDate eq '01022023' and Invoices/DocumentLines/U_EndDate eq '28022023' and Invoices/CardCode eq 'zp3')"
}

The result is like this :

{
"@odata.context" : "$metadata#Collection(Edm.ComplexType)",
"value" : [
{
"Invoices" : {
"DocEntry" : 19,
"DocNum" : 5
},
"Invoices/DocumentLines" : {
"ItemCode" : "10617",
"LineNum" : 0
}
}
],
"@odata.nextLink" : "/b1s/v2/QueryService_PostQuery?$expand=Invoices($select=DocEntry, DocNum),Invoices/DocumentLines($select=ItemCode,LineNum)&$filter=Invoices/DocEntry eq Invoices/DocumentLines/DocEntry and (Invoices/DocumentLines/ItemCode eq '10617' and Invoices/DocumentLines/U_StartDate eq '01022023' and Invoices/DocumentLines/U_EndDate eq '28022023' and Invoices/CardCode eq 'zp3')&$skip=1"
}

Is this possible to do with B1Sslayer ?

Regards,

Paul

How would you do an SQL query?

Stumbled upon this project by chance, and fell instantly in love. So much easier than that cumbersome OData client.
Ran into an issue, which I need a bit of help with.

How would you do an SQL query with this project?
As far as I can tell, the only way I could do it, is:

 var result = await Client.Request("SQLQueries('ItemPrices')/List")
                                     .SetQueryParam("itemCode", itemCode)
                                     .GetAsync<QueryResult<ItemPrice>>();

Where ItemPrices is, of course, the name of the query.

Oh, QueryResult has been defined like this:

public class QueryResult<T>
{
    public string SqlText { get; set; }
    public T[] Values { get; set; }
}

And, of course, ItemPrice is a class that reflects the fields being queried.

Model classes

Thanks for such a great library. I want to use it in a project, but I am new to SAP Business One and I am not sure what would I need to do or research in order to create the SAP B1 model classes. What would you recommend? Thanks!

Using Vulnerable Version of Newtonsoft (12.0.2)

When I use the nuget package in Linqpad, it notifies that the package is using a version of Newtonsoft.Json that has a vulnerability (12.0.2). See: GHSA-5crp-9r3c-p9vr

Recommend updating to a newer version of newtonsoft.json or switching to system.text.json.

Great package overall though!

Aspnet Core Web API

Hi @bgmulinari,

In first place, great Job!

I developed a web Api Core to integrate an external web application with SAP B1 to create Orders and more...
I used DI API, it works, but it i think is not the best approach to do that.

I developed authentication and authorization with JWT with identity core to authenticate to my web api.

After reading your article, it seems that your solution is too much better than mine, and save a lot of work, very clean ...

In my case, i can make posts, gets.

My endpoints:
base_url/api/v1/Login
base_url/api/v1/SalesOrder

My doubt is how too adapt and use your solution.

For example, I need to have an endpoint base_url/api/v1/Login, and instead of using my authentication logic, i can call LoginAsync inside my Authentication Controller ?

thanks!
cheers
Roger

SAP Service Layer - Login Response

Hello Mate,

I tried to Login using the following method -
var serviceLayer = new SLConnection("https://sapserver:50000/b1s/v1", "CompanyDB", "manager", "12345");

but how to fetch the session ID ? because I am getting complete object as NULL. Please find the below screenshot for your reference

image

regards
Rahul Jain

SeriesService

Hi!

I would like to know how would be done a request to SeriesService GetDocumentSeries:
image

The problem is: it's a POST call with a JSON that returns a list like it would be a normal GET request.

Multiple File Attachment Issue

Hello bgMulinari,

Greetings, Thanks for creating this wonderful library 👍 I am trying to use it in my project. We have the requirement to upload multiple files @ one go. So I was trying to put my hands-on the following methods but stumbling upon the following error -

I tried using all the methods but its failed

Method 1 - PostAttachmentsAsync --(fileName, byteArray)
Method 2 - PostAttachmentsAsync -- (fileName, Stream)

Screen-Shot 1

image

Screen-Shot 2

image

I also tried to upload the single file using the following method -

var attachmentEntry = await serviceLayer.PostAttachmentAsync(@"C:\files\myfile.pdf"); and it worked.

Also if possible Can you please share sample code of uploading multiple files because for me its getting failed :(

Response body with post request

Hello, is there a way to get the response of successful post request and return it to the client?
For example this request:
await serviceLayer.Request("Invoices").PostAsync(arReserve);
Typically, when performing a successful POST request, a response containing information about the created invoice is returned.

Login request with 30+ seconds response time

Hi, I am trying to verify user credentials doing this

await _serviceLayer.Request("Login").PostAsync(new SapLogin { CompanyDB = _configuration["Sap:DatabaseName"], Password = request.Password, UserName = request.Username });

When user credentials are correct I get response in 3-8 seconds, however when the credentials are wrong response time is between 30-40 seconds. Am I doing anything wrong?

I was using normal http request and maximum time normally would be 6 seconds.

Error while posting attachment

Hello guys,
I am continuously getting an error, so any kind of help will be highly appreciated.
I am sending file to PostAttachmentAsync end point but I receive an exception;
-- error code 404
-- error message Could not process request (Create File Error: C:\tmp) (Call failed. Error while copying content to a stream: POST https://xxxxxxxxxxxxxxxx:xxxx/b1s/v1/Attachments2)

Thanks You.

Patch Batch Request not remove items in a collection

Hello, I am using slBatchRequests PATCH, to eliminate some elements from the collection, it does not give an error but it does not make the change and this that I specify the WithReplaceCollectionsOnPatch extension, it should be noted that only in the Batch I present the problem because in PatchStringAsync it works fine, but Since there are several changes, that's why I want it to go in Batch.

The code

var req1 = new SLBatchRequest(
HttpMethod.Patch,
"WHS1('BV-091')",
"{"WHS2Collection": [{ "LineId":1,"U_Usuario": "manager","U_DesUsuario":"manager" }]}")
.WithReplaceCollectionsOnPatch()
.WithReturnNoContent();

            HttpResponseMessage[] batchResult = await _serviceLayer                       
            .PostBatchAsync(req1);

thanks for the help, great library B1Slayer

SSO with UI API

Can you add the ability to login from UI API without username and password, by using SBO_Application.Company.GetServiceLayerConnectionContext, like described in here ?

I have tested it successfully:

example of using:

string serviceLayerRoot = $"https://{Application.SBO_Application.Company.ServerName}:50000/b1s/v1";
var serviceLayer = new SLConnection(serviceLayerRoot, Application.SBO_Application.Company.GetServiceLayerConnectionContext, 30);
var bpList = await serviceLayer.Request("PriceLists").Filter("PriceListName eq '105900'").Select("PriceListNo").WithCaseInsensitive().GetAsync<List<PriceList>>();
int priceListNo = bpList.First().PriceListNo;

code add / changes:

#region Fields
Func<string, string> GetServiceLayerConnectionContext;



/// <summary>
/// Initializes a new instance of the <see cref="SLConnection"/> class.
/// Only one instance per company/user should be used in the application. 
/// </summary>
/// The Service Layer root URI. The expected format is https://[server]:[port]/b1s/[version]
/// <param name="GetServiceLayerConnectionContext">method from UI api</param>
/// <param name="sessionTimeout">session timeout value of ServiceLayer, see b1s.conf</param>
public SLConnection(string serviceLayerRoot, Func<string, string> GetServiceLayerConnectionContext, int sessionTimeout = 30) {
    ServiceLayerRoot = new Uri(serviceLayerRoot);
    CompanyDB = null;
    UserName = null;
    Password = null;
    Language = null;
    NumberOfAttempts = 3;
    LoginResponse = new SLLoginResponse {
        SessionTimeout = sessionTimeout
    };
    this.GetServiceLayerConnectionContext = GetServiceLayerConnectionContext;
    //
    FlurlHttp.ConfigureClient(ServiceLayerRoot.RemovePath(), client => {
        // Disable SSL certificate verification
        client.Settings.HttpClientFactory = new CustomHttpClientFactory();
        // Ignore null values in JSON
        client.Settings.JsonSerializer = new NewtonsoftJsonSerializer(new JsonSerializerSettings {
            NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore
        });
    });
}



// Session still valid, no need to login again
if (DateTime.Now.Subtract(_lastRequest).TotalMinutes < _loginResponse.SessionTimeout)
    return expectReturn ? LoginResponse : null;
//
if(GetServiceLayerConnectionContext != null) {
    string connectionContext = GetServiceLayerConnectionContext(ServiceLayerRoot.ToString());
    Cookies = new CookieJar();
    string[] cookieItems = connectionContext.Split(';');
    foreach (var cookieItem in cookieItems) {
        string[] parts = cookieItem.Split('=');
        if (parts.Length == 2) {
            Cookies.AddOrReplace(new Flurl.Http.FlurlCookie(parts[0].Trim(), parts[1].Trim(), ServiceLayerRoot.ToString()));
        }
    }
    _loginResponse.LastLogin = DateTime.Now;
    return null;
}

I didn't find a way to get the ServiceLayer sessiontimeout value, which is defined in b1s.conf, so for the moment i have added it as parameter with default value (default 30 min). Do you have an idea how it can be done automatically?

Working with multiple instances doesn't work

Hi Bruno,

When we connect 2 or more companies using B1Slayer the eventhandling is only working on the last connected company

Example :

B1Connection = new SLConnection(serviceLayerRoot, "LEEG", "manager", "demo");
B1Connection.AfterCall(async call =>
{
TextOk("AC :"+ $"Response: {call.HttpResponseMessage?.StatusCode}");
ResponseBody = await call.HttpResponseMessage?.Content?.ReadAsStringAsync();

        });
        B1Connection.OnError(async err =>
        {
            ErrorBody = await err.HttpResponseMessage?.Content?.ReadAsStringAsync();
        });

        B1Connection2 = new SLConnection(serviceLayerRoot, "SBODEMONL", "manager", "demo");
        B1Connection2.AfterCall(async call2 =>
        {
            ResponseBody2 = await call2.HttpResponseMessage?.Content?.ReadAsStringAsync();
            TextOk("AC2 :" + $"Response: {call2.HttpResponseMessage?.StatusCode}");
        });
        B1Connection2.OnError(async err2 =>
        {
            ErrorBody2 = await err2.HttpResponseMessage?.Content?.ReadAsStringAsync();
        });

In this example the Aftercall event of the first connection doesn't work. If we remove the 2nd the eventhandling on the first company is working fine.

Can this be solved ?

Regards,

Paul

Invalid session

          This should be fixed with version [1.3.1](https://github.com/bgmulinari/B1SLayer/releases/tag/1.3.1). Sorry for the delay on this release.

Thanks for your report, @it-AhmedTaha.

Originally posted by @bgmulinari in #31 (comment)

Hi, thank you for the new update. I have performed some tests with the new update, but the issue still persists. I believe there might be a problem in the logic of the ExecuteLoginAsync method, specifically in determining whether a new login should happen or not.

Here's the part of the code that I think might be incorrect:

if (DateTime.Now.Subtract(this._lastRequest).TotalMinutes < (double)(this._loginResponse.SessionTimeout - 1))
The issue seems to be that the _lastRequest variable is always updated with each new request, so the result of DateTime.Now.Subtract(this._lastRequest).TotalMinutes will never be greater than _loginResponse.SessionTimeout.

As a potential solution, I suggest utilizing the LastLogin time instead to calculate the time difference accurately. Here's a possible modification:

var timeDifference = DateTime.Now - _loginResponse.LastLogin;
if (timeDifference.TotalMinutes < 29)
    return expectReturn ? LoginResponse : null;

return values for Patch-methods?

As SAP Service Layer User Manual says, the using of PATCH or PUT to update an entity returns HTTP code 204:

On success, HTTP code 204 is returned without content.

Does it make sense to return something so it is possible to check for this code?
For example and testing I have returned the FlurlResponse:

using:

Flurl.Http.FlurlResponse ret = null;
try {
    ret = await serviceLayer.Request("Items", "124").PatchStringAsync("{\"ItemName\": \"xxx\"}");
}
catch {}
if (ret != null && ret.StatusCode == 204) {
    Application.SBO_Application.MessageBox("Success.");
}

code change:

public async Task<Flurl.Http.FlurlResponse> PatchStringAsync(string data)
{
    Flurl.Http.FlurlResponse response = null;
    await _slConnection.ExecuteRequest(async () =>
    {
        response = (FlurlResponse)await FlurlRequest.WithCookies(_slConnection.Cookies).PatchStringAsync(data);
        return response;
    });
    return response;
}

Selecting a custom field

If i try to select a user-defined fields like this

https://:50000/b1s/v1/BusinessPartners?$top=1&$select=CardCode, U_DOCUMENTO

{
"odata.metadata": "https://:50000/b1s/v1/$metadata#BusinessPartners",
"value": [
{
"CardCode": "PL17919001",
"U_DOCUMENTO": "19001"
}
]
}

this code returns error
B1SLayer.SLException: Property 'U_Documento' of 'BusinessPartner' is invalid

List bpList = await _serviceLayer
.Request("BusinessPartners")
.WithTimeout(TimeSpan.FromSeconds(3000))
.Select("CardCode, CardType, CardName, Address, Phone1, EmailAddress, U_Documento")
.WithPageSize(10)
.GetAsync<List>();

How can I select that kind of fieds?

Blank result

Not sure why I'm getting blank result here

            var bpList = await serviceLayer.Request("BusinessPartners")                
                .Select("CardCode, CardName")
                .OrderBy("CardName")                
                .WithCaseInsensitive()
                .GetAsync<List<MyBusinessPartnerModel>>();

  public class MyBusinessPartnerModel
    {
        string CardCode { get; set; }
        string CardName { get; set; }
    }

But in AfterCall it return data.

Tratamento de Login

Bom dia!

Como seria feito o tratamento do Login, sendo que a duração de cada Login dura 30 minutos.

Como eu verificaria se estaria conectado ou se preciso realizar um novo login.

Falha no Response do BATCH quando existe TRANSACTIONNOTIFICATION

Boa tarde,

Estamos com dificuldade em pegar o response quando é uma resposta da TRANSACTIONNOTIFICATION...

Apresenta uma exceção e não retorna a mensagem:
---> System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.IO.IOException: The response ended prematurely. at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) --- End of inner exception stack trace --- at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.DecompressionHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken) at Flurl.Http.FlurlRequest.SendAsync(HttpMethod verb, HttpContent content, CancellationToken cancellationToken, HttpCompletionOption completionOption) --- End of inner exception stack trace --- at B1SLayer.SLConnection.ExecuteRequest[T](Func1 action)
at B1SLayer.SLConnection.PostBatchAsync(IEnumerable1 requests, Boolean singleChangeSet)

Alguma sugestão para resolvermos essa questão?

Obs. Apenas quando é um retorno de TRANSACTIONNOTIFICATION que não conseguimos capturar!

Any way to bypass URL Encoding?

I'm trying to access the SQLQueries endpoint and pass in a parameter, which requires a url that looks like this:

GET /b1s/v2/SQLQueries('')/List?

I'm having problems calling this with B1Slayer though- I tracked the service layer logs and it looks like the issue is related to the URL encoding, my assumption is this comes from Flurl?

Given this sample code:

var attachmentInfo = await _serviceLayer.Request("SQLQueries('GetAttachmentCode')/List?fileName='test'").GetAsync();

The actual request that makes it to the service layer looks like this:

GET /b1s/v2/SQLQueries('GetAttachmentCode')/List%3FfileName='test'

The issue here seems to be at the '/List?' section of the URL- either B1Slayer or Flurl seems to be encoding the question mark, which the service layer doesn't seem to agree with. When I test the request in Postman, no issue- when I try it from B1Slayer (And from Postman with the ? pre-encoded to %3F) I get this error from SAP:

"error": {
"code": "-1008",
"message": "Command Not Found"
}

Is there any way to bypass the URL encoding that the library is doing to the request string?
Appreciate your time and your library!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.