Download Sample Project and NAV Objects
There are some decent blogs out there about Dynamics NAV .Net add-in controls, but I have not seen many that focus on using custom events and using custom event arguments (EventArgs in .Net). Many of the more advanced controls I’ve made for my employer require constant communication back and forth between the control and NAV. I thought it would be nice to throw up an example that includes the following:
- A custom .Net user control (for this demo I used a WinForms UserControl).
- A .Net add-in control to act as a mediator between the .Net user control and a page on NAV.
- A custom event arguments class to package up the relevant data sent by the event.
What this blog post will not be covering is how to create a control add-in, sign it, register it in NAV and add it to a page. The MSDN documentation covers that information well enough.
Also note that I have done this example in NAV 2013 R2. As far as I know, this will work in NAV 2009 R2 and 2015. I don’t know if versions prior to 2009 R2 will work (I believe that even 2009 R1, which did have the ability to create add-in controls, is missing some big improvements that allowed exposing public methods and events from the add-in control to the RTC).
Initial Load – Pre-populated from the Database |
NAV Responding to the Speak event |
Adding Whiskers the Cat |
Whiskers the Cat is now on the List |
NAV Responds to the Speak Event |
Chubbs the Dog Already Exists |
Pet Not Added – Details Not Cleared |
Pet Table and Primary Key. |
On the .Net side, I wrote some C# code to create an object that represents a Pet record. It takes the three fields as constructor arguments, which are stored in read-only fields and exposed through public properties. I also created an additional property called Display that formats the pet type and pet name for use in the ListBox (see screenshots in the See It In Action section):
Pet Class in C# |
One thing of note about this class is the serializable attribute (marked above the class name definition as [Serializable]). This attribute is required for any classes that you will pass as objects between NAV and .Net because serialization is how things are transferred between the two domains.
Next, I created the PetEventArgs class. This is used to package up information about the Pet object that I wish to pass between NAV and .Net. I chose to store the entire Pet object as a single field and exposed it as a property within the PetEventArgs class. However, I could have stored each Pet property as a separate PetEventArgs property just as easily. The advantage to the single object approach is that a change to the Pet class doesn’t require changes to the PetEventArgs class to expose the new property later. The disadvantage to this approach is that accessing the properties from the NAV side requires doing PetEventArgs.Pet.PropertyName rather than just PetEventArgs.PropertyName (the latter is cleaner, but the former is a small price to pay when only digging one level deeper in the object chain):
PetEventArgs Class in C# |
Notice that this class also required the serializable attribute. This class will wrap the Pet object and it is this class itself that will actually be passed around. You must serialize all the way through the object chain or you will get an error from NAV.
The next step was creating the user control. I chose to go with a .Net Windows.Forms.UserControl so that I could package the entire control into one UI element and add it to container. I could have created each piece of the control directly in the add-in control, but this takes away the advantage of using the UI designer in Visual Studio and requires hardcoding the locations.
Rather than show the PetUserControl code in it’s entirety, I will focus on showing parts relevant to the actions taken above in the See It In Action section.
Initial Loading From NAV Table
When you open up the page controlling the PetUserControlAddin, the following sequence of events occurs:
- The PetUserControl user control is created inside the PetUserControlAddin
- The PetUserControlAddin fires the AddInReady event
- The NAV Pet Card page responds to the AddInReady event by asking the Pet Utils codeunit to get the Pet records in the Pet table.
- The Pet Utils codeunit creates a .Net List, cycles through the Pet table, creates a Pet object for each record and then adds them to the list.
- The Pet Card page then passes the list of pets to the AddInitialPets method exposed by the PetUserControlAddin.
- The PetUserControlAddin sends the list of pets to the PetUserControl via the AddInitialPetsList method.
- The PetUserControl binds the list to the ListBox.
AddInReady Event |
Above is the definition for the AddInReady in the PetUserControlAddin class (the delegate { }; part is a trick to avoid having to check if the event has been registered before firing, which I have talked about here). It is marked with the ApplicationVisible attribute to ensure NAV will have access it when the add-in is bound to the NAV page. Also note that the event is marked as field:NonSerialized. This is to override the serialized attribute I have put on the entire add-in class. Events cannot be serialized and so you must mark them as non-serialized if you have marked the containing class as serialized.
Register and Fire AddInReady |
The AddInReady event is fired inside of the CreateControl method of the PetUserControlAddin class once the panel containing the PetUserControl has been created. I’m using a simple event forwarding trick here that I have mentioned in a previous blog post.
When the event fires, it is handled by the Pet Card page hosting the PetUserControlAddin (I named it PetAddin on the page).
PetUserControlAddin AddInReady Event Handler |
You’ll notice I simply forwarded the call to an OnAddInReady function within the page itself. The reason I have done this is very important. If you were to add something new to the add-in control (such as another publicly exposed event or method), it will not show up on the page unless you removed the control and then rebind it (unfortunately there is no other way to refresh the page). The problem with doing this is that all code that was directly inside the triggers is deleted. The good news is that replacing a single line for each event is not too much work (still not ideal, but what can you do). I always follow the pattern of creating an event handling function called On[EventName] and passing the parameters that were packaged up with the event itself. You will see this more when I show the Speak event in a later section.
OnAddInReady Function |
The OnAddInReady function passes a .Net List to a codeunit, which populates the list and returns it (passes it by reference since NAV has no way to return a .Net object from a function). This is straight forward records looping code, so I won’t show it. Once the list is populated, the AddInitialPets method found in the PetUserControlAddIn is passed the list of pets.
AddInitialPets Method |
A few things to note here. The ApplicationVisible attribute has been applied to the method to ensure that the method is exposed to NAV. Next, you’ll notice that the pets are a list of Objects not a list of Pets. I have done this since by default NAV does not work directly with .Net generics. To keep the code simpler on the NAV side (in the code I did not show for populating the list of pets in the Pet Utils codeunit). If you are looking at that code in the downloaded sample and you want to see how to create a list of a specific .Net type, take a look at Vjeko’s blog post (he recently changed his blog formatting and this appears to have messed up the way the code displays (in Chrome at least)… if you want to see an example of how to create generic objects in .Net in C/AL code, just leave a comment and I’ll post a downloadable sample). I always use a variation of this in production code. To combat the pets being Objects, I use a LINQ Cast extension method which then returns all objects that are able to be cast to the type Pet. Next, the list of pets is passed to the PetUserControl, which binds them to the ListBox. I will leave that part out or this blog post will become a novel.
Speak Event and PetEventArgs
I will show one more example from the user control that involves the PetEventArgs class. The speak event follows the same basic flow as the last section, except that it originates directly from the PetUserControl (rather than at the add-in) and does not return anything back from NAV to the PetUserControl. The entire process is as follows:
- The user selects a Pet from the PetUserControl in the ListBox.
- The user clicks the Speak button, which causes the PetUserControl to package up the selected Pet into a PetEventArg.
- The PetUserControl fires the Speak event and passes along the PetEventArg.
- The PetUserControlAddin handles the Speak event by forwarding it on via its own Speak event.
- The Pet Card page handles the Speak event fired from the PetUserControlAddin.
- The Pet Card page extracts the Pet from the PetEventArgs object and passes it to the Pet Utils codeunit.
- The Pet Utils codeunit displays a message, extracting information from the Pet object.
Speak Event and PetEventArgs |
Speak Event in PetUserControlAddin |
Forwarding the Speak Event in PetUserControlAddin |
PetUserControlAddin Speak Event Handler |
OnSpeak Function |
Dynamics Nav .Net Add-in Control with custom events and EventArgs
Jason,
wonderful blog!
I´m the IT manager and Nav programmer of a mid-size company in Germany, using Nav for 15 years now, while we are still on 2009 classic client with a huge number of individualizations. So far we couldn´t find a way to port our implementation of Nav to the RTC. While I somehow understand, why MS has changed Nav that way, I can´t understand how they could get out a product with such a poor support for document printing and with such ridiculous restrictions within pages. But we got tools like 4Nav and .Net Add-Ins, which hopefully will help, to get around that.
Regarding pages, it´s especially about the information density one can realize within a page. Our sales people need a maximum of information on one screen, which they can see without any switching. Pages on the other hand are such a waste of screen space with their huge fixed size fields and the “2 columns only” concept. With pure pages we we won´t have a chance to realize our modules within the RTC, and I hope that Windows.Forms Add-Ins will help us, to accomplish what we need – a high density of information without any switching (and maybe even more …).
So let me say thank you, for this perfect blog
Best regards
Michael
Hi Michael,
Thanks for reading! I’m glad you’ve found some useful information on my blog (although I’ve been slacking a bit lately in writing some new posts).
As for the issue with limited real estate on pages, perhaps you can make use of subpages to display information using lists/repeaters (I’m not sure if that fits your needs). Another option is fact boxes along the side of the page (particularly useful for aggregate type data such as totals and flow fields). Unfortunately I didn’t come to the NAV world until version 2009 R2, so I have no practical experience with forms to make a comparison to pages. Maybe ignorance is bliss 🙂
Hi Jason,I found your post very interesting. I am a complete beginner in C# and . NET world, but I am a consultant on NAV. I tried your solution on NAV2017 environment (Client And Application on same machine), with Visual studio 2015. But…I was not able to make it works. I can open the form (after changing all the DotNet variables on NAV with RunOnclient=Yes) and show the first 2 records in the Table.
After pressing Speak, I always have an error of the type “Cannot Load an instance of the following .NET…”. I debugged in Visual studio and the error is fired on eventhandler “Speak” in the ControlAddinExport page (the “main”). I tried many different way to write the code (writing it in extended mode instead of Lambda notation),but nothing change.
Thanks in advance for any help
Hi Davide,
I’m glad you found the post interesting. My initial suspicion would be a missing [Serializable] attribute on one of the classes. If not that, then perhaps one of the events are not overriding that attribute with [field:NonSerialized].
Have you checked the windows event log to see if there is any more information that can be extracted from the stack trace?
I know this post is getting old but I found it very useful. However, I was having an issue similar to Davide’s. The solution, for me, was to add the dll to the Add-ins folder of the service.
Hi
Great Blog
Can this work in Webclient? What are the options if we like to do similar thing in Webclient. They do support addins now.
Hi Jaspreet,
I’m glad you are finding my blog useful. Unfortunately I no longer work with Dynamics NAV, so anything beyond NAV 2016 I am not familiar with. For 2016, I believe you had to convert the add-in controls to JavaScript (or you could use TypeScript–a statically type subset of JavaScript) if you wanted to use them in the Web client.