Access protected file share

There was a post, where a user wanted to get read/write acces to a file share outside the current domain from within Nav.

This can be done via .Net:

NetworkCredential := NetworkCredential.NetworkCredential(username, password, domain); // Credentials for domain 2
CredentialCache := CredentialCache.CredentialCache;
Uri := Uri.Uri(‘\\server’); // the server name from domain 2
CredentialCache.Add(Uri, ‘Basic’, NetworkCredential);
Dirs := SysDir.GetDirectories(‘\\server\directory’); // get folder list from the file share
FOR i:=1 TO Dirs.GetLength(0) DO
DirListTxt := DirListTxt + FORMAT(Dirs.GetValue(i-1))+’\’; //.net index starts with 0
MESSAGE(DirListTxt); // print out the dir list

variables
NetworkCredential DotNet System.Net.NetworkCredential.’System
CredentialCache DotNet System.Net.CredentialCache.’System
Uri DotNet System.Uri.’System
Dirs DotNet System.Array.’mscorlib
SysDir DotNet System.IO.Directory.’mscorlib
DirListTxt Text
i Integer

 

How to: Create an automation for usage in Navision

The development of a COM component with .Net for usage in Navision needs a special approach. Start with a new C# project, project type Class Library. Set the project to “Make assembly com-visible”. The simple automation provides a string property and a method to show a .net message box.

// a sample code
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace SampleAutom
{
 // the interface, so that the methods and properties are shown in Nav
 // needed attributes InterfaceType and Guid
 [InterfaceType(ComInterfaceType.InterfaceIsDual)]
 // create a new Guid
 [Guid("9304DD04-5EF0-498E-893E-CB644CD34656")] 
 interface IMyInterface
 {
  // set a DispId for each method/property
  [DispId(1)]
  int MsgBox(string message);

  [DispId(2)]
  string Title { get; set; }
 }

 // class attributes
 [ClassInterface(ClassInterfaceType.AutoDual)]
 [Guid("D9E556F3-4D85-45C9-965A-DB3D918528CD")]
 // implement the interface
 public class TestCom : IMyInterface
 {
  string _title = "Dynamics Nav";

  public TestCom()
  { }

  // with property Title you can set/read the title of the msgbox
  public string Title
  {
    get
    { return _title; }
  
    set
    { _title = value; }
  }
  
  // method msgbox returns an integer value according the clicked button
  // Yes = 6, No = 7, Cancel = 2
  public int MsgBox(string message)
  {
    var result = MessageBox.Show(message, Title, 
       MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information);
    return (int)result;
  }
 }
}

To install the new automation run visual studio in admin mode. after restart and loading the project build/compile the project.

Now start Navision and create a new codeunit. In the OnRun trigger add following code:

// TestCom, Automation, 'SampleAutom'.TestCom 
// RetVal, Integer 
CREATE(TestCom);
TestCom.Title := '.Net Msgbox run in Navision';
RetVal := TestCom.MsgBox('Your message text ...');
MESSAGE(FORMAT(RetVal));
CLEAR(TestCom);

Now run the codeunit. It results in:

autom1    autom2

cheers

How to work with big item descriptions

Sometimes it’s needed to save very long descriptions, but in Nav text fields can have only 250 characters. Additional you may want to search in these long text values. You can add a couple of these text fields to save long texts or save the text in text files and add them to the item. But what about searching? Not that easy.
An other option to save long texts is the usage of blob fields. For that option i developed a solution.

First add a new field “Description 3” to table Item, type BLOB, subtype Memo.
Then edit page Item Card, add a global variable Desc3Txt of type text with no length. Add the variable as new field to the item card, Editable=False, MultiLine=Yes.
Add following code to trigger OnAfterGetRecord in item card page:

// InStr | InStream
CALCFIELDS("Description 3");
IF "Description 3".HASVALUE THEN BEGIN
  "Description 3".CREATEINSTREAM(InStr);
  InStr.READ(Desc3Txt);
END;

Add to trigger Desc3Txt – OnAssistEdit()

