`
haohappy2
  • 浏览: 327463 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Integration Interface to Connect with NAV(PHP)

 
阅读更多

An Existing Purpose-Built Website, Synchronizing the Information

 


If you’re lucky, you will find a website solution that is purpose built for the problem you’re solving.  A retail system is a good example.  In this scenario you just need to have a mechanism to synchronize the data, and often not in real-time.  For example, prices can be updated in a batch process nightly from NAV into a retail system, and sales information can be grabbed from that system and put into sales journals and posted in Dynamics NAV.  In this scenario you only need to custom build the synchronization component.  This component could connect to either your business web services or do regular updates from a landing area in your Dynamics NAV database (for example writing sales into the sales journal, and potentially reading current pricing or inventory from Dynamics NAV).

An Existing Custom Website, That Now Needs to Integrate Some NAV Data with Existing Data

 


Sometimes you’ve got a website with a database that already exists, and you just need to grab some of the data and merge it with some other information.  

If you need to merge the data, and the data does not need to be real-time then your integration option looks similar to the 3rd party component web site.

An Internal Website, or a Website that Needs Real-time Information

 


If it’s an internal website, or need real-time data then you can connect directly the Dynamics NAV SQL database or to the Dynamics NAV web services.

There are many variations of the above.  For example, you might want database replication to keep your NAV data available and not have web usage introduce risk or load on your company operations.  

Choosing the Integration Interface to Connect with NAV


There are several NAV interfaces, but you’ve really got two main options.  RTC business web services (Dynamics NAV pages or CodeUnits exposed as business web services), or direct to SQL server.  The best choice will really depend on your architecture, which is also affected by your licensing.

2009 Web Services, AJAX > RTC Web Services


If you’re using NAV 2009 you have the option of using the RTC web services.  These web services are really meant for SOA integration, however you could just expose the RTC web services directly over the Internet and then have a completely AJAX driven website connect to that, however the web service requires you to be authenticated against Active Directory.  This has a few implications:

  1. Licensing (again).   Every user would need windows CAL and a dynamics NAV C/AL.  This option is fine for your existing Dynamics NAV users as that license fee would already have been paid, but it makes it more costly to allow access for your customers or vendors.  It’s not an option at all for anonymous logins.
  2. Kerberos double (or more) hop.  Having gone through this several times it’s doable, however be aware that some IT departments will not allow web accessible computers to delegate authentication, which is a key step in configuring double-hop to work properly.  Determine now before you start whether this is a possible deployment scenario, because sometimes it’s a showstopper.

Another issue with the AJAX  Dynamics NAV Web service option is that the web services provided do not work out of the box with the two popular AJAX frameworks JQuery or EXTJS, which would require several JavaScript wrappers.  In 2013 that should change with the ODATA option that will be available.

2009 Web Services, but with Your own Custom Decorators and Controllers on Top


Dynamics NAV 2009 web services are WCF based, which makes it very simple to connect to from ASP.Net based systems.  This also provides the option to have your web site exposed with it’s own custom forms based authentication, while still connecting to NAV as a single application user (check your licensing options before you do this).  This makes it easy to execute NAV business logic from within your custom website, and provide access to data.

Direct to SQL Server


If you’re using Dynamics NAV 5 or earlier then web-services aren’t available as an option, and you will often need to just connect to SQL server directly.

Even if you’re on 2009 or later, the downside to using the NAV web services is that it is not the most efficient communication available.  For performance-minded applications this is not often a solution as you’ll have your website data layer, making an HTTP call to the web services, passing noisy XML data back and forth, and having that XML data serialize and de-serialize both on the side of Dynamics NAV and your custom website.

Your challenges with the direct to SQL Server option are:

  • No business logic is available.
  • The table naming convention of prefixing SQL server tables “CompanyName$” will make it more difficult to develop and deploy, and can also cause problems with newer frameworks such as Ruby on Rails, or MVC.Net

In a future blog post we’ll show how to use a new framework (MVC.Net) to connect to SQL server directly for the situations where the Dynamics NAV web services are not an option, and get around the table-naming problem.

 

Using Services

 

define('USERPWD', 'domain\user:password');
class NTLMStream
{
    private $path;
    private $mode;
    private $options;
    private $opened_path;
    private $buffer;
    private $pos;
    /**
     * Open the stream
      *
     * @param unknown_type $path
     * @param unknown_type $mode
     * @param unknown_type $options
     * @param unknown_type $opened_path
     * @return unknown
     */
    public function stream_open($path, $mode, $options, $opened_path) {
        $this->path = $path;
        $this->mode = $mode;
        $this->options = $options;
        $this->opened_path = $opened_path;
        $this->createBuffer($path);
        return true;
    }
    /**
     * Close the stream
     *
     */
    public function stream_close() {
        curl_close($this->ch);
    }
    /**
     * Read the stream
     *
     * @param int $count number of bytes to read
     * @return content from pos to count
     */
    public function stream_read($count) {
        if(strlen($this->buffer) == 0) {
            return false;
        }
        $read = substr($this->buffer,$this->pos, $count);
        $this->pos += $count;
        return $read;
    }
    /**
     * write the stream
     *
     * @param int $count number of bytes to read
     * @return content from pos to count
     */
    public function stream_write($data) {
        if(strlen($this->buffer) == 0) {
            return false;
        }
        return true;
    }
    /**
     *
     * @return true if eof else false
     */
    public function stream_eof() {
        return ($this->pos > strlen($this->buffer));
    }
    /**
     * @return int the position of the current read pointer
     */
    public function stream_tell() {
        return $this->pos;
    }
    /**
     * Flush stream data
     */
    public function stream_flush() {
        $this->buffer = null;
        $this->pos = null;
    }
    /**
     * Stat the file, return only the size of the buffer
     *
     * @return array stat information
     */
    public function stream_stat() {
        $this->createBuffer($this->path);
        $stat = array(
            'size' => strlen($this->buffer),
        );
        return $stat;
    }
    /**
     * Stat the url, return only the size of the buffer
     *
     * @return array stat information
     */
    public function url_stat($path, $flags) {
        $this->createBuffer($path);
        $stat = array(
            'size' => strlen($this->buffer),
        );
        return $stat;
    }
    /**
     * Create the buffer by requesting the url through cURL
     *
     * @param unknown_type $path
     */
    private function createBuffer($path) {
        if($this->buffer) {
            return;
        }
        $this->ch = curl_init($path);
        curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($this->ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
        curl_setopt($this->ch, CURLOPT_USERPWD, USERPWD);
        $this->buffer = curl_exec($this->ch);
        $this->pos = 0;
    }
}

 

class NTLMSoapClient extends SoapClient {
    function __doRequest($request, $location, $action, $version) {
        $headers = array(
            'Method: POST',
            'Connection: Keep-Alive',
            'User-Agent: PHP-SOAP-CURL',
            'Content-Type: text/xml; charset=utf-8',
            'SOAPAction: "'.$action.'"',
        );
        $this->__last_request_headers = $headers;
        $ch = curl_init($location);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_POST, true );
        curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
        curl_setopt($ch, CURLOPT_USERPWD, USERPWD);
        $response = curl_exec($ch);
        return $response;
    }

    function __getLastRequestHeaders() {
        return implode("\n", $this->__last_request_headers)."\n";
    }
}

 

// we unregister the current HTTP wrapper
stream_wrapper_unregister('http');
// we register the new HTTP wrapper
stream_wrapper_register('http', 'NTLMStream') or die("Failed to register protocol");

// Initialize Soap Client
$baseURL = 'http://localhost:7047/DynamicsNAV/WS/';
$client = new NTLMSoapClient($baseURL.'SystemService');

// Find the first Company in the Companies
$result = $client->Companies();
$companies = $result->return_value;
echo "Companies:<br>";
if (is_array($companies)) {
  foreach($companies as $company) {
    echo "$company<br>";
  }
  $cur = $companies[0];
}
else {
  echo "$companies<br>";
  $cur = $companies;
} 

 

Note that is return value is an array if there are multiple companies, but a company name if there is only one. I have NO idea why this is or whether I can write the code differently to avoid this.

Now I have the company I want to use in $cur and the way I create a URL to the Customer page is by doing:
$pageURL = $baseURL.rawurlencode($cur).'/Page/Customer';
echo "<br>URL of Customer page: $pageURL<br><br>";
 and then I can create a Soap Client to the Customer Page:
// Initialize Page Soap Client
$page = new NTLMSoapClient($pageURL);
 and using this, I read customer 10000 and output the name:
$params = array('No' => '10000');
$result = $page->Read($params);
$customer = $result->Customer;
echo "Name of Customer 10000:".$customer->Name."<br><br>";
 Last, but not least – lets create a filter and read all customers in GB that has Location Code set to RED or BLUE:
$params = array('filter' => array(
                                    array('Field' => 'Location_Code',
                                          'Criteria' => 'RED|BLUE'),
                                    array('Field' => 'Country_Region_Code',
                                          'Criteria' => 'GB')
                                  ),
                'setSize' => 0);
$result = $page->ReadMultiple($params);
$customers = $result->ReadMultiple_Result->Customer;
Note that Bookmark is an optional parameter and doesn’t need to be specified.
Now echo the customers and restore the http protocol – again, the return value might be an array and might not.
echo "Customers in GB served by RED or BLUE warehouse:<br>";

if (is_array($customers)) {
  foreach($customers as $cust) {
    echo $cust->Name."<br>";
  }
}
else {
  echo $customers->Name."<br>";
}

// restore the original http protocole
stream_wrapper_restore('http');
 All of the above will output this when the script is opened in a browser (on my machine running NAV 2009SP1 W1)
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics