Export (0) Print
Expand All

Talking with Navision: Say Hello to Navision and Expect Navision to Be Polite

 

Manuel Oliveira
Microsoft Portugal

September 2004

Applies to:
   Microsoft Navision

Summary: Navision is perceived by many as a closed application, in the sense that integrating the ERP with other technologies is not possible. The main purpose of this article is to show a way through this concern. (15 printed pages)

Contents:

Introduction
Some Facts
The Unavoidable "Hello World"
Conclusions

Introduction

If you have been developing in Navision for a while, you have probably experienced a stage in which you were pretty confident you could develop just about any piece of functionality within Navision.

Later, you learned you could create and instantiate Automation variables, meaning you could write some code within Navision that would interact with a Microsoft Excel sheet, for instance. Being able to do this has deepened your confidence.

Now you have been asked to expose the Navision business layer to a 3rd party application. This task poses a serious challenge, and your confidence is threatened when you suddenly realize your considerable universe of control must be triggered by a user interacting with Navision controls in a Navision client.

How can you transform such a system into an unattended server capable of asynchronously responding to external requests? Worry not, my friend. This article shows you how to build a tiny application that talks to Navision and waits for an answer. Conversely, it will show you how to instruct Navision to listen and to produce the expected answer.

Some Facts

There are some facts concerning Navision of which you should be aware before you start looking at the solution. You will see that the solution you are being given is merely some basic engineering, with these facts as the supporting science.

Be sure to understand the concepts of Automation, Windows service, and event handler (or delegate) before reading the following paragraphs.

This is a very powerful feature. Not only can you create and instantiate Automation variables, allowing you to issue commands to the Automation Server, but you will also be able to control how your Automation Server will react to events.

For instance, you are designing a Navision codeunit. You add an Automation variable that represents a Microsoft Excel workbook, and another one that represents the Microsoft Excel application. Let us suppose your code creates a new workbook and adds it to the application. By now, you should be able to write code that interacts with this workbook as if it were a human user.

By setting the application variable WithEvents property, a collection of event handler headers appears in the code pane, allowing you to write code for dealing with each of these events.

You could, for instance, insert a record into a Navision table each time a user saves the active workbook. Even better for proving this concept, a message box pops up stating that the workbook is about to be saved.

Note   This is true as long as the variable is not destroyed. If the user closes the workbook, the corresponding Navision variable will no longer handle its events, even if the workbook is reopened. Also, when the running Navision object returns, its variables are automatically cleared. Even though Excel will still be running, Navision will no longer recognize it as an Automation server.
OnRun()
IF NOT CREATE(ExApp,TRUE) THEN
  EXIT;
ExApp.Visible(TRUE);
ExBook := ExApp.Workbooks.Add;

ExApp::WorkbookBeforeSave(Wb : Automation "'Microsoft Excel 11.0 Object Library'.Workbook";SaveAsUI : Boolean;VAR Cancel : Boolean)
MESSAGE ('The Excel book ' + Wb.Name + ' is just being saved!');

As has been previously said, you will not be able to see this last message box, unless the variables state is somehow kept alive. If you keep reading, soon enough that message will pop-up.

Single-Instance Codeunits

Usually, every Navision object is run or called from within another object, but once the transaction is finished, the instance is reset and so are its variables.

There are some notable exceptions to this principle. By setting the object SingleInstance property, its state pertains between successive calls.

A simple example will help: imagine you have a codeunit which has a single Integer variable and your OnRun() trigger simply increments the variable by one and pops out a message box stating its current value.

By running the codeunit without changing the mentioned property, you will always read 1 (one). After setting the property, you will successively read 1, 2, 3, and so on.

OnRun()
i := i + 1;
MESSAGE (FORMAT (i));

Combining These Last Two Facts

Suppose you set your Excel workbook creator codeunit's SingleInstance property. What do you get? Essentially, after running the codeunit once, as long as you do not close either Navision or the Excel workbook, you can exercise other Navision functionalities and modules. Every time the Excel workbook is saved, a message box stating that the workbook is about to be saved will interrupt your Navision client, just as in Figure 1.

Figure 1. The message box you get when saving the workbook.

Notice the immense power you will get by combining such features. Still, you have one challenge to face: the "unattended" part of your task description. This leads us to the next fact, which may be a little harder to understand, as it involves the establishment of a somewhat complex scenario around your Navision environment.

Introducing the Navision Application Server

