Microsoft Dynamics NAV: Diagnosing Code Problems

Exclusive offer: get 50% off this eBook here
Microsoft Dynamics NAV 2009 Programming Cookbook

Microsoft Dynamics NAV 2009 Programming Cookbook — Save 50%

Build better business applications with Microsoft Dynamics NAV 2009 with this book and eBook

$35.99    $18.00
by Matt Traxinger | May 2011 | Enterprise Articles Microsoft

To celebrate the recent publication of the Microsoft Dynamics GP 2010 Reporting book, Packt is pleased to announce a series of attractive discounts on our wide range of Dynamics books. For more information click here.

Microsoft Dynamics NAV 2009 is a business management solution that helps simplify and streamline highly specialized business processes such as finance, manufacturing, customer relationship management, supply chains, analytics, and electronic commerce for small and medium-sized enterprises. ERP systems like NAV thus become the center of a company's day-to- day operations. When you learn to program in an environment like this, it opens up doors to many other exciting areas such as .NET programming, SQL Server, and Web Services.

In this article by Matt Traxinger, author of Microsoft Dynamics NAV 2009 Programming Cookbook, we will cover:

  • Using the debugger
  • Setting breakpoints
  • Using Code Coverage
  • Handling runtime errors
  • Using Client Monitor to diagnose problems
  • Finding errors when using NAS
  • Implementing Try / Catch / Finally

 

Microsoft Dynamics NAV 2009 Programming Cookbook

Microsoft Dynamics NAV 2009 Programming Cookbook

Build better business applications with NAV

        Read more about this book      

(For more resources on Microsoft Dynamics, see here.)

Introduction

No one writes perfect code on their first attempt. When running hundreds or even thousands of lines of code at a time, it can be extremely difficult to determine exactly where an error occurred and what caused it. That's why we have tools like the Debugger, Code Coverage, and Client Monitor in Microsoft Dynamics NAV.

For the most part the recipes in this article will not deal with writing your own code or writing better code. Instead we will focus more on how you can determine what is happening with code you have already written.

Using the debugger

This recipe will show you how to use the debugger to examine the code that is currently executing. We will demonstrate how to go through the code line-by-line and watch how values and objects change.

How to do it...

  1. Create a new codeunit from Object Designer.
  2. Add the following global variable:

    Microsoft Dynamics NAV: Diagnosing Code Problems

  3. Add the following global text constant:

    Microsoft Dynamics NAV: Diagnosing Code Problems

  4. Add a global function called ChangeCustomerName.
  5. The function should take in the following parameter:

    Microsoft Dynamics NAV: Diagnosing Code Problems

  6. Add the following code to the function:

    Customer.Name := NewName;

  7. Add the following code to the OnRun trigger:

    Customer.FINDFIRST;
    ChangeCustomerName(Text001);
    Customer.VALIDATE("Post Code");

  8. Save and close the codeunit.
  9. From the Tools menu in the NAV client select Debugger | Active (Shift + Ctrl + F11).
  10. From the Tools menu in the NAV client select Debugger | Breakpoint on Triggers (Shift + Ctrl + F12).
  11. Run the codeunit.

How it works...

When you run the codeunit, the Microsoft Dynamics NAV Debugger window will appear just like the one shown in the following screenshot:

(Move the mouse over the image to enlarge it.)

Before we get into the details of this window, we need to understand what caused it to appear. Setting the debugger to Active from the Tools | Debugger menu means that the debugger window will open every time the system encounters an error. In this case, though, we know our code doesn't produce any errors. We want to look at it anyway so we turn on the Breakpoint on Triggers option as well.

There are five components to the debugger window. The first is the menu and toolbar at the very top. They function just like any other toolbars you've seen. You can mouse over each button to get a tool tip of what it does.

The second component sits right below and contains the actual code from the current object. Here you can see a small yellow arrow pointing to the first line of our codeunit in the OnRun trigger. This is the line that is about to execute. Note that it has NOT executed yet. We'll explore each of the other three components as we move through our code.

Use the F8 key or click on the Step Into button on the toolbar. The window will now look like the one shown in following screenshot:

(Move the mouse over the image to enlarge it.)

The yellow arrow has moved to the second line of code and the first line has executed. Notice the red text in the bottom left-hand corner. This is the Variables window (bottom left window). It lists all the variables and their values from the current object. At first, our customer variable was uninitialized because we had not executed the Customer.FINDFIRST line. That line retrieved a record from the database causing the value of the variable to change. This text will only remain red until you take another step into the program.

The next line of code that will execute is:

ChangeCustomerName(Text001);

What is this Text001 variable? If you're unsure of the value of a text constant, or you don't want to scroll through a possibly long list of variables in the Context window, you can add a shortcut to the Watch List (bottom right window). Right-click on Text001 and go to Add Watch. The variable will be added to the Watch List along with its current value. Go ahead and hit F8 to step into the next line.

(Move the mouse over the image to enlarge it.)

The yellow arrow jumps to the function that we just called. That brings us to our last window, the Call Stack (bottom middle window). It is important to know how you got to the code you are currently viewing. By looking at the Call Stack you can see that we were in the OnRun trigger of the codeunit and then jumped to the ChangeCustomerName function. You can click on each level of the stack to see the code for that object.

(Move the mouse over the image to enlarge it.)

You may not always want to go through your code line-by-line, though. Try hitting the F5 key or the Go command from the Debug menu. This will cause you to jump to the next function which is called instead of the next line. You will find yourself in a complete new object, the Customer table. Notice how the Context menu completely changes because the old variables are no longer in scope. They do not belong to the current object being examined.

There's more...

One common annoyance is trying to stop the debugger. You will find yourself in the middle of debugging your code and have that "Aha! I know what's wrong!" moment. You will click on the "X" to close the window only to have the debugger pop right back up at you.

From the Debug menu click on Stop Debugging (Shift + F5). This will stop the debugger until you turn it on again and, more importantly, allow you to continue with your development. Stop Debugging also performs a rollback of the changes that have happened to the database since you started the debugger.

Setting breakpoints

Stepping through code line-by-line or function-by-function can take forever. Luckily there is an easy way to tell the debugger to stop right where we want it to.

How to do it...

  1. Create and save the same codeunit discussed in the Using the debugger recipe in this article.
  2. Design the codeunit.
  3. Go to the following line of code in the OnRun trigger:

    ChangeCustomerName(Text001);

  4. Press F9 twice.
  5. Go to the following line of code in the OnRun trigger:

    VALIDATE("Post Code");

  6. Press F9 once.
  7. Your window should look like the following screenshot:

    Microsoft Dynamics NAV: Diagnosing Code Problems

  8. Save and close the codeunit.
  9. From the Tools menu in the NAV client select Debugger | Active (Shift + Ctrl + F11).
  10. Run the codeunit.

How it works...

When running the debugger on this codeunit, it should stop on Customer.VALIDATE("Post Code") line of code. This is because we have set a breakpoint here, which was the filledin red circle at the left of that line. The debugger stops right where we tell it to, that is right before that line of code executes.

You will notice another mark. It is a red circle that is not filled. This is used to mark old breakpoints that you are not currently using. This is useful when you are trying to debug large amounts of code and want to temporarily remove a breakpoint or remember where you had one.

There's more...

The debugger is not perfect by any means. Some might even say it has a mind of its own sometimes. It doesn't always stop exactly where you want it to. It is common practice to set a breakpoint on a few successive lines of code in order to ensure that you stop in the general area.

Microsoft Dynamics NAV 2009 Programming Cookbook Build better business applications with Microsoft Dynamics NAV 2009 with this book and eBook
Published: October 2010
eBook Price: $35.99
Book Price: $59.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on Microsoft Dynamics, see here.)

Using Code Coverage

In some scenarios, it may be useful to see a high-level overview of which objects are used when running a process and what code is executed in those objects. This recipe will show you how to use the Code Coverage tool for exactly that purpose.

How to do it...

  1. From the NAV client menu, click on Tools Debugger | Code Coverage|. This will open the Code Coverage window.
  2. Click on the Start button.
  3. Navigate to the Customer Card in the menu suite by clicking on Sales and Marketing | Order Processing | Customers.
  4. Press F3 to insert a new record followed by Tab or Enter to save that record to the database.
  5. Close the Customer Card.
  6. Click on the Stop button on the Code Coverage window.
  7. You should now see a form similar to the one shown in the following screenshot:

    Microsoft Dynamics NAV: Diagnosing Code Problems

How it works...

The Code Coverage tool logs every line of code that is executed during a process. In this window, you can see every object that was used during the insertion process as well as the percentage of code (coverage ratio) that was executed in each object.

To view the details of the exact code that was executed in an object, select it in the list and click on the Code button. The Code Overview window will open.

Microsoft Dynamics NAV: Diagnosing Code Problems

Unfortunately, this window is not as straightforward as it might first appear. The lines of code that have been executed are shown in black. The lines of code that are not executed are shown in red.

The lines that are marked with the small diamond to the left of the line are executable lines of code. These lines are the only lines for which you can be sure that the information displayed is correct.

There's more...

One great use of Code Coverage is to determine all of the possible places where a value may have changed. For example, the Description field on a Sales Line.

You can use Code Coverage to log all of the code that is executed and then view it in the Code Overview window. This window actually shows all the code that has been logged, but applies a filter for the selected object. This filter can be removed like any other in order to view every line of code.

From there you can set a filter like "*Description*" or "*Description :=*" to find every line of code where the Description field is used or assigned a value. Using the Zoom feature (Ctrl + F8), you can select a line and quickly view which object it is in.

Running Code Coverage from code

You can also turn on Code Coverage from within your own code.

CodeCoverage.DELETEALL;

CODECOVERAGELOG := TRUE;

CODECOVERAGELOG := FALSE;
FORM.RUN(FORM::"Code Coverage");

You will first need to define a record variable named CodeCoverage of subtype Code Coverage and delete all records from it.

You can then turn Code Coverage on/off using the CODECOVERAGELOG function. To see what was logged, run the Code Coverage form (565).

Handling runtime errors

Runtime errors happen when you are actually executing code. Most of these errors present error messages that users cannot easily understand. This recipe will show you how to handle these errors as well as some of the most common ones.

