Wednesday, December 3, 2008

NAV Automation Object

It took me a good while, but I finally managed to get my first automation object created, compiled, and made visible in NAV. Stefano Demiliani provided most of the guidance that I needed on this, as well as a few postings on mibuso, but there were a few other tricks I picked up on in the process that I figured I'd share here. Also, since I couldn't find one anywhere else, I figured I'd post a complete Visual Studio sample project.

You can download the sample project at http://bitsofinsanity.blob.core.windows.net/public/NavAutomation.zip

Please note that I am far from an expert on COM objects - I figured out just enough to get my code to compile and work, but it's highly likely that my work is suboptimal, so if anyone has any ideas for improvement, please share them.

I suppose I should explain what I'm talking about. Microsoft Dynamics NAV is an ERP system that contains and manages our Finances, Accounting, HR, Events, and more. It has tables of data (backed by SQL Server), forms to display the data, reports, and more. Each of these object types can have code behind it controlling what it does. However, the extensibility is limited to using COM objects, which are called 'Automation Objects' in NAV. But so long as you can create a COM object properly, you can insert it into a table or form and, through that COM object, you can do whatever the .Net framework allows you to do.

In working to create a COM object, I already had a class and and assembly that performed the functionality I wanted. I wanted to wrap it with COM so that I could see my class and its methods and functions in Nav. I first tried by adding the COM tags to my existing class, and I could kindof-sortof get it to work, but it never really worked completely. So I ended up creating a separate assembly and a separate class that contained the COM wrapper. All it does is call the real class, which does the hard work (the sample code doesn't call any other classes, but you could easily change it to do so).


Notes on creating an Automation Object

Your assembly will need to be strongly named. To do this:
  1. Right-click on your project name, click 'Properties', click the 'Signing' tab on the left
  2. Check the 'Sign the assembly' checkbox'
  3. In the 'Choose a strong name key file' drop down, choose 'New'. Give it a file name (anything you want), and uncheck the 'Protect my key file with a password' checkbox. Click 'OK'.

Please note the Post-build events in the sample code (under the project Properties). Make sure the paths to gacutil and regasm are correct. I don't know why, but for some reason I found I had to run the regasm and gacutil commands twice to make the COM object visible in Nav, hence the duplication of the lines below. These should register it on your development box so that you can see it in Nav. If you want to use the automation object elsewhere, you'll have to register the assembly in a similar manner on the other client machines. The post build events are:
SET GACUTIL="C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\gacutil.exe"
SET REGASM="C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm.exe"


%REGASM% $(TargetFileName) /tlb:NavAutomation.tlb

%GACUTIL% /i $(TargetFileName)


%REGASM% $(TargetFileName) /tlb:NavAutomation.tlb

%GACUTIL% /i $(TargetFileName)

After registering the assembly and type library, open Nav, open an object, and create a variable of type 'Automation.' For the subtype, click on the '...' then click on the drill-up arrow by 'Automation Server.' That will list all the COM objects loaded in your system, and you should be able to find your custom Automation object by the Assembly name (for the sample code, it should be called NavAutomation).


Be sure to create your own GUIDs for your project. The GUIDs you need to change are the ones on the interface and class definition in the cs file.

The sample project is built in Visual Studio 2008 with C# on .Net 3.5, and I tested using it on Nav 5.0 SP1, all running on Windows Server 2003 with SP2. It should work with other versions of Visual Studio, .Net, Nav, and Windows, but I haven't tested those out.

If you have an overloaded function in C#, the COM object representation in Nav will change it around a little. For example, if you have overloaded a function called 'test' with 3 different definitions, it will show up as 'test', 'test_2', and 'test_3.' This is done automatically. See the Nav symbol menu to find out what the names actually come up as.

There is no automatic type conversion between Decimal values in .Net and Decimal values in Nav, so you'll have to do a work around if you need to use decimal for a parameter or return value. I used a string object, casting it appropriately on both sides.

The Nav side can't handle constructors that require parameters. To get around this, you can create a constructor with no parameters and a function called 'init' that handles the actual construction logic.

20 comments:

Dark Warrior Poet said...

I've been banging my head against a wall trying to get this VS2008 C# class to expose it's methods. You rock man!! Thanks for the help!

DarkWarriorPoet said...

Have you done any work with streams and Dynamics NAV?

NAV 2009 SP1 said...

Just what I was looking for! Thanks a lot !

Tim Larson said...

No, I've never looked into streams. I've used streams in objects that other people have coded, but I don't really understand them myself.

Anonymous said...

The requested URL /blog/NavAutomation.zip was not found on this server. :((

Tim Larson said...

Oops, my bad. I redid some stuff on my hosting provider and forgot about the NavAutomation.zip file. Please try again - it should be working now. http://www.larbo.com/blog/NavAutomation.zip

Anonymous said...

Can you describe what you do in Navision to call this dll? This is where I'm struggling now. I've done the same hello world example and what I've done so far:
1) Create Test form (from designer)
2) Create variable referencing dll in Global Code
3) Add a button to form
4) Add a textbox to form
5) From the Push trigger for button, call the variable.SayHello.

