Debugging Business Central APIs with OAuth

Again a quick one. A few weeks back, I spent a lot of time wondering why my API debugging session was not working. I tried adding breakpoints in different places, publishing and republishing the app, changing launch.json settings, but all in vain. Do you know why?.

Well, as basic Auth is depreciated, I was using OAuth 2.0 with client credentials flow/ S2S. But OAuth was setup with an ‘Application’ permission, not a ‘Delegated’ permission. And that was the reason why debugging did not work.

For debug-Attach, the user who has logged in to VS Code must be the same user who has logged in to the session to be attached to. This is possible only if the AAD user is configured with a delegated permission.

I have seen a few queries on API debugging in forums. Most of the developers would be knowing this already, still thought it would be worth sharing. Happy debugging!!!

You do not have the following permissions on CodeUnit Logon Management

A small tip. Yesterday while I was upgrading Business Central V13 to BC 2022 wave 1 (yeah the latest one.. I am excited :)), I got stuck with a permission error.

As the customer was on BC v13, it was a two step upgrade. As they were using v1 extensions, the upgrade was pretty simple and easy. So I got a chance to experience the true benefits of extensions-based BC upgrade :). Remembering the pain when we had to upgrade a customer’s database with tons of customizations to a latest NAV version.

Back to the permission issue. After starting the NAVDataUpgrade,’Get-NAVDataUpgrade’ showed the follwoing error:-

The function CheckPreconditions in the company initialization codeunit 104000 in company has failed due to the following error: 'You do not have the following permissions on CodeUnit Logon Management: Execute'

As I was a ‘super’ admin, I was not sure why there was a permission error. Finally I figured out that ‘Company’ was not blank in ‘User Personalization’ table and that was the culprit.

So please check your permissions and ‘User Personalization’ table in customer’s database before you start running the upgrade steps.

Migrate your web service endpoints from SOAP to OData – Part 2

The first part of the post is here if you haven’t seen.

Let’s see how we can convert the SOAP endpoint of a codeunit-XMLport import function into ODataV4 endpoint. So here is our web service function:-

codeunit 60000 "Web Service Demo"
{
    procedure ImportIntoWebServiceDemoBuffer(inputText: Text): Text
    var
        TempBlob: Codeunit "Temp Blob";
        WebServiceDemoBufferImport: XmlPort "Web Service Demo Buffer Import";
        DemoOutStream: OutStream;
        DemoInStream: InStream;
        OutputText: Text;
    begin
        TempBlob.CreateOutStream(DemoOutStream);
        DemoOutStream.WriteText(inputText);
        TempBlob.CreateInStream(DemoInStream);
        WebServiceDemoBufferImport.SetSource(DemoInStream);
        if not WebServiceDemoBufferImport.Import() then
            OutputText := GetLastErrorText()
        else
            OutputText := 'Imported successfully';
        exit(OutputText);
    end;
}

VariableText XMLport:-

xmlport 60001 "Web Service Demo Buffer Import"
{
    Caption = 'Web Service Demo Buffer Import';
    Format = VariableText;
    UseRequestPage = false;
    RecordSeparator = ';';
    schema
    {
        textelement(RootNodeName)
        {
            tableelement(WebServiceDemoBuffer; "Web Service Demo Buffer")
            {
                fieldelement(EntryNo; WebServiceDemoBuffer."Entry No.")
                {
                }
                fieldelement(VendorNo; WebServiceDemoBuffer."Vendor No.")
                {
                }
                fieldelement(ExternalDocumentNo; WebServiceDemoBuffer."External Document No.")
                {
                }
                fieldelement(GLAccountNo; WebServiceDemoBuffer."G/L Account No.")
                {
                }
                fieldelement(Amount; WebServiceDemoBuffer.Amount)
                {
                }
            }
        }
    }
}

We can call the above function using SOAP endpoint like below:-

### @name ImportIntoBuffer-SOAP
POST {{baseurl}}/v2.0/{{tenantID}}/Sandbox/WS/CRONUS%20AU/Codeunit/WebServiceDemo
Authorization: {{accessHeader}}
SoapAction:""
Content-Type: text/xml

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ImportIntoWebServiceDemoBuffer xmlns="urn:microsoft-dynamics-schemas/codeunit/WebServiceDemo">
      <inputText>1,20000,EXT0001,1110,100.00;2,10000,EXT0002,1110,200.00;</inputText>
    </ImportIntoWebServiceDemoBuffer>
  </soap:Body>
</soap:Envelope>

and we get the output like this:-

