In the first part, I explained how to request to list an app in AppSource, how to get a developer account and how to generate the offer in the AppSource publishing portal. If you have not had a chance to read it, you can find it here.
This time, I am going through the development of an app, explain the important steps to remember, and different suggestions to create a well-rounded App for Dynamics 365 for Financials. I am not going into the fundamentals of creating an extension, since this is well documented already, but I am going to look into the actual issues that can arise when you are trying to develop an app and what should be added to an app to make it well rounded.
First, don’t think of an app as just a small little piece of code that everyone will love to have and pay you a fortune for. It takes time and effort to come up with an idea, design the user interface and the features properly, and then turn that into a solid app. Remember, you will basically give a user some software that you won’t train them on and you won’t implement yourself. It gets installed and should be easy to understand and setup. That’s why the product documentation or help plays a big role in an app, but it should do that anyway in any ISV solutions.
If you have already looked at designing your app and looked at the code in stock NAV (since that is where you basically develop your functionality), you will find that not everything can be turned into an app. For instance, if you have an ISV solution for Dynamics NAV, you cannot guarantee that it will work as an app.
Why not? It is simple: The only way that you can hook into standard code with an app or extension is using the event pattern. Meaning, the application raises events and you can then subscribe to the events and execute your code. And one of the issues here is: There are just not enough events available – not at the right spots for you to be able to execute your code. Sometimes, you can redesign the application so that you can still get your functionality to work, but sometimes, it’s not possible and you will have to wait until Microsoft adds additional events – which they are doing all the time.
When you develop an app, there are a few different requirements that it must fulfil. For instance, you need to have tooltips defined for every single field and action that you display on a page. This can add some effort to it, but it makes it easier to understand for a user. And please, do not just add a tooltip to have a tooltip, but make them useful. For instance, when adding the “Salesperson Code” to a sales document, what helps more:
“Select the salesperson”
“Select the salesperson, who is responsible for the customer, and will receive the sales commissions for this sale. Please click here for more information” (and the link could link to a page in your online help describing how those things work).
You also need to define the ApplicationArea property on all fields and actions that you add to a page. If you don’t, they won’t be displayed in Dynamics 365 for Financials. You need to add one of the following two application areas or you can also add both:
From a code design perspective, there are many more requirements and the latest guide can be found on the SharePoint site for App development.
When the app is available on AppSource, the app is published to all environments on Dynamics 365 for Financials. When a customer selects to “install” it, it will be installed into the specific tenant, currently as a trial version and then the customer can buy it in the future. You must make sure that you handle the proper upgrade of your app functionality in this process. It is explained on MSDN in an overview.
Most times you don’t change your data model, so you can just call the following code for each table that you either added through your extension or where you added fields through your extension:
Where it gets tricky is, when you make changes to your data structure. I will show you the way that I handle it – doesn’t mean that it’s the best or only way, but it works well for me. For this, I am assuming that I have 2 tables: Table1 and Table2 and in the new version I am introducing Table3 and will move a couple fields from Table2 to Table3. The code below shows the entire upgrade functionality in this case.
ArchiveVersion := NAVAPP.GETARCHIVEVERSION;
// do this for all versions
// if the old version is below the data structure change, we have to move data around
IF ArchiveVersion < NavAppDataChange1 THEN BEGIN
IF NAVAPP.GETARCHIVERECORDREF(DATABASE::Table2,ArchiveRecRef) THEN
IF ArchiveRecRef.FINDSET THEN BEGIN
Total := ArchiveRecRef.FIELDCOUNT;
FieldNo := 1;
WHILE (I < Total) DO BEGIN
IF ArchiveRecRef.FIELDEXIST(FieldNo) THEN BEGIN
FieldRef := ArchiveRecRef.FIELD(FieldNo);
CASE FieldNo OF
Table3.Field1 := FieldRef.VALUE;
Table3.Field2 := FieldRef.VALUE;
IF Table2Ref.FIELDEXIST(FieldNo) THEN BEGIN
FieldRef2 := Table2Ref.FIELD(FieldNo);
FieldRef2.VALUE := FieldRef.VALUE;
I += 1;
FieldNo += 1;
IF NOT Table2Ref.INSERT THEN
UNTIL ArchiveRecRef.NEXT = 0;
END ELSE BEGIN
What happens in this code? The first piece is just restoring the table data for the first table. After that, if the old version of the app is greater than the version where the data structure change happened, just restore tables 2 and 3. If the old version is below the data structure change version, then we are getting a reference to the archived data and go through each record and move the fields in the proper table. In my case, field numbers 10 and 11 should go in the new table and the rest is in the original table. This is assuming that field 10 would be the primary key or the primary key is actually an AutoIncrement or it’s handled in the OnInsert trigger of table 3.
What else should we do at the time of installing? Well, if the app was never installed before, we might want to initialize default values, register a job queue, or something else. This should be done as a subscriber to the following event:
The minimum you should do here is the initialization of your setup. It is important to check, if it’s actually your app that is being installed. If not, just exit out of the event subscriber. However, and that is described in a great blog by Freddy, which you can find here, you might also want to actually call your setup page or the assisted setup wizard (see below) so that a user can configure your app right away and start using it.
One of the app validation requirements is that the app cannot leave any data behind. So, if you install your app, initialize and complete the setup, use your app, and then remove your app, the system should look the same (except for standard data added throughout the process). BUT: This is impossible to achieve. The only way to properly remove all traces would be, if you had an OnUninstall event or would implement it like the “upgrade codeunit”, which is called when you uninstall an app – no matter, if you uninstall it from within Dynamics 365 or via PowerShell. And Microsoft did not give us the tools to do so.
However, we can make some assumptions: If you are using PowerShell to uninstall an app, it is done during the tenant upgrade process. In this case, it is not needed that all traces are removed, since the new version of the app is installed right away after the tenant is upgraded. So, although this is an assumption that should be handled in code, we can make it and are save on this side.
Then we have to deal with the case that the user would uninstall the app through the web client. You might say “Why would that ever happen? My app is so cool, no one would uninstall it”, but we still have to deal with it. There are two places that you should cover via event subscription and then write the proper code that removes any setups done by your app that cannot be removed through the standard processes (such as, if you added a Job Queue Entry, you want to remove that).
It is strongly recommended by Microsoft (and by me) to add a setup wizard to your app, which walks the user through all setups and explains them well. You can find some examples in stock NAV and can use them to write your own wizard. Look at all the wizards starting at page 1803 “Assisted Company Setup Wizard”. I am not going into details here, but you will need to remember: The wizard is a page, so you need to add your ApplicationArea and ToolTip properties.
As part of my initialization of the setup, right after the app is installed, I am always registering my assisted setup wizard and you can use the following code snippet for that. I actually encapsulated that into a function and into its own codeunit, so it’s easier to just move the codeunit from one app to another and not have to rewrite the code.
WITH AssistedSetup DO BEGIN
IF GET([MyAssistedSetupPageID]) THEN
IF ("Assisted Setup Page ID" = 0) AND
("Item Type" <> "Item Type"::Group)
"Page ID" := [MyAssistedSetupPageID];
"Assisted Setup Page ID" := [MyAssistedSetupPageID];
Name := [DisplayNameOfMyAssistedSetup];
Order := [StartingIdOfFirstLicensedObject];
Visible := TRUE;"Item Type" := “Item Type”::”Setup and Help”;
Parent := 0;
"Help Url" := COPYSTR([LinkToYourOnlineHelp],1,MAXSTRLEN("Help Url"));
Status := Status::"Not Started";
Featured := FALSE;
You cannot assume that the user that installed the app actually has writes to insert new records into the Assisted Setup table. So, you need to define actual insert permissions in your codeunit to this table as well as all the tables that are used in the upgrade or initialize part – even standard tables, because you cannot assume that the user has rights for anything.
Sometimes, it might be necessary to execute only your code and not even any of the standard functionality at all. Imagine, you want to handle your own release functionality and not the standard function (although “Release” is part of the Extended Pack and not available in Dynamics 365 for Financials at this time – it still runs through the codeunit though).
How can you do that? You could subscribe to the “OnBeforeReleaseSalesDoc” event. But how do you stop the standard code from being executed? You can’t. The only way you could do that is that you COMMIT the transaction after your code and then raise an ERROR to stop any further processing. But this is a big problem: You don’t know what other apps did that ran before you, since you don’t have any control as to when your event subscription is executed. So, you could leave the database in an inconsistent state and you don’t want to do that.
You could also hook into the “Release” actions on all sales pages and do the same, but you would wind up with the same problem. The only real way of doing this is to create new Release actions on all pages that lead to only your code (for instance, with Codeunit.RUN in the properties of the action). Now, you will have two issues here:
Well, you can only force a user to do anything, if you take the choices away. Meaning, if you remove the standard action. But you can’t remove any controls from existing pages in your app. You also can’t make it invisible, since you are not allowed to change the Visible property on a page control. So, what do we have left? It’s a small trick that is not really “Best Practice”, but it gets you to where you need to be: Change the ApplicationArea property on the standard control to something that doesn’t exist. For instance, define the #Invisible application area. This way, it is not part of #Basic or #Suite anymore and won’t be displayed in Dynamics 365 for Financials. That was easy, what?
Obviously, when you are done developing your app, you need to have it properly tested. Do not rely on Microsoft to be your test team, this is not only unfair to them, but they will only test what you tell them to test, not more. So, you can write automated tests to do some unit testing and possible also process testing and you can also just test the functionality by executing it.
What is really important is that you test it with a proper license: Do not use your developer license, since you will have more rights than a normal user. Use the CRONUS license, for instance. But also define the proper permissions to test. Here is how I setup the permissions for my users when I test an app. I create two users ADMIN and USER (very creative, right?).
The ADMIN user will get SUPER permissions. The USER will get the same permissions the app validation team tests with. And those are the following:
– D365 FULL ACCESS
– EXTENSION MGT (since you are not managing the D365 extensions yet in your test)
An additional setup you should do in your test environment is that you set the Application Areas used in the instance to Basic and Suite only (or first Basic and test your app trial version and then add Suite and test your app again).
Once your extension is installed, please test the standard functionality in the areas you made modifications without defining your app specific permissions. This will guarantee that a user can still do everything, even though they have not setup the app yet. After that, set the proper permissions and test your app.
Believe me, this will save you some headaches – I went through them with my first app, before I knew the exact configuration the validation team is testing with. After you know that your functionality is working, write the use cases for Microsoft and make sure that you test every single use case specifically and word for word based on how you wrote it up to make sure that your scenarios work and make sense and that you included the proper test data.
That’s it for now, folks. I will talk about the packaging process and what to look out for there in my next blog. Let me know what you think and if you have any questions or suggestions.