My first Dynamics 365 Extension – step by step – eight step

Now we have gotten to Step 4 in the guided path Microsoft offered us on PartnerSource.

Step 4: Provide your offer information

After you have built your app, you will need to define all the attributes that will determine how your app will be listed in Microsoft AppSource. For example, your company information, your offer & plans, marketing information, support contact, and Microsoft AppSource categories.

To define the attributes, visit the Microsoft Azure Publishing Portal.

Refer to the Marketing Validation Guidance and checklist (coming soon) to get insight into the marketing requirements and recommendations.

 

 

Lets create a new Madeira offering.

Here I clicked on “Create Dev Center account and join the Azure program”.  This helped me link the development account I created in Step 2 to my Azure publishing.

Next I clicked on “Tell us about your company” and typed away.

Next step to describe my Extension.

Here we need a lot of information.  You can see the asterisk fields that are required.

In the Plan I just created a default plan.  There will be support for more later on.

Marketing, also a generic identifier.

Then go through the language and provide all the marketing information.

Put my name, email and phone in the support page.  In Categories I checked Business Application.  Then finally I requested approval to push to production.

Now I expect to be contacted by Microsoft and helped with getting my Extension online in Dynamics 365 for Financials.

My first Dynamics 365 Extension – step by step – seventh step

Help and notification.

To make one thing clear.  The help we are used to build for the help server is not yet available for Extensions. Therefore we must make sure that all the help we anticipate the user needs will be available from the product.

Microsoft have added tool tips to most of the fields in the application.  To make sure you follow the requirements create tool tips and make sure to have the property for Application Area  populated.

In the previous post about the installation process you can see that a link to the Extension help must be provided.  I did short videos and posted them to YouTube.

With NAV 2017 and Dynamics 365 for Financials Microsoft released a new notification framework.  It is important to use this framework in your Extension.  For example, when a user open the General Ledger Entries after the G/L Source Names Extension installation a notification will appear.

And when the user reacts to the notification a setup video will start inside the web client.

The notification disappears and will not be displayed again for this user.

If the administrator has done the Assisted Setup and the user has the required permissions we will show a different notification.

and that notification will play the usage video.

Now, let’s look at how to do this.

There is a new data type for Notification.  You define variable of type Notification, set the properties and send it on it’s way.

I start by catching the event when the General Ledger Entries page is opened.  Then, depending on the permissions the user has to G/L Source Names table I select between two notifications.

You can have up to three actions added to each notification.  An action must point to a public function in a Codeunit.  That function must have a single parameter of type Notification.

The notification ID is a Guid.  Refer to my last post on how to get a new Guid and keep that Guid for the notification.  The history of the notification – which  user has acted on it, is saved by this ID.  Change it and the history will be lost.  Today, all the notifications must have Local Scope.  The Global Scope is not yet supported by the clients.

In the action for both these notifications I start a YouTube video for the user.  If the user is running web client, including phone client and tablet client, the video will be started inside the client.  For other client types I will start the video in the default browser.

That concludes my development.  Next part is to submit my Extension to AppSource.  Stay tuned…

 

 

 

My first Dynamics 365 Extension – step by step – sixth step

Assisted Setup and Permissions.

But first lets look at the Extension Management Codeunit.

I want to store the appId in my Extension.  This appId is used in several tables in my tenant.  This appId must always be the same for this solution even if the solution is updated.  In my last post you can see that I am also setting my appId parameter in the Git repository.  There are few easy ways to get your appId.  NAV has the function CREATEGUID.  Powershell has the function New-Guid.  But perhaps the easiest way is to use this online Guid generator.

In OnNavAppUpgradePerDatabase – executed once when the Extension is installed, I want to assign my Extension Setup permission set to the user installing.  This is required to be able to run the Assisted Setup after installation.

In OnNavAppUpgradePerCompany – executed once in every company when the Extension is installed, I want to restore data for some of the Extension tables and delete rest of the archive data.  Even if I am just using tables as a temporary tables I still need to define what to do with the non-existing-archived data.  In here I also want to remove the persistent Assisted Setup record in every company.

So, why would I like to remove the Assisted Setup record?  Not because I like to make the user execute the setup every time the Extension is updated.  The Extension has its own Setup table.  In that Setup table I store the setup information belonging to this Extension and the Setup data is restored from archive during the installation as you can see above.

In the current release the Assisted Setup record is a database record.  I know that Microsoft intends to change this and use the same discovery pattern that is used in Service Connections.  When that happens the Assisted Setup record will be temporary only.  So, by designing my Extension in this way I make sure that I will still support Assisted Setup after this change.