The Navision Application Server (NAS) is a Windows Service. It has a collection of parameters that indicate where to find your Navision Database Server and which company to open, for example.

It also has a Start-up parameter value entry that enables you to pass a string to Navision. We will get back to this string shortly.

When you start the NAS windows service, it will use the given parameters to open a specific company within a specific database mounted on a specific server. This is very good, but because it has no user interface at all, its entry point should not be the usual main menu. It will rather exercise the trigger whose ID is 99 in codeunit 1.

Note   This feature is documented in the Navision Application Server Technical White Paper. Codeunit 1 (ApplicationManagement) has a trigger 99 named NASHandler. A successful matching of server, database, and company when starting the Application Server will fire this trigger.

This is where the string matters. It will be the argument that is passed to this trigger. If you design this codeunit, you will see that other string values are already considered by Navision as being valid start-up parameter values.

CASE Parameter OF
  'MAILLOG':
    CODEUNIT.RUN(CODEUNIT::"E-Mail Dispatcher");
  'ADCS':
    BEGIN
      ADCSNASStartup.SetNASID(COPYSTR(ParamStr,SepPosition + 1));
      ADCSNASStartup.RUN;
    END;
  ELSE
    IF CPApplnSrvSetup.GET(Parameter) THEN BEGIN
      CPApplnSrvMgt.SetNASID(Parameter);
      WORKDATE := 0D;
      IF CPApplnSrvMgt.GetSendMail THEN BEGIN
        MailHandler.RUN;
        MailHandler.StartCountDown(2000);
      END;
      IF CPApplnSrvMgt.GetPerformSynch THEN
        MsgDispatcher.RUN;
      IF CPApplnSrvMgt.GetPerformRequests THEN
        RequestHandler.RUN;
    END ELSE
      ERROR(Text018,Parameter);
END;

Shall We Combine Again?

By all means, yes. All you have to do is choose a start-up parameter value (EXCEL, for instance) and add an entry to the CASE command within trigger 99 that will be exercised whenever the NAS is started with such a parameter.

Click here for larger image.

Figure 2. A possible Navision Application Server configuration.

It will simply call your single instance codeunit in which the Excel workbook variable events are being handled from within Navision.

With such a scenario, after starting the NAS service with parameters like those presented in Figure 2, you will see Excel starting up from nowhere as no Navision client is necessarily present; this is the first interesting part.

Furthermore, you will get an event viewer message every time the workbook is saved, and this is the second interesting part; you know it is your Navision code that is producing the event viewer message, as it lacks a user interface for popping up such message boxes.

Although this may all be very nice stuff, it does not address the main challenge, which is having Navision communicate with any 3rd party application, and not just with Excel.

Introducing the Communication Components

The Navision Communication Components are a set of Automation Servers that allow you to send and receive information through a small variety of transport interfaces and protocols. At least sockets, named pipes, and message queues are available, so you will probably be able to experience differences in robustness and efficiency, based on which you can make your option.

Personally, I prefer message queues, as they offer an excellent message abstraction by

  • being able to queue them up according to their priority or simply on a first-in first-out basis;
  • respecting each message unity;
  • allowing you to put it inside a kind of envelope in which you state who is the message recipient.

This is pretty much what real world post-offices do with real world correspondence, and that is why I see a good abstraction.

Note   In this article, and for demonstration purposes, message queues will be used. However, any other choice will work with some code changes. I recommend you search for a devguide.chm file in your Navision product CD, which documents the differences I am talking about.

Each of these communication component types offer the developer an event handler that is fired up each time it receives some information. In the message queuing system scenario, it fires up whenever there is at least one message in the queue, just ready to be consumed.

Scrambling These Facts Altogether

Take a moment to think about what you could do if you take Microsoft Excel out from your scenario and you bring in one of these communication component types. You would handle the message arrival event instead of the workbook saving event.

In summary, you will have a windows service (NAS) starting up a single instance codeunit, which is capable of handling the arrival of messages. You are now able to asynchronously receive messages in Navision, and all you have to do is write the code to parse those messages, exercise the appropriate business layer functionality, and build an appropriate response to the initial message.

Figure 3. The proposed architecture logical diagram (Navision side).

Now, roll up your sleeves and prepare for some action.

The Unavoidable "Hello, World"

After having read and understood the last facts, you could easily deploy a simple system, in which you would achieve the task you have been assigned by cleverly combining what you have learned.

