Monday, January 1, 2024

Interface in Business Central

Hello Friends,

Interfaces are a tricky concept that can be difficult to understand. In this article, you will learn how to use interfaces in Business Central step by step. By following an existing interface, you will understand how to implement it to create your own interfaces.

  • What Is An Interface
  • How To Use An Existing Interface
  • Example Setup and Test
  • How To Call An Interface
  • Summary

What Is An Interface


An Interface is a concept that defines a set of methods that codeunits can implement. It does not contain any logic or implementation itself but specifies methods that must be implemented by a codeunit that implements the interface.

Business Central interfaces were introduced in 2020. They are a way of extending Business Central functionality without the need for events. Their benefits are that they enable abstraction, decoupling, and scalability.

How To Use An Existing Interface


Interfaces can be a bit difficult to understand at first. I’ve found that the best way to learn how to create an interface is to use an existing one. This way, you will understand the concept and the steps to follow.

E-Document Core is a new application introduced in BC23. We will be using an interface in this application for this example. We will follow this link to implement the interface.

Extending e-documents functionality – Business Central | Microsoft Learn

First, we need to identify the interface to implement. The “AL Explorer” can help with it. As you can see, there are two interfaces for this module. For simplicity, we will just implement the interface called “E-Document”.

To use the interface we have to “implement” it. You can do it by creating a codeunit like this one. When you hover over “E-Document” you will see that all these methods are needed:

You can create them manually. However, you can do it automatically using the tooltip “Implement Interface”.

And all the procedures will be created empty for you.

If you drill down on “E-Document” you will be able to see the interface with all its methods.

Finally, create an enumextension extending “E-Documet Format” and using our implementation.

The key here is that the enum can be extended with more values where each of them can have a completely different implementation. Giving the flexibility mentioned above.

The enum “E-Document Format” just implements the interface “E-Document”

At this point, the foundation is established. But there is no logic in our implementation yet. Let’s create a simple example.

Inside the “Check” function we will do a Testfield in “Your Reference” field like this:


Example Setup And Test


Let’s set it up in Business Central. We have to follow 4 steps:

  • Create an E-Document Service
  • Create an E-Document Workflow
  • Create a Document Sending Profile
  • Assign the Document Sending Profile to the customer

At this point, we can go ahead and create a new “E-Document Service” and select our enum value.

Create a very simple workflow like the following. Choosing the service just created.

Create a new Document Sending Profile by choosing the workflow just created:

Finally, assign it to the customer:

Now, when you try to release a sales document for this customer without “Your Reference” you will get an error:

This is just a simple example of an interface where we only created logic for one procedure.

How To Call An Interface


This interface, in particular, is called from this event in “E-Document Subscription” codeunit in E-Document core app:

We arrive at this procedure in which we can see that the interface is called with these two lines:

EDocumentInterface := EDocumentService."Document Format";
EDocumentInterface.Check(EDocSourceRecRef, EDocumentService, EDocumentProcessingPhase);

You can find another example in “E-Doc. Import” codeunit.


Summary


In short, what you need to use an interface is:

  1. Identify the interface to implement
  2. Implement it using a codeunit with its methods
  3. Identify the enum implementing the interface
  4. Extend the enum with your custom codeunit implemented

I hope this help someone to understand Interface

Thank you for reading.

Keep Sharing....Keep Growing...

Sunday, December 10, 2023

Snapshot Debug for debugging in Production Environment in BC SaaS

Hello Friends,

We can use Snapshot Debug for debugging in Production Environment in BC SaaS

Snapshot debugging allows a delegated admin to record AL code that runs on the server, and once it has run, debug the recorded snapshot in Visual Studio Code. For a delegated admin to create and download a snapshot file that exists on the server on behalf of an end-user, the delegated admin must be part of the D365 Snapshot Debug permission group.

From Visual Studio Code, you start a snapshot by creating a snapshot configuration file. There are two template configurations for a snapshot, which are accessed by selecting Add Configuration in Visual Studio Code.

AL: Initialize a snapshot debugging session locally

AL: Initialize a snapshot debugging session on cloud













