Quantcast
Channel: Dynamics 365 Blog
Viewing all 773 articles
Browse latest View live

MagicPath (DOWNLOADFROMSTREAM and UPLOADINTOSTREAM without a dialog box)

$
0
0

The C/AL commands DOWNLOADFROMSTREAM and UPLOADINTOSTREAM have the purpose of sending files between RTC and the NAV Server. A few times now, we had the question: How can we use these functions without it displaying the dialog box to select a file and folder name?

This is how you can automatically download and upload files without any user interactions:

The trick is to use MagicPath, like in codeunit 419 "3-Tier Automation Mgt.". MagicPath is initiated by setting the folder name to '<TEMP>' like this:
DOWNLOADFROMSTREAM(IStream,'','<TEMP>', '',MagicPath);

 

DOWNLOADFROMSTREAM

The code example below will copy a specific file from the NAV Server to the RTC machine with no questions asked about folder or file name or anything else:


IF NOT ISSERVICETIER THEN
  EXIT;
FileToDownload := 'c:\Temp\ServerFile.txt';
FileVar.OPEN(FileToDownload);
FileVar.CREATEINSTREAM(IStream);
DOWNLOADFROMSTREAM(IStream,'','<TEMP>', '',MagicPath);
MESSAGE('Path = ' + MagicPath);

FileVar.CLOSE;

 

Variables:

Name Data Type Length
FileToDownload  Text 180
FileVar File
IStream  InStream
MagicPath  Text 180

 


Now we have the file on the RTC machine, and MagicPath tells us its location. The location will be something like this:
C:\Users\[UserName]\AppData\Local\Temp\Microsoft Dynamics NAV\4612\__TEMP__ff7c5a286cfd463f9f7d92ae5b4757e2

The number 4612 in the MagicPath comes from the Process ID of RTC.

 

Handling files client side

So, what if we wanted to rename it to a specific name? We have the FILE object in C/AL, but of course since C/AL runs on the NAV Server and not on RTC, this won't work since the purpose of the above is exactly to copy the file to the client machine. Instead, use this automation:

'Microsoft Scripting Runtime'.FileSystemObject

Then create an instance ClientSide:
CREATE(FileSystemObject,TRUE,TRUE);


So, if you wanted to continue the code above and place and name the file to something specific on the client's machine, add these lines:


CREATE(FileSystemObject,TRUE,TRUE);
DestinationFileName := 'c:\Temp\newfile.txt';
IF FileSystemObject.FileExists(DestinationFileName) THEN
  FileSystemObject.DeleteFile(DestinationFileName,TRUE);
FileSystemObject.CopyFile(MagicPath,DestinationFileName);
FileSystemObject.DeleteFile(magicpath,TRUE);

 

 
UPLOADINTOSTREAM


MagicPath works both ways. But with DOWNLOADFROMSTREAM it creates MagicPath for you and tells you where it is. With UPLOADINTOSTREAM you need to know it in advance. Remember the MagicPath location above includes the Process ID of RTC. One way could be to work that out somehow. But what I would suggest instead, is to download a temp test file first, then see where MagicPath downloads it to. The path for upload will be the same:


// download a temp file to get MagicPath
FileVar.CREATETEMPFILE;
FileVar.CREATEINSTREAM(IStream);
DOWNLOADFROMSTREAM(IStream,'','<TEMP>', '',MagicPath);
FileVar.CLOSE;
MESSAGE(MagicPath);


Then extract the folder name from MagicPath:

FOR i := STRLEN(MagicPath) DOWNTO 1 DO BEGIN
  IF MagicPath[i] = '\' THEN BEGIN
    MagicPath := COPYSTR(MagicPath,1,i);
    i := 1;
  END;
END;

Once you know the location of MagicPath, the next step is to copy the file you want to upload into that folder:

FileToUpload := 'newfile.txt';
FolderName := 'c:\Temp\';

IF ISCLEAR(FileSystemObject) THEN
  CREATE(FileSystemObject,TRUE,TRUE);
FileSystemObject.CopyFile(FolderName + '\' + FileToUpload,MagicPath + '\' + FileToUpload);

Then use UPLOADINTOSTREAM to upload the file from MagicPath to the NAV Server:
UPLOADINTOSTREAM('','<TEMP>','',FileToUpload,IStream);


And finally, save the InStream to a file on the server:

FileVar.WRITEMODE(TRUE);
FileVar.CREATE('c:\Temp\OnServer.txt');
FileVar.CREATEOUTSTREAM(OStream);
COPYSTREAM(ostream,istream);
FileVar.CLOSE;

 

So, put all this together and the end result is:

The file c:\Temp\ServerFile.txt gets downloaded to C:\Temp\NewFile.txt, and then uploaded back to the server as C:\Temp\OnServer.txt.

 

 

Lars Lohndorf-Larsen

Dynamics NAV Support EMEA

 

 

 

 

 


C/SIDE Improvements in Dynamics NAV 2009 R2

$
0
0

Microsoft Dynamics NAV 2009 R2 has several new features as part of the C/SIDE development environment. After several years (and releases) of building a strong new platform, we are able to refocus our efforts onto making fantastic developer focused features for C/AL developers. You can expect this trend to continue with even more in NAV "7" and further but without further ado, I'd like to introduce you to some of your newest friends...

1. CTRL+F12

Also known as Go-To-Definition, I'm sure you'll become intimately familiar with the keyboard shortcut rather than use the feature name! Go-To-Definition (or GTD to those of us too busy to spell things out properly) enables you to jump to code and open designers with an easy shortcut. GTD works on code context, which means that if you're on a Table variable and you do CTRL+F12 then the Table Designer will spring open and you can easily review field information or check properties. COOL!

And if you're on a system trigger (like VALIDATE) and there is code in the Validate trigger, then CTRL+F12 will take you to that method! Awesome!

Lastly, and by now this won't be much of a surprise, if you're on a user-defined function then pressing CTRL+F12 will take you to that code. Kapow!

GTD doesn't work for rec or currform/currreport yet but we have room and time for improvements. Let us know if you'd like GTD to take you somewhere else in the app!

And if you don't like shortcut keys, you can also access Go-To-Definition via right-click or through the View menu!

Figure 1 - using Go-To-Definition on a Table variable will open Table Designer. Using it on a function will jump to the code for that function.

2. Lock/Unlock of Application Objects

Have you ever lost work because the developer sitting next to you modified the object you were currently working on? We can't restore your lost work but we can do something about losing more work in the future. Check out the two new fields in the Object Designer - Locked and Locked By. Now you can 'reserve' or place a lock on an application object and any subsequent developers will now get a 'read only' version of that application object. This means that other developers can still see the code but they can't save and overwrite your work.

To make sure that no one loses work, a 'read only' object can still be saved and compiled, but you can't overwrite the original ID.

Look for the client setting in Tools->Options called "Auto lock on Design" to make the feature activate automatically when you open an object.

Figure 2 - Two users, userA and userB are working in the same database. UserA can see that userB has already locked codeunits 80 and 81 and is trying to lock Codeunit 82.

 3. Page Designer Improvements

We've tweaked a couple of parts in Page Designer. Firstly, we completed the New Page Wizard so that it will now also offer you to pick factboxes for your pages. This was quite an oversight as factboxes make pages much more valuable than forms as you can include much more related information in them. Next, we changed the default naming of factboxes so that in the page you'll no longer see name and ID as <control20061721008> but instead see a much more readable name like <OutlookPart>. Nice!

Figure 3 - FactBox picker in the New Page Wizard.

 4. Structure Highlighting

Finding pages a bit hard to read? Need a microscope to see what's indented and what's not? We agree that it's hard to see where the structure is in pages and so we've introduced Structure Highlighting. Now any record (row) that is a container will be in bold. It's a simple change but it makes a huge difference to the readability of the pages. The bold lines break up the groups and the readability of the page is dramatically increased.

Figure 4 - Structure Highlighting. Showing a simple example on a small page here because it's hard to include a large page. However, with the container lines highlighted, the overall structure of the page is more readable. And if the page had more containers (FastTab groups, Repeater sections), then they would stand out quite prominently.

Recently Microsoft hosted a Hot Topic session that included the improvements discussed in this post. It is called  "Microsoft Dynamics NAV 2009 R2 Hot Topic: What's New for Developers." A recorded version of the session can be seen at the Partner Learning Center

We hope you enjoy the new features and, as ever, if you have suggestions for improvement you're welcome to get in touch (either in comments, directly to me in email: sglasson@microsoft.com, or using the MSConnect feedback tool).

Thank you!

Stuart 

Let NAV PLAY! (Media Play… of course)

$
0
0

In this blog you will find the source code in order to implement a client add-in based project with Windows Media Player. You would have a wide range of possibilities to use this simple add-in and enlarge this project on your own.

If you want to know more about client add-ins you can refer to MSDN link:

Extending the RoleTailored Client Using Control Add-ins in Microsoft Dynamics 2009 SP1

Step by step creation of the NavMP (NAV Media Player) add-in:

 (always remember the 'DodgeBall' rules: Develop, Sign, Place, Register, and Develop)

  1. DEVELOP your add-in (in Visual Studio)
  2. Strong SIGN and Build
  3. PLACE DLLs into Add-ins folder
  4. REGISTER the add-in in Table 2000000069, Client Add-in
  5. DEVELOP your C\AL code (in Object Designer)

My ingredients:

  • Visual Studio 2010 Professional
  • Windows 7 Enterprise
  • Windows Media player 12

DodgeBall rule 1: DEVELOP your add-In (in Visual Studio)

A. Create a New Class project

  1. Open Visual Studio (in this example I am using Visual Studio 2010)
  2. Create a New Project (CTRL+SHIFT+N) with these parameters:
  •  
    • Visual C# - Windows
    • Class library
    • .NET Framework 3.5
    • Name: NavMP
    • Location: C:\TMP (or whatever location you like)
    • Solution Name: NavMP
    • Create directory for solution

 

B. Create a Strong Name Key (SNK)

  1. Go to Project > Properties (NavMP Properties...)
  2. From the Project Properties form go to the Signing tab
  3. Tick the Sign the assembly option
  4. Create a New SNK (e.g. NavMP.snk)

 

C. Add a Windows Form to the class project and modify its properties

  1. In the main menu click on "Project" > "Add New Item" (Ctrl + Shift + A)
  2. Select "Windows Form"
  3. Click "Add"
  4. In this way, Visual Studio has added a brand new Windows Form to your project and inherit all the necessary references.

 

  • The brand new Form should be added to your project. And you just simply need to change the Form properties as reported below:
Property Value
AutoScaleMode None
Autosize True
FormBorderStyle None
Locked True
MaximizeBox False
MinimizeBox False
Size 212;207
Text NAV Media Player

 

D. Add a Media Player object  to the class project and modify its properties

  1. Normally, a Media Player object is not present as an item in the Toolbox. In order to add it you need to have Media Player installed in your machine. Then, you can just follow the next steps or simply refer to this useful How To video: http://msdn.microsoft.com/en-us/vcsharp/bb798025.aspx
  2. Go to the Toolbox and scroll down to the last available space ("General" group)
  3. Click on General > Right-click in the blank area > Select "Choose Items"

  • In the "Choose Toolbox Items" form > Select tab "COM Components" > check on "Windows Media Player" and click OK

  • Now the Windows Media Player item should be added to your toolbox

 

  • Select the Windows Media Player item and drop it into the Form created before

  • Right click on the Windows Media Player control added and click on "Properties"
  • Change the Windows Media Player properties as reported in the screens below and click OK

  • Change the Windows Media Player control item properties added to the Form
Property Value
(Name) NAVMediaPlayer
Anchor Top, Bottom, Left, Right
Ctlenabled True
enableContextMenu True
fullScren False
Location 0;0
Locked True
Margin 2;2;2;2
Size 287;256
stretchToFit True
uiMode full
windowlessVideo False

 

If you want to know more about the player object visit http://msdn.microsoft.com/en-us/library/dd564034(v=VS.85).aspx.

E. Add remaining References to the project

  1. In the Solution Explorer window select "Reference", right-click "Add Reference"
  2. Add a reference to Microsoft.Dynamics.Framework.UI.Extensibility

F. Develop (add code to) your NavMP project

  1. Locate your Form1.cs file in the Solution Explorer
  2. Right-click > View Designer (Shift+F7)

  • Replace the C# code with the one written below (or copy that from the image)

 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

 

namespace NavMP

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

        private void NavMediaPlayer_Enter(object sender, EventArgs e)

        {

            string MoviePath = @"C:\TMP\Presentation.avi";

            NavMediaPlayer.settings.autoStart = false;

            NavMediaPlayer.URL = MoviePath;

        }

    }

} 

  • Now, you need to add some code also in the Class
  • Right-click on the form and select "View Designer" (Ctrl + F7)
  • Select the Media Player control
  • Right-click on it and select "View Code" (F7)

  • Replace the C# code with the one written below (or copy that from the image)

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Linq;