// OutStr | OutStream 
// EditCtrl | DotNet | Archer.TextEdit.'Archer.TextEdit, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1465b259ee2284cb' 
CLEAR(EditCtrl);
EditCtrl := EditCtrl.TextEdit;
EditCtrl.Load(Desc3Txt);
EditCtrl.ShowDialog;
Desc3Txt := EditCtrl.Save;
EditCtrl.Close;
CLEAR(EditCtrl);

"Description 3".CREATEOUTSTREAM(OutStr);
OutStr.WRITE(Desc3Txt);
MODIFY;
CurrPage.UPDATE;

itse-1

Page item card with new multiline text field “Description 3” and Assist Button.

itse-2

Clicking on Assist Button starts the TextEdit Control and loads the current text. After changing the text and closing the text control, you are asked, if you want to change the text.

Now we need the opportunity to search within the new text/blob field. For that we need a new page Item Search. That new field cannot be searched using the standard search function.

Create a new page with objectid 50000, name Item Search. Add a group under the contentarea, add a global text variable SearchString, add a new field under the group with SearchString as SourceExpr. Add another group, add a new line with type part. Later we set the value for property PagePartID.

itse-3

To show the search result we need another page: type listpart, objectid 50001, name Item Search Result. As source of the new page we need a new table: objectid 50000, name Item Search Result.

itse-4

The new page 50001:

itse-5

Properties: Editable=False, SourceTable=50001, SourceTableTemporary=Yes.

Now add global function SetData(SearchFilter : Text) to the new page. Add following code to the new function:

// local variables
// Item, Record, Item 
// ItemSearchResultLine, Record, Item Search Result 
// InStr, InStream 
// Desc3Txt, Text 
// LineNo, Integer 

DELETEALL;

LineNo := 10;
Item.FINDSET;
REPEAT
  Item.CALCFIELDS("Description 3");
  IF Item."Description 3".HASVALUE THEN BEGIN
    Item."Description 3".CREATEINSTREAM(InStr);
    InStr.READTEXT(Desc3Txt);
    IF STRPOS(LOWERCASE(Desc3Txt),LOWERCASE(SearchFilter)) > 0 THEN BEGIN
      "Line No." := LineNo;
      "Item No." := Item."No.";
      Description := Item.Description;
      "Description 2" := Item."Description 2";
      "Description 3" := COPYSTR(Desc3Txt,1,250); // first 250 chars
      INSERT(FALSE);
      LineNo += 10;
    END;
  END;
UNTIL Item.NEXT = 0;

CurrPage.UPDATE(FALSE);

Now you can set the property PagePartID in the part line in page 50000 to 50001.

For calling the search function we need a Search button in page 50000.
Add following code to trigger Search – OnAction()
CurrPage.ItemSearchResultLines.PAGE.SetData(SearchString);

itse-6

The new Item Search Page with a search result.

You can download the TextEdit Control here.

Followup:
You could simplify the solution by:
* Search page: Use only page 50001, add a second group at the top with field SearchString. So page 50000 is not needed.
* Page Item Card: remove the textedit control and the according code, set field Desc3Txt to editable, add the code to fill the blob field “description 3” using outstream to trigger Desc3Txt-OnValidate.

cheers

 

Check License state of Objects

In Nav forums it’s quite often asked, how to check, if a object is within the current loaded license. There are some solutions for Nav 2009. I did not find a satisfying solution for newer Nav versions. So i’ve developed a “License Permission” Page, which lists Objects and their Permissions. Object IDs, which are not in the current loaded license, means there is no read or execute permission for that Object ID, are displayed red. When running the page the list of all nav objects in the database is loaded.

licperm

The buttons in the page:

licperm2

Reload Base Objects: List all current database Object IDs
Swap Read Filter: Switch between 3 Read Permission Filter settings ” “, Yes|Inherited, No Filter
Swap Execute Filter: same as button above for Execute Permission
Custom Objects Filter: List objects between Object ID 50.000 and 99.999.

licperm3

The object range buttons:

View Table Range: List all table Object IDs according the filter you set.
View Page Range: List all page Object IDs according the filter you set.
View Report Range: List all report Object IDs according the filter you set.
View CU Range: List all codeunit Object IDs according the filter you set.

You can download the page source code here.