<Soap:Envelope 
  xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
  <Soap:Body>
    <ImportIntoWebServiceDemoBuffer_Result 
      xmlns="urn:microsoft-dynamics-schemas/codeunit/WebServiceDemo">
      <return_value>Imported successfully</return_value>
    </ImportIntoWebServiceDemoBuffer_Result>
  </Soap:Body>
</Soap:Envelope>

Now let’s see how we can do the same with ODataV4

### @name ImportIntoBuffer-ODataV4
POST {{baseurl}}/v2.0/{{tenantID}}/Sandbox/ODataV4/WebServiceDemo_ImportIntoWebServiceDemoBuffer?company=CRONUS%20AU HTTP/1.1
Authorization: {{accessHeader}}
Content-Type: application/json

{
    "inputText": "3,20000,EXT0003,1110,100.00;4,10000,EXT0004,1110,200.00;"
}

Here is what we get:-

{
  "@odata.context": "{{baseurl}}/v2.0/{{tenantID}}/Sandbox/ODataV4/$metadata#Edm.String",
  "value": "Imported successfully"
}

Cool!! isn’t it?

An important note here is the first character of the input variable should be in lowercase.

But please note that as of now, we can’t use complex datatypes as input/return parameters. Hope Microsoft will enable this in a future version.

If your XMLport format is XML, you would need to pass your input XML as text like below:-

### @name ImportIntoBuffer-ODataV4
POST {{baseurl}}/v2.0/{{tenantID}}/Sandbox/ODataV4/WebServiceDemo_ImportIntoWebServiceDemoBuffer?company=CRONUS%20AU HTTP/1.1
Authorization: {{accessHeader}}
Content-Type: application/json