In NAV 2016 we had the Mini Role Center, page 9022.  Today this Role Center has been updated to fit Dynamics 365 for Financials.

From here the user can access the Extension installation we covered in the last post, and we can also access the Assisted Setup & Tasks.

I add my Assisted Setup by subscribing to the OnOpenPage Event in the above page.

Looking at the Assisted Setup record we can see that is has a media field for an Icon.  NAV setup data includes icon for the standard Assisted Setup items – so must we.

I decided to ship my icons with the code.  Each icon has a dedicated Codeunit.

To fit an icon into a Codeunit we need to convert the binary data to base64 data.  This we can do with the following code.

This allows me to create a Codeunit like this.

And then code like this to import the icon into my help resources.

Now, back to the Assisted Setup.  I start the setup for my Extension.

This sums up what needs to be done.  The basic setup, the one done with Set Defaults, is to assign permissions to all users based on their current permissions to the tables we build our extension around.

The G/L Source Name lookup table is read from a FlowField in G/L Entries.  We can therefore expect that everyone that has read access to G/L Entries must also have read access to the lookup table. The data in the lookup table is updated when ever any of the four master tables is updated.  Hence, everyone that has access to modify any of the master tables must have access to modify the lookup table.

The Set Defaults function assigns the Extension permission sets to users and user groups based on the current permissions and that should be enough in most cases.  If the user likes the advanced way that possibility is available.

When the user presses Finish the wizard data and the assigned permission sets are applied to the tenant database.

When I created the Setup Wizard page I started with a copy of the Email Setup Wizard, page 1805.

The model I have created to assign permission sets to users and user groups with a Wizard can easily be modified to fit any Extension.  Remember that all this code will soon be available on my GitHub account.

 

My first Dynamics 365 Extension – step by step – fifth step

Development and installation.

For my development I use source control management.  Soren Klemmensen did a short demo of that setup in his session in NAV TechDays 2016.  We are working on making that solution public in a few months.  In every branch we have a setup file in a json format. That file has all the parameters needed to build an extension.  Here is the one I use for the G/L Source Names project.  When I have this Extension completed I will publish it to my GitHub Account.

{
  "navVersion": "10.0.14199.0",
  "navProduct": "GLSOURCENAMES",
  "projectName": "NAV2017",
  "baseBranch": "master",
  "uidOffset": "70009200",  
  "versionList": "GLSN10.0",
  "objectProperties": "true",
  "datetimeCulture": "is-IS",
  "appId": "479e77f3-031a-49fe-bb6a-314464c6a9a8",
  "appName": "G/L Source Names",
  "appPublisher": "Objects4NAV.com",
  "appVersion": "1.0.0.0",
  "appCompatibilityId": "",
  "appManifestName": "G/L Source Names",
  "appManifestDescription": "G/L Source Names adds the source name to the G/L Entries page.  Source Name is the customer in sales transaction and the vendor in purchase transactions", 
  "appBriefDescription": "Source Names in G/L Entries",
  "appPrivacyStatement": "http://objects4nav.com/privacy",
  "appEula": "http://objects4nav.com/terms",
  "appHelp": "http://objects4nav.com/glsourcenames",
  "appUrl": "http://objects4nav.com/glsourcenames",
  "appIcon": "Logo250x250",  
  "appDependencies":
    [
  
    ],
  "appPrerequisites":
    [
  
    ],
  "permissionSets":
    [
  		{"id": "G/L-SOURCE NAMES",    "description": "Read G/L Source Names"},
  		{"id": "G/L-SOURCE NAMES, E", "description": "Update G/L Source Names"},
  		{"id": "G/L-SOURCE NAMES, S", "description": "Setup G/L Source Names"}
  	],
  "webServices":
    [
    
    ],
  "dotnetAddins":
    [
  
    ],
  "tableDatas":
    [
   
    ]
}

For every extension we need to make sure that the installation and configuration experience for the customer is easy.  When the Dynamics 365 for Financials user open the Extension Management page our Extension should be visible there.

Here we can see that we need to create images for our Extensions. Later in the process, when we register the Extension on AppSource we will need to supply images in different sizes for out Extension. The image that I used when I created my Extension is 250×250 points. Starting the installation will take the user through an installation wizard. In the installation process the user sees all the properties we add to the Extension manifest definition. I create the manifest with the following code.

$appManifastFilePath = (Join-Path $ExtensionPath "AppManifest.xml")
$appPackageFileName = (Join-Path $ExtensionPath 'AppPackage.navx')