using System.Text;

using Microsoft.Dynamics.Framework.UI.Extensibility;

using Microsoft.Dynamics.Framework.UI.Extensibility.WinForms;

using System.Windows.Forms;

using System.Drawing;

 

namespace NavMP

{

    // WinFormsControlAddInBase

    [ControlAddInExport("Cronus.DynamicsNAV.NMediaPlayer")]

    [Description("Let this Add-in Media Play")]

 

    public class TestMPClass : StringControlAddInBase

    {

        // Form

        protected override Control CreateControl()

        {

            Form1 MPForm = new Form1();

            MPForm.TopLevel = false;

            MPForm.Visible = true;

            return MPForm;

        }

    }

}

DodgeBall rule 2 : Strong SIGN and build

  1. Now it is all set up, you are ready to build your client add-in. In the main menu, go to "Build" > "Build NavMP". (This is automatically strong signed as per rule 1 point B.)

DodgeBall rule 3 : PLACE DLLs into Add-In folder

  1. Locate/copy/paste NavMP.dll, AxInterop.WMPLib.dll and Interop.WMPLib.dll (should be in your \NavMP\NavMP\bin\Debug folder) in the Add-ins folder of a machine where the RoleTailored client has been installed (typically C:\Program Files\Microsoft Dynamics NAV\60\RoleTailored Client\Add-ins).

DodgeBall rule 4 : REGISTER the Add-in in Table 2000000069 Client Add-in

  1. Launch the Visual Studio Command Prompt (VSCP)
  2. In the VSCP:

Sn -T "C:\TMP\NavMP\NavMP\bin\Debug\NavMP.dll"

 dfcb4863cf7c4cb0 is the PKT (Public Key Token) needed to register the AddIn into NAV.

(You will probably have another value, this is just an example)

  • Open Classic Client
  • Open Object Designer (SHIFT+F12)
  • Select Table object (ALT+B)
  • Run Table 2000000069 Client Add-in
  • Insert a new line with these values:
  • Field Value
    Control Add-in Name Cronus.DynamicsNAV.NMediaPlayer
    Public Key Token dfcb4863cf7c4cb0 (this is an example)
    Version 1.0.0.0
    Description Let this Add-in Media Play

DodgeBall rule 5 : DEVELOP your C/AL code

  1. Copy/paste the Txt format code written below in a new Notepad object or download the Txt file from the bottom of this blog

OBJECT Page 50100 Nav Media Player

{

  OBJECT-PROPERTIES

  {

    Date=;

    Time=;

    Modified=Yes;

    Version List=;

  }

  PROPERTIES

  {

    SourceTable=Table18;

    DataCaptionExpr=Text10000000;

    PageType=CardPart;

  }

  CONTROLS

  {

    { 1101100000;0;Container;

                ContainerType=ContentArea }

 

    { 1101100001;1;Field  ;

                ControlAddIn=[Cronus.DynamicsNAV.NMediaPlayer;PublicKeyToken=dfcb4863cf7c4cb0] }

  }

  CODE

  {

    VAR

      Text10000000@1101100000 : TextConst 'ENU=LET NAV Play !';

    BEGIN

    {

      WARNING ----------------------------------------------------------------------------

      YOU NEED TO CHANGE THE PublicKeyToken WITH THE ONE ASSIGNED TO YOUR OBJECT !!!

      ------------------------------------------------------------------------------------

    }

    END.

  }

}

  • Import the txt object
  • Compile the txt object newly created (Page 50100 Nav Media Player)
  • IMPORTANT: Create an AVI file called Presentation.avi in your C:\Tmp directory (create the directory as well, where needed)
  • Run Page 50100 Nav Media Player
  • Enjoy it!!!

This simple client add-in project may be used in, e.g.

  • Customer general presentations
  • Stream MP3 files
  • ... and many more

This simple client add-in project may be enlarged, e.g.

  • It could be possible to enlarge the project and bound this to a field in order to play whatever file in a share (no hardcoded c:\tmp\Presentation.avi)
  • ... and many more

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

Duilio Tacconi (dtacconi)

Microsoft Dynamics Italy

Microsoft Customer Service and Support (CSS) EMEA

Increased Deadlocking After Full Dynamics NAV Upgrade

$
0
0

I was recently on-site at a large Dynamics NAV customer attempting to help them get through a number of significant performance issues they encountered on their two previous failed attempts to upgrade from NAV 3.70 to NAV 5.00 SP1. In summary, they had in excess of 100 concurrent users running relatively well on NAV 3.70 with a high transaction volume. When they attempted to go live on the upgraded NAV 5.00 SP1 version they could barely get 10 users in the system without completely deadlocking each other and it was, for the lack of better term, unusable. During the previous two attempts to upgrade the partner had deployed the NAV Application Benchmark tool and coded in all the customer business cases and custom functionality to allow them to do their scalability testing (Very Cool!!). This is where I came in.

The first thing the partner and I did was run a 30-user mixed workload test for 10 minutes on the upgraded code just to see where we were at. During this test we received over 1100 SQL deadlocks. There were so many it was difficult to even categorize where they were coming from. We reviewed the C/AL code for each of the profiles we were using and with client monitor trying to track down issues with the locking order of the tables between these processes. Sounds easy, but it's not and took a considerable amount of time. As we were scanning through the code I happened to look over the shoulder of one of the partners who had the code up on his screen for one of the processes only reports we were using and I noticed a comment in the code that caught my attention.

            //CI-Perf 1.01 140206 CI-HJS 1.00

What first caught my attention was the word "Perf." I was very interest to know what performance modifications had been made to the code. The second thing that caught my attention was the date "140206," which means this performance enhancement dated back to 2006, when they were running NAV 3.70. Further inspection of the code revealed that several of these enhancements were to correct locking order bugs in the standard code and localization code for the 3.70 version. These changes were absolutely necessary on 3.70 to be able to achieve any type of scalability but on 5.00 SP1 they were fixing issues which no longer existed, causing several locking order violations in very key NAV processes and causing a huge number of deadlocks. We went through all the code and "unwound" these locking order changes and re-ran the benchmark tests*. Eventually we ended in a 100-user mixed workload test for 2 hours that produced > 30 deadlocks and these were on reservation entry and no. series, and we also achieved a very high transaction volume. 

The moral of the story is when you do code upgrades from version to version, especially when you have a large gap of versions in between, be very careful what C/AL performance optimizations you bring forward from the old version to the new, and evaluate each one to make sure it is still necessary or it could end up having the absolute opposite effect on the newer version and causing performance issues.

Thanks go out the partner who helped me work through all these issue while I was on-site!

*There were other changes made with executable versions and SQL and database configuration that I will not detail here.

Michael De Voe

Senior Premier Field Engineer

Microsoft Dynamics

Microsoft Certified Master - SQL Server 2008

Some tips about creating "Tree view" in List pages

$
0
0

In Microsoft Dynamics NAV 2009 in new Role Tailored Client we have new feature – we can show lists in “tree view”. We can expand/collapse nodes and make lists more interesting and more visual.

However this new feature isn’t documented very well and every developer needs to collect his own experience. With this article I will try to make this way easierJ

So if we want to see Tree View we need to have:

1.       Table with field type Integer. This field will get us major point – will show is record root or fork or leaf.

a.       Few words about “IndentationColumnName” field; (that how property to indentation field is named on Pages)

                                                              i.      Field could be only type Integer.

                                                            ii.      Allowed values are >=0. I didn’t find upper limit, but because view indentation shift is calculated based on this value then with “big” values filed is indented too far to right that becomes invisible.

                                                          iii.      Root record has “0” indentation. Higher numbers means higher indentation. There could be several “0” records placed on table.

2.       Page type List.

a.       On page designer there must be “Group” & subtype “Repeater” and we need to set on it:

                                                              i.      Property “ShowAsTree” = Yes

                                                            ii.      Property “IndentationColumnName” must be field name we defined in table as indentation field.

b.      Repeater must include fields we want to see in list. First field in list will be indented and will have ‘+-‘ signs for collapse/expand list. Indentation field could be not included to page.

If everything is done (and you have data in the table), then run page. Fortunately for me I have table with some data in it:

Few interesting points I want to have your attention:

  • Field “Int” is used for primary key
  • Field “Indentation” has indentation value used for Tree View.
  • Both fields are not used on page so field “Txt” has text showing which record and what indentation level is.
  • There are no relations between primary key and indentation. You see 0 indentation is record 18.

So run page and see:

If we “Expand All” we’ll see:

Wow it works J

Please keep in mind:

  • There could be few records with the same indentation.
  • If after record ‘A’ is going record ‘B’ with bigger indentation then we can expand record ‘A’.
  • View belongs to sorting. I sorted records based on “Indentation” value. So I see “full tree” started from “0 level”. But really “0 level” record is 18 record by primary key, and if change sorting to primary key I will see different tree.

You see now: first record has primary key 0 and indentation level 5, but next record has level 1 so my first record has no possibilities “collapse/expand”. Record 18 with level 0 is in the end of list and only one record is after it, so “my root records from previous view” now can expand/collapse only one record.

 

And last:

  • You can’t insert manually new records to “Tree View”. Please use code if you want to create new records from “Tree view”
  • You can delete records manually from “Tree view” one by one. If you will try to delete not expanded record then only this record will be deleted.
  • You can edit records in “Tree view”. You can modify even “Indentation level” field, just after modify “Refresh” (F5) and you will see “new view”.
  • There were few stability issues in “Tree view ”, so please use possible latest NAV builds
  • If you have questions/issues regarding this functionality, please contact our best ever NAV support team J

 

Gedas Busniauskas
Microsoft Lithuania
Microsoft Customer Service and Support (CSS) EMEA

.NET Interoperability in NAV 2009 R2

$
0
0

The greatest development platform in the world meets the greatest set of functional libraries, types, methods, and properties as Microsoft Dynamics NAV 2009 R2 allows developers to take advantage of the Microsoft .NET Framework! With NAV 2009 R2, you can reference external .NET components and make use of the Types and Functions from .NET Framework assemblies and from your own or 3rd party code.

Being able to use .NET from C/AL code has been planned for a long time - the whole NAV Server architecture released with NAV 2009 has been building to this time where we can finally reach out from the NAV C/AL context and make use of these functions. The feature is very much part of our roadmap going forward where we want to give developers more power and allow partners to create solutions with much broader reaches than can be achieved within a native C/AL environment.

Referencing an external component will be similar to the pattern that Automation developers are accustomed to - you can quickly choose a component from the variable declaration window and then start using the object in C/AL with full support from NAV's Symbol Menu (F5). 

When working with variables of type DotNet, we distinguish between two sorts: Add-ins and those registered from the Global Assembly Cache (GAC). The Add-in type are those that are custom made or custom written, they do not need to be strong-named, and they may also change or are updated often. These components must be copied into a new directory (the Add-ins directory) on the developer's C/SIDE installation in order to be utilized. Variables based on types registered in the GAC are less likely to be swapped around but support strong-named types and include components like the .NET Framework. .NET Framework components have the additional benefit that they are already deployed on computers so there is no need for additional deployment!

Rather than elaborate more on the properties and definitions, let's look at a sample. In this code sample, we will use methods from the .NET Framework to retrieve a list of processes running on the NAV Server and show the process IDs in a message box. The sample is not exactly an ERP task but shows what can now be achieved by using .NET and hopes to show that familiar code patterns can be applied.

This screenshot shows the whole solution:

Notice that we have declared two DotNet variables here - myproclist, which is a System.Array and holds our list of processes, and process, which is a System.Diagnotics.Process subtype. Both variables are from the .NET Framework and are thus registered in the system GAC and we don't need to worry about deploying them for when we run the code.

Notice also that unlike Automation (based on COM), you don't always need to CREATE the objects. In DotNet, objects need to have a constructor called when they are instance-based or they may be used directly if they are static. The GetProcesses method is a static method in .NET, as we can see in the information section in our ever-helpful Symbol Menu.

Notice also that the C/AL code is able to loop through the Array. Using arrays (be they NAV arrays or .NET System.Arrays) is such a common programming pattern that it would be very wrong of us to not include it.

Of course using .NET is only supported on the NAV Server and, like Automation, you can also run the .NET objects either on the NAV Server or on the RoleTailored client.

Using .NET in C/AL will open many new options - more than we can possibly imagine. We hope you'll enjoy using this feature from NAV 2009 R2 and go on to make some fantastic new solutions for all our customers!

Good luck and happy coding!

Stuart

To view a recorded version of the Hot Topic session about .NET Interoperability, see the Partner Learning Center.

Test Automation Series 2 - Creation Functions

$
0
0

With the introduction of the NAV test features in NAV 2009 SP1 it has become easier to develop automated test suites for NAV applications. An important part of an automated test is the setup of the test fixture. In terms of software testing, the fixture includes everything that is necessary to exercise the system under test and to expect a particular outcome. In the context of testing NAV applications, the test fixture mainly consists of all values of all fields of all records in the database. In a previous post I talked about how to use a backup-restore mechanism to recreate the fixture and also about when to recreate the fixture.

A backup-restore mechanism allows a test to use a fixture that was prebuild at some other time, that is, before the backup was created. In this post I'll discuss the possibility to create part of the fixture during the test itself. Sometimes this will be done inline, but typically the creation of new records will be delegated to creation functions that may be reused. Examples of creation functions may also be found in the Application Test Toolset.

Basic Structure

As an example of a creation function, consider the following function that creates a customer:

CreateCustomer(VAR Customer : Record Customer) : Code[20] 

Customer.INIT;
Customer.INSERT(TRUE);
CustomerPostingGroup.NEXT(RANDOM(CustomerPostingGroup.COUNT));
Customer."Customer Posting Group" := CustomerPostingGroup.Code;
Customer.MODIFY(TRUE);
EXIT(Customer."No.")

This function shows some of the idiom that is used in creation functions. To return the record an output (VAR) parameter is used. For convenience the primary key is also returned. When only the primary key is needed, this leads to slightly simplified code. Compare for instance

CreateCustomer(Customer);
SalesHeader.VALIDATE("Bill-to Customer No.",Customer."No.");

with

SalesHeader.VALIDATE("Bill-to Customer No.",CreateCustomer(Customer));

 Obviously, this is only possible when the record being created has a primary key that consists of only a single field.

The actual creation of the record starts with initializing all the fields that are not part of the primary key (INIT). If the record type uses a number series (as does Customer), the record is now inserted to make sure any other initializations (in the insert trigger) are executed. Only then the remaining fields are set. Finally, the number of the created customer is returned.

Field Values

When creating a record some fields will need to be given a value. There are two ways to obtain the actual values to be used: they can be passed in via parameters or they can be generated inside the creation function. As a rule of thumb when calling a creation function from within a test function, only the information that is necessary to understand the purpose of the test should be passed in. All the other values are "don't care" values and should be generated. The advantage of generating "don't care" values inside the creation functions over the use parameters is that it becomes immediately clear which fields are relevant in a particular test by simply reading its code.

For the generation of values different approaches may be used (depending on the type). The RANDOM, TIME, and CREATEGUID functions can all be used to generate values of different simple types (optionally combined with the FORMAT function).

In the case a field refers to another table a random record from that table may be selected. The example shows how to use the NEXT function to move a random number of records forward. Note that the COUNT function is used to prevent moving forward too much. Also note that If this pattern is used a lot, there may be a performance impact.

Although the use of random value makes it very easy to understand what is (not) important by reading the code, it could make failures more difficult to reproduce. A remedy to this problem is to record all the random values used, or to simply record the seed used to initialize the random number generator (the seed can be set using the RANDOMIZE function). In the latter case, the whole sequence of random values can be reproduced by using the same seed.

As an alternative to selecting random records, a new record may be created to set a field that refers to another table.

Primary Key

For some record types the primary key field is not generated by a number series. In such cases a simple pattern can be applied to create a unique key as illustrated by the creation function below:

CreateGLAccount(VAR GLAccount : Record "G/L Account") : Code[20]; 

GLAccount.SETFILTER("No.",'TESTACC*');
IF NOT GLAccount.FINDLAST THEN
GLAccount.VALIDATE("No.",'TESTACC000');
GLAccount.VALIDATE("No.",INCSTR(GLAccount."No."));
GLAccount.INIT;
GLAccount.INSERT(TRUE);
EXIT(GLAccount."No.")

The keys are prefixed with TESTACC to make it easy to recognize the records created by the test when debugging or analyzing test failures. This creation function will generate accounts numbered TESTACC001, TESTACC002, and so forth. In this case the keys will wrap around after 999 accounts are created, after which this creation function will fail. If more accounts are needed extra digits may simply be added.

Input Parameters

For some of the fields of a record you may want to control their values when using a creation function in your test. Instead of generating such values inside the creation function, input parameters may be used to pass them in.

One of the difficulties when defining creation functions is to decide on what and how many parameters to use. In general the number of parameters for any function should be limited. This also applies to creation functions. Parameters should only be defined for the information that is relevant for the purpose of a test. Of course that may be different for each test.

To avoid libraries that contain a large number of creation functions for each record, only include the most basic parameters. For master data typically no input parameters are required. For line data, consider basic parameters such as type, number, and amount.

Custom Setup

In a particular test you typically want to control a slightly different set of parameters compared to the set of parameters accepted by a creation function in an existing library. A simple solution to this problem is to update the record inline after it has been returned by the creation function. In the following code fragment, for example, the sales line returned by the creation function is updated with a unit price.

LibrarySales.CreateSalesLine(SalesLine.Type::"G/L Account",AccountNo,Qty,SalesHeader,SalesLine,);
SalesLine.VALIDATE("Unit Price",Amount);
SalesLine.MODIFY(TRUE);

When the required updates to a particular record are complex or are required often in a test codeunit, this pattern may lead to code duplication. To reduce code duplication, consider wrapping a simple creation function (located in a test helper codeunit) in a more complex one (located in a test codeunit). Suppose that for the purpose of a test a sales order needs to be created, and that the only relevant aspect of this sales order is that it is for an item and its total amount. Then a local creation function could be defined like

CreateSalesOrder(Amount : Integer; VAR SalesHeader : Record "Sales Header") 

LibrarySales.CreateSalesHeader(SalesHeader."Document Type"::Order,CreateCustomer(Customer),SalesHeader);
LibrarySales.CreateSalesLine(SalesHeader,SalesLine.Type::Item,FindItem(Item),1,SalesLine);
SalesLine.VALIDATE("Unit Price",Amount);
SalesLine.MODIFY(TRUE)

In this example a complex creation function wraps two simple creation functions. The CreateSalesHeader function takes the document type, a customer number (the customer is created here as well) as input parameters. The CreateSalesLine function takes the sales header, line type, number, and quantity as input. Here, a so-called, finder function is used that returns the number for an arbitrary item. Finder functions are a different type of helper functions that will be discussed in a future post. Finally, note that the CreateSalesLine function needs the document type and number from the header; instead of using separate parameters they are passed in together (with the SalesHeader record variable).

Summary

To summarize here is a list of tips to consider when defining creation functions:

  • Return the created record via an output (VAR) parameter
  • If the created record has a single-field primary key, return it
  • Make sure the assigned primary key is unique
  • If possible, have the key generated by a number series
  • The safest way to initialize a record is to make sure all triggers are executed in the same order as they would have been executed when running the scenario from the user interface. In general (when DelayedInsert=No) records are created by this sequence :
    • INIT
    • VALIDATE primary key fields
    • INSERT(TRUE)
    • VALIDATE other fields
    • MODIFY(TRUE)
  • Only use input parameters to pass in information necessary to understand the purpose of a test
  • If necessary add custom setup code inline
  • Wrap generic creation functions to create more specific creation functions
  • Instead of passing in multiple fields of a record separately, pass in the entire record instead

These and some other patterns have also been used for the implementation of the creation functions included in the Application Test Toolset.

Microsoft Dynamics NAV : Online Maps

$
0
0

Online map functionality that we've had in Dynamics NAV for some time now has changed for most of countries, after Maps were moved to Bing.

As maps were moved and collections changed, the code needs to be changed to align with this. This is scheduled for change in standard application.

Meanwhile, it is fairly simple to make some basic changes in current application code that will allow you to align existing functionality with Bing maps, for basic search functionalities in any case. Code changes are not many and are fairly simple, but stretch across several objects so I have uploaded the txt file containing these objects, for simplicity.

The changes i made here only align with new maps, functionality is more or less the same as it was, with few minor changes. Online map setup is a bit simpler, as no default links are needed nor inserted. Remember though when using this: when URL is built, it is the country/region code that will decide what language the site will open in.

Not all local sites are translated to local languages, so if one specifies NO (Norway) as a country (for example), maps will still show in English. For some countries (Germany, Spain, Italy, France,...)  selecting a country will show maps in local language. One should also keep in mind, for countries that don't have the collections in local languages - specifying country name in local language might confuse the search in some scenarios. Meaning if your user resides in Norway (example) and  customer resides in Sweden, address URL will contain country name:  Sverige (Norwegian for Sweden), and that will put the search off in some scenarios. All you need here is to either change the code so country code is not within URL (but that might give more hits to select from), or simply add a field to table 9 (Country/Regions) called ENUName. Use the data migration functionality to Import the attached XML file containing list of countries present in demo data and their ENU translations. Include only Code and ENUName fields when importing the file, and change the following line of codeunit 801 Online Map Utilities:

replace:


IF country.GET(Address[5]) THEN
  Address[5] := country.Name

with


IF country.GET(Address[5]) THEN
  Address[5] := country.ENUName

 

and your URL will always be built with country name in English, which would work for search in any language.

 


How to use an external .NET assembly in report layout for RTC reports

$
0
0

This post shows how to include an external .NET assembly in your report layout when designing reports for RTC. The focus here is NOT how to build a .NET assembly, but how you can include such an assembly in your report design. But still, we will begin by creating our own .NET assembly for use in the layout.

 

Creating a .NET assembly

To keep it simple, let's make a .NET assembly to just add up two numbers:

  1. Start Visual Studio.
  2. Create a new c# project of type Class Library, and name it MyReportAssembly.
  3. Change the default Class name from Class1 to AddNumbers.
  4. Create a public function called AddFunction, which takes two integers as parameters, and returns the sum of those two.

The whole project should look like this now:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyReportAssembly
{
    public class AddNumbers
    {
        public int AddFunction(int i, int j) {
            return (i + j);
        }
    }
}

That's all the functionality we need - as mentioned we want to keep it simple!

But we need to make a few more changes to the project before we can use it:

 

Add a strong named key

In Project Properties, on the Signing tab, select "Sign the assembly", select a New key (give it any name), and untick password protection.

 

Add AllowPartiallyTrustedCallers property

To allow this assembly to be called from the report layout you must set this property in Assemblyinfo.cs. So, in Assemblyinfo.cs, add these lines:

using System.Security;

[assembly: AllowPartiallyTrustedCallers]

Full details of why you need those two lines here: "Asserting Permissions in Custom Assemblies"

 

Compile to .NET 3.5

When I built this project using Visual Studio 2010, I was not able to install the assembly. And when trying to include it in my report layout I got this error: "MyReportAssembly.dll does not contain an assembly.". So if you will be using the installation instructions below, and you are using Visual Studio 2010, then change "Target Framework" to ".NET Framework 3.5" under Project Properties on the Application tab. This is the default target framework in Visual Studio 2008. Visual Studio 2010 defaults to version 4.0. I'm sure there are better ways to instlal this and still build it for .NET 4, but that's outside of the current scope. Also if it later complains about reference to Microsoft.CSharp, then just remove that from the project under references.

 

When this is done, build your project to create MyReportAssembly.dll

 

Installing your new .NET assembly

Again, the focus here is not on buildign and installing .NET assemblies, and I am no expert in that, so this is probably NOT the recommended way to install a .NET assembly, but it works just for the purpose of being able to see it in the report layout:

Start an elevated Visual Studio Command prompt, and go to the folder where your net MyReportAssembly.dll is (C:\Users\[UserName]\Documents\visual studio 2010\Projects\MyReportAssembly\MyReportAssembly\bin\Debug\). Then run this command:


gacutil /i MyReportAssembly.dll

It can be uninstalled again with this command:


gacutil /uf MyReportAssembly

After installing it, open this folder in Windows Explorer:


c:\Windows\Assembly


and check that you have the MyReportAssembly there. If not, then check if the section above about compiling it to .NET 3.5 applies to you.

 

Finally - How to use an exterenal .NET assembly in report layout
 

So now we finally come to the point of this post: How do you use your new assembly in the report layout:

  1. Design any report, and go to the report Layout.
  2. Create a reference to your assembly: Report -> Report Properties -> References. Browse for your new .dll and select it.
  3. On the Code tab, create a function which will create an instance of your assembly and call the function:

 Public Function Addnumbers(Num1 as Integer, Num2 as Integer) as Integer
  Dim MyAssembly as MyReportAssembly.AddNumbers
  MyAssembly = new MyReportAssembly.AddNumbers()
  Return MyAssembly.AddFunction(Num1,Num2)
End Function

 Then call this function nby adding this Expression to a TextBox:

=Code.Addnumbers(1,2)

And, finally, if you run the report like this, you would get the error "xyz, which is not a trusted assembly.". So back in the classic report design, in report properties, you just have to set the property EnableExternalAssemblies = Yes, and the report should run.

 

That was a lot of work for just adding up two numbers, but hopefully is shows what steps are needed to open up your report layout to endless opportunities. Note: I have no ideas if this will work with visual assemblies or anything which contains any UI at all. Any experiences with this, feel free to add to the end of this post.

 

As always, and especially this time since I'm in no way a c# developer:

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

 

Additional Information

If you plan to look further into this, then here are some recommended links:

"Using Custom Assemblies with Reports"

"Deploying a Custom Assembly"

If your assembly calls other services, it is likely you need to consider passing on the user's credentials. For more information on that, here is a good place to begin:

"Asserting Permissions in Custom Assemblies"

 

 

Lars Lohndorf-Larsen

Microsoft Dynamics UK

Microsoft Customer Service and Support (CSS) EMEA

 

Grouping in RoleTailored Client Reports

$
0
0

When moving to the RoleTailored client some people have experienced difficulties with grouping in RDLC reports in cases where the requirements are just a bit more complex than a basic grouping functionality.

I must admit that at the first glance it does not seem simple, but after a little research it does not look dramatically complex either. That motivated me to write this blog post and share my findings with all of you NAV report developers.

So let's take a look at the task we are trying to accomplish.

I have a master table, which contains a list of sales people - SalesPerson. The sales people sell software partially as on-premises software and partially as a subscription. There are two tables, which contain data for these two types of sale: OnPremisesSale and SubscriptionSale.

The example is artificial and is meant only to show different tricks on how to do grouping. The picture below shows the data for this report:

 For each sales person I need to output both sales of on-premises software and subscription sales and show the total sales. Something that looks like the following:

 

Now we have all required information, let's start solving the task.

1. First, I create an NAV report. Add all necessary data items, establish data item links for the proper joining of the data, and place all needed fields on the section designer in order to get them inside RDLC report.
See the picture below.

2. Next, I go to RDLC designer in Visual Studio. First I pick a list control, put the SalesPerson_Name field in the list, and set the grouping based on the SalesPerson_SalesPersonId field.
3. After that, I place a row with column captions on top of the list.

Design in Visual Studio as shown below.

4.  Now I need to place two tables inside the list- one for the on-premises software and one for the subscriptions.
A list can display detail rows or a single grouping level, but not both. We can work that around this limitation by adding a nested data region. Here I place a rectangle inside the list and place two tables inside this rectangle, one for On-Premises and one for Subscriptions. In each table, I add header text and add the CustomerName and Amount fields.

5.  I also add two text boxes for the sum of amount - one inside the rectangle to show total sales for the sales person and one outside to show the overall amount. Both contain the same formula: =SUM(Fields!SubscriptionSale_Amount.Value) + SUM(Fields!OnPremisesSale_Amount.Value)

 

The picture below shows the result of this design:

  

6. It looks more or less correct, but there are uneven strange empty spaces between rows. In order to detect the root cause of this problem let's add visible borders to our tables. Change the BorderStyle property to Solid for one of the tables and to Dashed for another.

So the result will look like this:

 

7. This result indicates that our report has two problems:

  • Tables do not filter out the empty rows. The result set contains joined data for both on-premises sales and subscriptions - so each data row has an empty part of it. The Data Zoom tool (Ctrl-Alt-F1) is your best friend when you need to learn how your data looks.
  • Empty tables should be hidden from the output.

