Welcome to MSDN Blogs Sign in | Join | Help

Creating an IBF Compliant Web Service for Navision

Since my previous blog entry was about how to build an IBF compliant Web service for Axapta, it would not have made sense if I did not do the same for Navision. So that's exactly what this entry all about: Creating an IBF compliant Web service for Navision.

Just as for Axapta I started with any prior knowledge about or experience with Navision. So the first thing I did was using MSN Search to find some reading on the topic of creating Web services for Navision. This resulted in the lecture of the two following MSDN articles:

Just as the other day with Axapta, these two articles basically provided me with enough information to get the job done. I say 'basically' since this time I need to confess that it took me more than just 2 hours. The most important reason for this was that apart from having to learn about working with the C/Side IDE and mastering the basics about the programming language C/AL, I encountered a lot of problems to get the basic infrastructure right. Getting the Navision Application Server and the Navision Database Server configured right turned out to be a big challenge for me.

For my first posting on this topic I've chosen to use the 'native' Navision Database Server instead of Microsoft SQL Server. And even while I'm using Navision 4.0 I didn't leverage the new XML Ports to compile the XML strings involved with this solution. This means however that the C/AL source files here below can also be used for previous versions of Navision.

For the implementation of the Web service I've chosen to make it as similar as possible as to the one I created for Axapta. One could say that both implementations are based on a common model. It's however not really perfect, but you'll see what I mean.

So you'll have again 4 classes in C#:

  • Customer
  • Customers
  • CustomerReferenceById
  • CustomersReferenceByCompanyName

Here you've the Customer class:

[XmlRoot("Customer", Namespace="Navision2IBF")]
public class Customer
{
    private string _accNum;
    private string _name;
    private string _street;
    private string _state;
    private string _city;
    private string _zip;
    private string _country;
   
    [XmlElement]
    public string AccountNumber {get{return _accNum;} set{_accNum = value;}}

    [XmlElement]
    public string Name {get{return _name;} set{_name = value;}}

    [XmlElement]
    public string Street {get{return _street;} set{_street = value;}}

    [XmlElement]
    public string State {get{return _state;} set{_state = value;}}

    [XmlElement]
    public string City {get{return _city;} set{_city = value;}}

    [XmlElement]
    public string Zip {get{return _zip;} set{_zip = value;}}

    [XmlElement]
    public string Country {get{return _country;} set{_country = value;}}
}

Here you've the Customers class:

public class Customers : CollectionBase
{
    // Get Customer as a specific index
    public Customer this[int index]
    {
        get {return (Customer) List[index];}
        set {List[index] = value;}
    }

    public int Add(Customer item)
    {
        return List.Add(item);
    }

    public int IndexOf(Customer item)
    {
        return List.IndexOf(item);
    }

    public void Insert(int index, Customer item)
    {
        List.Insert(index, item);
    }

    public void Remove(Customer item)
    {
        List.Remove(item);
    }

    public bool Contains(Customer item)
    {
        return List.Contains(item);
    }

    public void CopyTo(Customer[] destination, int index)
    {
        List.CopyTo(destination, index);
    }
}

Here you've the CustomerReferenceById class:

[XmlRoot("CustomerReferenceById", Namespace="Navision2IBF")]
public class CustomerReferenceById
{
    private string _accNum;

    [XmlAttribute]
    public string AccountNumber {get{return _accNum;} set{_accNum = value;}}

}

And finally below the CustomersReferenceByCompanyName class:

[XmlRoot("CustomersReferenceByCompanyName", Namespace="Navision2IBF")]
public class CustomerReferenceByCompanyName
{
    [XmlAttribute]
    public string companyName;
}

As was the case in the Axapta Web service I defined an interface that is implemented by the Web service:

public interface ICustomerInformationAccess
{
    Customer GetCustomerById(CustomerReferenceById customerId);
    Customers GetCustomersByName(CustomerReferenceByCompanyName customerName);
}

The actual implementation for this interface is where things get interesting:

public class NavisionWebService : System.Web.Services.WebService, ICustomerInformationAccess

