Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. C#
  4. C# and ODATA CRUD communication with D365BC web service

C# and ODATA CRUD communication with D365BC web service

Scheduled Pinned Locked Moved C#
csharpdatabasevisual-studiowinformswcf
7 Posts 2 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • C Offline
    C Offline
    clemenslinders
    wrote on last edited by
    #1

    I have a problem where C# cannot Create/Update/Delete through an ODATA web service running from D365BC. PS I am using VS 2019, C# winforms. Our company is going to use Dynamics 365 Business Central (D365BC), which uses the programming language AL. AL is a nice language but some things we will simply prefer to do in C#. In D365BC you can easily create tables and pages (objects that consume these tables). And for each page you can create a web service by simply clicking a checkbox. These web service can be used to communicate via SOAP/ODATA v3 and ODATA v4. All are available at the same time. So creating a web service that allows communication with a table created in D365BC is fairly easy. If I use the SOAP webservice I can perform all CRUD functions. But when I use ODATA (either v3 or v4) I can read from the web service but I cannot Create/Update or Delete. The reason that we may occasionally want to use ODATA, is that ODATA is faster than SOAP. The code I have in D365BC: A simple table where every field is a record in a SQL table.

    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;
    

    }

    The card page that uses the table and has the ability to make a web service out of the used table.

    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;
                }
    
    Richard DeemingR 1 Reply Last reply
    0
    • C clemenslinders

      I have a problem where C# cannot Create/Update/Delete through an ODATA web service running from D365BC. PS I am using VS 2019, C# winforms. Our company is going to use Dynamics 365 Business Central (D365BC), which uses the programming language AL. AL is a nice language but some things we will simply prefer to do in C#. In D365BC you can easily create tables and pages (objects that consume these tables). And for each page you can create a web service by simply clicking a checkbox. These web service can be used to communicate via SOAP/ODATA v3 and ODATA v4. All are available at the same time. So creating a web service that allows communication with a table created in D365BC is fairly easy. If I use the SOAP webservice I can perform all CRUD functions. But when I use ODATA (either v3 or v4) I can read from the web service but I cannot Create/Update or Delete. The reason that we may occasionally want to use ODATA, is that ODATA is faster than SOAP. The code I have in D365BC: A simple table where every field is a record in a SQL table.

      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;
      

      }

      The card page that uses the table and has the ability to make a web service out of the used table.

      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;
                  }
      
      Richard DeemingR Offline
      Richard DeemingR Offline
      Richard Deeming
      wrote on last edited by
      #2

      Rather than building the request body by hand, you should use a JSON serializer to create it. Add a NuGet package reference to JSON.NET[^], add a using directive for the Newtonsoft.Json namespace, and change your code to:

      var body = new
      {
      No = tbNo.Text,
      First_name = tbFirstName.Text,
      Last_Name = tbLastName.Text,
      FunctionName = tbFunctionName.Text,
      };

      string json = JsonConvert.SerializeObject(body);
      byte[] data = Encoding.UTF8.GetBytes(json);

      NB: You'll want to use the UTF8 encoding rather than ASCII, since that's what you've declared in your content-type header. I'd also be inclined to use the HttpClient class[^] rather than the low-level HttpWebRequest class: Call a Web API From a .NET Client (C#) - ASP.NET 4.x | Microsoft Docs[^]


      "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

      "These people looked deep within my soul and assigned me a number based on the order in which I joined" - Homer

      C 1 Reply Last reply
      0
      • Richard DeemingR Richard Deeming

        Rather than building the request body by hand, you should use a JSON serializer to create it. Add a NuGet package reference to JSON.NET[^], add a using directive for the Newtonsoft.Json namespace, and change your code to:

        var body = new
        {
        No = tbNo.Text,
        First_name = tbFirstName.Text,
        Last_Name = tbLastName.Text,
        FunctionName = tbFunctionName.Text,
        };

        string json = JsonConvert.SerializeObject(body);
        byte[] data = Encoding.UTF8.GetBytes(json);

        NB: You'll want to use the UTF8 encoding rather than ASCII, since that's what you've declared in your content-type header. I'd also be inclined to use the HttpClient class[^] rather than the low-level HttpWebRequest class: Call a Web API From a .NET Client (C#) - ASP.NET 4.x | Microsoft Docs[^]


        "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

        C Offline
        C Offline
        clemenslinders
        wrote on last edited by
        #3

        Hi Richard, Thanks for your reply. If I used the code you profided me but I cannot pass 'data' to _client as it expects a class and not Json data. If I create a class and fill it, like in the MS Docs page you provided, than I get error 401 not authorized.

                WorkersClass \_data = new WorkersClass();
                \_data.E\_Tag = string.Empty;
                \_data.No = tbNo.Text;
                \_data.First\_name = tbFirstName.Text;
                \_data.Last\_Name = tbLastName.Text;
                \_data.FunctionName = tbFunctionName.Text;
        
                var url = await CreateWorkerAsync(\_data);
        
            static async Task CreateWorkerAsync(WorkersClass \_worker)
            {
                string \_url = "https://api.businesscentral.dynamics.com/v2.0/SomeFunkyGuid/Sandbox/ODataV4/Company('CRONUS%20NL')/WorkersWebService";//Card Page
                string \_userName = "UserName";
                string \_wsKey = "Password";
                \_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\_userName, \_wsKey); 
                HttpResponseMessage response = await \_client.PostAsJsonAsync(\_url, \_worker);
                response.EnsureSuccessStatusCode();
        
                // return URI of the created resource.
                return response.Headers.Location;
            }
        

        I tried the following:

                var body = new
                {
                    E\_Tag = string.Empty,     // Unique key
                    No = tbNo.Text,
                    First\_name = tbFirstName.Text,
                    Last\_Name = tbLastName.Text,
                    FunctionName = tbFunctionName.Text,
                };
        
        
                string json = JsonConvert.SerializeObject(body);
                byte\[\] data = Encoding.UTF8.GetBytes(json);
        
                var url = await CreateWorkerAsync(data);
        

        The error I get is: Argument 1: cannot convert from byte[] to WorkersClass. If I look at _client.PostAsJsonAsync, than I don't see a other solution where it says I can pass Json data? I feel that I am close, but I think I miss the final step. Hope you can help me. Kind regards, Clemens Linders

        Richard DeemingR 1 Reply Last reply
        0
        • C clemenslinders

          Hi Richard, Thanks for your reply. If I used the code you profided me but I cannot pass 'data' to _client as it expects a class and not Json data. If I create a class and fill it, like in the MS Docs page you provided, than I get error 401 not authorized.

                  WorkersClass \_data = new WorkersClass();
                  \_data.E\_Tag = string.Empty;
                  \_data.No = tbNo.Text;
                  \_data.First\_name = tbFirstName.Text;
                  \_data.Last\_Name = tbLastName.Text;
                  \_data.FunctionName = tbFunctionName.Text;
          
                  var url = await CreateWorkerAsync(\_data);
          
              static async Task CreateWorkerAsync(WorkersClass \_worker)
              {
                  string \_url = "https://api.businesscentral.dynamics.com/v2.0/SomeFunkyGuid/Sandbox/ODataV4/Company('CRONUS%20NL')/WorkersWebService";//Card Page
                  string \_userName = "UserName";
                  string \_wsKey = "Password";
                  \_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\_userName, \_wsKey); 
                  HttpResponseMessage response = await \_client.PostAsJsonAsync(\_url, \_worker);
                  response.EnsureSuccessStatusCode();
          
                  // return URI of the created resource.
                  return response.Headers.Location;
              }
          

          I tried the following:

                  var body = new
                  {
                      E\_Tag = string.Empty,     // Unique key
                      No = tbNo.Text,
                      First\_name = tbFirstName.Text,
                      Last\_Name = tbLastName.Text,
                      FunctionName = tbFunctionName.Text,
                  };
          
          
                  string json = JsonConvert.SerializeObject(body);
                  byte\[\] data = Encoding.UTF8.GetBytes(json);
          
                  var url = await CreateWorkerAsync(data);
          

          The error I get is: Argument 1: cannot convert from byte[] to WorkersClass. If I look at _client.PostAsJsonAsync, than I don't see a other solution where it says I can pass Json data? I feel that I am close, but I think I miss the final step. Hope you can help me. Kind regards, Clemens Linders

          Richard DeemingR Offline
          Richard DeemingR Offline
          Richard Deeming
          wrote on last edited by
          #4

          I was referring to the code from your original post, where you're constructing a JSON string manually:

          Quote:

          string body = "{" + Environment.NewLine +
          "\"No\":" + tbNo.Text + "," + Environment.NewLine +
          "\"First_name\":\"" + tbFirstName.Text + "\"," + Environment.NewLine +
          "\"Last_Name\":\"" + tbLastName.Text + "\"," + Environment.NewLine +
          "\"FunctionName\":\"" + tbFunctionName.Text + "\"," + Environment.NewLine +
          "}";

          byte[] data = Encoding.ASCII.GetBytes(body);

          try
          {
          _request.ContentLength = data.Length;

          Stream requestStream = \_request.GetRequestStream();
          requestStream.Write(data, 0, data.Length);
          requestStream.Close();
          
          HttpWebResponse \_response = \_request.GetResponse() as HttpWebResponse;//Here we get the exception errors 400 or 405
          Console.WriteLine(\_response.StatusCode);
          

          }
          catch (Exception ex)
          {

          }

          You don't need to worry about the JSON serialization with the HttpClient and the PostAsJsonAsync method - it handles the serialization for you. You're getting a 401 error with your new code because you're passing the wrong values to the AuthenticationHeaderValue. The first parameter is the scheme, which should be "BASIC", and the second is the parameter, which should be the combined username and password. AuthenticationHeaderValue Constructor (System.Net.Http.Headers) | Microsoft Docs[^]

          string _userName = "UserName";
          string _wsKey = "Password";
          byte[] authenticationParameter = Encoding.UTF8.GetBytes(_userName + ":" + _wsKey);
          _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authenticationParameter));


          "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

          "These people looked deep within my soul and assigned me a number based on the order in which I joined" - Homer

          C 1 Reply Last reply
          0
          • Richard DeemingR Richard Deeming

            I was referring to the code from your original post, where you're constructing a JSON string manually:

            Quote:

            string body = "{" + Environment.NewLine +
            "\"No\":" + tbNo.Text + "," + Environment.NewLine +
            "\"First_name\":\"" + tbFirstName.Text + "\"," + Environment.NewLine +
            "\"Last_Name\":\"" + tbLastName.Text + "\"," + Environment.NewLine +
            "\"FunctionName\":\"" + tbFunctionName.Text + "\"," + Environment.NewLine +
            "}";

            byte[] data = Encoding.ASCII.GetBytes(body);

            try
            {
            _request.ContentLength = data.Length;

            Stream requestStream = \_request.GetRequestStream();
            requestStream.Write(data, 0, data.Length);
            requestStream.Close();
            
            HttpWebResponse \_response = \_request.GetResponse() as HttpWebResponse;//Here we get the exception errors 400 or 405
            Console.WriteLine(\_response.StatusCode);
            

            }
            catch (Exception ex)
            {

            }

            You don't need to worry about the JSON serialization with the HttpClient and the PostAsJsonAsync method - it handles the serialization for you. You're getting a 401 error with your new code because you're passing the wrong values to the AuthenticationHeaderValue. The first parameter is the scheme, which should be "BASIC", and the second is the parameter, which should be the combined username and password. AuthenticationHeaderValue Constructor (System.Net.Http.Headers) | Microsoft Docs[^]

            string _userName = "UserName";
            string _wsKey = "Password";
            byte[] authenticationParameter = Encoding.UTF8.GetBytes(_userName + ":" + _wsKey);
            _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authenticationParameter));


            "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

            C Offline
            C Offline
            clemenslinders
            wrote on last edited by
            #5

            Hi Richard, I changed the CreateWorkersAsync:

                static async Task CreateWorkerAsync(WorkersClass \_worker)
                {
                    string \_url = "https://api.businesscentral.dynamics.com/v2.0/SomeFunkyGuid/Sandbox/ODataV4/Company('CRONUS%20NL')/WorkersWebService";//Card Page
                    string \_userName = "UserName";
                    string \_wsKey = "Password";
                    byte\[\] \_authenticationParameter = Encoding.UTF8.GetBytes(\_userName + ":" + \_wsKey);
                    \_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(\_authenticationParameter));
                    HttpResponseMessage response = await \_client.PostAsJsonAsync(\_url, \_worker);
                    response.EnsureSuccessStatusCode();
            
                    // return URI of the created resource.
                    return response.Headers.Location;
                }
            

            I didn't make any other changes. I now no longer get the error 401 Not Authorized. Now I get the error: 400 Bad request We're one step closer. Do you have any idea why I could get this Bad request?? Kind regards, Clemens Linders

            Richard DeemingR 1 Reply Last reply
            0
            • C clemenslinders

              Hi Richard, I changed the CreateWorkersAsync:

                  static async Task CreateWorkerAsync(WorkersClass \_worker)
                  {
                      string \_url = "https://api.businesscentral.dynamics.com/v2.0/SomeFunkyGuid/Sandbox/ODataV4/Company('CRONUS%20NL')/WorkersWebService";//Card Page
                      string \_userName = "UserName";
                      string \_wsKey = "Password";
                      byte\[\] \_authenticationParameter = Encoding.UTF8.GetBytes(\_userName + ":" + \_wsKey);
                      \_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(\_authenticationParameter));
                      HttpResponseMessage response = await \_client.PostAsJsonAsync(\_url, \_worker);
                      response.EnsureSuccessStatusCode();
              
                      // return URI of the created resource.
                      return response.Headers.Location;
                  }
              

              I didn't make any other changes. I now no longer get the error 401 Not Authorized. Now I get the error: 400 Bad request We're one step closer. Do you have any idea why I could get this Bad request?? Kind regards, Clemens Linders

              Richard DeemingR Offline
              Richard DeemingR Offline
              Richard Deeming
              wrote on last edited by
              #6

              A 400 error would suggest that the server doesn't recognise the request format. Are you certain they accept JSON requests? Your original post mentioned a SOAP request, which is a dialect of XML. If they only support SOAP requests, you won't be able to post JSON data to the service.


              "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

              "These people looked deep within my soul and assigned me a number based on the order in which I joined" - Homer

              C 1 Reply Last reply
              0
              • Richard DeemingR Richard Deeming

                A 400 error would suggest that the server doesn't recognise the request format. Are you certain they accept JSON requests? Your original post mentioned a SOAP request, which is a dialect of XML. If they only support SOAP requests, you won't be able to post JSON data to the service.


                "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

                C Offline
                C Offline
                clemenslinders
                wrote on last edited by
                #7

                Hi Richard, I am pretty sure that Json shouldn't be a problem, because when I read the data I use a Json deserializer. There really are three different kind of web services: - SOAP - ODATA v3 - ODATA v4 This is the working code I use to read data using the ODATA V4 web service:

                        listBox1.Items.Clear();
                        WorkersReadFromAlWebService = new List();
                        string \_url = "https://api.businesscentral.dynamics.com/v2.0/SomeFunkyGuid/Sandbox/ODataV4/Company('CRONUS%20NL')/WorkersWebService";//CardPage
                                   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())
                        {
                            StreamReader \_reader = new StreamReader(\_responseStream, Encoding.UTF8);
                            string \_content = \_reader.ReadToEnd();
                
                            string \_jasonPart = GetJsonPartMultiRecord(\_content);
                
                            List \_jWorkers = JsonConvert.DeserializeObject\>(\_jasonPart);
                
                            foreach (var \_worker in \_jWorkers)
                            {
                                WorkersReadFromAlWebService.Add(\_worker);
                                listBox1.Items.Add(\_worker.Last\_Name + " / " + \_worker.First\_name + " (No: " + \_worker.No + ")");
                            }
                        }
                
                        ClearWorker();
                
                1 Reply Last reply
                0
                Reply
                • Reply as topic
                Log in to reply
                • Oldest to Newest
                • Newest to Oldest
                • Most Votes


                • Login

                • Don't have an account? Register

                • Login or register to search.
                • First post
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • World
                • Users
                • Groups