{
    "inputText": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
    <RootNodeName xmlns=\"urn:microsoft-dynamics-nav/xmlports/x60001\">
    <WebServiceDemoBuffer>    
    <EntryNo>2</EntryNo>   
    <VendorNo>10000</VendorNo>    
    <ExternalDocumentNo>EXT001</ExternalDocumentNo>   
    <GLAccountNo>1110</GLAccountNo>   
    <Amount>200.00</Amount>  
    </WebServiceDemoBuffer>
    </RootNodeName>"

Please note, you can use ‘WriteTo’ method to convert your input XMLDocument or JSON Object to Text.

Reference :- https://www.kauffmann.nl/2020/03/05/codeunit-apis-in-business-central/

Migrate your web service endpoints from SOAP to OData

 Important

SOAP is replaced by OData V4. The support for SOAP endpoints will be removed in a later release. We recommend that you migrate integrations to OData V4 or REST API web services as soon as possible.

Now, both OData and SOAP can be used with codeunit web services. As the support for SOAP endpoints are going to end, it is a good idea to work on converting your existing SOAP endpoints to OData.

Let’s see how we can convert a simple SOAP endpoint to ODataV4. Below function simply exports vendor data into text.

  codeunit 60000 "Web Service Demo"
{
    procedure ExportVendors() XMLOutput: Text
    var
        TempBlob: Codeunit "Temp Blob";
        ExportVendor: XmlPort "Export Vendor";
        ExpVendOutStream: OutStream;
        ExpVendInStream: InStream;
        TypeHelper: Codeunit "Type Helper";

    begin
        TempBlob.CREATEOUTSTREAM(ExpVendOutStream);
        ExportVendor.SETDESTINATION(ExpVendOutStream);
        ExportVendor.EXPORT();
        TempBlob.CREATEINSTREAM(ExpVendInStream);
        XMLOutput := TypeHelper.ReadAsTextWithSeparator(ExpVendInStream, '');
    end;
}

I used ‘ReadAsTextWithSeparator’ function from Type Heper codeunit to read the InStream into a text variable. You can also use a BigText variable instead.

This is how we call using SOAP

### @name ExportVendor-SOAP
POST {{baseurl}}/v2.0/{{tenantID}}/Sandbox/WS/CRONUS%20AU/Codeunit/WebServiceDemo
Authorization: {{accessHeader}}
SoapAction:""
Content-Type: text/xml

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ExportVendors xmlns="urn:microsoft-dynamics-schemas/codeunit/WebServiceDemo"/>
  </soap:Body>
</soap:Envelope>

Result:-

<Soap:Envelope 
  xmlns:Soap="http://schemas.xmlsoap.org/soap/envelope/">
  <Soap:Body>
    <ExportVendors_Result 
      xmlns="urn:microsoft-dynamics-schemas/codeunit/WebServiceDemo">
      <return_value>&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;&lt;RootNodeName 
        xmlns="urn:microsoft-dynamics-nav/xmlports/x60000"&gt;  &lt;Vendor&gt;    &lt;No&gt;10000&lt;/No&gt;    &lt;Name&gt;Fabrikam, Inc.&lt;/Name&gt;  &lt;/Vendor&gt;  &lt;Vendor&gt;    &lt;No&gt;20000&lt;/No&gt;    &lt;Name&gt;First Up Consultants&lt;/Name&gt;  &lt;/Vendor&gt;  &lt;Vendor&gt;    &lt;No&gt;30000&lt;/No&gt;    &lt;Name&gt;Graphic Design Institute&lt;/Name&gt;  &lt;/Vendor&gt;  &lt;Vendor&gt;    &lt;No&gt;40000&lt;/No&gt;    &lt;Name&gt;Wide World Importers&lt;/Name&gt;  &lt;/Vendor&gt;  &lt;Vendor&gt;    &lt;No&gt;50000&lt;/No&gt;    &lt;Name&gt;Nod Publishers&lt;/Name&gt;  &lt;/Vendor&gt;&lt;/RootNodeName&gt;
      </return_value>
    </ExportVendors_Result>
  </Soap:Body>
</Soap:Envelope>

Here is how we call the same codeunit web service using ODataV4. You won’t get the URL from web services page. But we can change WS to OData and add underscore symbol (_) function name after the web service name to generate our ODataV4 endpoint.

### @name ExportVendor-OData
POST {{baseurl}}/v2.0/{{tenantID}}/Sandbox/ODataV4/WebServiceDemo_ExportVendors?company=CRONUS%20AU HTTP/1.1
Authorization: {{accessHeader}}

and here is what we get ๐Ÿ™‚

{
  "@odata.context": "https://api.businesscentral.dynamics.com/v2.0/{{tenantID}}/Sandbox/ODataV4/$metadata#Edm.String",
  "value": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><RootNodeName xmlns=\"urn:microsoft-dynamics-nav/xmlports/x60000\">  <Vendor>    <No>10000</No>    <Name>Fabrikam, Inc.</Name>  </Vendor>  <Vendor>    <No>20000</No>    <Name>First Up Consultants</Name>  </Vendor>  <Vendor>    <No>30000</No>    <Name>Graphic Design Institute</Name>  </Vendor>  <Vendor>    <No>40000</No>    <Name>Wide World Importers</Name>  </Vendor>  <Vendor>    <No>50000</No>    <Name>Nod Publishers</Name>  </Vendor></RootNodeName>"
}

Hurry up!! Migrate your SOAP endpoints to OData ๐Ÿ™‚

Read more about this:-

https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/webservices/web-services#page-web-services

https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-creating-and-interacting-with-odatav4-bound-action

Change your profile

What would be the first thing that comes to the mind of a NAV consultant if someone asks about restricting user access?. Permission sets!!. But it was painful in NAV. I am amazed by how profiles make so simple.

Profile has been there for a while and most of you will already been utilizing its capability. But I still thought of writing about this cool feature in BC. What makes it even lovable is that it can be done without a single line of code ๐Ÿ™‚

Suppose a junior sales order processor joined a company and he/she should not be able to view the sensitive financial data. Just copy an existing profile, do whatever show/hide/movement you want and save it. Then go to ‘user settings’ page and assign it to a user. You can even export the customized profile as a zip file from your docker environment/sandbox and import it into the customer’s environment. Profile object and page customization object can also be included in extensions.

Don’t forget ‘My settings’ page. The user might go there and change the role. So when you customize a profile to limit the access, search ‘My Settings’ page and hide ‘Role’ field. I think this is far more easier than playing with permission sets. Of course you should assign bare minimum permissions to the user.

Read more about profiles :- https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-design-profiles

PinPad Control addin

Happiness is trying out something new and sharing it with our awesome BC community :). Well, there is nothing new about this control addin, its a small jquery pinpad. I saw a post about pinpad control addin in mibuso a couple of weeks back and planned to try it out during my free time.

Let me share the github link here: https://github.com/manjusree16/AL-Demos/tree/master/MyPinPad

Search ‘pinpad’ in BC and the following screen appears:-

I have hardcoded the pin as ‘1234’ in AL code. But you can use azure key vault or some other safe place to store the pin. This demo extension will run a page if you enter the correct pin.

AL receives the pin sent by javascript and this is done by Microsoft.Dynamics.NAV.InvokeExtensibilityMethod. Watch this amazing live stream video by Vjecko to learn more – https://www.youtube.com/watch?v=Rp3XhZ1i7H0&t=342s

Item images in a factbox

