AL Extensions: Importing and Exporting Media Sets

One of the things that has changed when you are building V2 extensions for a cloud environment is that you cannot access most functions that work with physical files.

This presents a bit of a challenge when it comes to working with the media and media set field types, as the typical approach is to have an import and export function so that a user can get pictures in and out of the field.

An example of this is the Customer Picture fact box that’s on the Customer Card:

WorkingWithMediaFields1

As you can see, the import and export functions in C/Side leverage the FileManagement codeunit in order to transfer the picture image to and from a physical picture file. These functions are now blocked.

So…..we have got to take another approach. Enter streams.

Using the in and out stream types we can recreate the import and export functions without using any of the file based functions.

An import function would look like the following. In this example, the Picture field is defined as a Media Set field.

local procedure ImportPicture();
var
   PicInStream: InStream;
   FromFileName: Text;
   OverrideImageQst: Label 'The existing picture will be replaced. Do you want to continue?', Locked = false, MaxLength = 250;
begin
   if Picture.Count > 0 then
     if not Confirm(OverrideImageQst) then
       exit;

  if UploadIntoStream('Import', '', 'All Files (*.*)|*.*', FromFileName, PicInStream) then begin
    Clear(Picture);
    Picture.ImportStream(PicInStream, FromFileName);
    Modify(true);
  end;
end;

The UploadIntoStream function will prompt the user to choose a local picture file, and from there we upload that into an instream. At no point do we ever put the physical file on the server. Also note that the above example will always override any existing picture. You do not have to do this as media sets allow for multiple pictures. I’m just recreating the original example taken from the Customer Picture page.

For the export we have to write a bit more code. When using a Media Set field, we do not have access to any system function that allows us to export to a stream. To deal with this all we need to do is loop through the media set and get each of the corresponding media records. Once we have that then we can export each of those to a stream.

That would look like this:

local procedure ExportPicture();
var
   PicInStream: InStream;
   Index: Integer;
   TenantMedia: Record "Tenant Media";
   FileName: Text;
begin
   if Picture.Count = 0 then
      exit;

   for Index := 1 to Picture.Count do begin
      if TenantMedia.Get(Picture.Item(Index)) then begin
         TenantMedia.calcfields(Content);
         if TenantMedia.Content.HasValue then begin
            FileName := TableCaption + '_Image' + format(Index) + GetTenantMediaFileExtension(TenantMedia);
            TenantMedia.Content.CreateInStream(PicInstream);
            DownloadFromStream(PicInstream, '', '', '', FileName);
         end;
      end;
   end;
end;

We use the DownloadFromStream function to prompt the user to save each of the pictures in the media set. As in our first example, there are no physical files ever created on the server, so we’re cloud friendly!

You may notice that I use the function GetTenantMediaFileExtension in the export example to populate the extension of the picture file. Since the user can upload a variety of picture file types, we need to make sure we create the file using the correct format.

The function to do this is quite simple, however there is no current function in the product to handle it, so you’ll have to build this yourself for now. Hopefully in the near future this function will be added by Microsoft.

local procedure GetTenantMediaFileExtension(var TenantMedia: Record "Tenant Media"): Text;
begin
   case TenantMedia."Mime Type" of
      'image/jpeg' : exit('.jpg');
      'image/png' : exit('.png');
      'image/bmp' : exit('.bmp');
      'image/gif' : exit('.gif');
      'image/tiff' : exit('.tiff');
      'image/wmf' : exit('.wmf');
   end;
end;

Until next time, happy coding!

 

Advertisements

Create a Dynamics 365 Sandbox Environment

Microsoft has given us the ability to create a sandbox environment based on the Dynamics 365 platform. This will allow you to try out new setups, extensions, and even develop new extensions in a safe isolated environment. This is perfect for testing new extensions in a true Dynamics 365 environment.

Each Dynamics 365 subscription is allowed to create one sandbox environment for no additional charge. You can even tear down and reset your sandbox as necessary.

To create a sandbox environment:

  1. Sign in to your production instance of the Financials service.
  2. Choose the Search for Page or Report icon, enter Sandbox Environment, and then choose the related link
  3. Select Create.
    Another tab in your browser will open for finishing the setup of your sandbox environment.
  4. When the sandbox environment is ready, you will be redirected to sandbox environment’s Welcome wizard.
  5. Choose Learn more to read about scenarios that you can try in a sandbox environment. Or, choose Close to continue to the Role Center of your Financials sandbox instance.
  6. At the top of the Role Center, a notification appears to inform you that this is a sandbox environment. You can also see the type of the environment in the title bar of the client.

Once the sandbox has been created, you can access each environment directly with the following URLs:

For more information, see the original Microsoft article here.

Happy coding!

Dynamics 365 ‘Tenerife’ Release Plan

Quick update on the release plan for the next iteration of Dynamics 365 ‘Tenerife’ (fka Dynamics NAV).

The next release for on-premise deployments was announced to be in the spring of 2018. This is a change from the typical October releases that we’ve been used to for the past 7 years or so.

We’re not sure what the final product name will be at this point.

This change in the release date came as quite a shock to the audience at Directions NA 2017. So much of a shock that Microsoft announced during the closing keynote that they will look into the current release plan to see if there’s anything they can do to get it out ahead of plan.

That’s all for now, happy coding!

Thoughts on Directions NA 2017

I’m back home after spending some time at Directions NA 2017. It was…..crazy and amazing! It’s always a fun and informative conference, and this year definitely did not disappoint.

I’m not going to focus on ‘the crazy’, as I’m sure you’ve probably seen countless other posts about it already. 🙂

Dynamics 365 ‘Tenerife’. Is it the final name? No. Is the NAV name being dropped? Sounds like it.

Dynamics 365 Tenerife: it’s cloud; it’s on-premise; it’s really freakin’ cool!

One more thing. No matter if you are a coder, an implementer, a sales person, or a little bit of everything, things are changing, and they’re changing FAST. Scary? Of course…..change always is, but I have 100% confidence that Microsoft’s direction is the right direction to go. We have been living in an online world for a number of years. No longer is the message “the cloud is coming”. The message is now “the cloud is here”. Hopefully you’ve been moving to the cloud already. If not, you are behind the times; it’s time to get your products to the cloud ASAP!

For developers, you have to put back on your learning hat and if you’ve not already started, begin looking at extension development using the new development tools, namely Visual Studio Code. It’s a new tool, but not “really” a new language. C/AL is a great language, so why would Microsoft do away with it? Consider the new AL language as ‘C/AL 2.0’ if you want. Same syntax, plus a load of new features that makes development even faster than it was in C/Side. With built in object types to connect to web services or process xml data, to new string builder commands, the development platform has been improved to an absolutely amazing level…….and that’s not even considering all of the goodies that Visual Studio Code brings us. Customize your development tool to however you like using a massive library of different extensions. Visual Studio Code has quickly become my go-to tool for much more than AL coding.

There’s so many cool things that need to be talked about in detail. The Microsoft Graph API that is being developed for the Dynamics 365 platform is something I want to spend some time with in the near future. Finally having a documented common set of APIs is something that I think is going to make our lives as developers extremely better…….and yes, the APIs work on premise too. 🙂

So many things to learn outside of what we’d typically refer to as the “NAV system”. Case in point, I spoke at a session regarding test drives on AppSource, which up until 2 weeks ago, was a completely foreign topic that I knew nothing about, and never thought I’d need to know anything about. Turns out though, with minimal development work, it’s incredibly easy to hook up your Dynamics 365 published app so that any Office 365 user (admin or not) can spin up a session to try it out, free of charge to them.

I know I’m probably forgetting a load of other cool things that were covered at Directions NA 2017, but trust me when I say that Microsoft is stepping on the pedal hard and going full steam ahead. Dynamics 365 Tenerife is a testament to that for sure.

It’s all so very exciting!

Until next time, happy coding!

 

 

Find Out if an Extension is Installed, part 2

“Hi, this is Library, is Extension 1 home? What about Extension 2, are they home too?”

In my previous post on this topic, I explained how you can use the NAV App Installed App system table to see if a specific extension is installed. I later found out though that the ability to access that table may not always be available in the Dynamics 365 for Financials platform, so back to square one. I want a stable long-lasting solution.

Alas…events!

First…some background on why I need this functionality. I’m developing 2 extensions that share a common library, but I want to develop these extensions so that they are independent, meaning that a customer is able to install only one of the extensions if they choose to, and not be forced to install both. I also need certain features in each extension to act differently depending on if the other extension is installed or not. I know….never simple. 🙂

For those that are not aware, when you submit an extension to AppSource, you are able to submit along with it a library extension. Multiple extensions can be dependent on the same library, which makes it easy to deliver foundation type functions that are shared amongst your extensions. The library extension will not be seen in AppSource, but it will be automatically installed when any of the dependent extensions are installed.

What this also means is that when I am developing in the context of one of my “functional” extensions, I am able to directly reference objects that are contained in my library because the library is guaranteed to exist when the functional extension is installed. What I cannot do though is the opposite, because although the library extension knows that at least one functional extension was installed, it does not know directly which one it was. Make sense!? Clear as mud I know. 🙂

In the example below, let’s assume that I have a “Library” extension, and two functional extensions, brilliantly named “Extension 1” and “Extension 2”.

First, I need to create a codeunit in my library extension that will act as the “extension handler” so to speak. In other words it will house the functions required to determine what extensions are installed. In my library codeunit, I’ll add the following functions:

CheckIsExtensionInstalled(ExtensionAppID : GUID) : Boolean
IsInstalled := FALSE;
IsExtensionInstalled(ExtensionAppID,IsInstalled);
EXIT(IsInstalled);

GetExtension1AppID() : GUID
EXIT('743ba26c-1f75-4a2b-9973-a0b77d2c77d3');

GetExtension2AppID() : GUID
EXIT('a618dfa7-3cec-463c-83f7-7d8f6f6d699b');

LOCAL [IntegrationEvent] IsExtensionInstalled(ExtensionAppID : GUID;VAR IsInstalled : Boolean)

The above functions allow me to call into the codeunit to determine if either of my functional extensions are installed. The GUIDs that I used are samples above, but you should use the same GUID that is used in the corresponding extension manifest file.

The piece that makes this all possible is the published event IsExtensionInstalled. What I can do now is from within each functional extension, I can subscribe to that event so that each extension can basically give “answer” the library when it asks if it’s installed.

To do that, I create 2 more codeunits, one in each functional extension. These codeunits will contain a subscriber to the event that we published in the library codeunit. This way, if the extension is installed, its subscriber will respond to the event and let the library know that it is installed. If the extension is not installed then there won’t be anyone home to answer that call.

Extension 1

LOCAL [EventSubscriber] OnCheckIsExtensionInstalled(ExtensionAppID : GUID;VAR IsInstalled : Boolean)
IF ExtensionAppID = ExtensionHandler.GetExtension1AppID THEN
  IsInstalled := TRUE;

Extension 2

LOCAL [EventSubscriber] OnCheckIsExtensionInstalled(ExtensionAppID : GUID;VAR IsInstalled : Boolean)
IF ExtensionAppID = ExtensionHandler.GetExtension2AppID THEN
  IsInstalled := TRUE;

So how do we use all of this? Easy, of course. The example below shows code that you could use from either of the functional extensions, so that your extensions can act differently depending on what other extensions are installed.

IF LibraryCodeunit.CheckIsExtensionInstalled(LibraryCodeunit.GetExtension1AppID) THEN
  MESSAGE('Extension 1 is installed.');

IF LibraryCodeunit.CheckIsExtensionInstalled(LibraryCodeunit.GetExtension2AppID) THEN
  MESSAGE('Extension 2 is installed.');

There, easy right? If you want to try it out yourself, you can grab the above example on GitHub here.

Now, while you can do this if you own all of the source code for each extension, you cannot use this solution to determine if “any old random extension” is installed, as you need to add the subscriber function to the functional extension code. But, if you are developing out multiple extensions and if you need to know which of them are installed, then this solution will work wonderfully!

Happy coding!

Published!

Well, it finally happened…..

The Dynamics 365 for Financials extension (or app…..your choice) that I’ve been working on has finally made it to AppSource! You can check out our listing here.

untitled

BuildFood – Recipes is the first extension from IndustryBuilt Software Corp., who also happen to make JustFood. The extension focuses on the needs of the small bakeries out there, and as the marketing gurus say, it can

“manage recipes, create ingredient declarations, track allergens and calculate nutrients.”

With the extension being validated back in November, it has been a somewhat lengthy wait before seeing it published, but it’s awesome to see it available now! When we first formed the “extension team” as it was first referred, we weren’t sure what to expect from this “new way” of building solutions, and it became evident early on that we needed to tackle everything (marketing, product design, development) with fresh eyes and a new point of view. I think everyone involved has done a fantastic job of that if I do say so!

So……we’re published. Cool….but we are not stopping yet! We’re not too far from submitting the next version of BuildFood – Recipes to AppSource so stay tuned for that! Some great new features coming in the next version! We’re also working on our second extension, but I’m not giving away any details on that until marketing has their chance to let loose with it. 🙂

Oh, if you want to know more about BuildFood, click here!

Happy coding…

 

Back from Copenhagen…

I’m writing this on the plane heading back to Toronto after spending a few days at the Microsoft Development Center in Copenhagen (great building!!). I was participating in a set of meetings around how to wow the Dynamics 365 for Financials customer.

It’s all about the customer experience, or the journey if you will. From the time they select and install an extension, the customer needs to have an experience that makes them want to continue, and makes them want to register and purchase your extension. Let’s face it, first impressions are huge, especially in a SaaS world where choice is king. If a customer needs to jump through hoops to get your extension setup before they can even try it out, chances are they’re going to move on.

While I can’t get into the details as they are yet to be made public, what I can say is that it was great for IndustryBuilt Software Corp. to be included in the select group of partners that were chosen to participate alongside some key Microsoft resources, in order to help define the standards that all partners can (and I hope will!) adopt in order to make their extensions great. A ton of ideas were discussed and everyone came away from the meetings with new information on how to not only improve their own extensions, but ways in which we can create tools and samples that other partners can use. Can’t wait to see all of the final products!

As an added bonus, I spent a bit of time playing with extensions v2, which I’ve been wanting to do for a while now. I even begin converting my existing v1 extension into a v2 extension, and conversion process is slick. There’s always clean up to be done after any conversion tool, but it gets you close! The tool is not available yet so I’m not spilling any beans on it now, but I’ll be working with Microsoft to resolve some of the issues that I did encounter, so perhaps I can share more information later.

That’s it for now, and as I said, more information will come on this when it has been made public, but for now, rest assured there is a strong dedication and commitment from Microsoft on ensuring that whether the customer is new to ERP or they’re a seasoned veteran, their software experience will be fantastic!