So I will make two fixes in order to address these two bugs:

  • In the table's Property window, on the Filter tab set a filter: Fields!OnPremisesSale_CustomerName.Value > '' for one table and Fields!SubscriptionSale_CustomerName.Value > '' for another. (Note: In the Value field, you must enter two single quotes.)
  • For each table, set the Visibility expression to =IIF(CountRows("table2") > 0, False, True). The table name should be replaced with an actual table name. Table names are different, so the visibility expressions should also be different. Please avoid a copy/paste error here and do not forget to wrap the name with quotes.

As an addition, I will make some minor changes to improve the report's readability: change fonts, font size, font style, and add separator lines.

All these modifications will produce the following output:

 

That is exactly it what I wanted to achieve at the beginning.

I also have some tips, which might be helpful in your future reporting endeavors:

  1. Use lists. In many cases it is more convenient than creating a complex grouping inside a table. You can nest a list or any other data region inside the list.
  2. Do not forget that a list can display detail rows or a single grouping level, but not both. It can be worked around by adding a nested data region including another list.
  3. If there are some issues with layout, then make the invisible elements of the design temporarily visible - set visible style for table borders or change color of the different elements of the layout.
  4. Build inside Visual Studio. That can catch layout errors and reduce development time.

 - Yuri Belenky

Manage Notes / My Notifications in RTC using a COM Stream Wrapper

$
0
0

With NAV 2009 it has been made available to the users a messaging system based on Notes / MyNotes page parts. Note records are stored as BLOBs inside table 2000000068 Record Link. It is known that you cannot handle Notes using normal C/AL code and, in particular, correctly stream in and out the content of those BLOB fields.

In this blog you will find the source code in order to implement a COM Stream Wrapper object to write and read Notes. You may use this COM object to generate and handle Notes when needed without being bound to Notes and MyNotes system part. This object may give you much more flexibility in your RTC code development.

NOTE: the Stream Wrapper is working correctly (writing) only when code is executed in a RTC based environment. It will give unpredictable and wrong results if executed using Classic Client.

If you want to know more about Notes you can refer to MSDN link:

Touring the RoleTailored Client Pages http://msdn.microsoft.com/en-us/library/dd301400.aspx

My ingredients:

  • NAV 2009 SP1 (with latest HF applied)
  • Visual Studio 2010 Professional
  • VS Command Prompt 2010 (from SDK)
  • Windows 7 Enterprise

Develop the COM StreamWrapper.dll in Visual Studio

A. Create a New Class Project

  1. Open Visual Studio (in this example I am using Visual Studio 2010) with elevated privileges (Run As Administrator)
  2. Create a New Project (CTRL+SHIFT+N) with these parameters
    • Visual C# - Windows
    • Class library
    • .NET Framework 3.5
    • Name: StreamWrp
    • Location: C:\TMP (or whatever location you like)
    • Solution Name: StreamWrp
    • Create directory for solution

 B. Create a Strong Name Key (SNK) and set correct Properties for the Class project

  1. Go to Project > Properties (StreamWrp Properties...)
  2. From the Project Properties form go to "Application*" tab
  3. Enable on Resources, the "Icon and Manifest" option
  4. Click on "Assembly Information" button
  5. In the "Assembly Information" form use the following GUID (or create brand new one):
    • 74f87d09-198a-4d81-a056-53271f21d4dd
  6. In the "Assembly Information" form check "Make assembly COM-Visible" and click OK
  7. From the Project Properties form go to the "Build" tab
  8. In the "General" section tick "Define DEBUG content", "Define TRACE content" and "Allow unsafe code"
  9. From the Project Properties form go to the "Signing" tab
  10. Tick "Sign the assembly"
  11. Create a new Strong Name Key (SNK) e.g. SWtest.snk

C. Develop (add code to) your StreamWrp project

  1. Add References (replace the existing ones) to the following Namespaces
    • System
    • System.Data
    • System.XML
  2. Locate your Class1.cs file in the Solution Explorer
  3. Right click > View Designer (Shift+F7)
  4. Replace the C# code automatically written with the one written below

// Copyright © Microsoft Corporation. All Rights Reserved.

// This code released under the terms of the

// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)

using System;

using System.IO;

using System.Collections.Generic;

using System.Text;

using System.Runtime.InteropServices;

using System.Runtime.InteropServices.ComTypes;

 

namespace StreamWrp

{

    [ComVisible(true)]

    [Guid("2F870D88-FEA5-4F27-81FB-6775D7436E52")]

    public interface IStreamHelper

    {

        string Text

        {

            get;

            set;

        }

        int Transform(int encodeRead, int encodeWrite, IStream reader, IStream writter);

    }

    [ClassInterface(ClassInterfaceType.None)]

    [Guid("B4E5F8F4-5225-4B3A-998A-B82A8A7C6B8E")]

    public class StreamHelper : IStreamHelper

    {

        public string Text

        {

            get

            {

                throw new Exception("The method or operation is not implemented.");

            }

            set

            {

                throw new Exception("The method or operation is not implemented.");

            }

        }

        public int Transform(int encodeRead, int encodeWrite, IStream reader, IStream writter)

        {

            byte[] pv = new byte[4098];

            int read = 0;

            unsafe

            {

                IntPtr pcbRead = new IntPtr(&read);

                reader.Read(pv, pv.Length, pcbRead);

            }

            MemoryStream innerStream = new MemoryStream(pv, 0, read);

            string note = String.Empty;

            if (innerStream.Length != 0)

            {

                //Select InS encoding and ReadChars Ins

                Encoding inEncode;

                if (encodeRead != 0)

                {

                    inEncode = Encoding.GetEncoding(encodeRead);

                    using (BinaryReader innerreader = new BinaryReader(innerStream, inEncode))

                    {

                        note = new string(innerreader.ReadChars((int)innerStream.Length));

                        innerreader.Close();

                    }

                }

                else

                {

                    using (BinaryReader innerreader = new BinaryReader(innerStream))

                    {

                        note = new string(innerreader.ReadChars((int)innerStream.Length));

                        innerreader.Close();

                    }

                }

                MemoryStream stream2 = new MemoryStream();

                //Select OutS Encoding and Write OutS

                Encoding outEncode;

                if (encodeWrite != 0)

                {

                    outEncode = Encoding.GetEncoding(encodeWrite);

                    using (BinaryWriter writer = new BinaryWriter(stream2, outEncode))

                    {

                        writer.Write((string)note);

                        writer.Close();

                        pv = stream2.ToArray();

                    }

                }

                else

                {

                    using (BinaryWriter writer = new BinaryWriter(stream2))

                    {

                        writer.Write((string)note);

                        writer.Close();

                        pv = stream2.ToArray();

                    }

                }

                unsafe

                {

                    IntPtr pcbWrite = new IntPtr(&read);

                    writter.Write(pv, pv.Length, pcbWrite);

                }

            }

            return read;

        }    

    }

}

D. Strong sign and build your COM object

  1. Now it is all set up, you are ready to build your StreamWrapper COM object. In the main menu, go to "Build" > "Build StreamWrp F6" (this is automatically strong signed as per properties setting).
  2. NOTE : a WARNING message may arise

Warning               1              Type library exporter warning processing 'StreamWrp.IStreamHelper.Transform(reader), StreamWrp'. Warning: Type library exporter could not find the type library for 'System.Runtime.InteropServices.ComTypes.IStream'.  IUnknown was substituted for the interface.     StreamWrp

This is just a Warning about a substitution from IStream to IUnknown. There is no problem with this warning message. The compilation is successful.

 

E. Place DLLs into a folder and Register them

  1. Locate/Copypaste StreamWrp.dll and StreamWrp.tlb (should be in your \StreamWrp\StreamWrp\bin\Debug folder) in the Add-In folder of a machine where Role Tailored Client has been installed. (typically C:\Program Files\Microsoft Dynamics NAV\60\RoleTailored Client\Add-ins)
  2. Launch the Visual Studio Command Prompt (VSCP) with elevated privilege (Run As Administrator).
  3. In the VSCP, navigate through the location where you have located the dlls. (typically C:\Program Files\Microsoft Dynamics NAV\60\RoleTailored Client\Add-ins)
  4. Once you are located in the right directory type:

regasm /tlb:StreamWrp StreamWrp.dll /codebase

(hit return)

NOTE: there can be some warning messages

(type)

gacutil /I StreamWrp.dll

(hit return)

Develop the C/AL code

A. Develop the C/AL code to WRITE and READ Notes in RTC environments

How this COM Wrapper works? It accepts a Stream and returns a modified Stream. Nothing more.

It needs to be feed up with 2 encoding values depending if the Wrapper is used to write or read Notes.

A useful example of encoding (overall if there are special characters that need to be handled, e.g. double S, umlaut, etc.) can be found at this link:

http://msdn.microsoft.com/en-us/library/system.text.encoding.windowscodepage.aspx

In this example, I am using a DEU standard database, therefore I am using IBM437 CodePage to correctly encode/decode the stream (note that IBM437 is also part of the Windows CodePage 1252).

The 2 following Codeunits attached in TXT format are used to Write and Read Notes.

NOTE: in order to let this example works you must have, at least, 1 note created from RTC (it merely use a copy from the last record, just as example).

This is the C/AL code snippet to WRITE Notes using RTC

...

// Copyright © Microsoft Corporation. All Rights Reserved.

// This code released under the terms of the

// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)

IF ISSERVICETIER THEN BEGIN

CLEAR(NoteText);

// Add special chars

NoteText.ADDTEXT(STRSUBSTNO(Text1000000001,USERID,TODAY,TIME) + ' - ìèòàù - Österreich - ');

// Browse country table and create the Note by pasting Code and Name into the NoteText

CountryRec.RESET;

IF CountryRec.FINDFIRST THEN REPEAT

  NoteText.ADDTEXT(' ** Country ' + FORMAT(CountryRec.Code)+ ' - ' + FORMAT(CountryRec.Name));

UNTIL CountryRec.NEXT = 0;

// Find the last Record Link to retrieve the ID

RecordLink.RESET;

RecordLink.FINDLAST;

LinkID := RecordLink."Link ID"; 

// Create ID+1 Record Link with empty Note (copy the link above)

LinkID := LinkID + 1;

RecordLink2.INIT;

RecordLink2."Link ID" := LinkID;

RecordLink2."Record ID" := RecordLink."Record ID";

RecordLink2.URL1 :=RecordLink.URL1;

RecordLink2.Type := RecordLink2.Type :: Note;

RecordLink2.Created := CURRENTDATETIME;

RecordLink2."User ID" := USERID;

RecordLink2.Company := COMPANYNAME;

RecordLink2.Notify := TRUE;

// Stream the NoteText inside the note

RecordLink2.CALCFIELDS(Note);

RecordLink2.Note.CREATEOUTSTREAM(OutS);

NoteText.WRITE(OutS);

RecordLink2."To User ID" := USERID; 

RecordLink2.INSERT;

// Find the record inserted in order to 'adjust' it with the StreamWrapper

RecordLink2.INIT;

RecordLink2."Link ID" := LinkID;

RecordLink2.FIND('=');

RecordLink2.CALCFIELDS(Note);

RecordLink2.Note.CREATEINSTREAM(InS); 

RecordLink2.Note.CREATEOUTSTREAM(OutS);

// Let the COM StreamWrapper transform the Blob correctly

EncodeIn := 437;  //CodePage IBM437

EncodeOut := 0;   //No CodePage in output

IF ISCLEAR(Transform) THEN

  CREATE(Transform);

InSVar := InS;

OutSVar := OutS;

Transform.Transform(EncodeIn, EncodeOut, InSVar, OutSVar);

RecordLink2.MODIFY();

END; 

MESSAGE('WRITE : DONE');

...

And this is the C/AL code snippet to READ Notes using RTC

...

IF ISSERVICETIER THEN BEGIN

  CLEAR(TempBlobRec);

  CLEAR(NoteText);

  // Find the right Record Link

  RecordLink.RESET;

  RecordLink.FINDLAST;

  IF RecordLink.Note.HASVALUE THEN BEGIN

    RecordLink.CALCFIELDS(Note);

    RecordLink.Note.CREATEINSTREAM(InS);   //Note --> InS

    // Init a Temp Blob

    IF TempBlobRec.GET(10000) THEN

      TempBlobRec.DELETE;

    TempBlobRec.INIT;

    TempBlobRec."Primay Key" := 10000;

    TempBlobRec.INSERT;

    // Stream the 'modified back' Note onto this Blob field

    TempBlobRec.GET(10000);

    TempBlobRec.CALCFIELDS(Blob);

    TempBlobRec.Blob.CREATEOUTSTREAM(OutS);

    // Let the COM StreamWrapper transform the Blob correctly

    EncodeIn := 0;    // Read raw data from BLOB

    EncodeOut := 437;   // Use CodePage IBM437 in output

    IF ISCLEAR(Transform) THEN

      CREATE(Transform);

    InSVar := InS;

    OutSVar := OutS;

    Transform.Transform(EncodeIn, EncodeOut, InSVar, OutSVar);

    TempBlobRec.MODIFY; 

    // Get the modified Blob rec and read it

    TempBlobRec.GET(10000);

    TempBlobRec.CALCFIELDS(Blob);

    TempBlobRec.Blob.CREATEINSTREAM(InS2);

    // Algorithm to READ the Blob output

    IsFirstTxtLine := TRUE;

    WHILE NOT (InS2.EOS()) DO BEGIN

      Int:= InS2.READ(Txt);

      IF Int <> 0 THEN BEGIN

        IF IsFirstTxtLine THEN BEGIN

          LengthStr := STRLEN(Txt);

          CASE LengthStr OF

            1..126 : Txt := COPYSTR(Txt,3,STRLEN(Txt));

            127 : Txt := COPYSTR(Txt,4,STRLEN(Txt));

            ELSE

              Txt := COPYSTR(Txt,5,STRLEN(Txt));

          END;

          IsFirstTxtLine := FALSE;

        END;

        MESSAGE(Txt);

      END;

      CLEAR(Txt);

      CLEAR(Int);

    END;

  END; 

END;

MESSAGE('READ - DONE');

...

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

Duilio Tacconi (dtacconi)

Microsoft Dynamics Italy

Microsoft Customer Service and Support (CSS) EMEA

A special thanks to Jorge Alberto Torres - DK-MBS NAV Development

Let NAV Speak! (with .NET Interop and NAV 2009 R2)

$
0
0

In this blog is described a very simple usage of the .NET interoperability feature with Microsoft Dynamics NAV 2009 R2 and can be considered an extension of my previous blog: http://blogs.msdn.com/b/nav/archive/2010/07/09/let-nav-speak-with-a-simple-and-useful-client-add-in.aspx.

It is intended just to familiarize you with this brand new feature proposed with the NAV 2009 R2 release.

If you want to know more about .NET interoperability for NAV 2009 R2, please refer to MSDN link:

Extending Microsoft Dynamics NAV Using Microsoft .NET Framework Interoperability

http://msdn.microsoft.com/en-us/library/gg502499.aspx

The .NET interoperability code snippet in this blog is based on the System.Speech namespace.

http://msdn.microsoft.com/en-us/library/ms554861(v=VS.90).aspx

My ingredients:

  • NAV 2009 R2
  • Windows 7 Enterprise

Create your "Speak it!" action and let NAV speak Customer Names :

1. Open Classic Client

2. Go to the Object Designer

3. Select Page object (alt+g)

4. Select Page 22 "Customer List"

5. Design Page 22 "Customer List" (alt+d)

6. Select View > Page Actions (al+v, o)

7. Create a new Action (F3) in the ActionItems container (see below)

8. Change the properties of the Action as below:

Caption - Speak it !

Image - ViewComments

Promoted - Yes

PromotedCategory - Process

PromotedIsBig - Yes

9. Edit the code in the Speak it! Action (F9)

10. Add those LOCAL variables

dnSpeech

DataType: DotNet

Subtype: System.Speech, Version=3.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35.System.Speech.Synthesis.SpeechSynthesizer

RUNONCLIENT: Yes
Synth

DataType: DotNet

Subtype: System.Speech, Version=3.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35.System.Speech.Synthesis.SpeechSynthesizer

RUNONCLIENT: Yes

dnVoiceGender

DataType: DotNet

Subtype: System.Speech, Version=3.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35.System.Speech.Synthesis.VoiceGender

RUNONCLIENT: Yes

I

DataType: Integer

 

NOTE: RUNONCLIENT is a property of each DotNet variable (shift+f4)

11. Add this code snippet in the OnAction trigger

...

// Copyright © Microsoft Corporation. All Rights Reserved.

// This code released under the terms of the

// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)

// Use the constructor to create a new Synthesizer

Synth := dnSpeech.SpeechSynthesizer();

// http://msdn.microsoft.com/en-us/library/system.speech.synthesis.voicegender(VS.90).aspx

// This is an enumeration assignment:

// 0=NotSet,1=Male,2=Female,3=Netural

dnVoiceGender := 2; //Female

// NOTE: it will take the voice depending on what is

//  installed locally (e.g. Windows 7 has Microsoft Anne voice)

Synth.SelectVoiceByHints(dnVoiceGender);

// Please, speak slowly (range [-10:10])

Synth.Rate := -3;

Synth.SetOutputToDefaultAudioDevice();

Synth.Speak(FORMAT(Name));

// Row below is not needed. Synth is a local variable therefore it will be

// automatically disposed

// Synth.Dispose;

...

12. Save and compile (ctrl+s) page 22 "Customer List"

Now... you are ready to let NAV speech the Customer Name from the customer list by simply click on the "Speak it!" action.

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

Best Regards,

Duilio Tacconi (dtacconi)

Microsoft Dynamics Italy

Microsoft Customer Service and Support (CSS) EMEA

Manage Files in RTC WITHOUT using FILE virtual table. (with .NET interop and NAV 2009 R2)

$
0
0

In this blog you will find attached a very simple usage of .NET interoperability with Dynamics NAV 2009 R2 with the objective of basic file management (move, copy, open, delete) WITHOUT using the FILE virtual table and is intended just to familiarize with this brand new feature proposed with NAV 2009 R2 release.

File management is made at client side (RoleTailored client side).

If you want to know more about .NET interoperability for NAV 2009 R2, please refer to MSDN link:

Extending Microsoft Dynamics NAV Using Microsoft .NET Framework Interoperability

http://msdn.microsoft.com/en-us/library/gg502499.aspx

.NET interoperability (variables) are generally based on the System.IO namespace:

System.IO.File

http://msdn.microsoft.com/en-us/library/system.io.file(v=VS.80).aspx

 System.IO.Directory

http://msdn.microsoft.com/en-us/library/system.io.directory(v=VS.80).aspx

 System.IO.FileInfo     

http://msdn.microsoft.com/en-us/library/system.io.fileinfo(v=VS.80).aspx

 

My ingredients:

  • Microsoft Dynamics NAV 2009 R2
  • Windows 7 Enterprise

What you will find in the TXT file:

  • 2 Tables
    • T.50100 Client Files Management Buffer
    • T.50300 Client Files Management Setup
  • 1 Page
    • P.50200 Client Files Management

 

What to do to let it work (initial setup):

1. Populate the Client Files Management Setup table 50300 with the appropriate values :

  • User ID -> windows login (without domain)
  • Default From Path -> Initial path where to take the file from (NOTE: do not add the backslash at the end, the application will do this for you)
  • Default To Path -> Path where to copy/move files to (NOTE: do not add the backslash at the end, the application will do this for you)

 

2. Once you have completed setup as per point 1, now you are really ready to manage files using the RTC and simply run Page 50200 Client Files Management.

Action buttons Legend:

 Move Back - Move Forward

These are enabled since the beginning and permit the user to browse up and down the Directories hierarchy.

OPEN File - DELETE File

Once you have selected your source file (From File), these buttons will be shown. They permit the user to Open (a simple HYPERLINK) or delete the file selected. If the To file is selected (the "Done" option in the To File is selected) these actions refer to the To File. (Obviously you can change this how you wish, since you have the code under your fingers).

 

COPY File - MOVE File

These are the clue actions. They are enabled once the user has selected both the source file (From File) and the destination file (To File) and checked the 2 "Done" Options for "Manage File" and "Into File" groups (as reported in the first screenshot).

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

Best Regards,

Duilio Tacconi (dtacconi)

Microsoft Dynamics Italy

Microsoft Customer Service and Support (CSS) EMEA

How to change thousands/decimals separators in RDL reports

$
0
0