Interesting because the implementation is completely different from what we had to do for Axapta. The way you integrate with Navision is based on interacting with an instance of the Navision Application Server (NAS) via Microsoft Message Queuing (MSMQ) messages. Sending and receiving these MSMQ messaging is pretty straight forward in Visual C# - at least if you've some experience with using Visual Studio .NET to create MSMQ applications.

For sending MSMQ messages I declared following variable as part of the NavisionWebService class:

private System.Messaging.MessageQueue mqToNavision;

And for receiving MSMQ messages:

private System.Messaging.MessageQueue mqFromNavision;

Both are initialized as part of the InitializeComponent() method:

private void InitializeComponent()
{
    this.mqToNavision = new System.Messaging.MessageQueue();
    this.mqFromNavision = new System.Messaging.MessageQueue();

    this.mqToNavision.Path = "FormatName:DIRECT=OS:contoso1\\private$\\tonavision";
    this.mqFromNavision.Path = "FormatName:DIRECT=OS:contoso1\\private$\\fromnavision";
}

Here you've the implementation for GetCustomerById:

[WebMethod]
public Customer GetCustomerById(CustomerReferenceById accNum)
{
    Customer cust = null;
    string request = "GetCustomerById(" + accNum.AccountNumber + ")";
    mqToNavision.Send(request, "Navision MSMQ-BA");

    mqFromNavision.Formatter = new System.Messaging.XmlMessageFormatter(new Type[] {typeof(Customer)});

    try
    {
        System.Messaging.Message msg = mqFromNavision.Receive(new System.TimeSpan(0, 0, 0, 30));
        cust = (Customer) msg.Body;
    }
    catch(Exception x)
    {
        System.Diagnostics.Debug.WriteLine(x.Message);
    }

    return cust;
}

The implementation for GetCustomersByName is similar:

public Customers GetCustomersByName(CustomerReferenceByCompanyName name)
{
    Customers customers = new Customers();

    string request = "GetCustomersByName(" + name.companyName+ ")";
    mqToNavision.Send(request, "Navision MSMQ-BA");

    mqFromNavision.Formatter = new System.Messaging.XmlMessageFormatter(new Type[] {typeof(Customers)});

    try
    {
        System.Messaging.Message msg = mqFromNavision.Receive(new System.TimeSpan(0, 0, 0, 30));
        customers = (Customers) msg.Body;
    }
    catch(Exception x)
    {
        System.Diagnostics.Debug.WriteLine(x.Message);
    }

    return customers;
}

The hard part for a "Microsoft-classic"-guy like me was writing the C/AL code that is responsible for sending and receiving the MSMQ messages at the other side. Luckily the MSDN articles mentioned above are written so that you don't really need much prior knowledge about Navision to understand the basics.

It comes down to creating 2 new C/AL codeunits and slightly modifying the standard codeunit ApplicationManagement.

These new codeunits are:

  • Navision2IBF
  • Navision2IBFBiz

The first one - Navision2IBF - is where the event handler MessageReceived() is implemented that is responsible for reading and writing to and from MSMQ. From within this event handler the second codeunit - Navision2IBF - is called for doing the actual business logic and compiling the XML data to be returned via an MSMQ message. The standard codeunit ApplicationManagement is responsible for making sure that the event handler MessageReceived() as implemented in Navision2IBF gets instantiated. This is done with a slight modification in the function NASHandler() that parses NAS start-up parameters:

NASHandler(NASID : Text[260])
...
    IF CGNASStartedinLoop = FALSE THEN
        CASE Parameter OF
            ...
            'NAVISION2IBF':
                CODEUNIT.RUN(CODEUNIT::Navision2IBF);
...

 

The implementation for the Navision2IBF looks like this:

OnRun()
    CLEARALL;

    IF ISCLEAR(MQBus) THEN
        CREATE(MQBus);

    IF ISCLEAR(CC2) THEN
        CREATE(CC2);

    IF ISCLEAR(XMLDom) THEN
        CREATE(XMLDom);

    CC2.AddBusAdapter(MQBus,1);
    MQBus.OpenReceiveQueue('.\private$\toNavision', 0, 0);

 