Inspiration to write this post is also a mibuso forum question about item pictures – https://forum.mibuso.com/discussion/76246/display-mediaset-images-in-factbox. I have been playing around with this during the weekend and learned a few things about media and mediaset.

Even though ‘Picture’ field in Item is of type MediaSet, it only allows us to import a single image. I found a yammer post about this :- https://www.yammer.com/dynamicsnavdev/#/Threads/show?threadId=873801493&search_origin=global&scoring=linear1Y-prankie-group-private-higher&match=any-exact&search_sort=relevance&page=1&search=mediaset. But I couldn’t find any latest update on this.

So this is what I tried to accomplish. Import multiple pictures into Tenant Media, assign these to an item’s MediaSet and display all of them in a factbox with Next and Previous actions.

Something like this ๐Ÿ™‚
wish I could show it as a gif

The reason why I used ‘Config.ย Mediaย Buffer’ as a temp table is just because I wanted a table with two primary keys – one is of course ‘Item No.’ and the second is a serial no. for the images. Here is my GitHub link :- https://github.com/manjusree16/AL-Demos/tree/master/ItemMediaSet

I know there should be tons of possible improvements in my code :P. Please do let me know what all you find out.

Be smart with SmartLists

My insipration to write about SmartList is a mibuso forum post – https://forum.mibuso.com/discussion/76204/displaying-global-dimensions-in-a-query. At the first look, even though I haven’t explored a SmartList before, I was confident that we could set the captions of Shortcut Dimensions using CaptionClass. But sadly, we can’t do that to resolve the issue.

So SmartLists are queries shown on a page. Please note that we should open the page from the Role Cener to view them. I didn’t know this when I checked it for the first time. I could see it initially but later on, I was wondering where it disappeared. So if you want to see SmartLists, bookmark your list page and open it from Role Center.

An important property related to SmartList is QueryCategory. If we specify the same QueryCategory in a query and a page, the page will display the query as a SmartList.

Unfortunately, we don’t have CaptionClass property available for fields in a query. Hopefully Microsoft will make it available in an upcoming version so that we can have the actual captions for shortcut dimensions in SmartLists.

Read more about SmartLists here – http://www.mynavblog.com/2020/10/19/from-gp-to-bc-the-smartlists/

Why there is API pages when we have OData?

A small post. As you know, we can expose pages and queries as OData web services. Have you ever wondered why there is a separate API page when we already have this option?. I have seen such a query in Yammer and thought it’s worth sharing here.

We can expose pages and queries as OData Web Services, but that doesn’t make them APIs – they are just OData Web Services. API is a new (not so new though) construct in Business Central. There are a number of pre-build APIs available in BC. You can see the list here: https://docs.microsoft.com/en-us/dynamics-nav/api-reference/v2.0/

APIs are versioned – meaning that when you use a v1.0 of an API, it never changes (it might be deprecated at some point in time, but it doesn’t change). Currently we have the version v2.0.

One of the problems with OData web services is that if a page changes, the contract is broken.The technology behind the two might look similar, but the API is designed to be a stable contract, like the Microsoft Graph API, the Azure DevOps API, etc.

Unlike our usual pages, we cannot view API pages from within BC. Performance wise too, APIs do a great job.

Learn more about APIs here:https://docs.microsoft.com/en-us/learn/modules/work-with-api/

https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-develop-custom-api

Do look at my custom API example :- https://mybusinesscentraldiary.wordpress.com/2021/02/18/custom-api-example-v2-0/

Custom API Example v2.0

Just to let you know that I upgraded/modified our Custom API Order (https://wordpress.com/post/mybusinesscentraldiary.wordpress.com/368) to version v2.0.

Watch https://events1.social27.com/MSDyn365BCLaunchEvent/agenda/player/72576 for an awesome launch event video that explains what’s new on business central API version v2.0. A few important points I noticed are:

  1. SystemId will be the ODataKeyFields. Entities can even be created by giving a specific SystemId. Records can be selected with the help of GetBySystemId function.
  2. No more complex types are there in API v2.0. Instead, we should use subpages. A huge advantage of this is that we can do deep inserts.
  3. Entity captions and Entity Set Captions are two new properties and these are localizable. I have added how you can view entityDefinition end point in Call_API.http file.

Here goes the github link : https://github.com/manjusree16/AL-Demos/tree/master/CustomAPIv2

Please do look at Call_API.http file where I have added batch call requests. You can learn more about batch calls here – https://www.kauffmann.nl/2020/12/18/batch-calls-with-business-central-apis-1-basic-operation/. Don’t forget to read all the three posts.

Have fun with APIs ๐Ÿ™‚

Create your website with WordPress.com
Get started