In "old good classic" NAV we have feature (and property) named AutoFormatType. Together with property AutoFormatExpr we can set decimal to any format we need.
This could be done in the way: we describe new AutoFormatType in the codeunit 1 in function "AutoFormatTranslate(AutoFormatType : Integer;AutoFormatExpr : Text[80]) : Text[80])"
For example I can describe that i want to have decimal in the way 1 234 567,89 and doesn't matter what regional settings i have, or i want to format decimal depending on "customer on sales invoice" language but without changes regional settings.
So this works in Classic Client reports but it doesn't in RDL, because RDL format decimals according regional settings or if say more precisely: according .NET culture settings.

But what to do if we want to format decimal on the same report regarding some national rules?
We want to have:
1 234 567,89
or
1.234.567,89
or
1,234,567.89

in ReportLayout design we can find that every Textbox has property named Language. Value could be: Default, Afrikaans,Afrikaans (South Africa) and etc. So if we for example set Language = French, then decimal will be 1 234 567,89; if Language=German (Germany) - number will be 1.234.567,89, if Language=English (United States) - number 1,234,567.89 and etc.

 

But this is "hardcode" language set. What if we want to set language dynamically? Lets say i want to set report language in request page and choose between: Default; English (US), German, French formats.

Then I open report in report designer and created variable "OptLang" type option and OptionString is Default,English (US),German (Germany),French.

Add this variable to Request page:

And in the sections I add textbox which SourceExp=OptLang and DataSetFieldName = Lang. Dont forget to set this textbox Visible=No as we need this textbox only to transfer value to RDL report.

Now in the RDL reportlayout we see datafield "Lang" in DataSet. It value could be the same as we set in option string, but ATTENTION: we can't directly fill this value to property Language. This property accepts only values as it are described in http://msdn.microsoft.com/en-us/goglobal/bb896001.aspx. So it is French we need to assign "fr-FR"; is English (US) then "en-US"; if German (Germany) then "de-DE"
Easest way to do that is to create new public Function in report properties code. This function returns correct value depending on value we send to it.

 

Now return to RDL sections, select whole row (or all textboxes) and in language parameter write in expression: =code.SetLang(Fields!Lang.Value)

 

Now save and compile everything.
Run report from RTC.
In open request page chose language you want and preview report.

 

You see that now decimals are displayed with separators we want.

German

French

English (US) 

 

That is all what i want to show.
BTW: how number will be displayed you can set in Format property by describing string like it is described at http://msdn.microsoft.com/en-us/library/dwhawy9k(v=VS.95).aspx

Special thanks to Claus and Roman :)

 

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

Best Regards

Gedas Busniauskas

Microsoft Lithuania
Microsoft Customer Service and Support (CSS) EMEA

Currency Exchange Rates from RSS

$
0
0

An often requested feature is automatic download of currency exchange rates, and this short article illustrates how you can build your own using RSS feeds and MS XMLDOM 6.0 which should work with all NAV versions 2.0 to 2009R2. Going forward, you should consider using DotNet types instead.

There are a lot of RSS feeds out there, some free and some not. The basis of all of them is that you can simply load them into an xml document and parse the data from there. The good news is that the RSS format is standardized. The bad news is that every RSS feed provider has a lot of freedom to specify the data as they choose. Therefore we will just look at one example using Yahoo, and you may build your own from there.

If you go to e.g. http://pipes.yahoo.com/pipes/pipe.run?_id=_kHVIdZ13hGXgt1VwmH_9A&_render=rss you will see a list of currencies and their exchange rates relative to USD as shown here:

If you download the rss feed and save it as an xml file, you get a layout similar to this:

<?xml version="1.0"?>

<rss version="2.0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/" xmlns:yt="http://gdata.youtube.com/schemas/2007">

  <channel>

    <title>USD Exchange Rates</title>

    <description>Grabs USD exchange rates from Yahoo Finance and outputs as RSS feed.</description>

    <link>http://pipes.yahoo.com/pipes/pipe.info?_id=_kHVIdZ13hGXgt1VwmH_9A</link>

    <pubDate>Mon, 18 Apr 2011 07:55:57 +0000</pubDate>

    <generator>http://pipes.yahoo.com/pipes/</generator>

    

      <item>

        <title>AED</title>

        <description>3.6729</description>

        <guid isPermaLink="false">1</guid>

        <pubDate>Mon, 18 Apr 2011 09:00:00 +0000</pubDate>

      </item>

      <item>

        <title>AFN</title>

        <description>43.00</description>

        <guid isPermaLink="false">2</guid>

        <pubDate>Mon, 18 Apr 2011 10:53:00 +0000</pubDate>

      </item>

And so on...

The task is then just to parse the information.

I have attached two objects in text format, table 50000 and codeunit 50000 that illustrate how you might implement some code that parses the input and updates the currency exchange rate table.

NOTE!! The code is sample code and serves only the purpose of illustration and cannot be guaranteed to work or to be correct, and Microsoft does not take any responsibility in how the code is utilized. Furthermore, there may be legal or financial responsibilities in utilizing a particular RSS feed, and the use of Yahoo's RSS feed is only one example.

-Bardur Knudsen


Getting Started with an Add-in Assembly for Microsoft Dynamics NAV 2009 R2

$
0
0

One of the new key features for Microsoft Dynamics NAV 2009 R2 is the interoperability extension with the .NET Framework, also called the DotNet interop feature. This feature allows the AL code to call out to .NET types and use the exposed methods and properties. The types can be system types installed with the .NET Framework, or other applications that register assemblies in the Global Assembly Cache (GAC), or it can be custom assemblies that are stored in subfolders relative to the Microsoft Dynamics NAV server or client.

This article will in a few words explain how to get started with an assembly that will be stored in the Add-in folder and the target audience is intended to be the AL developer that has no or very little experience with programming in the .NET environment.

Introduction

The following tasks will be described:

  1.  Implement and compile a demo assembly (with or without Visual Studio).
  2.  Deploy the compiled assembly.
  3.  Develop and test the demo assembly.
  4.  Improve deployment and security.

The demo assembly that will be used in this article will be kept very simple and is only intended as a visualization of the principles.

Implement and Compile a Demo Assembly (with or without Visual Studio)

The demo is based on a small assembly that only implements one type (class) that contains properties, a constructor and a method. Use your favorite text editor to create the C# file “DemoAddin.cs” containing the following code:

// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)

namespace DemoAddIn
{
    using System;
 
    public class DemoAddIn
    {
        /// <summary>
        /// Private field where the constructor stores the name of the 
        /// demo object.
        /// </summary>
        private readonly string demoName;
 
        /// <summary>
        /// Gets the private read only field demoName.
        /// </summary>
        public string DemoName { get return demoName; } }
 
        /// <summary>
        /// Backing field for the property
        /// </summary>
        private string getSetProperty;
 
        /// <summary>
        /// Gets or sets a property using a backing field. Don’t return null values to AL
        /// as this can give unpredictable results.
        /// </summary>
        public string GetSetProperty
        {
            get return getSetProperty ?? String.empty; }
            set { getSetProperty = value; }         }           /// <summary>         /// Initializes a new instance of the DemoAddIn class and assign a value to the         /// demoName field.         /// </summary>         /// <param name="name">Name of the demo object.</param>         public DemoAddIn(string name)         {             demoName = name;         }           /// <summary>         /// Sample function that illustrate how to use function parameters and          /// return data.         /// </summary>         /// <param name="arg">Function parameter</param>         /// <returns>The function arguemnt value or the constant string Empty if the          /// parameter is null or empty.</returns>         public string FunctionWithArgument(string arg)         {             return String.IsNullOrEmpty(arg) ? "Empty" : arg;         }     } }

The assembly can be compiled from any C# compiler, and two common setups are shown below:

Visual Studio

Create a new solution and project named “DemoAddIn”. The project must be created using the template type “Class Library”. By default, you will have a file called class1.cs, which can be deleted from the project. Add a new C# file to the project and implement the class listed above. It should not be necessary to change any project options and when you can build the output successfully, you are ready to deploy the assembly to the NAV clients and server. For information on how to create new C# projects in Visual Studio, please refer to the MSDN guide How to: Create Solutions and Projects.

Note: If you use Visual Studio 2010, please make sure that the target framework is set to .NET Framework 3.5 or 2.0.X. Microsoft Dynamics NAV 2009 R2 does not support assemblies compiled for .NET Framework 4.0. The framework version is set using the project properties and can be found in the Application tab.

The compiled output will be stored in a folder named “bin\Debug” beneath the project folder.

.NET Framework Compiler

The framework compiler is a simple command line tool that is installed together with the framework and is available on all machines that uses .NET Framework.

  1. Open a command prompt and navigate to the folder containing the DemoAddin .cs file
  2. Compile the file using the command (replace Framework64 with Framework if the compilation takes place in a 32 bit OS).

%windir%\Microsoft.NET\Framework64\v2.0.50727\csc.exe /debug /target:library DemoAddIn.cs

This will create two files in the current directory.

  1. DemoAddIn.dll, which is the assembly
  2. DemoAddIn.pdb, which contains symbol and debug information

When the assembly is final, then remove the /debug flag and add the desired optimizations. If the symbol file is desired, then add the option /debug:pdbonly.

Deploy the Compiled Assembly

The two files generated in the previous steps have to be deployed to the Add-ins folders within the product folders (it’s most convenient if the files are stored in a subfolder within the Add-ins folder; use the name DemoAddIn for the target folder):

  1. Design time using the Microsoft Dynamics NAV Classic Client.
    %ProgramFiles(x86)%\Microsoft Dynamics NAV\60\Classic\Add-ins\DemoAddIn
  2. Server service
    %ProgramFiles(x86)%\Microsoft Dynamics NAV\60\Service\Add-ins\DemoAddIn.
  3. The Microsoft Dynamics NAV RoleTailored client
    %ProgramFiles(x86)%\Microsoft Dynamics NAV\60\RoleTailored Client\Add-ins\DemoAddIn.

The environment variable %ProgramFiles(x86)% will point to the Program Files folder that is used for 32 bit applications in a 64 bit operating system. If the current operating system is 32 bit, then use %ProgramFiles%, which by default points to C:\Program Files.

Deployment during development of the Add-in assembly will be easier if a script like the following is created in the project folder:

@echo off
setlocal

set AssemblyBase=DemoAddIn

set root=%ProgramFiles(x86)%
if "%ProgramFiles(x86)%" == "" set root=%ProgramFiles%

call :deploy %AssemblyBase% "%root%\Microsoft Dynamics NAV\60\Classic"
call :deploy %AssemblyBase% "%root%\Microsoft Dynamics NAV\60\Service"
call :deploy %AssemblyBase% "%root%\Microsoft Dynamics NAV\60\RoleTailored Client"
goto :EOF