Below configuration added in launch.json

        {

          "name": "snapshotInitialize: Microsoft production cloud",
          "type": "al",
          "request": "snapshotInitialize",
          "environmentType": "Production",
          "environmentName": "production",
          "tenant": "0cd33610-3443-4d21-a9a1-5b0f3ae1dc84",
          "breakOnNext": "WebClient",
          "userId": "BCADMIN",
          "executionContext": "DebugAndProfile",
          "snapshotVerbosity": "Full"
      }

Useful shortcut for snapshot debug:

F7             Start a snapshot debugging session    //Start   

                 Opne BC Production Env and Process Tran. and Generate Error

Alt+F7     Finish a snapshot debugging session   //Finish  

                 Wait for Few min to system to download zip file in Snapshot folder

Shift+F7   List all available snapshots          

                  //List of downloaded Snapshot - Click on Snapshot to start debug


PS: Status of a snapshot debugging session

  • Initialized – A request is issued and the server is waiting for the next session to be snapshot debugged based on the above rules.
  • Started – You have attached to an end-user session to snapshot debug.
  • Finished – When the snapshot debugging session has finished.
  • Downloaded – When the snapshot file is downloaded.


























Done.

Thank you for reading.
Keep Sharing....Keep Growing...

Debug Web Service (SOAP or REST), Job Queue and User Session

 Hello Friend

Business Central provide facility to debug any of below session in Production or Sandbox environment in On-Prime Version

 - Any User Session

- Web Service Session (SOAP or REST)

- Job Queue Session

More details available on this blog:

https://arquiconsult.com/en/debug-attach/

https://yzhums.com/35078/

Finding and fixing bugs or errors is part of our daily tasks. This process may take a lot of time if we do not use the debugger and, even more, if the error occurs in a Web Service or in a Job Queue task.

In the older versions, the solution was to use the “Debug Next” functionality, but in Visual Studio Code that functionality is not present. No problem, we’ll use Attach.

To Enable debug we need User ID or Session ID

  Setting              Description
userId                            The GUID of the user on whose behalf a snapshot debugging will be started. For on-premises, this can also be the user name in user password authentication scenarios. The user must be able to start, or have a session type opened that is specified in the breakOnNext parameter. For more information, see JSON Files.
sessionId                                       A session ID for the user specified above in userId.

In request parameter we can setup either "launch" or "attach"

Below are few example of configuration to setup in launch.json:

1) For Debug any Web Service session in BC SaaS

{
    "name": "Webservice_Debug",
    "type": "al",
    "request": "attach",
    "environmentType": "Sandbox",
    "environmentName": "AmericasDev",            
    "breakOnError": "All",          
    "tenant": "2474247c-aa32-48ba-b9a0-38e470449497",
    "breakOnNext": "WebServiceClient",
    "userId": "OAUTH_BC"          
}

2) For Debug any User Session in BC On Prime

{
    "name": "UserSessionDebg_Debug",
    "type": "al",
    "request": "attach",
    "server": "http://10.110.98.168:8080/",
    "serverInstance": "T002",
    "port": 2749,
    "authentication":"Windows",
    "breakOnError": "All",              
    "breakOnNext": "WebClient",
    "sessionId":9589    
}

3) For Debug Job Queue Session in BC on prime

{
    "name": "JobQueue_Debug",
    "type": "al",
    "request": "attach",
    "server": "http://10.110.98.168:8080/",
    "serverInstance": "T002",
    "port": 2749,
    "authentication":"Windows",
    "breakOnError": "All",              
    "breakOnRecordWrite": "None",    
    "breakOnNext": "Background"
}

4) Debug webservice session in BC on prime

{
    "name": "Webservice_Debug_OnPrime",
    "type": "al",
    "request": "attach",
    "server": "http://10.110.98.168:8080/",
    "serverInstance": "T002",
    "port": 2749,
    "breakOnError": "All",              
    "breakOnNext": "WebServiceClient",
    "userId": "OAUTH_BC"          
}  

You can fine the Session ID in BC from front end.

Goto Help and Support and at bottom you can view session id





















Similar you can get it from BC Admin center



















































For BC on Prime we can create page from table "Active Session" where you can see the session and instance name of us


Action Session Page AL Source:

page 50571 "DEV Kill Session"
{
    ApplicationArea = All;
    Caption = 'Kill Sessions (Danger)';
    Editable = true;
    PageType = List;
    SourceTable = "Active Session";
    UsageCategory = Tasks;
    InsertAllowed = false;
    ModifyAllowed = false;

    layout
    {
        area(Content)
        {
            repeater(Group)
            {
                field("User ID"; Rec."User ID")
                {
                    ApplicationArea = All;
                    StyleExpr = StyleExp;
                    ToolTip = 'Specifies the value of the User ID field';
                }
                field("Session ID"; Rec."Session ID")
                {
                    ApplicationArea = All;
                    StyleExpr = StyleExp;
                    ToolTip = 'Specifies the value of the Session ID field';
                }
                field("Login Datetime"; Rec."Login Datetime")
                {
                    ApplicationArea = All;
                    StyleExpr = StyleExp;
                    ToolTip = 'Specifies the value of the Login Datetime field';
                }

                field("Client Type"; Rec."Client Type")
                {
                    ApplicationArea = All;
                    StyleExpr = StyleExp;
                    ToolTip = 'Specifies the value of the Client Type field';
                }
                field("Client Computer Name"; Rec."Client Computer Name")
                {
                    ApplicationArea = All;
                    StyleExpr = StyleExp;
                    ToolTip = 'Specifies the value of the Client Computer Name field';
                }
                field("Server Instance ID"; Rec."Server Instance ID")
                {
                    ApplicationArea = All;
                    StyleExpr = StyleExp;
                    ToolTip = 'Specifies the value of the Server Instance ID field';
                }
                field("Server Instance Name"; Rec."Server Instance Name")
                {
                    ApplicationArea = All;
                    StyleExpr = StyleExp;
                    ToolTip = 'Specifies the value of the Server Instance Name field';
                }
                field("Database Name"; Rec."Database Name")
                {
                    ApplicationArea = All;
                    StyleExpr = StyleExp;
                    ToolTip = 'Specifies the value of the Database Name field';
                }
            }
        }
    }

    actions
    {
        area(Processing)
        {
            action(Kill)
            {
                ApplicationArea = All;
                Caption = 'Kill Session';
                Image = Stop;
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;
                PromotedOnly = true;
                ToolTip = 'Kill selected session';

                trigger OnAction();
                var
                    KillMsg: Text;
                    KillLbl: Label '%1 killed your current session.', Comment = '%1 User Id';
                begin
                    if Rec."Session ID" = SessionId() then
                        exit;

                    KillMsg := StrSubstNo(KillLbl, UserID());
                    ClearLastError();
                    if not StopSession(Rec."Session ID", KillMsg) then
                        Error(GetLastErrorText);
                end;
            }

            action(Delete)
            {
                ApplicationArea = All;
                Caption = 'Delete Session';
                Image = Stop;
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;
                PromotedOnly = true;

                trigger OnAction();
                var
                    KillMsg: Text;
                    KillLbl: Label '%1 killed your current session.', Comment = '%1 User Id';
                begin
                    if Rec."Session ID" = SessionId() then
                        exit;

                    Rec.Delete(TRUE);
                end;
            }

            action(Kill_Selected)
            {
                ApplicationArea = All;
                Caption = 'Kill Session (Selected Record)';
                Image = Stop;
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;
                PromotedOnly = true;
                ToolTip = 'Kill selected session';

                trigger OnAction();
                var
                    KillMsg: Text;
                    KillLbl: Label '%1 killed your current session.', Comment = '%1 User Id';
                begin
                    Killrec();
                end;
            }

            action(Delete_Selected)
            {
                ApplicationArea = All;
                Caption = 'Delete Session (Selected Record)';
                Image = Stop;
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;
                PromotedOnly = true;
                ToolTip = 'Delete Selected Session';

                trigger OnAction();
                var
                    KillMsg: Text;
                    KillLbl: Label '%1 killed your current session.', Comment = '%1 User Id';
                begin
                    Deleterec();
                end;
            }
        }
    }

    trigger OnAfterGetRecord()
    begin
        StyleExp := 'standard';
        if Rec."Session ID" = SessionId() then
            StyleExp := 'strong';
    end;

    local procedure Killrec()
    var
        RecKill: Record "Active Session";

        KillMsg: Text;
        KillLbl: Label '%1 killed your current session.', Comment = '%1 User Id';
    begin
        CurrPage.SetSelectionFilter(RecKill);
        IF RecKill.FindSet() Then begin
            repeat
                if RecKill."Session ID" <> SessionId() then begin

                    KillMsg := StrSubstNo(KillLbl, UserID());
                    ClearLastError();
                    if not StopSession(RecKill."Session ID", KillMsg) then
                        Error(GetLastErrorText);
                End;
            until RecKill.Next() = 0;
        end;

    end;


    local procedure Deleterec()
    var
        DelRec: Record "Active Session";

    begin
        CurrPage.SetSelectionFilter(DelRec);
        IF DelRec.FindSet() Then begin
            repeat
                if DelRec."Session ID" <> SessionId() then begin
                    DelRec.Delete(TRUE);
                End;
            until DelRec.Next() = 0;
        end;

    end;

    var
        StyleExp: Text;
}

Done.
I hope it will help someone

Thank you for reading.
Keep Sharing.....Keep Growing....

Saturday, August 5, 2023

Assigning a user account Logon as Service Rights

Hello Team,


While we install NAV or BC Service and we select and specific user instead of network service

We ave got error that User is not approved as Service User

To solve it we need to add user as Service Login User account

Follow below steps

Assigning a user account Logon as Service Rights

If you need to assign a user account Logon as Service rights:

  1. Open Windows control panel.
  2. Open Administrative Tools.
  3. Open Local Security Policy.
  4. In the left pane, click Security Settings ►Local PoliciesUser Rights Assignments.
  5. In the right-hand pane, find the policy Log on as a service.
  6. Right-click Logon as a service, and then click Properties.
  7. In the Properties box, add the domain/serviceuser, and then click OK.

 

I have face this many time so decide to create blog on it.

Thank you for reading..

Keep Sharing....Keep Growing...

Friday, July 14, 2023

API Calling Post Method Most Common Error - misused header name 'authorization' business central

 Hello Friend

You must face below error while you try to call API post method with mullitple header value

misused header name 'authorization' business centra
















This error come because of invalid content type handle in API call

Below example you can see where we need to pass 2 header value


















BC Working Code:

           action("Chat GTP")
            {
                ApplicationArea = Suite;
                Caption = 'Chat GTP';
                Image = Production;
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;
                PromotedOnly = true;

                trigger OnAction()
                var
                    myInt: Integer;
                    Gpt_JsonObject_L: JsonObject;
                    Gpt_JsonText_L: Text;
                    Content: HttpContent;
                    Client: HttpClient;
                    Headers: HttpHeaders;
                    Request: HttpRequestMessage;
                    Response: HttpResponseMessage;
                    JSONResponse: Text;
                    JObject: JsonObject;
                begin

                    Gpt_JsonObject_L.Add('model', 'text-davinci-003');
                    Gpt_JsonObject_L.Add('prompt', 'how are you?');
                    Gpt_JsonObject_L.Add('temperature', 0.2);
                    Gpt_JsonObject_L.WriteTo(Gpt_JsonText_L);

                    Request.Method := 'POST';
                    Request.SetRequestUri('https://api.openai.com/v1/completions');

                    Headers.Clear();
                    Request.GetHeaders(Headers);
                    Headers.Add('Authorization', 'Bearer ' + 'sk-FxpqNnCvf1qTbz0m1EXGT
3BlbkFJ6B7V3oIcHHKnjs1N4c6O');
                    Content.WriteFrom(Gpt_JsonText_L);

                    Content.GetHeaders(Headers);
                    Headers.Remove('Content-Type');
                    Headers.Add('Content-Type', 'application/json');
                    Request.Content := Content;

                    if not Client.Send(Request, Response) then
                        Error('API Authorization token request failed...');

                    JSONResponse := '';
                    Response.Content().ReadAs(JSONResponse);

                    if not JObject.ReadFrom(JSONResponse) then
                        Error('Invalid response, expected a JSON object');

                    Message(JSONResponse);

                end;

Response will show in msg




















To lear more watch this video:

https://www.youtube.com/watch?v=QmCXUm0bEfU

I hope this help someone

Thank you for reading.

Keep Sharing....Keep Growing....