$params = @{ 
  Id = $SetupParameters.appId
  Name = $SetupParameters.appManifestName 
  Publisher = $SetupParameters.appPublisher 
  Version = $SetupParameters.appVersion 
  Description = $SetupParameters.appManifestDescription }
if ($SetupParameters.appBriefDescription -ne "") { $params.Brief = $SetupParameters.appBriefDescription }
if ($SetupParameters.appCompatibilityId -ne "") { $params.CompatibilityId = $SetupParameters.appCompatibilityId }
if ($SetupParameters.appPrivacyStatement -ne "") { $params.PrivacyStatement = $SetupParameters.appPrivacyStatement }
if ($SetupParameters.appEula -ne "") { $params.Eula = $SetupParameters.appEula }
if ($SetupParameters.appHelp -ne "") { $params.Help = $SetupParameters.appHelp }
if ($SetupParameters.appUrl -ne "") { $params.Url = $SetupParameters.appUrl }
if ($SetupParameters.appPrerequisities -ne "") { $params.Prerequisites = $SetupParameters.appPrerequisities }
if ($SetupParameters.appDependencies -ne "") { $params.Dependencies = $SetupParameters.appDependencies }

New-NAVAppManifest @params | New-NAVAppManifestFile -Path $appManifastFilePath -Force

The image and a set of screenshot images are used when creating the Extension package file (navx).

if ($SetupParameters.appIcon -ne "") {  
  $iconPath = (Get-ChildItem -Path $ImagesPath -Filter ($SetupParameters.appIcon + "*")).FullName
} else {
  $iconPath = ""
}

if (Test-Path $ScreenshotsPath) {
  $screenShots = (Get-ChildItem -Path $ScreenshotsPath).FullName
} else {
  $screenShots = @{}
}

$params = @{
      Path = $appPackageFileName 
      SourcePath = $ResourceFolder }
if (Test-Path $iconPath) { $params.Logo = $iconPath }
if ($screenShots.Length -gt 0) { $params.ScreenShots = $screenShots }

Get-NAVAppManifest `
  -Path $appManifastFilePath `
  | New-NAVAppPackage @params -Force

In the below screenshots you should be able to see how my parameters are used in the installation process.  I have not yet seen my screenshots used in the Extension installation.

Web links, in the order of appearance are the appUrl, appHelp, appEula and appPrivacyStatement parameters.

Installation done and the user has logged in again and ready to start the Extension setup process.

If the Extension contains any tables then it must also contain permission sets for every table.  From the parameters above you can see that I have three permission sets for my Extension.  One for read permissions, second for update permissions and the third for the Extension setup permissions.  I expect that the user that is installing the Extension has full permissions and is able to assign permissions to other users.

In a simple Extension like this, Extension that does not require any company based setup we still need to make sure that every user has the required permissions to use the Extension.  For this task I use the Assisted Setup feature.

Catch my next blog to read all about that.

 

Using REST/Json web services from NAV

One of my most popular blog entry is the one about Json.  I have also had some questions outside this website about this topic.

This week I got a task.  We need to communicate with a payment service that uses REST web services and Json file format.

posapi

I got a document describing the service.  Some methods use GET and some use POST.  Here is how I did this.

In the heart of it all I use Codeunit 1297, “Http Web Request Mgt.”.

getaccesstoken

Every time we talk to this POS API we send an Access Token.  If we don’t have the token in memory (single instance Codeunit), we need to get a new one.  That is what the above code does.

The ParameterMgt Codeunit is what I want to focus on.  You can see that I start by inserting my “Authorization Key” into the RequestBodyBlob.  As usual, I use the TempBlob.Blob to get and set my unstructured data.

setapirequest

The interesting part here is that I use an XMLPort to create the data I need to post to the Api.

apiauthenticatexml

A simple one in this example, but nothing says it can’t be complex.  Then I convert the Xml to Json with a single function.

converttojson

The last TRUE variable means the the Document Element will be skipped and the Json will look like it is supposed to.

apikey

The REST service response is Json.

token

And to read the Json response we take a look at the GetAccessToken function.

getaccesstokenfunction

Here I start by converting from Json to Xml.

convertfromjson

And make sure my Document Element name is “posApi”.

apiaccesstokenxml

And I have the result.

As you can see from the documentation some of the Json data is more complex.  This method will work nevertheless.

For more complex date I always create tables that matches the Json structure.  These table I use temporary through the whole process so the don’t need to be licensed tables.  Here is an example where this XMLPORT

getauthorization

will read this Json

getauthorizationjson

I suggest that with our current NAV this is the easiest way to handle REST web services and Json.