Don’t worry about DotNet version in C/AL

When using DotNet data type in NAV C/AL we normally lookup a sub type to use.  When we do the result can be something like

Newtonsoft.Json.Linq.JObject.'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'

Then, what will happen when moving this code from NAV 2016 to NAV 2017 and NAV 2018.  The Newtonsoft.Json version is not the same and we will get a compile error!

Just remove the version information from the sub type information.

Newtonsoft.Json.Linq.JObject.'Newtonsoft.Json'

And NAV will find the matching Newtonsoft.Json library you have installed and use it.

This should work for all our DotNet variables.

REST Web Services using Json and requiring authentication

But first…

Registration for NAV TechDays 2017 have been opened.  I will do a workshop on web services and json.  I will be using both C/AL and AL with VS Code in this workshop.

Make sure to register for the conference and if possible go to one or two of the workshops.

Now to the topic.  Yesterday I started to develop an integration solution for bokun.io.  Their API is RESTful and uses Json file formats.  It also requires authentication.

In a project like this I usually start by using the OCR Service Setup from standard NAV.  Create a Setup table and a page.

Looking at the API documentation we can see that we need to use HmacSHA1 with both Access Key and Secret Key to authenticate.  In other project I used HmacSHA256 with the Access Key for the Azure API.

First part of the authentication is the time stamp created in UTC.  I find it easy to use the DateTime DotNet variable to solve this.  There are two different formatting I needed to use.

REST service normally just use GET or POST http methods.  The authentication is usually in the request headers.  This is an example from bokun.is

The GetSignature function is

The Secret Key string and the Signature is converted to a byte array.  The Crypto class is constructed with the Secret Key Byte Array and used to compute hash for the Signature Byte Array. That hash is also a byte array that must be converted to a base64 string.  This will give you the HmacSHA1 signature to use in the request header.

My Azure project is using HmacSHA256 but the code is similar.

Azure displays the Access Keys in base64 format while bokun.is has a normal string.

A little further down the line I choose not to use XML Ports, like I did here, but still convert Json to Xml or Xml to Json.

I use the functions from Codeunit “XML DOM Management” to handle the Xml.  This code should give you the general idea.

OBJECT Codeunit 60201 Bokun.is Data Management
{
  OBJECT-PROPERTIES
  {
    Date=;
    Time=;
    Version List=;
  }
  PROPERTIES
  {
    OnRun=BEGIN
          END;

  }
  CODE
  {
    VAR
      XMLDOMMgt@60200 : Codeunit 6224;

    PROCEDURE ReadCurrencies@1(ResponseString@10035985 : Text;VAR CurrencyBuffer@10035988 : TEMPORARY Record 60201);
    VAR
      XmlDocument@60202 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlDocument";
      ResultXMLNodeList@60201 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlNodeList";
      ResultXMLNode@60200 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlNode";
    BEGIN
      XmlDocument := XmlDocument.XmlDocument;
      XmlDocument.LoadXml(JsonToXml('{"Currency":' + ResponseString + '}'));
      XMLDOMMgt.FindNodes(XmlDocument.DocumentElement,'Currency',ResultXMLNodeList);
      FOREACH ResultXMLNode IN ResultXMLNodeList DO
        ReadCurrency(ResultXMLNode,CurrencyBuffer);
    END;

    LOCAL PROCEDURE ReadCurrency@60205(ResultXMLNode@60201 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlNode";VAR CurrencyBuffer@60200 : TEMPORARY Record 60201);
    BEGIN
      WITH CurrencyBuffer DO BEGIN
        INIT;
        Code := XMLDOMMgt.FindNodeText(ResultXMLNode,'code');
        "Currency Factor" := ToDecimal(XMLDOMMgt.FindNodeText(ResultXMLNode,'rate'));
        Payment := ToBoolean(XMLDOMMgt.FindNodeText(ResultXMLNode,'payment'));
        INSERT;
      END;
    END;

    PROCEDURE ReadActivities@60201(ResponseString@10035985 : Text);
    VAR
      XmlDocument@60202 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlDocument";
      ResultXMLNodeList@60201 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlNodeList";
      ResultXMLNode@60200 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlNode";
    BEGIN
      XmlDocument := XmlDocument.XmlDocument;
      XmlDocument.LoadXml(JsonToXml(ResponseString));
    END;

    PROCEDURE GetActivityRequestJson@10035986(NoOfParticipants@60200 : Integer;StartDate@60201 : Date;EndDate@60202 : Date) Json : Text;
    VAR
      XmlDocument@10035987 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlDocument";
      CreatedXMLNode@10035988 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlNode";
    BEGIN
      XmlDocument := XmlDocument.XmlDocument;
      XMLDOMMgt.AddRootElement(XmlDocument,GetDocumentElementName,CreatedXMLNode);
      IF NoOfParticipants <> 0 THEN
        XMLDOMMgt.AddNode(CreatedXMLNode,'participants',FORMAT(NoOfParticipants,0,9));
      IF StartDate <> 0D THEN
        XMLDOMMgt.AddNode(CreatedXMLNode,'startDate',FORMAT(StartDate,0,9));
      IF EndDate <> 0D THEN
        XMLDOMMgt.AddNode(CreatedXMLNode,'endDate',FORMAT(EndDate,0,9));
      Json := XmlToJson(XmlDocument.OuterXml);
    END;

    PROCEDURE XmlToJson@94(Xml@10035985 : Text) Json : Text;
    VAR
      JsonConvert@10017292 : DotNet "'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'.Newtonsoft.Json.JsonConvert";
      JsonFormatting@10017296 : DotNet "'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'.Newtonsoft.Json.Formatting";
      XmlDocument@10017291 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlDocument";
    BEGIN
      XmlDocument := XmlDocument.XmlDocument;
      XmlDocument.LoadXml(Xml);
      Json := JsonConvert.SerializeXmlNode(XmlDocument.DocumentElement,JsonFormatting.Indented,TRUE);
    END;

    PROCEDURE JsonToXml@95(Json@10035985 : Text) Xml : Text;
    VAR
      JsonConvert@10017293 : DotNet "'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'.Newtonsoft.Json.JsonConvert";
      XmlDocument@10017291 : DotNet "'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Xml.XmlDocument";
    BEGIN
      XmlDocument := JsonConvert.DeserializeXmlNode(Json,GetDocumentElementName);
      Xml := XmlDocument.OuterXml;
    END;

    LOCAL PROCEDURE GetDocumentElementName@97() : Text;
    BEGIN
      EXIT('Bokun.is');
    END;

    LOCAL PROCEDURE ToDecimal@98(InnerText@10035985 : Text) Result : Decimal;
    BEGIN
      IF NOT EVALUATE(Result,InnerText,9) THEN EXIT(0);
    END;

    LOCAL PROCEDURE ToInteger@92(InnerText@10035985 : Text) Result : Decimal;
    BEGIN
      IF NOT EVALUATE(Result,InnerText,9) THEN EXIT(0);
    END;

    LOCAL PROCEDURE ToBoolean@91(InnerText@10035985 : Text) Result : Boolean;
    BEGIN
      IF NOT EVALUATE(Result,InnerText,9) THEN EXIT(FALSE);
    END;

    LOCAL PROCEDURE ToDate@93(InnerText@10035985 : Text) Result : Date;
    BEGIN
      IF NOT EVALUATE(Result,COPYSTR(InnerText,1,10),9) THEN EXIT(0D);
    END;

    LOCAL PROCEDURE ToDateTime@99(InnerText@10035985 : Text) Result : DateTime;
    BEGIN
      IF NOT EVALUATE(Result,InnerText,9) THEN EXIT(0DT);
    END;

    BEGIN
    END.
  }
}

 

 

Mr. Singelton I presume

We do have one Mr. Singelton in our Dynamics NAV MVP group.  However, this blog post is not about him.

Singelton is a well known Design Pattern.  A Singelton object is a one instance object that is shared in the whole application.  Vjeko showed an example of this in his recent post.

With a Singelton class it is possible to store values that can later be retrieved by another call not related to the first.

It is common practise in Dynamics NAV to create a single-instance Codeunit to keep data that needs to be accessible from all processes within the user session.  A Singelton class is common for all user sessions on the server.

The example Vjeko talks about it that you can store data in a Singelton class to implement a state information into an otherwise stateless web service.

In a recent post I talked about a Variable Store Codeunit.  That was a single-instance Codeunit where I used the DotNet Dictionary to store and retrieve values.  This worked well inside a single user session.

I wanted to make a similar functionalilty available as a Singelton class that would be common for all sessions.  The class code is in c#

[code lang=”csharp”]using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SingeltonDictionary
{
public class SingeltonDictionary
{
private static SingeltonDictionary instance;
private Dictionary<string, string=""> NavDictionary = new Dictionary<string, string="">();

private SingeltonDictionary() { }

public static SingeltonDictionary Instance
{
get
{
if (instance == null)
{
instance = new SingeltonDictionary();
}
return instance;
}
}
public bool AddToDictionary(string KeyName, string Value)
{
if (NavDictionary.ContainsKey(KeyName))
return false;
else
{
NavDictionary.Add(KeyName, Value);
return true;
}
}
public void AddReplaceInDictionary(string KeyName, string Value)
{
ClearFromDictionary(KeyName);
NavDictionary.Add(KeyName, Value);
}
public bool ClearFromDictionary(string KeyName)
{
if (NavDictionary.ContainsKey(KeyName))
{
NavDictionary.Remove(KeyName);
return true;
}
else
return false;
}
public bool GetFromDictionary(string KeyName, ref string Value)
{
return NavDictionary.TryGetValue(KeyName,out Value);
}
public void ClearDictionary()
{
NavDictionary.Clear();
}
}
}
[/code]

The Singelton Dictionary NAV add-in can be downloaded here -> Microsoft.Dynamics.Nav.SingeltonDictionary

There is another reason why I wanted to create this add-in.  I have a solution for the Windows Client to be able to easily create textboxes and buttons for a NAV Page.  One of the problems with this add-in can be solved with a Singelton class.

The solution has a Panel Class that is built when the add-in is initiated on the page.  On top of that panel any number of buttons and textboxes are added dynamically.  To support this the panel has the autosize property set and will resize as needed.

The problem is that you can’t start to add things to the panel until it has been initiated and at that time NAV draws the page and decides how much space is needed for the panel.  At the time NAV decides the panel is empty and therefore no space is allocated for the panel.

It is not possible to change a property or call a method on the add-in until the add-in is ready and the page has been created.  So, here I solve this by using the Singelton class.  I make the Singelton add-in a reference in the Buttons & TextBoxes add-in and the Singelton class can be accessed the whole time.

In my NAV Page I added the Singelton NAV add-in as a client-side DotNet variable and added three lines of Singelton code.

SingeltonInit2

Similar in my Buttons & TextBoxes add-in I added two properties to read these Singelton values.

SingeltonGet

When the panel is created I make sure that I read these properties.

panelsize

It is important to use the XML format in NAV when using this Singelton Dictionary.  Make sure to always use FORMAT(<value>,0,9) and on the other side use EVALUATE(<value>,SingeltonValue,9).

Evaluate

 

 

The function call was ambiguous. No matching method was found.

One of the problems with using DotNet in C/AL is that sometimes the C/AL compiled does not have enough information for select between different methods in the DotNet object.

As you might have gathered from my last posts, I am using DotNet more and more.  It saves a lot of code writing and it is fast.

Today I was handling a web service that is delivering decimal value formatted with the is-IS method.  The decimal character is a comma.  I wanted to make sure that my C/AL code would be able to handle that no matter what the underlying language or region settings where.

Getting the decimal value was pretty straight forward.  Just used the method Microsoft uses in Codeunit 10 with a predefined is-IS.  Now for the other way around.  Found a method for the System.String to format a decimal to a text based on the same is-IS.

StringFormat1

But here comes the problem.

StringFormatError

Ok, Vjeko sais everything can be solved with reflection – well perhaps not everything, but this problem sure can.

I listed all the methods for the System.String and found the one I wanted to use.  Did not have to use the System.Activator since this is a static class.

StringWithReflection

Yes, a few more lines but they are effective and there is always fun to get challanged.  So, if you ever get this error message again, reflection should be able to solve the problem.

here is the ISLFormatAndEvaluate code to download.

Run a Table in NAV

One of the things we developers and consultants miss the most from the “Classic Times” is the ability to run a table to edit the data. Sure we can run a table from the Developement Environment but we are not always working with direct access to the SQL database.

My solution is to have a Page running on the object table (Table2000000001). From the page I can start Pages, Reports, Codeunits and XML Ports directly with a simple line of code, but to start a Table is more complex.

The first solution was to use HYPERLINK on the result from the GETURL function. That works fine in a local environment, but when you have installed multiple Dynamics NAV versions or running Dynamics NAV from a ClickOnce installation things start to break.

To fix this I stop using HYPERLINK and start the Dynamics NAV client with arguments to run a table. First step is to find the current client path. This path can be the usual System Drive path but it can also be a User Application Path if using ClickOnce. The function to locate the client path uses DotNet.

[code] PROCEDURE GetClientPath@1100408003() : Text;
VAR
ClientAssembly@1100408001 : DotNet "’mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Reflection.Assembly" RUNONCLIENT;
ClientPath@1100408000 : DotNet "’mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.IO.Path" RUNONCLIENT;
BEGIN
ClientAssembly := ClientAssembly.GetExecutingAssembly;
EXIT(ClientPath.GetDirectoryName(ClientAssembly.Location));
END;[/code]

And based on the client Path I look for the ClientConfiguration.config file and start the client with the GETURL results.

[code] LOCAL PROCEDURE ViewRecords@1100408000();
VAR
AddinMgt@1100408000 : Codeunit 10000207;
PathHelper@1100408008 : DotNet "’mscorlib’.System.IO.Path";
ClientFileHelper@1100408007 : DotNet "’mscorlib’.System.IO.File" RUNONCLIENT;
ClientProcess@1100408005 : DotNet "’System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Diagnostics.Process" RUNONCLIENT;
ClientProcessWindowStyle@1100408004 : DotNet "’System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Diagnostics.ProcessWindowStyle" RUNONCLIENT;
ClientProcessStartInfo@1100408003 : DotNet "’System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′.System.Diagnostics.ProcessStartInfo" RUNONCLIENT;
StartCommand@1100408002 : Text;
ArgumentList@1100408006 : Text;
ClientPath@1100408001 : Text;
ClientConfigurationPath@1100408009 : Text;
BEGIN
ClientPath := AddinMgt.GetClientPath;
ClientConfigurationPath := PathHelper.Combine(ClientPath,’ClientUserSettings.config’);
IF ClientFileHelper.Exists(ClientConfigurationPath) THEN
ArgumentList := STRSUBSTNO(‘-settings:"%1" ‘,ClientConfigurationPath);
ArgumentList += GETURL(CLIENTTYPE::Windows, COMPANYNAME, OBJECTTYPE::Table, ID);
ClientPath := PathHelper.Combine(ClientPath,’Microsoft.Dynamics.Nav.Client.exe’);
ClientProcessStartInfo := ClientProcessStartInfo.ProcessStartInfo(ClientPath);
ClientProcessStartInfo.Arguments := ArgumentList;
ClientProcessStartInfo.WindowStyle := ClientProcessWindowStyle.Normal;
ClientProcess := ClientProcess.Start(ClientProcessStartInfo);
END;[/code]

To repeat my last comment, just use DotNet đŸ™‚

Just use DotNet

Day by day I am moving closer to DotNet programming in Dynamics NAV. More and more of the things I like to do are more easily solved with DotNet than with native C/AL code.

In most cases I can use the standard DotNet types but in some cases I need to build a small DotNet Class to solve the problem.

An example of this landed on my desk yesterday. A colleague of mine needed to be able to print text to the local label printer. Perhaps this can be solved with a simple report but in this case something more was needed. I asked the Internet – how do I print a text file with c# code. Got some answers and selected the one I liked the most.

Normally I rewrite the c# code with DotNet objects in C/AL but in this case I could not. The reason was that I needed the DotNet objects to be executed on the client side, and using DotNet to print requires an event handler. DotNet events are not supported on the client side so I needed to create a class.

I create a c# Class Project in Visual Studio and can use the code I found with only a few modifications.

[code lang=”csharp”] public class NAVTextFilePrinter
{
public void PrintText(string printerName, string fontName, float fontSize, string[] linesToPrint)
{
Font printFont = new Font(fontName, fontSize);
PrintDocument docToPrint = new PrintDocument();
docToPrint.DocumentName = "NAV Text File";
docToPrint.PrinterSettings.PrinterName = printerName;
docToPrint.PrintPage += (s, ev) =>
{
int count = 0;
float yPos = 0;
float leftMargin = ev.MarginBounds.Left;
float topMargin = ev.MarginBounds.Top;

foreach (string line in linesToPrint)
{
yPos = topMargin + (count * printFont.GetHeight(ev.Graphics));
ev.Graphics.DrawString(line, printFont, Brushes.Black, leftMargin, yPos, new StringFormat());
count++;
}
ev.HasMorePages = false;
};
docToPrint.Print();
}
}
[/code]

The class is compiled with DotNet 4.5 and added to the Client Add-ins folder. If you are using NAV version 2015 or newer just add it to the Server Add-ins folder.

Using this in NAV is easy

PrintNAVText

Microsoft.Dynamics.Nav.Client.TextFilePrinter can be downloaded from here.

I like to use DotNet when I am handling text – in general. The DotNet object System.String has a lot of functions that can be useful. Just use

String := String.Copy(NAVString);

and your NAV String can now be handled with all the functionality available with DotNet.  Good example is the Renumbering Tool I created a few months ago.

Another example here – where I need to create all the directory tree leading to the file I need to copy.

CreateDirectoryTree

One of my favorite is the Global Variable Store Codeunit, where I use the DotNet Dictionary to store variables globally. This can be used to minimize the footprint of your code changes. For example, if you need to pass a new variable to a function and you don’t want to change the function – just store the variable before you call the function and retrieve it inside the function. It is even possible to pass a whole record this way.

Downloadable Global Variable Store Codeunit

 

Clean up your trail

Boy, have these last months been busy.

Now I am working on a management solution for our cloud offering.  This solution is going to give the control of the services and the tenants to a NAV user interface.  I am running Powershell scripts from NAV (thanks Waldo) and things are looking good so far.

I extended the Powershell functionality to be able to read an XML response like Waldo describes here.

One of the things I need to do is to maintain files for this management solution.  For example a NAVData file, SQL backup file and a NAV license file.  When ever I execute a Powershell script from NAV I write these files to a temporary file path and point Powershell over there.

I can’t be sure that every Powershell execution is a success and I can’t leave the temporary files around.  I must delete them.  The solution in my case is to create a single instance Codeunit and apply a DotNet List object.

I only needed a server version, but with DotNet this can be extended to handle the client side as well.

VariableStore

 

So, every time I create a temporary file I add the file to a DotNet List.  Even if the execution fails the temporary file is still in the list.  When the execution finishes all the listed temporary files are removed.  If the execution fails then the next successful execution will also remove the previous temporary file.

As you can imagine this type of a Codeunit can be used in many scenarios.  By using DotNet Dictionary it is easy to store parameters with names in one place and retrieve them in another.

For example if you need to add a parameter to a standard function, then adding the parameter to the Dictionary before you execute the function and retrieving it within the function will leave the function footprint unchanged and your customization upgradeable.

Selection Filter to Clipboard

I have now seen three posts about using Excel to help create a Pipe Filter.  The latest one from Mohana is similar to the way I normally do this.  His post is a followup from the original video from Kerry Rosvold and an alternative from Mark Brummel.

If this is something you frequently need to do, why don’t use NAV and skip the Excel part ?

Lets imagine how that could look like.

CustomerList

We add a button the ribbon, select what ever we like from the customer list and click the button.

Clipboard

Now it just a matter of using the filter. Paste it where ever you need it.

FilteredCustomerList

And this is with just a few lines of code.

CustomerListModification

Page21 Delta file

New renumbering tool with DotNet power

No, there is not a dotnet add-in, just using dotnet in C/AL.

I have made updates to this tool.  Read about it here.

There is an old version of a renumbering tool on this blog.  It works in the classic client but who uses the classic client any more ?

I wanted to upgrade this tool to NAV 2015 (and NAV 2013 R2).  There where a few issues that I needed to handle.  First; how to upload a large object file to the server, how to read through millions of lines to find what I was looking for, how to replace string in all these lines without having to wait for days.

That is what I did.  One Table and One Page.  The code is all in the Table, not the ideal setup but I preferred fewer objects over prettier design.  On the other hand this it not that big of an object so every developer should find the way through the code in there.

I have been sharing objects on this blog and have also started to share and sell objects on Objects4NAV.com.  These objects are all in the range from 50.000 to 99.999.  This tool will make it so much easier to import these objects into the database.  Just download the renumbering tool and manually install it into the database and use the tool from there.

A little spoiler, I just used this tool on an object file with just under four million lines and needed to renumber 116 objects.  The tool completed this in two minutes and forty two seconds.

So, how does it work?

RenumberingPage

The page has only three columns.  Source object type, source object id and the new destination id.  If you leave the destination id blank the line will be ignored.  If you need to renumber an object to a number that already exists in the database start by renumber the old object before reusing the object id.

In here we have five functions:

  • Read Object Lines will populate the renumbering lines from an object file.
  • Suggest IDs will look for available id by searching the license permission and will update the renumbering lines.
  • Write to Excel will export the renumbering lines to an Excel worksheet.
  • Read from Excel will import the same format from an Excel worksheet.
  • Update  Object File will apply the object changes in the renumbering lines to an object file and will save a new object file.

If you have this tool in the customer database then you can import the object file you want to add to the database, get a suggestion for new ids, export a new object file and import that one into the database.  This will only take a few seconds.

Lets take a closer look at the code.  Perhaps that will help you in some of your ongoing task or in the future.

The first thing that the tool does is to upload the object file to the server temporary folder.  In NAV we have a function for this purpose but that one can only handle a limited amount of data.  A four million lines of code will not be uploaded with the standard method so I had to create another one.

UploadFileToServer

The magic word here is streaming.  This code will take 4KB of data in each portion and upload to the server.  It will repeat until done and I don’t think you will find a useful NAV file that will not be uploaded with this code.

Once the file is on the server the tool loads the whole file into memory.

LoadFileIntoMemory

An array of string is used to store all the object lines.

This is the starting point.  From this point I can loop through all the lines and do what ever I want.  However, doing that line by line will take forever so I have applied a few tricks on the way.

When the tool is loading an object file into the renumbering lines it will use the dotnet string function split to break the line into smaller bits.  When the tool finds an object it will search the string array for the end of that object before continuing and can therefore skip all lines except the ones needed to build the renumbering lines.

When renumbering, instead of applying each renumbering line to every code line, the tool combines five thousand lines into one dotnet string variable and uses the dotnet function replace to update the code.  After each chunk of code is updated it is downloaded from the server to the client side and written to the local file system.

ReplacementCode

Well, what are you waiting for.  Download the renumbering tool and start using it today.

 

 

 

Automatic Deployment of Microsoft .NET Framework Interoperability and Control Add-in Assemblies in NAV 2015

Sitting here at the airport in Copenhagen with my laptop waiting for my ride home to Iceland.

I wanted to write about a topic that I did not hear or see in Directions during this week.  Windows Client add-ins are now automatically downloaded from the server to the client if they are needed.

A fellow MVP Arend-Jan has already blogged about this but I want to explain a little bit more.

There are a few rules to follow to make sure that this works.  First is the assembly name.  The assembly name must be “Microsoft.Dynamics.Nav.Client.<Class Name>” if the server is supposed to find it.

AssemblyName

Put each new assembly in a dedicated folder in the server add-ins folder.  If other components are needed for this assembly then put them in the same folder.  The server will copy the whole folder to the client.

UserHelperFolder

In the Developement Environment make sure that your server is selected in Tools-Options.

DevToolsOptions

Then when looking for an assembly a new option is to look in the server add-ins folder.

AssemblyLookup

Adding an assembly into your code to run at the client will now work without any further actions.

HelperClassInTable370

And now – when I print to Excel the new Excel window opens in front of Dynamics NAV 2015.

iExcelInFront

 

To quote Microsoft directly:

“Automatic Deployment of Microsoft .NET Framework Interoperability and Control Add-in Assemblies

Microsoft Dynamics NAV 2015 makes it easier for the system administrators to deploy client-side assemblies for .NET Framework interoperability and client control add-ins on computers that are running the Microsoft Dynamics NAV Windows client or Microsoft Dynamics NAV Development Environment. You can now install the assemblies in the Add-ins folder on the computer that is running Microsoft Dynamics NAV Server. By default, this is the C:\Program Files\Microsoft Dynamics NAV\80\Service\Add-ins folder. When an operation from the client requires an assembly, Microsoft Dynamics NAV Server automatically deploys the assembly to a temporary folder on the client computer.

For example, if Microsoft Dynamics NAV Windows client opens a page that contains a control add-in, Microsoft Dynamics NAV Server will find the control add-in assembly by name in the Add-ins folder. Then, it deploys the assembly to the client computer in the %TEMP%\Microsoft Dynamics NAV\Add-Ins folder of user who is running the client. Subsequently, the deployed assembly will be used whenever the page is opened.

Similarly, if the development environment requires a control add-in, for example, when you compile an object, then the control add-in assembly will be deployed by the Microsoft Dynamics NAV Server to the local temporary folder for the current user on the computer that is running the development environment.

Note
To be deployed, an assembly must comply with the following Microsoft Dynamics NAV Server configuration settings: Chuck Size, Max Upload Size, and Prohibited File Types.

If a .NET Framework interoperability or control add-in assembly is updated and its version number changes, Microsoft Dynamics NAV Server will deploy the updated assembly to the client computer the next time that the client requests the assembly. The updated assembly is put in a subfolder of the %TEMP%\Microsoft Dynamics NAV\Add-Ins folder, where the subfolder has the assembly’s version number as its name. This implementation means that you do have to remove the older versions of assemblies that are stored on the client computer.

To support compatibility with earlier version of Microsoft Dynamics NAV, before Microsoft Dynamics NAV Server deploys an assembly to a client, the client looks for the assembly in the local Add-ins folder (for example, C:\Program Files (x86)\Microsoft Dynamics NAV\80\RoleTailored Client\Add-ins). If the assembly is not found, then the client will request the assembly from Microsoft Dynamics NAV Server. Javascript-based client add-ins have been using this deployment technique since Microsoft Dynamics NAV 2013 R2.”