How to do it...

  1. Create a new codeunit from Object Designer.
  2. Add the following global variables:

    Microsoft Dynamics NAV: Diagnosing Code Problems

  3. Add the following code to the OnRun trigger:

    Selection := STRMENU('Show Error,Handle Error', 1);
    IF Selection = 1 THEN
    Customer.GET
    ELSE
    IF NOT Customer.GET THEN
    ERROR(' Unable to find a customer with a blank number.
    \Are you sure you have selected a customer?');

  4. Save and close the codeunit.

How it works...

This codeunit allows you to select between having NAV handle an error for you or handling it with custom code. If you choose to let NAV handle the error for you, you will be presented with this error message:

Microsoft Dynamics NAV: Diagnosing Code Problems

This message can be confusing for new users. Its interpretation can be different depending on the user. The following is not a stretch:

Customer No.. Double quote does not exist.

For those who have been using NAV for a while, this message is obvious. Those users know that two single quotes represents something blank and that this message is saying that a customer record with a blank number does not exist.

Now look at the message that is displayed when we handle the error:

Microsoft Dynamics NAV: Diagnosing Code Problems

This error message was "trapped" by surrounding the function call with a conditional. The GET function, and many others, returns a Boolean value. If this value is not used by the developer and it is false, an error is thrown. We still want to throw an error, but we want one that makes sense to everyone. Here we tell the user what went wrong and a possible solution.

Using Client Monitor to diagnose problems

Client Monitor is a tool that collects statistics about client/server communication. It will let you find out where your code is slow and show you every line of code that executes from start to finish. This recipe will show you how to use it.

How to do it...

  1. Create a new codeunit from Object Designer.
  2. Add the following global variable:

    Microsoft Dynamics NAV: Diagnosing Code Problems

  3. Add the following code to the OnRun trigger:

    Customer.FINDFIRST;
    SLEEP(5000);
    Customer.FINDLAST;

  4. Save and close the codeunit.
  5. From the Tools menu in the NAV client click on Client Monitor.
  6. Click the Start button in the window that then appears.
  7. Run the codeunit you created.
  8. Click on the Stop Button on the Client Monitor window.
  9. You should now see a form similar to the following screenshot:

    Microsoft Dynamics NAV: Diagnosing Code Problems

How it works...

It can be difficult to parse through all of the data that programs like this collect. We will not begin to cover everything that the Client Monitor reports on, but instead will examine our very short codeunit.

Let's look at the output from the Client Monitor to see if we can match it up to what our codeunit did.

Please note that some output that deal with selecting the Object from Object Designer have been removed from the following result set shown.

Date

Time

Entry

No.

Function

Name

Parameter

No

Parameter

Number

Data

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

1

 

 

 

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

2

 

 

 

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

3

 

 

 

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

14

 

 

 

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

15

Source

Trigger/

Function

 

OnRun()

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

16

Source

Line No.

2

 

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

17

Source Text

 

Customer.

FINDFIRST;

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

50

Search

Result

 

-

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

51

Record

Found

 

No.='10000'

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

55

Records

Read

2

 

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

60

Reads

2

 

3/10/2010

8:55:08.712

AM

4

FIND/

NEXT

100

Elapsed

Time (ms)

 

 

3/10/2010

8:55:13.712

AM

5

FIND/

NEXT

1

Table

18

Customer

3/10/2010

8:55:13.712

AM

5

FIND/

NEXT

2

Search

Method

 

 

3/10/2010

8:55:13.712

AM

5

FIND/

NEXT

3

Key

 

No.

3/10/2010

8:55:13.712

AM

5

FIND/

NEXT

14

Source

Object

 

Codeunit

50605 Client

Monitor

3/10/2010

8:55:13.712

AM

5

FIND/

NEXT

15

Source

Trigger/

Function

 

OnRun()

3/10/2010

8:55:13.712

AM

5

FIND/

NEXT

16

Source

Line No.

4

 

3/10/2010

8:55:13.712

AM

5

FIND/

NEXT

17

Source Text

 

Customer.

FINDLAST;

3/10/2010

8:55:13.712

AM

5

FIND/

NEXT

50

Search

Result

 

+

3/10/2010

8:55:13.712

AM

5

FIND/

NEXT

51

Record

Found

 

No.='IC1030'

3/10/2010

8:55:13.712

AM

5

FIND/

NEXT

55

Records

Read

2

 

3/10/2010

8:55:13.712

AM

5

FIND/

NEXT

60

Reads

1

 

03/10/10

8:55:13.712

AM

5

FIND/

NEXT

100

Elapsed

Time (ms)

 

 

Each action corresponds to an Entry No. in the table. Each entry number has multiple parameters. We will begin with entry number 4.

Parameters 1 and 3 tell us that we are dealing with the Customer table and have it sorted on the Parameter No. key, which in this case is the primary key. That means we probably did not use the SETCURRENTKEY command. Parameter 2, Search Method, has a value of "-". From older versions of NAV we know that this is a FIND('-'), or what is now a FINDFIRST (the actual code is shown in Parameter No. 17, but it is nice to be able to understand the output).

Parameter numbers 14 and 15 tell us that this code is being called from the OnRun trigger of our Client Monitor codeunit. Parameter 16 gives more specifics about the exact line number of the code. Note that this is based on the entire object, not the line number of the trigger or function. Trigger definitions (gray bars is Code View) also count as lines. This is important to know because we could have multiple Customer.FINDFIRST commands and we will need to know which one we are dealing with.

The remaining parameters show us the record that was returned and the number of database reads (or writes if this was an INSERT/MODIFY/RENAME command).

Note that this code executed at 8:55:08 AM. Entry No. 5 did not execute until 8:55:13 AM. This tells us that we have some sort of network problem. In reality, this is an artificial problem created by the SLEEP(5000) command. We introduced a five second delay to show what the output would look like if there were actual network issues.

Finding errors when using NAS

The Navision Application Server, or NAS, does everything a normal NAV client can do, except that it doesn't show anything on the screen. This can present challenges to figuring out what has gone wrong when running your code using NAS. This recipe will show you how to debug this type of code.

Getting ready

You must already have the NAV Application Server installed on the machine on which you are working.

How to do it...

  1. Copy your developer license into the install directory for the application server. On a typical install this is C:\Program Files (x86)\Microsoft Dynamics NAV\60\ Application Server. The license file should be named fin.flf.
  2. Open a command prompt.
  3. Run the following command:

    "Path to Application Server\nassql" debug, appservername="NAS",
    servername="Your Server Name", database="Your Database
    Name",company="Your Company Name", startupparameter="NEP-",
    objectcache=32000, nettype=tcp

How it works...

The NAS Snap-in Console does not allow you to start an NAS service in debug mode, so we have to start it manually from the command line. This command is designed to error-out quickly by passing a start up parameter of NEP- instead of NEP-1.

When the command is run, the normal NAV debugger window will open with Codeunit 1 loaded. From here you can use the normal debugger commands to step through the code.

There's more...

You can also create your own codeunit that calls the NASHandler function in Codeunit 1, ApplicationManagement to get similar results.

Implementing Try / Catch / Finally

The Try / Catch / Finally syntax has been around in languages like C# .NET for a very long time. Unfortunately, it has never made it into C/AL. This recipe will show you how to implement this type of control structure so that you can display error messages and still have your code continue to execute.

How to do it...

  1. In Visual Studio create a new Class Library Project.
  2. Add a file named ITryCatchFinally.cs with the following code:

    using System.Runtime.InteropServices;

    namespace TryCatchFinally
    {
    [ComVisible(false)]
    public delegate void OnTry();

    [ComVisible(false)]
    public delegate void OnCatch(string errMessage);

    [ComVisible(false)]
    public delegate void OnFinally();

    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [ComVisible(true)]
    public interface ITryCatchFinally
    {
    event OnTry NAVTry;
    event OnCatch NAVCatch;
    event OnFinally NAVFinally;

    void Execute();
    }
    }

  3. Add a file named ITryCatchFinallyEvents.cs with the following code:

    using System.Runtime.InteropServices;

    namespace TryCatchFinally
    {
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [ComVisible(true)]
    public interface ITryCatchFinallyEvents
    {
    [DispId(0x60020001)]
    void NAVTry();

    [DispId(0x60020002)]
    void NAVCatch(string errMessage);

    [DispId(0x60020003)]
    void NAVFinally();
    }
    }

  4. Add a file named TryCatchFinally.cs with the following code:

    using System;
    using System.Runtime.InteropServices;

    namespace TryCatchFinally
    {
    [ComSourceInterfaces(typeof(ITryCatchFinallyEvents))]
    [ProgId("TryCatchFinally")]
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public class TryCatchFinally : ITryCatchFinally
    {
    public event OnTry NAVTry;
    public event OnCatch NAVCatch;
    public event OnFinally NAVFinally;

    public TryCatchFinally()
    {
    }

    public void Execute()
    {
    OnTry();
    }

    private void OnTry()
    {
    try
    {
    NAVTry();
    }
    catch (Exception e)
    {
    try { OnCatch(e); } catch { }
    }
    finally
    {
    try { OnFinally(); } catch { }
    }
    }

    private void OnCatch(Exception exception)
    {
    if (exception != null)
    {
    NAVCatch(exception.Message);
    }
    }

    private void OnFinally()
    {
    NAVFinally();
    }
    }
    }

  5. View the Properties of the project.
  6. On the Build tab check the Register for COM interop checkbox.
  7. Save and compile the objects.
  8. Create a new codeunit from Object Designer.
  9. Add a global Automation variable named TCF of subtype 'TryCatchFinally'.

    TryCatchFinally

  10. Add the following global variable:

    Microsoft Dynamics NAV: Diagnosing Code Problems

  11. Set the following property on the variable:

    Microsoft Dynamics NAV: Diagnosing Code Problems

  12. Add the following code to the OnRun trigger:

    CREATE(TCF);
    TCF.Execute();

  13. Add the following code to the TCF::NAVTry event:

    ERROR('NAV has encountered an error.');
    MESSAGE('This message should never be displayed.');

  14. Add the following code to the TCF::NAVCatch event:

    MESSAGE('The following error was caught:\%1', errMessage);

  15. Add the following code to the TCF::NAVFinally event:

    MESSAGE('NAV will now perform some cleanup.');

  16. Save and close the codeunit.

How it works...

We will not go into the details about how the C# .NET code works. For that you should refer related articles on msdn.microsoft.com/.

The ITryCatchFinally.cs file is a basic interface. Any class that implements this interface will need to define three events (NAVTry, NAVCatch, and NAVFinally) and a method called Execute.

We also need to implement an interface that will expose the events in our NAV object. This is the ITryCatchFinallyEvents.cs file. We again define our three events, but give each of them a special attribute called DispId. This ID allows the code that will be written in the NAV events to be linked back to these functions.

The last file is a class named TryCatchFinally. Our Execute method, which will be called from NAV, is simply a wrapper for our OnTry method. OnTry executes the code we have added in the NAVTry method in our NAV object. If an error is found, it is caught and execution moves to the OnCatch event. Lastly, no matter what happens, the OnFinally event is called.

When we add the Automation control to our NAV object and set the WithEvents property to Yes, our three events appear. In our NAVTry event we intentionally throw an error. A message action has been placed under that error to show that execution of the event does in fact stop when the error is encountered. In the other events, OnCatch and OnFinally, we add messages to show that although we have encountered an error, NAV code will continue to execute.

There's more...

The downside to this solution is that you must declare an Automation variable for every try / catch / finally block you want to execute. This can cause your code to become difficult to read and follow.

You can also mimic this behavior using the IF CODEUNIT.RUN THEN syntax. This is easier, but you have to buy a codeunit (admittedly, a cheap thing to buy) for every line of code you need to do this on.

Most errors can be caught using simple conditionals and this form of error trapping should be used only when absolutely necessary.

Summary

This article explained how to use built-in NAV tools such as Debugger and Client Monitor to find problems in your code. You also learnt techniques for structuring your code so that you can bypass any errors that might occur.


Further resources on this subject:


Microsoft Dynamics NAV 2009 Programming Cookbook Build better business applications with Microsoft Dynamics NAV 2009 with this book and eBook
Published: October 2010
eBook Price: $35.99
Book Price: $59.99
See more
Select your format and quantity:

About the Author :


Matt Traxinger

Matt Traxinger graduated from the Georgia Institute of Technology in 2005 with a B.S. in Computer Science, specializing in Human Computer Interaction and Cognitive Science. After college he took a job as an add-on developer using a language he was unfamiliar with for a product he had never heard of: Navision. It turned out to be a great decision.

In the years following Matt learned all areas of the product and earned Microsoft Certified Business Solutions Professional certifications in both technical and functional areas of NAV. He continues to stay current with new releases of the product and is certified in multiple areas for versions 4.0, 5.0, and 2009.

Currently Matt works in Norcross, GA, for Canvas Systems, one of the largest resellers of new and refurbished computer equipment as an in-house NAV Developer and Business Analyst. He supports multiple offices in the United States as well as locations in the United Kingdom and the Netherlands.

In his spare time you can find him on the online communities Mibuso.com and DynamicsUser.net under the name MattTrax, helping others learn more about the Dynamics NAV software.

Books From Packt


Microsoft Dynamics NAV 2009 Application Design
Microsoft Dynamics NAV 2009 Application Design

Microsoft Dynamics NAV Administration
Microsoft Dynamics NAV Administration

Microsoft Dynamics GP 2010 Reporting
Microsoft Dynamics GP 2010 Reporting

Microsoft Dynamics Sure Step 2010
Microsoft Dynamics Sure Step 2010

Microsoft Dynamics GP 2010 Cookbook
Microsoft Dynamics GP 2010 Cookbook

Microsoft Dynamics GP 2010 Implementation
Microsoft Dynamics GP 2010 Implementation

Microsoft Dynamics AX 2009 Administration
Microsoft Dynamics AX 2009 Administration

Implementing Microsoft Dynamics NAV 2009
Implementing Microsoft Dynamics NAV 2009


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software