Nonetheless, I will present one such simple proof-of-concept. A small Microsoft Visual C# application will be built that has two textboxes and one button, like you can see in Figure 4.

The user will be able to fill the first text box and press the button. If the communication is successful, then the second text box will be filled with a capitalized version of the first textbox.

Figure 4. The Visual C# application main form layout.

Obviously, this could be a standalone application, as you really do not need Navision to capitalize your strings. On the other hand, if you can use Navision to asynchronously serve an external request for a string capitalization, you will be able to regain your confidence in being able to use Navision for any purpose.

Preparing the Environment (Navision Side)

As you have seen in Figure 3, there is an environment that must be established prior to the development of our scenario.

You may install the Navision client, the database server, and the application server from the Navision product CD.

Note   I am currently using Navision 3.70, which is the most recent version at the date of this article. Although I experienced sensible improvements in the Navision Application Server, you can use any version, as long as it features this windows service.

I recommend you start by installing the client, as it will also provide you a demonstration database you can use for this purpose. Then, install the database server.

Tip   Be sure to select Custom install, which will allow you to manually size the cache. By not doing so, your server will allocate a tremendous amount of memory for this purpose and you might not have enough resources to cope with this. My experience tells me a 20000Kb figure is just fine for lab use. Also, be sure to bind your server to the database installed, along with the client.

Third, install the Navision Application Server. This will install two windows services (one for each database server type: Navision Server or SQL Server). We will be using Navision Server as database server, so you can disable the other one. If you experience security problems like the lack of permissions, you may want to grant the service the Log On As right, binding it to an administrative account.

Along with the services, a console is also installed, which allows you to manage your NAS instances. Figure 2 depicts a possible details pane in such a console.

Tip   Do not start an Application Server instance without previously being sure that all its parameters will allow the service to connect to Navision using the entry point you want. Otherwise, your Application Server will be trying to connect, which consumes resources and fills your Application Log with useless messages.

Finally, be sure that the Message Queuing is installed. This varies according to your operating system. In Windows XP, you may install it from the Add/Remove Windows Components button in the Add or Remove Programs option in Control Panel.

Add two queues. Mine are public, but you may want or need to create private queues instead. Later, when referring to your private queues, you will need to prefix its name with private$. Give them suggestive names like fromNavision and toNavision.

Your environment is ready to be customized. That is the next step.

Customizing Navision to Suit Your Needs

As we are describing a way to open Navision to the external development world, it is nice for us to bring the burden of message processing to the Navision side, so as to guarantee as much transparency as possible to the external development environment.

In other words, the code you will develop in Navision must conform to the messaging format adopted by our C# application. As you will see, if you have a string variable and simply send that string to a message queue (which may be done with a single line of code in C#), you will be sending an XML document, which has a single node whose name is string and whose content is the content of the string you are sending. For instance, if you were sending the message in Figure 4, you would be getting the following XML document:

<?xml version="1.0">
<string>Hello, world!</string>

Bearing this in mind, let us see how to customize your Navision database.

First of all, create a new codeunit. Add to this codeunit the following global variables:

Name DataType Subtype WithEvents
MQBus Automation 'Navision MS-Message Queue Bus Adapter'.MSMQBusAdapter  
CC2 Automation 'Navision Communication Component version 2'.CommunicationComponent Yes
InMsg Automation 'Navision Communication Component version 2'.InMessage  
InS InStream    
XMLDom Automation 'Microsoft XML, v3.0'.DOMDocument  
XMLNode Automation 'Microsoft XML, v3.0'.IXMLDOMNode  
OutMsg Automation 'Navision Communication Component version 2'.OutMessage  
OutS OutStream    

By having specified that you want Navision to receive the events from the automation control named CC2, you will notice that an additional procedure header will appear in your code pane:

CC2::MessageReceived(VAR InMessage : Automation "''.IDISPATCH")

This is where you will write the code that will be run whenever a certain queue receives a message. That queue must be configured elsewhere. The most obvious place to configure our queue is the OnRun() trigger. It should appear as follows:

OnRun()
CREATE(MQBus);
CREATE(CC2);
CREATE (XMLDom);
CC2.AddBusAdapter(MQBus,1);
MQBus.OpenReceiveQueue('.\toNavision',0,0);

The first 3 lines of code create the automation variables. The fourth line adds a bus adapter to the communication component automation variable. This is where your code would be different, should you have chosen another bus adapter (like the socket bus adapter, for instance) instead.