The license information is saved in table “License Permission”. With a page on base of that table the windows client freezes, because the data load is really huge, especially, when filtering. So i developed that kind of solution.

cheers

Dotnet Events are not shown

There are 2 scenarios, where embedded events of .net (dotnet) assemblies can/should be displayed in the c/al code … or maybe not.

Scenario 1:
You use a control-addin like the PingPong Control-Addin (Microsoft.Dynamics.Nav.Client.PingPong). For usage it’s needed that a control-addin is listed in the Control-Addin Page. If so, you can select it from the list, wenn you set a value in property ControlAddin of Page’s field. If the control-addin has embedded events, they should be displayed immediately at the end of the fields triggers in the c/al code.

DisplayEvents3

If not, then

  • the assembly has no events
  • the eventhandling in the assembly was developed not correctly
  • the from the Control-Addin referenced assembly Microsoft.Dynamics.Framework.UI.Extensibility has the wrong version! This assembly is referenced by every Control-Addin. Microsoft ships with each version (build, CU) mostly (always?) new build versions of all the Nav assemblies.

 

Scenario 2:

You declare a global variable of type dotnet and use a .net assembly, which has embedded events. These events are only shown, if you set the variable’s property “WithEvents” to Yes.

DisplayEvents2
If the property is set to No (default), the events are not shown in the c/al code.
if you have set it to true, after that to false, the events stay … for the moment. Compile, re-edit the object and the events in the c/al code are gone.

DisplayEvents1

P.S.: The rumour that the WithEvents property is obsolete, is wrong! It is used in global variables of type dotnet  and is mandatory for the usage of embedded windows events. It was also used in old COM/OCX Components. That one is obsolete in newer Nav versions.

Working with Excel Documents in Nav

nav-xls

Dynamics Nav is shipped with the opportunity to export every report as Excel document. Additional there is Table ExcelBuffer for exporting each kind of data. An example for this table’s usage is the action “Export to Excel” in Page “G/L Budget”.

In table ExcelBuffer there are used some very helpful .net assemblies shipped with Dynamics Nav to cover the interaction with excel.

In Nav forums there are quite often questions about importing or exporting data from/to Excel. So i wrote a sample, where you can see, how to work with these assemblies.

xls1

  • An excel file is opened
  • Adding a new sheet
  • Write a value to the new sheet
  • Close the WorkbookWriter Object to close the current Excel session
  • Open the Excel document in Excel with standard Excel Interop assembly

xls2

The variables: this is the Nav 2013 version, so here is used Microsoft.Dynamics.Nav.OpenXml vs. 7.0. In Nav 2015 use vs. 8.0 instead.

 

DECSTR – Decrease integer value in string

INCSTR is a C/AL command, which is often used to increase document numbers, which usually start with some letters followed by a number, e.g. AB-00010.

With INCSTR(‘AB-00010’)  you get then ‘AB-00011’.

In a Nav forum a member asked, if there is a simple way for decreasing such a document no. don’t know, why he needs that, but it’s a nice idea.

So, after a little research and finding some typical looooong c/al code solutions, i developed my own small, very cool solution using .net class Regex:

OnRun()
// loc. variables
//DocNo : Code 20

DocNo := DecStr(‘AB-00010’);
MESSAGE(DocNo);

LOCAL DecStr(DocNo : Code[20]) : Code[20]
// loc. variables
//Prefix: Text
//NoString: Text
//Number: Integer
//Regex: DotNet System.Text.RegularExpressions.Regex.’System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
//DotNetInt: DotNet System.Int32.’mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
//PadLength: Integer

Prefix := Regex.Replace(DocNo,’\d+’,”);
NoString := Regex.Match(DocNo,’\d+’).Value;
Number := DotNetInt.Parse(NoString);
IF Number > 0 THEN BEGIN
Number := Number – 1;
PadLength := STRLEN(DocNo) – STRLEN(Prefix) – STRLEN(FORMAT(Number));
EXIT(Prefix + PADSTR(”,PadLength,’0′) + FORMAT(Number));
END;

ERROR(‘Resulting number would be negative.’);


Results in: AB-00009

the resulting number has the same length.


Additional the links i found: