Closed clemenslinders closed 4 years ago
@clemenslinders Sorry this is a little late. Perhaps this will help someone.
We are using Simple.OData.Client to communicate with D365BC web services and APIs. When we create the ODataClientSettings object, we pass a base URL which is something like
Web Services: https://api.businesscentral.dynamics.com/v2.0/
We create the client from the Client Settings:
ODataClientSettings clientSettings = new ODataClientSettings("https://api.businesscentral.dynamics.com/v2.0/<tenant_id>/sandbox/ODataV4/")
{
IncludeAnnotationsInResults = true,
IgnoreResourceNotFoundException = true,
IgnoreUnmappedProperties = true,
};
IODataClient client = new ODataClient(clientSettings);
When we make the OData call, we pass the name of the method (i.e. name of web service in BC):
IBoundClient<Worker> command = client.For("Company").Key("CRONUS%20NL").NavigateTo<Worker>("WorkersWebService");
var workers = await command.FindEntriesAsync();
Hi wintg-zns,
Thanks for your response,
I have one small problem with your code sample.
You use: IBoundClient <Worker
So Worker is a class, but how do you define this class?
Is there an automated way to inherit the correct properties and if not what should it contain.
It would be very nice if you could give me a sample of that.
And how about passing the network credentials in your sample?
@clemenslinders, my apologies for not being more clear.
Worker is the name of your class from your example above. It is the class that represents the object to be retrieved/inserted/updated/deleted. I tried to give an example that followed your example as much as possible.
I'm not quite sure which properties you want to inherit. We have a library that handles/wraps the management of ODataClients and their associated settings. The code looks almost identical to the code above that creates an ODataClientSettings object with a few conditional statements to handle a few options that we configure.
We pass our credentials pretty much the same way you do:
clientSettings.Credentials = new NetworkCredential("UserName", "Password");
BC doesn't use anything other than basic auth, right now, but our library has some stubbed code to potentially handle other authentication schemes when BC gets around to implementing them.
Hi Wintg-zns,
Thanks for your reply.
Currently I am on holiday.
In approx 14 days I will give it a try.
Kind regards,
Clemens Linders
Hi Wintg-zns,
Thanks for your reply, I can now say that I have it working.
As there is very little about this topic I will put my whole test code here so that others can benefit from this as well.
AL language: CpWorkers.AL: page 50108 "Workers Card" { PageType = Card; ApplicationArea = All; UsageCategory = Administration; SourceTable = Workers;
layout
{
area(Content)
{
group(General)
{
field("No."; "No.")
{
ApplicationArea = Basic;
Importance = Promoted;
}
field("First name"; "First name")
{
ApplicationArea = Basic;
}
field("Last name"; "Last name")
{
ApplicationArea = Basic;
}
field(FunctionName; FunctionName)
{
ApplicationArea = Basic;
}
}
}
}
}
LpWorkers.AL: page 50109 "Workers List" { PageType = List; ApplicationArea = All; UsageCategory = Lists; SourceTable = Workers;
layout
{
area(Content)
{
repeater(Group)
{
field("No."; "No.")
{
ApplicationArea = Basic;
}
field("First name"; "First name")
{
ApplicationArea = Basic;
}
field("Last Name"; "Last Name")
{
ApplicationArea = Basic;
}
field(FunctionName; FunctionName)
{
ApplicationArea = Basic;
}
}
}
}
}
TabWorkers.AL: table 50109 "Workers" { DataClassification = ToBeClassified;
fields
{
field(1; "No."; Code[20])
{
DataClassification = ToBeClassified;
}
field(10; "First name"; Text[50])
{
DataClassification = ToBeClassified;
}
field(20; "Last Name"; Text[50])
{
DataClassification = ToBeClassified;
}
field(40; FunctionName; Text[50])
{
DataClassification = ToBeClassified;
}
}
trigger OnInsert()
var
myInt: Integer;
begin
end;
trigger OnModify()
var
myInt: Integer;
begin
end;
trigger OnDelete()
var
myInt: Integer;
begin
end;
}
WorkersWebService.xml: <?xml version = "1.0" encoding = "utf-8" ?>
WorkersWS.xml (not used in this sample) <?xml version = "1.0" encoding = "utf-8" ?>
Don't forget to publish the code in D365BC and in D365BC (search for web services) copy the Odata4 address.
C# code: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Simple.OData.Client;
namespace OdataTestSimpleClient { public partial class Form1 : Form { public class Worker { private string No; private string Firstname; private string LastName; private string FunctionName;
public string no { get => No; set => No = value; }
public string firstname { get => Firstname; set => Firstname = value; }
public string lastName { get => LastName; set => LastName = value; }
public string functionName { get => FunctionName; set => FunctionName = value; }
}
public Form1()
{
InitializeComponent();
}
private async void button1_ClickAsync(object sender, EventArgs e)
{
System.Net.NetworkCredential networkCredential = new System.Net.NetworkCredential("USERNAME", "PASSWORD");
ODataClientSettings clientSettings = new ODataClientSettings("https://api.businesscentral.dynamics.com/v2.0/SomeFunkyGuid/Sandbox/ODataV4/", networkCredential)
{
IncludeAnnotationsInResults = true,
IgnoreResourceNotFoundException = true,
IgnoreUnmappedProperties = true,
};
IODataClient client = new ODataClient(clientSettings);
IBoundClient<Worker> command = client.For("Company").Key("CRONUS%20NL").NavigateTo<Worker>("WorkersWebService");
var workers = await command.FindEntriesAsync();
}
}
}
workers now gets filled with the list of workers that you entered (using the card page) in D365BC.
If you see it like this, it looks very easy, but I tried many many ways and this is the only way that works for ODATA.
I hope more people will find this solution.
Again my thanks to: Wintg-zns.
Kind regards,
Clemens Linders
LS,
I am trying to communicate with a D365BC web service. If I use HttpClient there is no problem.
But if I use Simple.Odata.Client v4 than I get an error: Not found: {"error":{"code":"BadRequest_NotFound","message":"The request URI is not valid. Since the segment 'WorkersWebService' refers to a collection, this must be the last segment in the request URI or it must be followed by an function or action that can be bound to it otherwise all intermediate segments must refer to a single resource.
I installed the following packages: //Added Nuget: Install-Package Newtonsoft.Json -Version 12.0.3 //Added Nuget: Install-Package Microsoft.AspNet.WebApi.Client -Version 5.2.7 //Added Nuget: Install-Package Simple.OData.V4.Client -Version 5.12.0
I created a global variable:
As no credentials are set I included at OnLoad:
And as Button.click I included:
The error occurs at this line: var workers = await Client.For().FindEntriesAsync();
I think the problem lies in authentication.
The code I use with HttpClient is: private void btnFindWorkerOdata_Click(object sender, EventArgs e) {//WORKS OK listBox1.Items.Clear(); WorkersReadFromAlWebService = new List();
string _url = "https://api.businesscentral.dynamics.com/v2.0/SomeFunkyGuid/Sandbox/ODataV4/Company('CRONUS%20NL')/WorkersWebService";
HttpWebRequest _request = (HttpWebRequest)WebRequest.Create(_url);
_request.ContentType = "application/json; charset=utf-8";
_request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes("UserName:Password"));
_request.PreAuthenticate = true;
HttpWebResponse _response = _request.GetResponse() as HttpWebResponse;
using (Stream _responseStream = _response.GetResponseStream())
{
var x = _response.Headers.AllKeys;
StreamReader _reader = new StreamReader(_responseStream, Encoding.UTF8);
string _content = _reader.ReadToEnd();
string _jasonPart = GetJsonPartMultiRecord(_content);
List _jWorkers = JsonConvert.DeserializeObject<List>(_jasonPart);
foreach (var _worker in _jWorkers)
{
WorkersReadFromAlWebService.Add(_worker);
listBox1.Items.Add(_worker.Last_Name + " / " + _worker.First_name + " (No: " + _worker.No + ")");
}
}
ClearWorker();
}
This HttpClinet code works, but inserting a record is my real problem so that is why I want to try Simple.Odata.Client. But before I can do that I need to be able to communicate with the web service
I hope someone can help me.
Kind regards,
Clemens Linders