These components have been designed in such a way that the developer configures and adds a bus adapter to a communication component whose events are captured by Navision, and Navision offers an event handler that maintains its interface regardless of the adapter that has been added to it. In other words, as long as your adapter is configured to receive messages, your handler will fire whenever there is a message and its argument will carry the message body.

The adapter configuration is the fifth line, in which you state to which message queue your code will be listening for new messages.

As you remember, you must set the SingleInstance property for your new codeunit, so these variables remain alive after the trigger returns. While in the code pane, select the View menu and then Properties to see your codeunit's properties.

And now, your "worker thread."

CC2::MessageReceived(VAR InMessage : Automation "''.IDISPATCH")
// get the message
InMsg := InMessage;
InS := InMsg.GetStream();

// load the message into an XML document and find a node
XMLDom.load (InS);
XMLNode := XMLDom.selectSingleNode ('string');

// open the response queue and create a new message
MQBus.OpenWriteQueue('.\fromNavision',0,0);
OutMsg := CC2.CreateoutMessage('Message queue://.\fromNavision');
OutS := OutMsg.GetStream();

// build the contents of your message
XMLNode.text := UPPERCASE (XMLNode.text);

// fill the message and send it
OutS.WRITE(XMLDom.xml);
OutMsg.Send(0);

Let us take a moment here to better understand what we are doing.

// get the message
InMsg := InMessage;
InS := InMsg.GetStream();

In the first two lines of code, you are getting the contents of the message that you are receiving into a Navision stream variable.

// load the message into an XML document and find a node
XMLDom.load (InS);
XMLNode := XMLDom.selectSingleNode ('string');

Then, you are using the XML DOM to take your Navision stream as if it were an open file and to load it into an XML document. Again, using the DOM functionality, you query the XML document for a node named string.

// open the response queue and create a new message
MQBus.OpenWriteQueue('.\fromNavision',0,0);
OutMsg := CC2.CreateoutMessage('Message queue://.\fromNavision');
OutS := OutMsg.GetStream();

Now, you open your reply queue and create a new message. Then you initialize your output stream variable to be the new message stream.

// build the contents of your message
XMLNode.text := UPPERCASE (XMLNode.text);

This line of code represents the business logic. In a real environment, you would like to be calling complex Navision functions in this section. Now you see it is possible. You could, for instance, be receiving an XML document carrying the header and the lines of an invoice that you would now be inserting in their proper places and then posting, exercising whichever internal modules Navision would find appropriate.

In this case and for the sake of simplicity, your business logic is as simple as a string capitalization.

// fill the message and send it
OutS.WRITE(XMLDom.xml);
OutMsg.Send(0);

Once your response is complete, it is sent to the client.

Note   You have only changed the contents of the request message, which may not be the case in a real scenario. In a real scenario, you may be getting an invoice for posting as a request and you will probably want to reply with a success/failure message instead of echoing your invoice back to the client application. In our case, it is convenient to keep the message structure and simply capitalize the inner data.

We have used the communication components, we have set the SingleInstance property, and we have written the code to handle automation variables events. We are just missing the Application Server part, which will transform all this effort into a fully-automated, unattended process. You may want to run this codeunit manually for a few times before you automate it. This way, you will be able to use the Navision Debugger.

As you have seen before, it is codeunit 1 you should change if you want the Application Server to automatically run the codeunit you created. Just create a global variable in codeunit 1 that points to your codeunit (named NAVCOMM, for instance), locate the NASHandler trigger, and add the case option as depicted below:

CASE Parameter OF
  'MAILLOG':
    CODEUNIT.RUN(CODEUNIT::"E-Mail Dispatcher");
  'ADCS':
    BEGIN
      ADCSNASStartup.SetNASID(COPYSTR(ParamStr,SepPosition + 1));
      ADCSNASStartup.RUN;
    END;
  'NAVCOMM':
    BEGIN
      NAVCOMM.RUN;
    END;
  ELSE
    IF CPApplnSrvSetup.GET(Parameter) THEN BEGIN
      CPApplnSrvMgt.SetNASID(Parameter);
      WORKDATE := 0D;
      IF CPApplnSrvMgt.GetSendMail THEN BEGIN
        MailHandler.RUN;
        MailHandler.StartCountDown(2000);
      END;
      IF CPApplnSrvMgt.GetPerformSynch THEN
        MsgDispatcher.RUN;
      IF CPApplnSrvMgt.GetPerformRequests THEN
        RequestHandler.RUN;
    END ELSE
      ERROR(Text018,Parameter);