:deploy 

set AddInfolder="%~f2\Add-ins\%~1"
if not exist %AddInFolder% mkdir %AddInFolder%
if exist %~1.dll copy %~1.dll %AddInFolder%
if exist bin\debug\%~1.dll copy bin\debug\%~1.dll %AddInFolder%

if exist %~1.pdb copy %~1.pdb %AddInFolder%
if exist bin\debug\%~1.pdb copy bin\debug\%~1.pdb %AddInFolder%

goto :EOF

It will copy the assembly and symbol files to the respective Add-in folders. If the project is being managed by Visual Studio, the script can be called from the PostBuildEvent so the binaries are deployed automatically on every successful build. Notice that the NAV server and client must be stopped before a new version is deployed as the application will keep the files locked until they are stopped. It’s not possible to update any Add-in assembly while a previous version is loaded as the OS will lock the files as soon as an application has loaded it. Adding this to the deployment script can be an option, but care should be taken so data isn’t lost (don’t kill the wrong client or server). 

Develop and Test the AL Codeunit that Uses the Demo Assembly

Create a new codeunit in the Classic client and name it 'Demo AddIn' (use a free objectid in the 50.000-100.000 range).

In the OnRun trigger create the following variables:

Name DataType Subtype Length
demoAddIn DotNet DemoAddIn.DemoAddIn
demoString DotNet System.String (mscorlib)
textVar Text 512
intVar Integer

Note: for the Subtype of the DotNet DataType variables, use the assembly picker to select the DemoAddIn assembly from the “Dynamics NAV” tab and choose the type DemoAddIn.DemoAddIn from the “.NET Type List” that appears when the assembly is selected.

The sample code utilizing the demo AddIn can be implemented as:

// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)

// Create an instance of the DemoAddIn type. The constructor takes a string argument
// which is used to initialize an internal variable.
demoAddIn := demoAddIn.DemoAddIn('Input data');

// Use the property 'DemoName' to read the name set in the constructor.
MESSAGE('DemoAddIn contains  %1', demoAddIn.DemoName);

// Test that the property ‘GetSetProperty’ contains an empty string
if not demoString.IsNullOrEmpty(demoAddIn.GetSetProperty) then
  error('The uninitialized property should return an empty string');

// Use the property setter to assign an AL string to the internal CLR string variable.
demoAddIn.GetSetProperty := 'Value set with property';

// Read the property and see that the value has been set accordingly.
MESSAGE('DemoAddIn contains  %1', demoAddIn.GetSetProperty);

// Call a function in the AddIn that takes a string argument and returns the string
// given in the argument.
textVar := demoAddIn.FunctionWithArgument('Using FunctionWithArgument to change data');
MESSAGE('Argument data is %1', textVar);

Compile the codeunit in the classic client and run it through the Microsoft Dynamics NAV RoleTailered client (RTC). As it’s hard to run codeunits from the RTC client, it can be convenient to create a small page object from which a codeunit can be run.

The sample primarily uses types from the demo add-in, but uses also a method from the CLR String class to check the validity of a string value. Typical scenarios involve types from both user defined assemblies and the Framework libraries (very often from mscorlib, System, System.Xml). If you don’t know which assembly implements a given type, you can get the information from the MSDN online library on msdn.microsoft.com. Do a search for the type in the library and find the Assembly specification on the page the documents the class implementing the type.

Example: Search for System.String and select the first entry in the search results (will be “String Class (System)”). From the screen shot below, it’s seen that the String type is defined in the assembly “mscorlib”.

Suggested Reading

The MSDN documentation for Microsoft Dynamics NAV R2 contains an Update guide which documents the .NET interop feature in R2 and gives a couple of examples of simple use cases. See http://msdn.microsoft.com/en-us/library/gg502499.aspx for more information.

Another helpful source is the discussion forum on www.mibuso.com.

- Niels-Henrik Sejthen

Commerce Gateway with Isolated Hosts and Biztalk 2010

$
0
0

The following error may be encountered processing an XML document in the receive pipeline when using Commerce Gateway with Biztalk 2010 and Visual Studio 2010:

There was a failure executing the receive pipeline: "Microsoft.BizTalk.DefaultPipelines.XMLReceive, Microsoft.BizTalk.DefaultPipelines, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "XML disassembler" Receive Port: "Navision_to_CommerceGateway" URI: "CG://Navision_to_CommerceGateway" Reason: The document specification <Assembly Name> from assembly <Assembly Name, Version=1.0.0.0, Culture=neutral, PublicKeyToken=Token ID> failed to load. Verify the schema for this document specification is deployed and is in the Global Assembly Cache. 

In this scenario, Commerce Gateway Service hosts a BizTalk isolated host. The receive pipeline is executed within the context of this service. BizTalk Server 2010 by default deploys and looks for assemblies in the .Net Framework 4.0 Global Assembly Cache (GAC). The Commerce Gateway Service, however, by default runs on .NET Framework 2.0 and looks for assemblies in the .NET Framework 2.0 GAC. To work around this error, you can place the assemblies used by the receive pipeline in the .NET Framework 2.0 GAC.

To place these assemblies in the .NET Framework 2.0 GAC, use the gacutil tool, which ships with the .NET Framework. The tool will be located under <root>\ProgramFiles\MicrosoftSDKs\Windows\v6.0\Bin\gacutil.exe. Note that there might be multiple versions of the gacutil tool on your computer. To determine that you are using the correct version (version targeting .NET 2.0 and which will install into .NET 2.0 assembly cache), Open a command prompt as an administrator, browse to this path and type gacutil.exe. If you see a version number less than 4.0,, you have the correct version of the tool.

You can read more about how to use the tool to install assemblies into the GAC here: http://msdn.microsoft.com/en-us/library/ex0ss12c(v=vs.80).aspx.

Currency Exchange Rates from RSS (using .NET Interop)

$
0
0

This great blog post from Bardur Knudsen (Currency Exchange Rates from RSS) sounded to me like an invitation to extend it using .NET interop variables with Microsoft Dynamics NAV 2009 R2 based on System.XML namespace.

http://msdn.microsoft.com/en-us/library/system.xml(v=VS.80).aspx

Attached to this blog you will find 3 objects in TXT format (1 table, 1 codeunit, 1 page).

Inside the codeunit there is a useful code that may be worth looking at.

This code has been written to perform the same action and achieve the same result BUT using 3 different approaches in order to understand how .NET interop works compared with Automation Server usage.

Those 3 different approaches are:

  1. Use System.XML dotNet variables running at Service side (property RunOnClient = No)
  2. Use System.XML dotNet variables running at Role Tailored Client side (Property RunOnClient = Yes)
  3. Use MSXMLDOM automation (as it was in the previous blog post) at Client side.

 

 

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

Duilio Tacconi (dtacconi)

Microsoft Dynamics Italy

Microsoft Customer Service and Support (CSS) EMEA

Let NAV BEEP! (with .NET Interop and Microsoft Dynamics NAV 2009 R2)

$
0
0

It is known that in Role Tailored Based environment BEEP C/AL function is not supported.

http://msdn.microsoft.com/en-us/library/dd301405.aspx

This simple blog is based on SystemSounds Class from System.Media namespace

http://msdn.microsoft.com/en-us/library/ms143809.aspx 

My Ingredients :

  • NAV 2009 R2 Role Tailored Client
  • Windows 7 Enterprise

Attached you will find 1 unbounded page object in TXT format.

The code is fairly simple: there are just 5 lines of code related to 5 page actions.

dnBeep.Asterisk.Play();

dnBeep.Beep.Play();

dnBeep.Exclamation.Play;

dnBeep.Hand.Play();

dnBeep.Question.Play();

In order to have more fun with this object, I invite you to edit Control Panel:

Control Panel\Appearance and Personalization\Personalization

And push the sounds Action button (NOTE: normally “Question” is not present in system Themes) while switching between themes.

REMEMBER: Raise high the volume of your earphones / speakers.

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use.

Duilio Tacconi (dtacconi)

Microsoft Dynamics Italy

Microsoft Customer Service and Support (CSS) EMEA

Presence Control Add-In in Microsoft Dynamics NAV with Lync

$
0
0

Some may have seen this Add-In somewhere already as it has been around for some time. This is a high level explanation to how it was actually done.

First thing to mention here is that the base for this Add-In is a publicly available sample of Presence Controls intended for Office Communicator 2007. You can find the Sample Code, prerequisites and updates as well as related stuff here: Microsoft Office Communicator 2007 Presence Controls. You can use these Presence Controls with either Microsoft Office Communicator 2007 or with Microsoft Lync.

So in order to build this Sample Presence Add-In Control for Dynamics NAV, you can simply use the output from the above sample, which is a Set of Presence Controls contained in – PresenceControls.dll

Next, create a new Project and include a reference to the Presence Control Set – this is the Dynamics NAV Add-In Project.

For more details on the basics structure and references for a Dynamics NAV Add-In: How to: Create a RoleTailored Client Control Add-in.

After that you have your CreateControl() function which is returning the Add-In control to NAV. The two main things you want to return here are the Objects to be shown in your add-in on screen together The Presence Control and a Textbox: 

Dim persona As New persona ‘the presence control, a persona object from the Presence Controls Set

Dim txtbox As New System.Windows.Forms.TextBox ‘a regular text box

In order to display these two controls nicely “as one” you may want to place them in some kind of parent container/containers to have them Size and move properly together. I used System.Windows.Forms.TableLayoutPanel but there are probably better ways to do this.

Finally you will have to need to ensure the control is updated properly:

The Persona Control has a SipUri property, which is basically what you need to bind your Add-In Value to, so you need to ensure that changes to the value are passed both to the Textbox and the Persona Object.

As usual there are a number of ways some nicer than other to achieve this, you could either bind the Persona property SipUri to the Textbox Value itself or handle the updates of the value to both controls manually in your add-in, but the basics are to simply ensure the SipUri of the Persona always needs to be kept as the same as the value of the TextBox, for example:

' Copyright © Microsoft Corporation. All Rights Reserved.
' This code released under the terms of the
' Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
Public Property Value() As String Implements Microsoft.Dynamics.Framework.UI.Extensibility.IValueControlAddInDefinition(Of String).Value
        Get
            Return txtbox.Text
        End Get
        Set(ByVal value As String)
           persona.SipUri = value
           txtbox.Text = value
       End Set
End Property

If all works out well you’ll end up with an Add-in control looking something like this. (See video)

-Johan Emilsson

Viewing all 773 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>