However I'm getting automation variable has not been instantiated.

Tim Larson said...

Before you do anything with variable, you need to instantiate it like this:

CREATE(variable);

See http://msdn.microsoft.com/en-us/library/dd355255.aspx for more details

Anonymous said...

Yes I tried the create and it errors as well. I took the attached project and went through and it still fails. Any recommendations?

On the push of a button, I'm just trying to get it say hello.

- OnPush()
CREATE(TestNav);
MESSAGE(TestNav.HelloWorld());

The error message is:
This message is for C/AL programmers:

Could not create an instance of the OLE control or Automation server identified by GUID={CF0CD81C-5F7D-A670-38AC32F6C640} 1.0:{156C2968-5FEA-41D8-9186-C8DCA9871C91}:'NavAutomation'.INavAutomation. Check that the OLE control or Automation server is correctly installed and registered.

Now I only ran the regasm and gactutil once. The attached project file has it calling the same command twice.

Thanks ...

Tim Larson said...

I had the same experience - When working with the command line tools, the only way I found to get it to register properly was to run those commands twice.

As an alternative, you can create a MSI setup project in visual studio, tell it to be a COM project, add your Automation project to the setup project, then deploy the MSI.

Anonymous said...

Good new solved one problem on to others.

Good news for all others: Be sure to set global variable to reference the class NOT the interface. This has been my problem all along. I finally figured out after I imported the sample FOB file. So it should say following this example:
'NavAutomation'.NavAutomation

NOT:

'NavAutomation'.INavAutomation

My next issue: I'm trying to figure out is how to create a method with incoming parameters:

public string SayHelloWithValue(string incoming)
{
return "Hello with a " + incoming + " message.";
}
I see the Method exposed in Navision, but it errors saying:
Could not invoke the member SayHelloWithValue. The OLE control or Automation server returned the following message:
The requested member does not exist, or call tried to set the value of a read-only property.

Any tips?

Also any concerns on re-deploying changes like this. I add a method or add a property for instance. Do I have to re-run regasm and gacutil to deploy the new dll?

Thanks ....

Anonymous said...

By the way the Navision code looks like this:

NavAutomationSample.SayHelloWithValue('this is a test');

Thanks again, hopefully the answers will help others.

Anonymous said...

Hi, nice work., can you have a download link for the vs 2005 version????


thanks :d

Tim Larson said...

No, I don't have a VS2005 download. But you can probably use VS2008 express, which is free.

Anonymous said...

Hello,
I want to ask help, how can I make this automation with output = Table, and How to retrieve it in Navision.

I'm kind of stuck here, any help appreciated...

Thanks,
Ferry

Tim Larson said...

As for tables, check out my latest blog post from this morning. I give sample code on how to do that. Great question!

Anonymous said...

Tim, you link to the NavAutomation.zip is broken again.

Tim Larson said...

OK, I've updated the download link, so you should be able to get it again.

Anonymous said...

And now Drop.IO is discontinued, so the link be gone again. =\

Tim Larson said...

OK, it's now on Dropbox - the new link is above. Let's hope that keeps working. Thanks for letting me know about the broken link.