ParseRequest(string : Text[250])
   
Request := COPYSTR(string, 1, STRPOS(string, '(') - 1);
    auxstring := COPYSTR(string, STRPOS(string, '(') + 1, STRLEN(string) - STRPOS(string, '(') - 1);
    argpos := 1;
    commapos := STRPOS(auxstring, ',');
    WHILE (commapos <> 0) DO
        BEGIN
            Parameters[argpos] := COPYSTR(auxstring, 1, commapos - 1);
            auxstring := COPYSTR(auxstring, STRPOS(auxstring, ',') + 1);
            argpos := argpos + 1;
            commapos := STRPOS(auxstring, ',');
        END;
    Parameters[argpos] := auxstring;
    parcount := argpos;

 

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');

    // Parse the request and according to the request variable, redirect to the appropriate function
    ParseRequest(XMLNode.text);

    CASE Request OF
        'GetCustomerById':
            Navision2IBFBiz.GetCustomerById(Parameters[1], XMLDom);
        'GetCustomersByName':
            Navision2IBFBiz.GetCustomersByName(Parameters[1], XMLDom);
    END;

    // Open the response queue and create a new message
    MQBus.OpenWriteQueue('.\private$\fromNavision', 0, 0);
    OutMsg := CC2.CreateoutMessage('Message queue://.\private$\fromNavision');
    XMLDom.save(OutMsg.GetStream());
    OutMsg.Send(0);


 

In stead of explaining all the details about the code I invite you to read the articles mentioned above since my code is for 80% a straight copy of what you'll find there.

And finally here you've the code for the other codeunit - Navision2IBFBiz:

GetCustomerById(CustomerID : Code[30];VAR XMLDom : Automation "'Microsoft XML, v3.0'.DOMDocument")

    IF ISCLEAR(XMLDom) THEN
        CREATE(XMLDom);

    XMLDom.loadXML('<?xml version="1.0"?><Customer></Customer>');
    XMLRoot := XMLDom.documentElement;

    CustomerRecord.SETFILTER("No.", CustomerID);

    IF CustomerRecord.FIND('-') THEN BEGIN
        AddAttribute(XMLRoot, 'xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
        AddAttribute(XMLRoot, 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
        AddAttribute(XMLRoot, 'xmlns', 'Navision2IBF');
        AddElement(XMLRoot, 'AccountNumber', XMLNode, CustomerRecord."No.");
        AddElement(XMLRoot, 'Name', XMLNode, CustomerRecord.Name);
        AddElement(XMLRoot, 'Street', XMLNode, CustomerRecord.Address);
        AddElement(XMLRoot, 'State', XMLNode, '');
        AddElement(XMLRoot, 'City', XMLNode, CustomerRecord.City);
        AddElement(XMLRoot, 'Zip', XMLNode, CustomerRecord."Post Code");
        AddElement(XMLRoot, 'Country', XMLNode, CustomerRecord."Country Code");
    END;

 

GetCustomersByName(Name : Text[250];VAR XMLDom : Automation "'Microsoft XML, v3.0'.DOMDocument")

    IF ISCLEAR(XMLDom) THEN
        CREATE(XMLDom);

    XMLDom.loadXML('<?xml version="1.0"?><ArrayOfCustomer></ArrayOfCustomer>');
    XMLRoot := XMLDom.documentElement;

    CustomerRecord.SETFILTER(Name, Name + '*');

    IF CustomerRecord.FIND('-') THEN BEGIN
        AddAttribute(XMLRoot, 'xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
        AddAttribute(XMLRoot, 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');

        REPEAT
            XMLCustomerNode := XMLRoot.ownerDocument.createElement('Customer');
            XMLRoot.appendChild(XMLCustomerNode);
            AddElement(XMLCustomerNode, 'AccountNumber', XMLNode, CustomerRecord."No.");
            AddElement(XMLCustomerNode, 'Name', XMLNode, CustomerRecord.Name);
            AddElement(XMLCustomerNode, 'Street', XMLNode, CustomerRecord.Address);
            AddElement(XMLCustomerNode, 'State', XMLNode, '');
            AddElement(XMLCustomerNode, 'City', XMLNode, CustomerRecord.City);
            AddElement(XMLCustomerNode, 'Zip', XMLNode, CustomerRecord."Post Code");
            AddElement(XMLCustomerNode, 'Country', XMLNode, CustomerRecord."Country Code");
        UNTIL (CustomerRecord.NEXT = 0);
    END;

 

AddElement(...)

    CreatedXMLNode : Automation ...

    IF ISCLEAR(XMLNode) THEN
        MESSAGE('XMLNode is NULL');
 

    NewElement := XMLNode.ownerDocument.createNode('element', NodeName, 'Navision2IBF');

    NewElement.text := NodeValue;

    XMLNode.appendChild(NewElement);

    CreatedXMLNode := NewElement;

 

AddAttribute(VAR XMLNode : Automation "'Microsoft XML, v3.0'.IXMLDOMNode";Name : Text[260];NodeValue : Text[260])

    IF NodeValue <> '' THEN BEGIN
        XMLNewAttributeNode := XMLNode.ownerDocument.createAttribute(Name);
        XMLNewAttributeNode.nodeValue := NodeValue;
        XMLNode.attributes.setNamedItem(XMLNewAttributeNode);
    END;
 

I plan to complete this posting later with some more details to get you started without having to solve all the problems I encountered. However right now I need to run...

Published Friday, May 20, 2005 9:59 AM by yvesk

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# re: Creating an IBF Compliant Web Service for Navision

Thursday, March 30, 2006 3:51 AM by Chris Vandecaveye
How is this done in Axapta?

# re: Creating an IBF Compliant Web Service for Navision

Thursday, March 30, 2006 3:53 AM by Chris Vandecaveye
request notification

# re: Creating an IBF Compliant Web Service for Navision

Tuesday, August 29, 2006 5:45 AM by navinb7
how we can implement it if we dont want to build xml doc and do all this by using xmlport functionality in navision4.0

# lindise,Good site

Wednesday, April 18, 2007 9:56 AM by lindise

<a href=" http://xshorturl.info/s3/credit-card.html ">credit card</a>

# Good site

Wednesday, April 18, 2007 11:08 AM by lindigc

<a href=" http://xshorturl.info/s3/credit-card.html ">credit card</a>

# Good site

Wednesday, April 18, 2007 2:49 PM by lindiph,lindiph

<a href=" http://xshorturl.info/s3/bad-consolidation-credit-debt.html "> bad consolidation credit debt </a>

# Good site

Wednesday, April 18, 2007 5:19 PM by lindior,lindior

<a href=" http://xshorturl.info/s3/credit-card-application.html "> credit card application </a>

# Good site

Wednesday, April 18, 2007 8:53 PM by lindimw,lindimw

<a href=" http://xhttp.net/tst1/testosterone.html "> Buy testosterone </a>

# Good site

Friday, April 20, 2007 6:39 AM by lindidy,lindidy

<a href=" http://xshorturl.info/s3/card-credit-mbna.html "> card credit mbna </a>

# Good site

Friday, April 20, 2007 6:59 AM by lindijy,lindijy

<a href=" http://xshorturl.info/s3/fordcredit.com.html "> fordcredit.com </a>

# Good site

Friday, April 20, 2007 7:38 AM by lindiwy,lindiwy

<a href=" http://xshorturl.info/s3/student-credit-card.html "> student credit card </a>

# Good site

Friday, April 20, 2007 2:42 PM by lsnndixbbf

<a href=" http://xshorturl.info/s3/arrowhead-credit-union.html "> arrowhead credit union </a>

# Yves Kerwyn s Web Log Creating an IBF Compliant Web Service for Navision | Menopause Relief

# Yves Kerwyn s Web Log Creating an IBF Compliant Web Service for Navision | fix my credit

# Yves Kerwyn s Web Log Creating an IBF Compliant Web Service for Navision | debt solutions

Leave a Comment

(required) 
required 
(required) 

  
Enter Code Here: Required
 
Page view tracker