bgmulinari / b1slayer Goto Github PK
View Code? Open in Web Editor NEWA lightweight SAP Business One Service Layer client for .NET
License: MIT License
A lightweight SAP Business One Service Layer client for .NET
License: MIT License
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.
Many thanks in advance.
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?
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
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
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)
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?
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
How can I set a local certificate file to the requests?
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.TaskAwaiter
1.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()
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
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"
}]
}
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?
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"
}
]
}
Hi,
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.').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).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.");
}
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");
}
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
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
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.
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
Hi,
I need to connect to SAP with several users.
Is there a way to provide user and password when the service is running and not in Program.cs?
Thank you in advance.
Perhaps it would be nice to have such an example in the README.md:
serviceLayer.Request("AlternateCatNum(ItemCode='123',CardCode='456',Substitute='789')").PatchAsync(new { DisplayBPCatalogNumber = "tYES" });
Hello Mate,
Can u pls create blog over SAP Community so that your work can be rched to more Tech users.
I dont have your email address so I wrote here :)
regards
Rahul Jain
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!
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
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
I needed some way to make multiple file upload to obtain a single attachment entry... It's this actually possible?
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.
Hi Bruno,
Is it possible to add the inlinecount to the request ?
example :
https://api.demoserver:50000/b1s/v2/Users?$select=UserCode,UserName&$inlinecount=allpages
In Postman it's working fine.
I know you have GetCountAsync() but I don't want to make 2 call's when only 1 is enough
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!
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!
How can I cancel a sales order for example?
Eu não encontrei no B1SLayer uma chamada para cancelar um documento. Como faço isso usando o B1SLayer?
It would be to call the post below.
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
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
regards
Rahul Jain
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
Screen-Shot 2
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 :(
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.
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.
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.
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
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?
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
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;
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;
}
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?
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.
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.
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](Func
1 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!
Hello there,
I wanted to filter out the attachment using file name, but I am unable top do so.
Any kind of help would be appreciated.
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!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.