END;

Of course, you should configure your Navision Application Server Manager so that your NAS instance has this Start-up parameter value (NAVCOMM). Otherwise, it will not start your codeunit, thus not being able to serve the external requests.

At this point it is safe to start your Application Server instance. Expect to see an event in the Application Log like the one in Figure 5.

Figure 5. A successful Navision Application Server start.

Creating a Client Application for this Service

Having brought as much of the burden to Navision, one should expect the least possible effort onto the other end-point. With this architecture, that assumption is correct and you will soon see how little you should be concerned with Navision when formatting your requests and receiving back your answers.

Open the Microsoft Visual Studio .NET development environment and create a new Windows application project.

Onto the main form, lay three controls as depicted in Figure 4. Name the top textbox as txtSend, the button as btnSend, and the lower textbox as txtReceive.

Figure 6. Visual Studio .NET Server Explorer showing our queues.

Now, drag both queues you created for this purpose onto your form, thus creating two System.Messaging.MessageQueue instances. You can use the Server explorer as in Figure 6. Name the queues appropriately (mqFromNavision and mqToNavision).

Add the following code to your Form constructor:

public Form1()
{
   //
   // Required for Windows Form Designer support
   //
   InitializeComponent();

   //
   // TODO: Add any constructor code after InitializeComponent call
   //
   mqFromNavision.Formatter = new
      System.Messaging.XmlMessageFormatter(new Type[] {typeof(String)});
}

This line of code will inform the mqFromNavision variable it will be expecting messages carrying strings.

Open the property page for this variable and click the Events button. Double click the ReceiveCompleted entry. This will create a delegate that you will fill as follows:

private void mqFromNavision_ReceiveCompleted(object sender, System.Messaging.ReceiveCompletedEventArgs e)
{
   System.Messaging.Message m = 
      mqFromNavision.EndReceive(e.AsyncResult);
   txtReceive.Text = (string)m.Body;
}

Receiving a message is as simple as taking it off the queue and simply copying its data to the lower textbox. Remember you have already told the system what kind of messages you will be receiving in this queue, so you can safely take its body and show it elsewhere. Notice you do not see any particular references to Navision here.

The only thing that is missing is being able to send messages to Navision. Here, you will see the only required external reference to Navision. It should go as follows:

private void btnSend_Click(object sender, System.EventArgs e)
{
   mqToNavision.Send (txtSend.Text, "Navision MSMQ-BA");
   mqFromNavision.BeginReceive (new System.TimeSpan (0,0,0,30));
}

When sending messages to Navision, label them with Navision MSMQ-BA, otherwise they will not be recognized by the CC2::MessageReceive Navision trigger implemented above.

Conclusions

There should be, at least, some thoughts about what we have done here.

The first (and perhaps the most serious) thing about doing this kind of integration in which you would want to rely upon the Navision business model, is exactly relying upon it. It may be a cumbersome development problem once you dive into the complex business logic that protects itself against wrong input data.

Navision has been designed to have human users sitting on Navision clients. With this in mind, if the user is presenting erroneous data, rolling back the transaction and producing an error message box is a good thing. However, if your user is the Application Server, rolling back the transaction in such a situation may not be a good idea, especially if the system has not sent a response message to whoever issued its request.

It is your responsibility to pre-validate the input data so that it does not configure an error according to the business model, or to change the business model so that its errors are handled in a softer model. As long as you guarantee a response message (with an error message in such a case) your external applications will experience no strange issues.

Consider wrapping the communication layer into a web service. Your external applications will appreciate having to invoke web methods instead of instantiating a message that goes to a message queue. Whether you choose to build a web service or not, consider stating your message queue paths as configuration keys. This will allow you to easily point your scenario to a different set of queues, should you change your environment.

Also, it is a best practice to wrap your code into exception catchers and handlers. We have not done so, as we were only proving a concept. If, however, something goes wrong (for instance, if the NAS service is down), you will be experiencing some fantastic unhandled exceptions and, even though we love seeing them once in a while, users do not.

Manuel Oliveira is an Alliance Engineer working in Microsoft Portugal. He is a former Microsoft Business Solutions Product Manager who worked closely with partners interested in having their applications integrated with Navision. He implemented a connector between Navision 3.70 and Autodesk® Inventor® 8.0 using a similar approach to the one used in this article.

Show:
© 2014 Microsoft