<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Freddys Blog</title><link>http://blogs.msdn.com/freddyk/default.aspx</link><description /><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP1 (Build: 61025.2)</generator><item><title>Displaying Company information in Card pages</title><link>http://blogs.msdn.com/freddyk/archive/2009/11/29/displaying-company-information-in-card-pages.aspx</link><pubDate>Sun, 29 Nov 2009 20:03:32 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9929885</guid><dc:creator>freddyk</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9929885.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9929885</wfw:commentRss><description>&lt;p&gt;I received a question from a customer, who is running multiple companies and often have multiple instances of NAV open with different companies. On the main page, they do have information about what company is the active:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/DisplayingCompanyinformationinCardpages_A991/image_2.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/DisplayingCompanyinformationinCardpages_A991/image_thumb.png" width="506" height="284" /&gt;&lt;/a&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;but when they have opened a number of Task Pages in the various instances of NAV, they cannot distinguish one from the other.&lt;/p&gt;  &lt;p&gt;Example:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/DisplayingCompanyinformationinCardpages_A991/image_4.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/DisplayingCompanyinformationinCardpages_A991/image_thumb_1.png" width="439" height="312" /&gt;&lt;/a&gt;&amp;#160; &lt;/p&gt;  &lt;p&gt;This image tells you the page and the Customer name – and you can easily identify the right page when Alt+TAB’ing between pages, but if you have multiple companies this doesn’t help you a lot.&lt;/p&gt;  &lt;h3&gt;So what determines the caption?&lt;/h3&gt;  &lt;p&gt;The fields used in the caption on a page is determined by:&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;DataCaptionExpr&lt;/strong&gt; on the page. This is an expression, which can use fields, functions etc. to build up a caption. If that isn’t defined, the client looks for&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;DataCaptionFields&lt;/strong&gt; on the page. This is a collection of fields, which are used to build the caption by adding them together with a character 183 (middle dot) between them. If that isn’t defined, the client looks for&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;DataCaptionFields&lt;/strong&gt; on the table, which basically is the same as DataCaptionFields on the page.&lt;/p&gt;  &lt;p&gt;In a standard NAV, there is no DataCaptionExpr nor DataCaptionFields defined on the Customer Card, but on the Customer table you find:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/DisplayingCompanyinformationinCardpages_A991/image_6.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/DisplayingCompanyinformationinCardpages_A991/image_thumb_2.png" width="531" height="297" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;In order to add the Company name behind the caption you will need to change the DataCaptionExpr on the Customer Card to f.ex.&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;quot;No.&amp;quot; + ' · ' + Name + ' ['+COMPANYNAME+']'&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;which would cause the Customer Card to look like&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/DisplayingCompanyinformationinCardpages_A991/image_8.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/DisplayingCompanyinformationinCardpages_A991/image_thumb_3.png" width="602" height="399" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;You can of course select to change the expression to whatever you like – or maybe create some function, which automagically returns a caption, only real flipside is that you need to modify the card pages, on which you need this functionality. In the end this is probably not a very large number.&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;font color="#0080ff"&gt;Freddy Kristiansen&lt;/font&gt;&lt;/strong&gt;      &lt;br /&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9929885" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/Client+Tier/default.aspx">Client Tier</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009+SP1/default.aspx">NAV 2009 SP1</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/COMPANY/default.aspx">COMPANY</category></item><item><title>What COMPANY to use?</title><link>http://blogs.msdn.com/freddyk/archive/2009/11/29/what-company-to-use.aspx</link><pubDate>Sun, 29 Nov 2009 18:22:33 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9929875</guid><dc:creator>freddyk</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9929875.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9929875</wfw:commentRss><description>&lt;p&gt;As you know, when creating an application consuming NAV Web Services you need to specify the Company name as part of the URL to the Web Service, but what company should you be using?&lt;/p&gt;  &lt;p&gt;Some applications are web front-ends placing data from the web application into NAV. For applications like this you typically would have a config file in which you specify what company things needs to go to. For these applications, this post adds no further value.&lt;/p&gt;  &lt;p&gt;Other applications are integration applications, like a lot of the applications you can see on my blog:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Search &lt;/li&gt;    &lt;li&gt;“My” gadgets &lt;/li&gt;    &lt;li&gt;MAP &lt;/li&gt;    &lt;li&gt;Edit In Excel &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;for all of these applications, it really doesn’t make sense to run with a different company than the users default company.&lt;/p&gt;  &lt;p&gt;Example – if you search through your NAV data – you really want to search through the data in the active company – not just any company.&lt;/p&gt;  &lt;p&gt;Wouldn’t it be nice if you could type in the URL&lt;/p&gt;  &lt;p&gt;&lt;a href="http://localhost:7047/DynamicsNAV/WS/&amp;lt;default&amp;gt;/Codeunit/Search"&gt;/Codeunit/Search&amp;quot;&amp;gt;http://localhost:7047/DynamicsNAV/WS/&amp;lt;default&amp;gt;/Codeunit/Search&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;and then the &lt;strong&gt;&amp;lt;default&amp;gt;&lt;/strong&gt; would be replaced by the authenticated users default company – unfortunately this doesn’t work (I have suggested this feature for v7 though:-)). Instead, we have to do the work in the Web Service consuming application. Easiest solution is of course to create a Codeunit with a function, returning the default company of a user, call that and then build your URL for calling the Page / Codeunit web service.&lt;/p&gt;  &lt;p&gt;A function like that could be:&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;&lt;u&gt;GetDefaultCOMPANY() : Text[30]           &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;Session.SETRANGE(&amp;quot;My Session&amp;quot;,TRUE);        &lt;br /&gt;Session.FINDFIRST;        &lt;br /&gt;WindowsLogin.SETRANGE(ID,Session.&amp;quot;User ID&amp;quot;);        &lt;br /&gt;WindowsLogin.FINDFIRST;        &lt;br /&gt;UserPers.SETRANGE(&amp;quot;User SID&amp;quot;,WindowsLogin.SID);        &lt;br /&gt;UserPers.FINDFIRST;        &lt;br /&gt;EXIT(UserPers.Company);&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;The problem with this approach is (as you probably already figured out) that every call to a Web Service will require 2 roundtrips instead of one and for Page based Web Service access there really isn’t much you can do better.&lt;/p&gt;  &lt;p&gt;For Codeunit based Web Service access you can however avoid a lot of these roundtrips by using a very simple pattern in the way you write your functions. I have rewritten my search method to return a Text[30] and start off with the following lines of code:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;company := GetDefaultCOMPANY();     &lt;br /&gt;IF company &amp;lt;&amp;gt; COMPANYNAME THEN      &lt;br /&gt;&amp;#160; EXIT(company);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and the consumer will have to build up the URL for the Web Service in code with whatever company (the first in the list of companies would be just fine), call the web service and if it returns a different company than the one used to invoke the web service, build a new URL and try again.&lt;/p&gt;  &lt;p&gt;In the Search gadget this would look like (the lines in Red are the important changes)&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// the &amp;quot;real&amp;quot; search function     &lt;br /&gt;function doSearch(searchstring) {      &lt;br /&gt;&lt;font color="#ff0000"&gt;&amp;#160;&amp;#160;&amp;#160; specifiedCompany = GetCompany();       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; usedCompany = specifiedCompany;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; if (specifiedCompany == &amp;quot;default&amp;quot;) {        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (myCompany == &amp;quot;&amp;quot;) {        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Companies = GetCompanies();        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (Companies != null)        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; myCompany = Companies[0].text;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; usedCompany = myCompany;        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; // Get the URL for the NAV 2009 Search Codeunit     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; var URL = GetBaseURL() + encodeURIComponent(usedCompany) + &amp;quot;/Codeunit/Search&amp;quot;; &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; // Create XMLHTTP and send SOAP document     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; xmlhttp = new ActiveXObject(&amp;quot;Msxml2.XMLHTTP.6.0&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; xmlhttp.open(&amp;quot;POST&amp;quot;, URL, false, null, null);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; xmlhttp.setRequestHeader(&amp;quot;Content-Type&amp;quot;, &amp;quot;text/xml; charset=utf-8&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; xmlhttp.setRequestHeader(&amp;quot;SOAPAction&amp;quot;, &amp;quot;DoSearch&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; xmlhttp.Send('&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;&amp;lt;soap:Envelope xmlns:soap=&amp;quot;' + SoapEnvelopeNS + '&amp;quot;&amp;gt;&amp;lt;soap:Body&amp;gt;&amp;lt;DoSearch xmlns=&amp;quot;' + CodeunitSearchNS + '&amp;quot;&amp;gt;&amp;lt;searchstring&amp;gt;' + searchstring + '&amp;lt;/searchstring&amp;gt;&amp;lt;result&amp;gt;&amp;lt;/result&amp;gt;&amp;lt;/DoSearch&amp;gt;&amp;lt;/soap:Body&amp;gt;&amp;lt;/soap:Envelope&amp;gt;'); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; // Find the result in the soap result and return the rsult     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; xmldoc = xmlhttp.ResponseXML;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; xmldoc.setProperty('SelectionLanguage', 'XPath');      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; xmldoc.setProperty('SelectionNamespaces', 'xmlns:soap=&amp;quot;' + SoapEnvelopeNS + '&amp;quot; xmlns:tns=&amp;quot;' + CodeunitSearchNS + '&amp;quot;'); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#ff0000" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; userCompany = xmldoc.selectSingleNode('//tns:return_value');     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; myCompany = userCompany.text; &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#ff0000" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; if ((specifiedCompany == &amp;quot;default&amp;quot;) &amp;amp;&amp;amp; (myCompany != usedCompany)) {     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Default company has changed - research      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return doSearch(searchstring);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160; … do the actual searching&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;}&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;In this sample I use three variables:&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;specifiedCompany&lt;/strong&gt; is the company specified in the config file (&lt;em&gt;default&lt;/em&gt; means use users default company)&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;usedCompany&lt;/strong&gt; is the company used to invoke the last WS method&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;myCompany&lt;/strong&gt; is my current belief of the users current company, which gets replaced if a method returns a new default company.&lt;/p&gt;  &lt;p&gt;Using a pattern like this will help lowering the number of round trips and still allow your consuming application to use the users default company.&lt;/p&gt;  &lt;p&gt;This “trick” is only possible in NAV 2009 SP1. NAV 2009 RTM will change the users default company to the company you use to invoke the Web Service with – which again will cause the above function to always return the same company name as the one you invoke the Web Service with.&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;font color="#0080ff"&gt;Freddy Kristiansen&lt;/font&gt;&lt;/strong&gt;      &lt;br /&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9929875" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/Web+Services/default.aspx">Web Services</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/URL/default.aspx">URL</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Gadget/default.aspx">Gadget</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009+SP1/default.aspx">NAV 2009 SP1</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/COMPANY/default.aspx">COMPANY</category></item><item><title>Extending page Web Services (and creating a Sales Order again)</title><link>http://blogs.msdn.com/freddyk/archive/2009/11/17/extending-page-web-services-and-creating-a-sales-order-again.aspx</link><pubDate>Tue, 17 Nov 2009 14:07:35 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9923569</guid><dc:creator>freddyk</dc:creator><slash:comments>2</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9923569.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9923569</wfw:commentRss><description>&lt;p&gt;It has been working in the same way since NAV 2009, but I still get asked often how this works, so why not write up a quick post on this. I also realize that my prior post on how to create Sales Orders through Web Services was made very complex due to compatibility with NAV 2009.&lt;/p&gt;  &lt;p&gt;This post only works in NAV 2009 SP1 and will show how to extend the Order page with a Post function and show how to Create a Sales Order from C# and post it.&lt;/p&gt;  &lt;h3&gt;Extending the page&lt;/h3&gt;  &lt;p&gt;First of all, we need to create a codeunit with the function, we want to add to the Order page.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/ExtendingpageWebServicesandcreatingaSale_561D/image_2.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/ExtendingpageWebServicesandcreatingaSale_561D/image_thumb.png" width="580" height="355" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Then we expose this codeunit with the same name as the page we want to extend, without putting a check in the published column&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/ExtendingpageWebServicesandcreatingaSale_561D/image_4.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/ExtendingpageWebServicesandcreatingaSale_561D/image_thumb_1.png" width="640" height="222" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; All functions in the codeunit needs to have the first parameter be of the same type as the base record as the page you want to extend, else the page will no longer be available and you will get an error in the event log on the Service Tier.&lt;/p&gt;  &lt;p&gt;Now taking a look at the WSDL in a browser will show us the new function as a first class citizen&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/ExtendingpageWebServicesandcreatingaSale_561D/image_6.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/ExtendingpageWebServicesandcreatingaSale_561D/image_thumb_2.png" width="930" height="593" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;and we can start using this.&lt;/p&gt;  &lt;h3&gt;Creating a Sales Order through Web Services&lt;/h3&gt;  &lt;p&gt;This might seem like repeating myself from a prior &lt;a href="http://blogs.msdn.com/freddyk/archive/2009/05/28/handling-sales-orders-from-page-based-web-services-in-nav-2009sp1-and-rtm.aspx" target="_blank"&gt;post&lt;/a&gt;, but that post did contain a lot of other information, which really isn’t necessary if you only target SP1.&lt;/p&gt;  &lt;p&gt;Creating an order is a 3 step process:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Create the Order Header&lt;/li&gt;    &lt;li&gt;Fill out the Order Header and create the Order lines&lt;/li&gt;    &lt;li&gt;Fill out the Order lines&lt;/li&gt; &lt;/ol&gt;  &lt;h3&gt;Creating the Order header&lt;/h3&gt;  &lt;p&gt;Is really simple&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;Order_Service service = new Order_Service();     &lt;br /&gt;service.UseDefaultCredentials = true; &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;Order order = new Order();     &lt;br /&gt;service.Create(ref order); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;After this we have a Order Number and an empty order – exactly like leaving the order No. field on the Sales Order Page.&lt;/p&gt;  &lt;h3&gt;Fill out the Order Header and create the Order lines&lt;/h3&gt;  &lt;p&gt;In this sample I will just fill out the Sell_to_Customer_No – a number of the other Order Header fields will be auto-updated when updating the order&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;order.Sell_to_Customer_No = &amp;quot;10000&amp;quot;;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Then we need to create the Order lines – in this sample I will create 5. BTW – It is NOT trivial to add an order line after the fact, so I suggest you add the needed number of lines in one go:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;order.SalesLines = new Sales_Order_Line[5];     &lt;br /&gt;for (int i = 0; i &amp;lt; 5; i++)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; order.SalesLines[i] = new Sales_Order_Line();      &lt;br /&gt;service.Update(ref order); &lt;/font&gt;&lt;/p&gt;  &lt;h3&gt;Fill out the Order lines&lt;/h3&gt;  &lt;p&gt;In this sample, I will just create 5 lines with green ROME guest chairs.&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;for (int i = 0; i &amp;lt; 5; i++)     &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; order.SalesLines[i].Type = OrderPageRef.Type.Item;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; order.SalesLines[i].No = &amp;quot;1960-S&amp;quot;;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; order.SalesLines[i].Quantity = 1;      &lt;br /&gt;}      &lt;br /&gt;service.Update(ref order); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;That’s it – the order is created and you can find it in the Client.&lt;/p&gt;  &lt;h3&gt;And at last… – Post the order&lt;/h3&gt;  &lt;p&gt;Having created the order, now it is time to post the order&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;service.PostOrder(order.Key); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;As you can see, the function takes a Record parameter, but we give it a Key.&lt;/p&gt;  &lt;p&gt;Note, that calling a function does not make an implicit Update – meaning that if you have done changes to the record in C# and call the function, you will get an error when calling update later. Reason – the PostOrder function has changed the record and will tell you that the record was changed by another user.&lt;/p&gt;  &lt;p&gt;After calling a function on a page you will need to Re-read the record if you need to do more work.&lt;/p&gt;  &lt;p&gt;That’s it&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;font color="#0080ff"&gt;&lt;strong&gt;Freddy Kristiansen&lt;/strong&gt;&lt;/font&gt;       &lt;br /&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9923569" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/Web+Services/default.aspx">Web Services</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Sales+Order/default.aspx">Sales Order</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009+SP1/default.aspx">NAV 2009 SP1</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Page+Extension/default.aspx">Page Extension</category></item><item><title>Synchronize A/D users to NAV</title><link>http://blogs.msdn.com/freddyk/archive/2009/11/16/synchronize-a-d-users-to-nav.aspx</link><pubDate>Mon, 16 Nov 2009 17:58:24 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9923059</guid><dc:creator>freddyk</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9923059.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9923059</wfw:commentRss><description>&lt;p&gt;During my work with demos like Edit In Excel, I wanted to make sure that these things would work in all localized versions of NAV 2009 SP1 – meaning that I needed to install 14 different databases and 14 running Service Tier’s. Having done that, I also wanted to allow my colleagues who needed to check something, access to these service tiers.&lt;/p&gt;  &lt;p&gt;For a geek (like me:-)), that problem looks like something you need to write an application for, even though it probably takes more time than it would be to add the users one by one whenever needed, but it certainly is more fun to write the application – and… maybe somebody else can actually use the ideas from this to do something cool.&lt;/p&gt;  &lt;h3&gt;How to do?&lt;/h3&gt;  &lt;p&gt;Basically what I want to do is, to enumerate the Remote Desktop Users of the computer and make sure that these users are SUPER in NAV. Now, I can do this in NAV calling out to a COM object enumerating my users – but that really wouldn’t help me, because I would have to start NAV with every service tier or database and launch that action.&lt;/p&gt;  &lt;p&gt;So, first I created a codeunit in NAV, exposed this codeunit as Web Services. Then I created a console application running on a schedule on my server, which enumerates the users and invoke the Web Service function to check all users are created in NAV.&lt;/p&gt;  &lt;p&gt;Of course the scheduled application has to run with elevated permissions and that user needs to be able to change users in NAV as well.&lt;/p&gt;  &lt;h3&gt;The NAV code&lt;/h3&gt;  &lt;p&gt;The codeunit contains two functions:&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;&lt;u&gt;ImportUserSID(SID : Text[119];Role : Code[20]) : Boolean           &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;IF NOT Winlogin.GET(SID) THEN        &lt;br /&gt;&amp;#160; EXIT(FALSE);        &lt;br /&gt;Winlogin.INIT;        &lt;br /&gt;Winlogin.SID := SID;        &lt;br /&gt;Winlogin.INSERT();        &lt;br /&gt;IF NOT WinAccess.GET(SID) THEN        &lt;br /&gt;BEGIN        &lt;br /&gt;&amp;#160; WinAccess.INIT;        &lt;br /&gt;&amp;#160; WinAccess.&amp;quot;Login SID&amp;quot; := SID;        &lt;br /&gt;&amp;#160; WinAccess.&amp;quot;Role ID&amp;quot; := Role;        &lt;br /&gt;&amp;#160; WinAccess.INSERT;        &lt;br /&gt;END;        &lt;br /&gt;EXIT(TRUE);&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;SynchronizeUsers()         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;DATABASE.SYNCHRONIZEALLLOGINS();&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;BTW. Calling SynchronizeAllLogins running through Web Services didn’t seem to work when called through Web Services and running enhanced database security – whether this is a bug or a problem in my setup – I don’t know, but I am going to file a bug on it.&lt;/p&gt;  &lt;p&gt;As you can see, the ImportUserSID does not modify the role if the user is already in the login table – it could of course be modified to do that easily, but it wasn’t necessary for my usage.&lt;/p&gt;  &lt;h3&gt;The C# code for enumerating an A/D group&lt;/h3&gt;  &lt;p&gt;Lets just look at the code:&lt;/p&gt;  &lt;p&gt;&lt;font color="#800080" face="Courier New"&gt;DirectoryEntry dir = new DirectoryEntry(&amp;quot;WinNT://localhost/Administrators&amp;quot;);     &lt;br /&gt;Console.WriteLine(&amp;quot;Enumerating users&amp;quot;);      &lt;br /&gt;foreach (object obj in (IEnumerable)dir.Invoke(&amp;quot;members&amp;quot;))      &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DirectoryEntry user = new DirectoryEntry(obj);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(user.Path);      &lt;br /&gt;}      &lt;br /&gt;Console.WriteLine(&amp;quot;Done&amp;quot;);      &lt;br /&gt;Console.ReadLine();&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Running this on my computer outputs the following:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/SynchronizeADuserstoNAV_7E8F/image_2.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/SynchronizeADuserstoNAV_7E8F/image_thumb.png" width="677" height="342" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;The next step is, to get the SID’s for each user – now you would think that there should be an SID function on the user object returning a string, but unfortunately it isn’t that simple. Luckily somebody invented the Internet – and luckily somebody was kind enough to post some information for us to use.&lt;/p&gt;  &lt;p&gt;Have a look at &lt;a title="http://www.netomatix.com/GetUserSid.aspx" href="http://www.netomatix.com/GetUserSid.aspx"&gt;http://www.netomatix.com/GetUserSid.aspx&lt;/a&gt; where I found the following code, which seems to work for the purpose.&lt;/p&gt;  &lt;p&gt;On the user object you have a property collection. One of these properties is called objectSid, which is a byte[] and that can be transformed into a SID string using the following function:&lt;/p&gt;  &lt;p&gt;&lt;font color="#800080" face="Courier New"&gt;private static string ConvertByteToStringSid(Byte[] sidBytes)     &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; StringBuilder strSid = new StringBuilder();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; strSid.Append(&amp;quot;S-&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; try      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Add SID revision.      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; strSid.Append(sidBytes[0].ToString());      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Next six bytes are SID authority value.      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (sidBytes[6] != 0 || sidBytes[5] != 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; string strAuth = String.Format      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; (&amp;quot;0x{0:2x}{1:2x}{2:2x}{3:2x}{4:2x}{5:2x}&amp;quot;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; (Int16)sidBytes[1],      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; (Int16)sidBytes[2],      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; (Int16)sidBytes[3],      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; (Int16)sidBytes[4],      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; (Int16)sidBytes[5],      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; (Int16)sidBytes[6]);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; strSid.Append(&amp;quot;-&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; strSid.Append(strAuth);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; else      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Int64 iVal = (Int32)(sidBytes[1]) +      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; (Int32)(sidBytes[2] &amp;lt;&amp;lt; 8) +      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; (Int32)(sidBytes[3] &amp;lt;&amp;lt; 16) +      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; (Int32)(sidBytes[4] &amp;lt;&amp;lt; 24);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; strSid.Append(&amp;quot;-&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; strSid.Append(iVal.ToString());      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#800080" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Get sub authority count...     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; int iSubCount = Convert.ToInt32(sidBytes[7]);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; int idxAuth = 0;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; for (int i = 0; i &amp;lt; iSubCount; i++)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; idxAuth = 8 + i * 4;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; UInt32 iSubAuth = BitConverter.ToUInt32(sidBytes, idxAuth);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; strSid.Append(&amp;quot;-&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; strSid.Append(iSubAuth.ToString());      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; catch (Exception ex)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return &amp;quot;&amp;quot;;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; return strSid.ToString();      &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Using this function, you can now write out the SID’s for each user:&lt;/p&gt;  &lt;p&gt;&lt;font color="#800080" face="Courier New"&gt;DirectoryEntry dir = new DirectoryEntry(&amp;quot;WinNT://localhost/Administrators&amp;quot;);     &lt;br /&gt;Console.WriteLine(&amp;quot;Enumerating users&amp;quot;);      &lt;br /&gt;foreach (object obj in (IEnumerable)dir.Invoke(&amp;quot;members&amp;quot;))      &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; DirectoryEntry user = new DirectoryEntry(obj);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(user.Path);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; System.DirectoryServices.PropertyCollection col = user.Properties;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; byte[] sidBytes = col[&amp;quot;objectSid&amp;quot;].Value as byte[];      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; if (sidBytes != null)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; string strSid = ConvertByteToStringSid(sidBytes);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (!string.IsNullOrEmpty(strSid))      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(&amp;quot;SID=&amp;quot; + strSid);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;}       &lt;br /&gt;&lt;/font&gt;&lt;font color="#800080" face="Courier New"&gt;Console.WriteLine(&amp;quot;Done&amp;quot;);     &lt;br /&gt;Console.ReadLine(); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Of course we are not in the business of writing SID’s for users in a console application, but you should now have the building blocks for creating whatever mechanism to add A/D users to NAV, exposing the codeunit we talked about at first and then calling these web services functions from the console application.&lt;/p&gt;  &lt;p&gt;Good luck&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;font color="#0080ff"&gt;&lt;strong&gt;Freddy Kristiansen&lt;/strong&gt;        &lt;br /&gt;&lt;/font&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;font color="#800080" face="Courier New"&gt;&lt;/font&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9923059" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/Web+Services/default.aspx">Web Services</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/A_2F00_D/default.aspx">A/D</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009+SP1/default.aspx">NAV 2009 SP1</category></item><item><title>Directions US 2009</title><link>http://blogs.msdn.com/freddyk/archive/2009/11/16/directions-us-2009.aspx</link><pubDate>Mon, 16 Nov 2009 14:09:11 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9922935</guid><dc:creator>freddyk</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9922935.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9922935</wfw:commentRss><description>&lt;p&gt;Directions US 2009 is over – running from 11/10 – 11/14 with a serious of deep dive sessions on Saturday. The entire event was professionally executed and the quality of everything was just great. A huge congratulations to the comity on this event, a huge congratulations to the partner community for having these events and a huge thank you from Microsoft for inviting us to be part of this.&lt;/p&gt;  &lt;p&gt;I had the pleasure of hosting 3 sessions – one on “What’s new in NAV 2009SP1 for the developer” and 2 deep dive sessions on Web Services as the 2 final sessions on Saturday, and we actually had a pretty good number of people staying for these very last sessions.&lt;/p&gt;  &lt;h3&gt;So how did it go?&lt;/h3&gt;  &lt;p&gt;I liked the session survey’s! They had a question called: “Do I want to see this speaker again next year?” – which instead of trying to analyze other responses gives a direct indication of whether I will plan a trip to San Diego next year – I actually don’t know whether or not we will get the survey results…&lt;/p&gt;  &lt;p&gt;If I where to grade my own performance, I would probably give me a B- for the What’s new session and an A for my deep dive sessions. When starting the What’s new session, I had a section prepared about what’s new for the user, but the majority of this was already shown in the Keynote. I decided to ask how many people attended the keynote on Tuesday – and then quickly go over the demo for the people that did NOT attend the keynote, so I did…&lt;/p&gt;  &lt;p&gt;&lt;em&gt;“How many people did NOT attend the Keynote?”&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;was the question – and only one single person raised his hand… (which actually was my own Manager in Microsoft – so I knew that shouldn’t do a what’s new for the user for him alone). This totally took me by surprise me and I ended up finalizing my session 10 minutes early.&lt;/p&gt;  &lt;p&gt;The deep dive sessions went better – people where active asking questions and understanding things and I had absolutely no problem filling out 2 hours talking about Web Services – without just repeating things that everybody have seen before. The majority of the audience knew about Web Services and had also tried using Web Services before – that was very good.&lt;/p&gt;  &lt;p&gt;I wanted to do a recording of the session and make it available here – that did not work for me. Instead I said that I would post info about all the different areas covered in the session on my blog (BTW – I was happy to see, that the majority of the audience knew my blog). The things covered in my session, which you will see as posts on my blog are:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Codeunit web services and data types&lt;/li&gt;    &lt;li&gt;Page web services and filters&lt;/li&gt;    &lt;li&gt;Extending page web services&lt;/li&gt;    &lt;li&gt;Using XMLports to read and write data&lt;/li&gt;    &lt;li&gt;What Company to use&lt;/li&gt;    &lt;li&gt;Using NAV Web Services from&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;PHP&lt;/li&gt;      &lt;li&gt;Java&lt;/li&gt;      &lt;li&gt;C#&lt;/li&gt;      &lt;li&gt;VB&lt;/li&gt;      &lt;li&gt;VBScript&lt;/li&gt;      &lt;li&gt;Javascript&lt;/li&gt;      &lt;li&gt;Silverlight&lt;/li&gt;      &lt;li&gt;Mobile&lt;/li&gt;      &lt;li&gt;C/AL&lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;Edit In Excel R2 (I actually promised that long time ago)&lt;/li&gt;    &lt;li&gt;Synchronize A/D users to NAV&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;In the session I only had samples in PHP and Java – I will write up a end-to-end scenario and implement this in all these languages/platforms so that people can overcome the initial connection/interaction problems when using NAV Web Services.&lt;/p&gt;  &lt;p&gt;A lot of things to do – so I better get going…&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;font color="#0080ff"&gt;&lt;strong&gt;Freddy Kristiansen&lt;/strong&gt;        &lt;br /&gt;&lt;/font&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9922935" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/Directions/default.aspx">Directions</category></item><item><title>Timer events on a page</title><link>http://blogs.msdn.com/freddyk/archive/2009/11/03/timer-events-on-a-page.aspx</link><pubDate>Tue, 03 Nov 2009 17:13:43 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9916852</guid><dc:creator>freddyk</dc:creator><slash:comments>2</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9916852.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9916852</wfw:commentRss><description>&lt;p&gt;Have you ever wanted to have an event raised every 10th second on a page in the RoleTailored Client?&lt;/p&gt;  &lt;p&gt;Wait no more – here is how you can do just that in Microsoft Dynamics NAV 2009SP1.&lt;/p&gt;  &lt;h3&gt;A Timer control is a Non-Visual Add-In&lt;/h3&gt;  &lt;p&gt;I have seen a number of development platforms treat a Timer as a Non-Visual Add-In (including .net) – so I thought I would try to create a non-visual Add-In for NAV – and what better than create the Timer. A Timer should not be visible to the user, but it should be able to raise events.&lt;/p&gt;  &lt;p&gt;There are different ways to create a Non-Visual control, but the most obvious method will not work.&lt;/p&gt;  &lt;p&gt;Adding a control and setting Visible to FALSE – will cause the control to be optimized away – it will never be created.&lt;/p&gt;  &lt;p&gt;You can however create a Non-Visual control in other ways:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Set the visible property to a global variable, which is false.&lt;/li&gt;    &lt;li&gt;Set the size (and MinSize, MaxSize) of the Control to 0, 0.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The first approach would require you to add a variable called something like falsevar on each page you use the Timer Control – and that isn’t really what we want – so I will use the second approach.&lt;/p&gt;  &lt;h3&gt;Well – then everything seems pretty simple – right?&lt;/h3&gt;  &lt;p&gt;Yes and No.&lt;/p&gt;  &lt;p&gt;It is very simple to create a non-visual control which instantiates a timer and fires events – Yes, but what if the service tier opens up a modal dialog (like a CONFIRM command) – then I would suggest that we do NOT keep firing events.&lt;/p&gt;  &lt;p&gt;For this purpose our control needs to subscribe to two application level events.&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.application.enterthreadmodal.aspx" target="_blank"&gt;Application.EnterThreadModal&lt;/a&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.application.leavethreadmodal.aspx" target="_blank"&gt;Application.LeaveThreadModal&lt;/a&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;What is my Application? Well, that is of course the RoleTailored Client. Your WinForms Control gets created as a first class citizen in the RoleTailored Client and of course you have access to the Application events as well. In fact there are all kinds of things you can do and all kinds of things you shouldn’t do.&lt;/p&gt;  &lt;p&gt;Always bare in mind that if you start to go outside the control itself – think whether this is necessary, think future compatibility if the RoleTailored Client changes various things and remember to clean up.&lt;/p&gt;  &lt;p&gt;For the two events above – they are pretty clear – EnterThreadModal is fired when the application enters Modal state and LeaveThreadModal is fired when the application leaves the modal state.&lt;/p&gt;  &lt;h3&gt;Remember to clean up – your mother isn’t here!&lt;/h3&gt;  &lt;p&gt;When coding in .net you often don’t need to consider cleaning up – the garbage collector will come and clean everything up. Now that isn’t always true.&lt;/p&gt;  &lt;p&gt;In the case of the Application Level events – when you subscribe to an event, you actually give the Application object a pointer to your object – telling it to call you whenever something happens. This in fact means that the garbage collector is not allowed to cleanup anymore – it doesn’t matter that the page is closed, your control is gone – the Application object still maintains a reference to your object and therefore it will stay.&lt;/p&gt;  &lt;p&gt;Of course this doesn’t apply when you subscribe to events in your own control, since the object holding the reference to your object goes out of scope at the same time as yourself.&lt;/p&gt;  &lt;p&gt;Hmmm – admitted – I am probably getting too nerdy now – but it is rather important to understand this in order to avoid memory leaks and these memory leaks will affect the RoleTailored Client – not only your Add-In.&lt;/p&gt;  &lt;p&gt;Instead of going further into detail – the curious read can read much more about garbage collection on msdn: &lt;a href=" http://msdn.microsoft.com/en-us/library/ms973837.aspx" target="_blank"&gt;Garbage Collector Basics and Performance Hints&lt;/a&gt;.&lt;/p&gt;  &lt;h3&gt;Let’s look at the code&lt;/h3&gt;  &lt;p&gt;The way I have implemented the Timer control is like this&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;[ControlAddInExport(&amp;quot;FreddyK.TimerControl&amp;quot;)]     &lt;br /&gt;public class TimerControl : StringControlAddInBase, IStringControlAddInDefinition      &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; EventHandler EnterThreadModal;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; EventHandler LeaveThreadModal;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Timer timer = null;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; int interval = 0;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; int count = 0; &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Constructor - Setup timer and Application event subscriptions      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public TimerControl()      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; EnterThreadModal = new EventHandler(Application_EnterThreadModal);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; LeaveThreadModal = new EventHandler(Application_LeaveThreadModal);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Application.EnterThreadModal += EnterThreadModal;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Application.LeaveThreadModal += LeaveThreadModal;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer = new Timer();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer.Tick += new EventHandler(timer_Tick);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Dispose method - cleanup timer and Application event subscriptions      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; protected override void Dispose(bool disposing)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; base.Dispose(disposing);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (disposing)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Application.EnterThreadModal -= EnterThreadModal;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Application.LeaveThreadModal -= LeaveThreadModal;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (timer != null)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer.Stop();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer.Dispose();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer = null;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Event handler for Application.EnterThreadModal      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; void Application_EnterThreadModal(object sender, EventArgs e)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer.Stop();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Event handler for Application.LeaveThreadModal      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; void Application_LeaveThreadModal(object sender, EventArgs e)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (timer.Interval != 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer.Start();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Create the native Add-In Control      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; protected override Control CreateControl()      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Create a panel with the size 0,0      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Panel panel = new Panel();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; panel.BorderStyle = BorderStyle.None;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; panel.MinimumSize = new Size(0, 0);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; panel.MaximumSize = new Size(0, 0);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; panel.Size = new Size(0, 0);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return panel;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Timer tick handler - raise the Service Tier Add-In Event      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; void timer_Tick(object sender, EventArgs e)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Stop the timer while running the add-in Event      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer.Stop();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Invoke event      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.RaiseControlAddInEvent(this.count++, &amp;quot;&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Restart the timer      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer.Start();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Override to specify that Caption should be omitted      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public override bool AllowCaptionControl      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; get      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return false;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Override to specify that value has not changed      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public override bool HasValueChanged      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; get      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return false;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Value for the Timer Control - the value is the number of 1/10's of a second between Tick events      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// NOTE: every event is sent from the Client to the Service Tier - meaning that this is not intended      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ///&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; for events executing more frequently than 1/10's of a second      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public override string Value      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; get      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return base.Value;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; set      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; base.Value = value;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (!int.TryParse(value, out interval))      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; interval = 0;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; interval = interval * 100;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (timer != null &amp;amp;&amp;amp; timer.Interval != interval)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer.Interval = interval;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; count = 0;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (interval == 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer.Stop();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; else      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; timer.Start();      &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;} &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;   &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;/font&gt;&lt;/p&gt; A couple of things to note&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The Value is set on the Control even it doesn’t seem necessary – that is the reason for checking whether the interval has changed before doing anything.&lt;/li&gt;    &lt;li&gt;We don’t really use the native control, the Panel(0,0), for anything – it is only there for the RoleTailored Client to have something to hold on to – returning null causes the RoleTailored Client to display an Add-In error.&lt;/li&gt;    &lt;li&gt;I stop the timer while running the server side event. The primary reason for this is to ensure we don’t get multiple events triggered simultaneously and this causes the interval time to be applied after the event returns – not from the time the event started.&lt;/li&gt;    &lt;li&gt;If you setup the Timer to trigger an event every 10 seconds – it will do so when there has been 10 seconds without any modal dialogs. If this isn’t what you want, you should setup the trigger to fire every second and look when the Add-In event Index parameter is 10.&lt;/li&gt; &lt;/ul&gt;  &lt;h3&gt;How to use the Control&lt;/h3&gt;  &lt;p&gt;For a test, we create a sample page like this:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/Timereventsonapage_54E5/image_12.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/Timereventsonapage_54E5/image_thumb_5.png" width="798" height="207" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;with the following global variables:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/Timereventsonapage_54E5/image_4.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/Timereventsonapage_54E5/image_thumb_1.png" width="702" height="307" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;and the following triggers:&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;&lt;u&gt;OnOpenPage()           &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;timer := '10';&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;&lt;u&gt;timer - OnControlAddIn(Index : Integer;Data : Text[1024])           &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;count := Index;&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;As you can see, the timer is set to trigger once a second and the Index in the AddIn event actually counts the number of times the trigger has been fired, so the count will be counting.&lt;/p&gt;  &lt;p&gt;Now you might wonder - why is the Timer caption &lt;strong&gt;Timer – DO NOT REMOVE&lt;/strong&gt;?&lt;/p&gt;  &lt;p&gt;The reason for this is, that the RoleTailored Client doesn’t really know about the concept Non-Visual controls and as you probably know, personalization can remove everything from a page – including your timer:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/Timereventsonapage_54E5/image_14.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/Timereventsonapage_54E5/image_thumb_6.png" width="721" height="488" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;If you remove this control – the Timer will of course stop.&lt;/p&gt;  &lt;p&gt;You can find the Visual Studio project and the TimerTest.fob &lt;a href="http://www.freddy.dk/TimerControl.zip" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;font color="#0080ff"&gt;Freddy Kristiansen&lt;/font&gt;&lt;/strong&gt;      &lt;br /&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9916852" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Client+Tier/default.aspx">Client Tier</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Extensibility/default.aspx">Extensibility</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009+SP1/default.aspx">NAV 2009 SP1</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Add-Ins/default.aspx">Add-Ins</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/AL/default.aspx">AL</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Timer/default.aspx">Timer</category></item><item><title>Using touchscreens with the RoleTailored Client</title><link>http://blogs.msdn.com/freddyk/archive/2009/11/01/using-touchscreens-with-the-roletailored-client.aspx</link><pubDate>Sun, 01 Nov 2009 18:00:20 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9915900</guid><dc:creator>freddyk</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9915900.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9915900</wfw:commentRss><description>&lt;p&gt;I LOVE the RoleTailored Client, I LOVE the fact that everything is metadata driven and i LOVE what this will give us (us being everybody using NAV) going forward. As a result of the investments leading to NAV 2009, NAV has by far the most modern UX and the new framework allows us to innovate faster and more consistent than any other ERP solution out there.&lt;/p&gt;  &lt;p&gt;We can change the UX to follow Microsoft Office 2010 if we decide to, without having to do a wash through all pages and modify those to follow the UX. We can create new UI paradigms and allow the existing pages to be reused and we will make sure that the UX is consistent throughout NAV.&lt;/p&gt;  &lt;p&gt;I do however also acknowledge that sometimes, love just isn’t enough – for some scenarios, the RoleTailored Client doesn’t make things easier for us and we need to consider what to do.&lt;/p&gt;  &lt;p&gt;In this post I will try to explain a way to handle one of these scenarios – creating a page with buttons that can be used from a Touch Screen like:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/UsingtouchscreenswiththeRoleTailoredClie_116BE/image_2.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/UsingtouchscreenswiththeRoleTailoredClie_116BE/image_thumb.png" width="320" height="402" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;As you might&amp;#160; guess – this requires Visual studio:-)&lt;/p&gt;  &lt;h3&gt;Button Panels&lt;/h3&gt;  &lt;p&gt;I have collected a number of screenshots from various applications using touch screens – and it is very common to have one or more panels of buttons and other information from NAV. It is no secret that you could of course just create a button panel like this in Visual Studio using WinForms and you would be on your way, but the problem here is, that you would put the decision on location, size, captions and visuals of the buttons into your Visual Studio solution.&lt;/p&gt;  &lt;p&gt;You would have to have a way of describing the looks and the functionality of the button panel from NAV in order to capture your business logic in one place. Thinking more about this – I found myself trying to describe something I had seen before…&lt;/p&gt;  &lt;p&gt;A “string” that would describe the visuals, the flow, the positions and the functionality of a panel – that sounds a lot like HTML and Javascript, so if I decided to go with a browser using HTML and Javascript – how in earth would I raise an event on the Service Tier from inside my browser?&lt;/p&gt;  &lt;h3&gt;Escaping from Javascript&lt;/h3&gt;  &lt;p&gt;I decided to go forward with the browser idea and try to find out how to escape from Javascript – and it turned out to be pretty simple actually.&lt;/p&gt;  &lt;p&gt;On the WebBrowser Control there is a property called &lt;a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser.objectforscripting.aspx" target="_blank"&gt;ObjectForScripting&lt;/a&gt;. By setting that property you are now able to escape to that object from Javascript using &lt;strong&gt;window.external.myfunction(myparameters);&lt;/strong&gt;. In Fact – all the methods in the class you specify in ObjectForScripting are now available from Javascript.&lt;/p&gt;  &lt;h3&gt;Show me the code!!!&lt;/h3&gt;  &lt;p&gt;If you haven’t created Microsoft Dynamics Add-Ins before, you might want to read some of the basics on &lt;a href="http://blogs.msdn.com/cabeln" target="_blank"&gt;Christian’s blog&lt;/a&gt;, especially the following post explains the basics pretty well:&lt;/p&gt;  &lt;p&gt;&lt;a title="http://blogs.msdn.com/cabeln/archive/2009/05/06/add-ins-for-the-roletailored-client-of-microsoft-dynamicsnav-2009-sp1-part1.aspx" href="http://blogs.msdn.com/cabeln/archive/2009/05/06/add-ins-for-the-roletailored-client-of-microsoft-dynamicsnav-2009-sp1-part1.aspx"&gt;http://blogs.msdn.com/cabeln/archive/2009/05/06/add-ins-for-the-roletailored-client-of-microsoft-dynamicsnav-2009-sp1-part1.aspx&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Assuming that you are now a shark in creating Add-Ins – we can continue:-)&lt;/p&gt;  &lt;p&gt;Let’s first of all create the native WinForms Control. We can use the WebBrowser unchanged – although the WebBrowser comes with an error, which sometimes surfaces in NAV. If you set the DocumentText in the browser control before it is done rendering the last value of DocumentText – it will ignore the new value. Frankly I want an implementation where the last value wins – NOT the first value. I handle that by subscribing to the DocumentCompleted event and check whether there is a newer value available. I also don’t want to set the value in the WebBrowser if it hasn’t changed.&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;public class MyWebBrowser : WebBrowser     &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; private string text;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; private string html = Resources.Empty; &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Constructor for WebBrowser Control      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public MyWebBrowser()      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(MyWebBrowser_DocumentCompleted);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Handler for DocumentCompleted event      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// If we are trying to set the DocumentText while the WebBrowser is rendering - it is ignored      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Catching this event to see whether the DocumentText should change fixes that problem      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; void MyWebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (this.DocumentText != this.html)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.DocumentText = this.html;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Get/Set the Text of the WebBrowser      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public override string Text      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; get      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return text;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; set      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (text != value)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; text = value;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (string.IsNullOrEmpty(value))      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; html = Resources.Empty;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; else      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; html = text;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.DocumentText = html;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and now the Add-In part of the Control.&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;[ControlAddInExport(&amp;quot;FreddyK.BrowserControl&amp;quot;)]     &lt;br /&gt;public class BrowserControl : StringControlAddInBase, IStringControlAddInDefinition      &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; MyWebBrowser control; &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; protected override Control CreateControl()     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control = new MyWebBrowser();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.MinimumSize = new Size(16, 16);&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.MaximumSize = new Size(4096, 4096);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.IsWebBrowserContextMenuEnabled = false;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.ObjectForScripting = new MyScriptManager(this);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.ScrollBarsEnabled = false;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.ScriptErrorsSuppressed = true;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.WebBrowserShortcutsEnabled = false;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.Dock = DockStyle.Fill;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return control;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; public void clickevent(int i, string s)     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.RaiseControlAddInEvent(i, s);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; public override bool AllowCaptionControl     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; get      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return false;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; public override bool HasValueChanged     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; get      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return false;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; public override string Value     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; get      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return base.Value;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; set      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; base.Value = value;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (this.control != null)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.control.Text = value;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;} &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Things to note:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;I am using DockStyle.Fill to specify that the Control should take up whatever space is available.&lt;/li&gt;    &lt;li&gt;ObjectForScripting is set to an instance of the MyScriptManager class&lt;/li&gt;    &lt;li&gt;the clickevent method raises the Add-In Event on the Service Tier with the parameters coming from the caller.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The MyScriptManager could look like this:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;[ComVisible(true)]     &lt;br /&gt;public class MyScriptManager      &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; BrowserControl browserControl; &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; public MyScriptManager(BrowserControl browserControl)     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.browserControl = browserControl;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; public void clickevent(int i, string s)     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; browserControl.clickevent(i, s);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;}&lt;/font&gt; &lt;/p&gt;  &lt;p&gt;and as you might have guessed – this allows Javascript in the WebBrowser to invoke statements like:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;window.external.clickevent(i, s);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Note that you need to have &lt;strong&gt;ComVisible(true)&lt;/strong&gt; on the ScriptManager class.&lt;/p&gt;  &lt;p&gt;Of course you need to sign the DLL, copy the DLL to the Add-Ins folder and create an entry in the Client Add-Ins table.&lt;/p&gt;  &lt;p&gt;You can download the source to the Visual Studio project &lt;a href="http://www.freddy.dk/NAVBrowserControl.zip" target="_blank"&gt;here&lt;/a&gt; – and if you use this, the public key token for this add-in is &lt;strong&gt;58e587b763c2f132&lt;/strong&gt; and the Control Add-In Name is &lt;strong&gt;FreddyK.BrowserControl&lt;/strong&gt;. &lt;/p&gt;  &lt;h3&gt;Let’s put the BrowserControl to work for us&lt;/h3&gt;  &lt;p&gt;Assuming that we have built the BrowserControl, copied and registered it – we will not build a page with two fields:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/UsingtouchscreenswiththeRoleTailoredClie_116BE/image_6.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/UsingtouchscreenswiththeRoleTailoredClie_116BE/image_thumb_2.png" width="792" height="187" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;and of course create two global Variables (HTML as BigText and Value as Decimal).&lt;/p&gt;  &lt;p&gt;The pagetype of the page is set to CardPart (in order to avoid the menus – I know this kind of bends the rules of the RoleTailored Client, but since this is a page that wasn’t supposed to be – I think we should manage).&lt;/p&gt;  &lt;p&gt;on the Value field – set the DecimalPlaces to &lt;strong&gt;0:10&lt;/strong&gt; and on the browser field – set the ControlAddIn property to point to our Browser Control: &lt;strong&gt;FreddyK.BrowserControl;PublicKeyToken=58e587b763c2f132&lt;/strong&gt;.&lt;/p&gt;  &lt;p&gt;Now in the OnOpenPage of the page – put the following lines:&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;&lt;u&gt;OnOpenPage()           &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;CLEAR(HTML);        &lt;br /&gt;HTML.ADDTEXT('&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;Hello World&amp;lt;/body&amp;gt;&amp;lt;html&amp;gt;');&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;this should give us the following page when running:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/UsingtouchscreenswiththeRoleTailoredClie_116BE/image_8.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/UsingtouchscreenswiththeRoleTailoredClie_116BE/image_thumb_3.png" width="320" height="402" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;A couple of things to think about when writing the “real” code:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;We do not want to work directly in our HTML global variable, since any change in this would cause the UI to request an update.&lt;/li&gt;    &lt;li&gt;If we want to use images in the HTML code, these images needs to be copied to the Client Tier – I do that using DownloadTempFile from the 3-Tier Management codeunit (varibale called TT).&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The code to download the 3 images used (normal button, wide button and tall button) could be:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;buttonurl := 'file:///'+CONVERTSTR(TT.DownloadTempFile(APPLICATIONPATH + 'button.png'),'\','/');     &lt;br /&gt;tallbuttonurl := 'file:///'+CONVERTSTR(TT.DownloadTempFile(APPLICATIONPATH + 'tallbutton.png'),'\','/');      &lt;br /&gt;widebuttonurl := 'file:///'+CONVERTSTR(TT.DownloadTempFile(APPLICATIONPATH + 'widebutton.png'),'\','/');&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and the code to create the HTML/Javascript code could look like this:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;CLEAR(TEMP);     &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;!DOCTYPE html PUBLIC &amp;quot;-//W3C//DTD XHTML 1.0 Transitional//EN&amp;quot; ');      &lt;br /&gt;TEMP.ADDTEXT('&amp;quot;&lt;/font&gt;&lt;a href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&amp;quot;"&gt;&lt;font color="#0000ff" face="Courier New"&gt;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&amp;quot;&lt;/font&gt;&lt;/a&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;gt;');     &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;html xmlns=&amp;quot;&lt;/font&gt;&lt;a href="http://www.w3.org/1999/xhtml&amp;quot;"&gt;&lt;font color="#0000ff" face="Courier New"&gt;http://www.w3.org/1999/xhtml&amp;quot;&lt;/font&gt;&lt;/a&gt;&lt;font color="#0000ff" face="Courier New"&gt; &amp;gt;');     &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;head&amp;gt;'); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Create Stylesheet for the visuals     &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;style type=&amp;quot;text/css&amp;quot;&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;#160; td { width:64px; font-size:xx-large; background-image:url('''+buttonurl+''') }');      &lt;br /&gt;TEMP.ADDTEXT('&amp;#160; tr { height:64px }');      &lt;br /&gt;TEMP.ADDTEXT('&amp;#160; a { color:#000000; text-decoration:none }');      &lt;br /&gt;TEMP.ADDTEXT('&amp;#160; body { margin:0px; background-color:#FAFAFA }');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;/style&amp;gt;'); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Create Javascript function for invoking AL Event     &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;#160; function click(i, s) {');      &lt;br /&gt;TEMP.ADDTEXT('&amp;#160;&amp;#160;&amp;#160; window.external.clickevent(i, s);');      &lt;br /&gt;TEMP.ADDTEXT('&amp;#160; }');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;/script&amp;gt;'); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;TEMP.ADDTEXT('&amp;lt;/head&amp;gt;');     &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;body&amp;gt;'); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Create Table with Controls     &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;table cellpadding=&amp;quot;0&amp;quot; cellspacing=&amp;quot;5&amp;quot;&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;tr&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(7, '''')&amp;quot;&amp;gt;7&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(8, '''')&amp;quot;&amp;gt;8&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(9, '''')&amp;quot;&amp;gt;9&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;tempstyle := 'background-image:url('''+tallbuttonurl+''')';      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td style=&amp;quot;'+tempstyle+'&amp;quot; rowspan=&amp;quot;2&amp;quot; align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(-1, ''+'')&amp;quot;&amp;gt;+&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;/tr&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;tr&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(4, '''')&amp;quot;&amp;gt;4&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(5, '''')&amp;quot;&amp;gt;5&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(6, '''')&amp;quot;&amp;gt;6&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;/tr&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;tr&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(1, '''')&amp;quot;&amp;gt;1&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(2, '''')&amp;quot;&amp;gt;2&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(3, '''')&amp;quot;&amp;gt;3&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(-1, ''-'')&amp;quot;&amp;gt;-&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;/tr&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;tr&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(-1, ''.'')&amp;quot;&amp;gt;.&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(0, '''')&amp;quot;&amp;gt;0&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;tempstyle := 'width:133px; background-image:url('''+widebuttonurl+''')';      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;td style=&amp;quot;'+tempstyle+'&amp;quot; colspan=&amp;quot;2&amp;quot; align=&amp;quot;center&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;javascript:click(-1, ''='')&amp;quot;&amp;gt;=&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;/tr&amp;gt;');      &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;/table&amp;gt;'); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;TEMP.ADDTEXT('&amp;lt;/body&amp;gt;');     &lt;br /&gt;TEMP.ADDTEXT('&amp;lt;/html&amp;gt;');      &lt;br /&gt;HTML := TEMP;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;   &lt;p&gt;&lt;/p&gt;    &lt;p&gt;Meaning that every click on any button is routed back to the Add-In Event – and the actual calculator is then implemented in AL Code.&lt;/p&gt;    &lt;p&gt;I am not going to go in detail about how to create a calculator, since this is pretty trivial and really not useful – the thing to take away from this sample is how to create button panels in HTML and have every button pressed routed to NAV for handling.&lt;/p&gt;    &lt;p&gt;The Calculator .fob file (one page) and the 3 images used in this example can be downloaded &lt;a href="http://www.freddy.dk/Calculator.zip" target="_blank"&gt;here&lt;/a&gt; – but again – this is just a “stupid” example. I do think that the technology can come in handy in some cases.&lt;/p&gt;    &lt;p&gt;Now, I am aware, that this is not going to solve all issues and you shouldn’t try to twist this to hold all your forms in order to be able to manage colors and font sizes – but it can be used in one-off pages, where you have a page that needs to be used in a warehouse or other locations where you might want huge fonts or touch screen button panels.&lt;/p&gt;    &lt;p&gt;Enjoy&lt;/p&gt;    &lt;p&gt;&lt;em&gt;&lt;font color="#0080ff"&gt;&lt;strong&gt;Freddy Kristiansen&lt;/strong&gt;          &lt;br /&gt;&lt;/font&gt;&lt;/em&gt;&lt;em&gt;PM Architect        &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9915900" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Client+Tier/default.aspx">Client Tier</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/BigText/default.aspx">BigText</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Javascript/default.aspx">Javascript</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Extensibility/default.aspx">Extensibility</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009+SP1/default.aspx">NAV 2009 SP1</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Add-Ins/default.aspx">Add-Ins</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Touch+Screen/default.aspx">Touch Screen</category></item><item><title>Running Code on the Client without deploying COM objects</title><link>http://blogs.msdn.com/freddyk/archive/2009/10/14/running-code-on-the-client-without-deploying-com-objects.aspx</link><pubDate>Wed, 14 Oct 2009 17:41:38 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9907259</guid><dc:creator>freddyk</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9907259.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9907259</wfw:commentRss><description>&lt;p&gt;&lt;strong&gt;Yes&lt;/strong&gt;, it can be done!&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;No&lt;/strong&gt;, it isn’t .net code – nor AL code.&lt;/p&gt;  &lt;h3&gt;Why?&lt;/h3&gt;  &lt;p&gt;It started out as me being a little too fast when stating that you could easily download a file to the Client and attach it to Outlook without any user interaction - and as you might know that is true, but you might also know that if you go the recommended route:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;FILE.DOWNLOAD(FileName, '', '&amp;lt;TEMP&amp;gt;','', ToFile);     &lt;br /&gt;Mail.NewMessage('','',Name,'',ToFile,TRUE);      &lt;br /&gt;FILE.ERASE(FileName);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Then you will get an e-mail that looks like this:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/RunningCodeontheClientwithoutdeployingCO_D0AE/image_4.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/RunningCodeontheClientwithoutdeployingCO_D0AE/image_thumb_1.png" width="611" height="528" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;People might not assume that this actually is &lt;strong&gt;Invoice no. 10103&lt;/strong&gt; in PDF format. What you of course want to have is:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/RunningCodeontheClientwithoutdeployingCO_D0AE/image_2.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/RunningCodeontheClientwithoutdeployingCO_D0AE/image_thumb.png" width="611" height="528" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;So, how do we get there.&lt;/p&gt;  &lt;p&gt;I actually did respond to a post on mibuso a while back (&lt;a title="http://www.mibuso.com/forum/viewtopic.php?f=32&amp;amp;t=29806" href="http://www.mibuso.com/forum/viewtopic.php?f=32&amp;amp;t=29806"&gt;http://www.mibuso.com/forum/viewtopic.php?f=32&amp;amp;t=29806&lt;/a&gt;) about how this could be done, but that would involve a COM object deployed to all clients and not everybody wants that (although I have posted a method on how to do this automatically).&lt;/p&gt;  &lt;p&gt;The problem here is, that file.download always places the file in a temporary directory with a temporary filename – and there is (to my knowledge) no other way to copy a file to the Client.&lt;/p&gt;  &lt;p&gt;Assuming that this is correct, how do we then rename a file on the client without having to deploy COM objects?&lt;/p&gt;  &lt;h3&gt;I said without deploying COM objects, not without USING COM objects&lt;/h3&gt;  &lt;p&gt;As you know, we can run COM objects on the server or on the Client and one of the COM objects, which ships with Windows can come in handy here. The Windows Script Host – if we instantiate this COM object we can actually give the component a VB Script to execute in the context of the COM component (which would be either on the Server or on the Client).&lt;/p&gt;  &lt;h3&gt;Windows Script Host&lt;/h3&gt;  &lt;p&gt;Yes, WSH is legacy – but it is widely used and it is included on all Windows versions from Windows XP and up. I am not going to make this a tutorial on VBScript and WSH – for that you can find a number of good posts on the internet – or start by reading msdn&lt;/p&gt;  &lt;h3&gt;&lt;a title="http://msdn.microsoft.com/en-us/library/ms950396.aspx" href="http://msdn.microsoft.com/en-us/library/ms950396.aspx"&gt;http://msdn.microsoft.com/en-us/library/ms950396.aspx&lt;/a&gt;&lt;/h3&gt;  &lt;h3&gt;Creating a script function / method&lt;/h3&gt;  &lt;p&gt;The method AddCode on the Windows Script Host COM object is used to add sourcecode to the component.&lt;/p&gt;  &lt;p&gt;Note, that you need to add an entire function / method in one call and note, that each line needs to be terminated by a CR.&lt;/p&gt;  &lt;p&gt;You also need to specify what language you use, the control supports JScript and VBScript.&lt;/p&gt;  &lt;p&gt;A VBScript function which returns &lt;strong&gt;Hello &amp;lt;name&amp;gt;&lt;/strong&gt; could look like this:&lt;/p&gt;  &lt;p&gt;&lt;font color="#808000" face="Courier New"&gt;function Hello(who)     &lt;br /&gt;&amp;#160; Hello = &amp;quot;Hello &amp;quot;&amp;amp;who      &lt;br /&gt;end function&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Creating this function in a Client side COM component could look like:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;IF CREATE(objScript,TRUE,TRUE) THEN     &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; CR := ' '; CR[1] := 13;      &lt;br /&gt;&amp;#160; objScript.Language := 'VBScript';      &lt;br /&gt;&amp;#160; objScript.AddCode(      &lt;br /&gt;&amp;#160; 'function Hello(who)'+CR+      &lt;br /&gt;&amp;#160; '&amp;#160; Hello = &amp;quot;Hello &amp;quot;&amp;amp;who'+CR+      &lt;br /&gt;&amp;#160; 'end function');      &lt;br /&gt;END;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;The way I write this is, that I try to maintain the structure of the VBScript even though it is inside a string in NAV, maybe I am fooling myself, but I think it is more readable.&lt;/p&gt;  &lt;h3&gt;Invoking a script function / method&lt;/h3&gt;  &lt;p&gt;There are two ways of invoking a script method:&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Eval&lt;/strong&gt; – used to invoke a function, and get a return value back.&lt;/p&gt;  &lt;p&gt;The above function could be called using&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;MESSAGE(FORMAT(objScript.Eval('Hello(&amp;quot;Freddy&amp;quot;)')));&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Note – when calling functions, VBScript wants your parameters embraced by parentheses.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;ExecuteStatement&lt;/strong&gt; – used to invoke a method which doesn’t return anything&lt;/p&gt;  &lt;p&gt;Let’s rewrite the above function to a method and have the method show a MessageBox:&lt;/p&gt;  &lt;p&gt;The VBScript could look like:&lt;/p&gt;  &lt;p&gt;&lt;font color="#808000" face="Courier New"&gt;sub Hello(who)     &lt;br /&gt;&amp;#160; MsgBox &amp;quot;Hello &amp;quot;&amp;amp;who, 0, &amp;quot;Title”      &lt;br /&gt;end sub&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and creating this function in a COM object and calling the method could look like:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;IF CREATE(objScript,TRUE,TRUE) THEN     &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; CR := ' '; CR[1] := 13;      &lt;br /&gt;&amp;#160; objScript.Language := 'VBScript';      &lt;br /&gt;&amp;#160; &lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;objScript.AddCode(     &lt;br /&gt;&amp;#160; 'sub Hello(who)'+CR+      &lt;br /&gt;&amp;#160; '&amp;#160; MsgBox &amp;quot;Hello &amp;quot;&amp;amp;who, 0, &amp;quot;Test&amp;quot;'+CR+      &lt;br /&gt;&amp;#160; 'end sub');      &lt;br /&gt;&amp;#160; objScript.ExecuteStatement('Hello &amp;quot;Freddy&amp;quot;');      &lt;br /&gt;END;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Note – when calling methods (or sub’s) VBScript does NOT want the parameters embraced by parentheses.&lt;/p&gt;  &lt;h3&gt;Some sample scripts&lt;/h3&gt;  &lt;p&gt;&lt;strong&gt;&lt;u&gt;Rename a temporary file&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#808000" face="Courier New"&gt;function RenameTempFile(fromFile, toFile)     &lt;br /&gt;&amp;#160; set fso = createobject(&amp;quot;Scripting.FileSystemObject&amp;quot;)      &lt;br /&gt;&amp;#160; set x = createobject(&amp;quot;Scriptlet.TypeLib&amp;quot;)      &lt;br /&gt;&amp;#160; path = fso.getparentfoldername(fromFile)      &lt;br /&gt;&amp;#160; toPath = path+&amp;quot;\&amp;quot;+left(x.GUID,38)      &lt;br /&gt;&amp;#160; fso.CreateFolder toPath      &lt;br /&gt;&amp;#160; fso.MoveFile fromFile, toPath+&amp;quot;\&amp;quot;+toFile      &lt;br /&gt;&amp;#160; RenameTempFile = toPath      &lt;br /&gt;end function&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;As you can see, I am doing exactly what I responded on the mibuso thread &lt;a href="http://www.mibuso.com/forum/viewtopic.php?f=32&amp;amp;t=29806" target="_blank"&gt;here&lt;/a&gt; – just in VBScript instead – which then requires no client side install.&lt;/p&gt;  &lt;p&gt;BTW this function is actually used in ClausL’s post about &lt;a href="http://blogs.msdn.com/nav-reporting/archive/2009/10/08/send-email-with-pdf-attachment-in-nav-2009.aspx" target="_blank"&gt;sending e-mail with PDF attachments&lt;/a&gt;, which proves that we do talk with our colleagues at Microsoft:-). Note that there is no good way of creating a GUID from VBScript – I (mis)use the fact that every instance of Scriptlet.TypeLib gets assigned a new GUID.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;u&gt;Get Machine name&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#808000" face="Courier New"&gt;function GetComputerName()     &lt;br /&gt;&amp;#160; set net = createobject(&amp;quot;wscript.network”)      &lt;br /&gt;&amp;#160; GetComputerName = net.ComputerName      &lt;br /&gt;end function&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;I know, that you also can read an environment variable – but this way you can actually get all kind of information on the network though this.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;u&gt;Launch an application&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#808000" face="Courier New"&gt;sub Notepad()     &lt;br /&gt;&amp;#160; set shell = createobject(&amp;quot;WScript.Shell&amp;quot;)      &lt;br /&gt;&amp;#160; shell.Run &amp;quot;notepad.exe&amp;quot;      &lt;br /&gt;end sub&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Yes, you can do this by using the Shell object directly in NAV, like:&lt;/p&gt;  &lt;p&gt;&lt;i&gt;&lt;font color="#0000ff"&gt;Shell&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Automation&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 'Microsoft Shell Controls And Automation'.Shell&lt;/font&gt;&lt;/i&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;CREATE(objShell,True,true);     &lt;br /&gt;objShell.Open('c:\windows\system32\notepad.exe');&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;I just wanted to show that you that stuff like this can be done in VBScript too, and note, that the Shell object in VBScript and in NAV is not the same.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;u&gt;Asking a simple question&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#808000" face="Courier New"&gt;function Input(question, title, default_answer)     &lt;br /&gt;&amp;#160; Input = InputBox(question, title, default_answer)      &lt;br /&gt;end function&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;A couple of partners have told me, that they are unhappy with the discontinuation of INPUT from NAV and having to create pages for even the simplest questions. Running the following code:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;IF CREATE(objScript,TRUE,TRUE) THEN     &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; CR := ' '; CR[1] := 13;      &lt;br /&gt;&amp;#160; objScript.Language := 'VBScript'; &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160; objScript.AddCode(     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; 'function Input(question, title, default_answer)'+CR+      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; '&amp;#160; Input = InputBox(question, title, default_answer)'+CR+      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 'end function'); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160; s := objScript.Eval('Input(&amp;quot;How old are you?&amp;quot;, &amp;quot;A simple question&amp;quot;, &amp;quot;&amp;quot;)');     &lt;br /&gt;&amp;#160; MESSAGE(s);      &lt;br /&gt;END;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Brings up this dialog on my machine:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/RunningCodeontheClientwithoutdeployingCO_D0AE/image_8.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/RunningCodeontheClientwithoutdeployingCO_D0AE/image_thumb_3.png" width="377" height="170" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Who knows, maybe somebody can use this as an alternative to INPUT.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;u&gt;Read the RoleTailored Client configuration file&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#808000"&gt;&lt;font face="Courier New"&gt;function ReadConfigFile()       &lt;br /&gt;&amp;#160; set shell = CreateObject(&amp;quot;WScript.Shell&amp;quot;)        &lt;br /&gt;&amp;#160; folder = shell.ExpandEnvironmentStrings(&amp;quot;%LOCALAPPDATA%&amp;quot;)        &lt;br /&gt;&amp;#160; if folder = &amp;quot;&amp;quot; then folder = shell.ExpandEnvironmentStrings(&amp;quot;%USERPROFILE%&amp;quot;)&amp;amp;&amp;quot;\Local Settings\Application Data&amp;quot;        &lt;br /&gt;&lt;/font&gt;&lt;/font&gt;&lt;font color="#808000" face="Courier New"&gt;&amp;#160; filename = folder&amp;amp;&amp;quot;\Microsoft\Microsoft Dynamics NAV\ClientUserSettings.config&amp;quot;     &lt;br /&gt;&amp;#160; set fso = createobject(&amp;quot;Scripting.FileSystemObject&amp;quot;)      &lt;br /&gt;&amp;#160; set file = fso.OpenTextFile(filename, 1)      &lt;br /&gt;&amp;#160; ReadConfigFile = file.ReadAll()      &lt;br /&gt;end function&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Note that I have NOT tested this function under Windows XP – I know that LOCALAPPDATA is not defined on Windows XP and I think the line:&lt;/p&gt; &lt;font color="#808000" face="Courier New"&gt;&amp;#160; if folder = &amp;quot;&amp;quot; then folder = shell.ExpandEnvironmentStrings(&amp;quot;%USERPROFILE%&amp;quot;)&amp;amp;&amp;quot;\Local Settings\Application Data&amp;quot;   &lt;br /&gt;&lt;/font&gt;  &lt;p&gt;should take care of finding the right folder – if anybody can confirm that, then add that as a comment to this post.&lt;/p&gt;  &lt;p&gt;Bringing up a MESSAGE with the outcome of this function on my machine gives me this dialog:&lt;/p&gt;  &lt;p&gt;   &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/RunningCodeontheClientwithoutdeployingCO_D0AE/image_6.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/RunningCodeontheClientwithoutdeployingCO_D0AE/image_thumb_2.png" width="501" height="458" /&gt;&lt;/a&gt; &lt;/p&gt;    &lt;p&gt;I don’t know whether that could come in handy, but maybe it can spawn off some good ideas.&lt;/p&gt; &lt;/p&gt;  &lt;h3&gt;Wrapping up&lt;/h3&gt;  &lt;p&gt;As you can see, you can do a lot of things in VB Script on the Client (or on the Server). There are a number of scripts you can find on the internet to work with the A/D (create, delete and enumerate users).&lt;/p&gt;  &lt;p&gt;Of course there a limitations as to what you can do in VBScript and it isn’t a real alternative to writing a COM component, but for something it is easy and straightforward – and it doesn’t require any client side installation of components and this works in both Classic and RTC.&lt;/p&gt;  &lt;p&gt;You can download the rename function from ClausL’s post about &lt;a href="http://blogs.msdn.com/nav-reporting/archive/2009/10/08/send-email-with-pdf-attachment-in-nav-2009.aspx" target="_blank"&gt;sending e-mail with PDF attachments&lt;/a&gt;. You will need to do copy, paste and maybe modify the other samples in order to use them.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;font color="#0080ff"&gt;Freddy Kristiansen         &lt;br /&gt;&lt;/font&gt;&lt;/strong&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9907259" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/Service+Tier/default.aspx">Service Tier</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Client+Tier/default.aspx">Client Tier</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Automation/default.aspx">Automation</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/COM/default.aspx">COM</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/XP/default.aspx">XP</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/A_2F00_D/default.aspx">A/D</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/INPUT/default.aspx">INPUT</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/VBScript/default.aspx">VBScript</category></item><item><title>Word Management</title><link>http://blogs.msdn.com/freddyk/archive/2009/09/20/word-management.aspx</link><pubDate>Sun, 20 Sep 2009 19:51:36 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9897291</guid><dc:creator>freddyk</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9897291.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9897291</wfw:commentRss><description>&lt;p&gt;As with the release of Microsoft Dynamics NAV 2009, I was also deeply involved in the TAP for Microsoft Dynamics NAV 2009 SP1. My primary role in the TAP is to assist ISVs and partners in getting a customer live on the new version before we ship the product.&lt;/p&gt;  &lt;p&gt;During this project we file a lot of bugs and the development team in Copenhagen are very responsive and we actually get a lot of bugs fixed – but… not all – it happens that a bug is closed with “By Design”, “Not Repro” or “Priority too low”.&lt;/p&gt;  &lt;p&gt;As annoying as this might seem, I would be even more annoyed if the development team would take every single bug, fix it, run new test passes and punt the releases into the unknown. Some of these bugs then become challenges for me and the ISV / Partner to solve, and during this – it happens that I write some code and hand off to my contact.&lt;/p&gt;  &lt;p&gt;Whenever I do that, two things are very clear&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;The code is given as is, no warranty, no guarantee&lt;/li&gt;    &lt;li&gt;The code will be available on my blog as well, for other ISV’s and partners to see&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;and of course I send the code to the development team in Copenhagen, so that they can consider the fix for the next release.&lt;/p&gt;  &lt;h3&gt;&lt;/h3&gt;  &lt;h3&gt;Max. 64 fields when merging with Word&lt;/h3&gt;  &lt;p&gt;One of the bugs we ran into this time around was the fact that when doing merge with Microsoft Word in a 3T environment, word would only accept 64 merge fields. Now in the base application WordManagement (codeunit 5054) only uses 48 fields, but the ISV i was working with actually extended that to 100+ fields.&lt;/p&gt;  &lt;p&gt;The bug is in Microsoft Word, when merging with file source named .HTM – it only accepts 64 fields, very annoying.&lt;/p&gt;  &lt;p&gt;We also found that by changing the filename to .HTML, then Word actually could see all the fields and merge seemed to work great (with one little very annoying aberdabei) – the following dialog would pop up every time you open Word:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/WordManagement_8AA5/clip_image002_2.jpg"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="clip_image002" border="0" alt="clip_image002" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/WordManagement_8AA5/clip_image002_thumb.jpg" width="502" height="232" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Trying to figure out how to get rid of the dialog, I found the right parameters to send to Word.OpenDataSource, so that the dialog would disappear – but… – then we are right back with the 64 fields limitation.&lt;/p&gt;  &lt;p&gt;The reason for the 64 field limitation is, that Word loads the HTML as a Word Document and use that word document to merge with and in a document, you cannot have more than 64 columns in a table (that’s at least what they told me).&lt;/p&gt;  &lt;p&gt;I even talked to PM’s in Word and got confirmed that this behavior was in O11, O12 and would not be fixed in O14 – so no rescue in the near future.&lt;/p&gt;  &lt;h3&gt;Looking at WordManagement&lt;/h3&gt;  &lt;p&gt;Knowing that the behavior was connected to the merge format, I decided to try and change that – why not go with a good old fashion .csv file instead and in my quest to learn AL code and application development, this seemed like a good little exercise.&lt;/p&gt;  &lt;p&gt;So I started to look at WordManagement and immediately found a couple of things I didn’t like&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;MergeFileName := RBAutoMgt.ClientTempFileName(Text029,'.HTM');     &lt;br /&gt;IF ISCLEAR(wrdMergefile) THEN      &lt;br /&gt;&amp;#160; CREATE(wrdMergefile,FALSE,TRUE);      &lt;br /&gt;// Create the header of the merge file      &lt;br /&gt;CreateHeader(wrdMergefile,FALSE,MergeFileName);      &lt;br /&gt;&amp;lt;find the first record&amp;gt;      &lt;br /&gt;REPEAT      &lt;br /&gt;&amp;#160; // Add Values to mergefile – one AddField for each field for each record      &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160; wrdMergefile.AddField(&amp;lt;field value&amp;gt;);     &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160; // Terminate the line     &lt;br /&gt;&amp;#160; wrdMergefile.WriteLine;      &lt;br /&gt;UNTIL &amp;lt;No more records&amp;gt;      &lt;br /&gt;// Close the file      &lt;br /&gt;wrdMergefile.CloseFile;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;now wrdMergefile is a COM component of type 'Navision Attain ApplicationHandler'.MergeHandler and as you can see, it is created Client side, meaning that for every field in every record we make a roundtrip to the Client (and one extra roundtrip for every record to terminate the line) – now we might not have a lot of records nor a lot of fields, but I think we can do better (said from a guy who used to think about clock cycles when doing assembly instructions on z80 processors back in the start 80’s – WOW I am getting old:-))&lt;/p&gt;  &lt;p&gt;One fix for the performance would be to create the file serverside and send it to the Client in one go – but that wouldn’t solve our original 64 field limitation issue. I could also create a new COM component, which was compatible with MergeHandler and would write a .csv instead – but that wouldn’t solve my second issue about wanting to learn some AL code.&lt;/p&gt;  &lt;h3&gt;Creating a .csv in AL code&lt;/h3&gt;  &lt;p&gt;I decided to go with a model, where I create a server side temporary file for each record, create a line in a BigText and write it to the file. After completing the MergeFile, it needs to be downloaded to the Client and deleted from the service tier.&lt;/p&gt;  &lt;p&gt;The above code would change into something like&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;MergeFileName := CreateMergeFile(wrdMergefile);     &lt;br /&gt;wrdMergefile.CREATEOUTSTREAM(OutStream);       &lt;br /&gt;CreateHeader(OutStream,FALSE); // Header without data&amp;#160; &lt;br /&gt;&amp;lt;find the first record&amp;gt;      &lt;br /&gt;REPEAT      &lt;br /&gt;&amp;#160; CLEAR(mrgLine);      &lt;br /&gt;&amp;#160; // Add Values to mergefile – one AddField for each field for each record      &lt;br /&gt;&amp;#160; AddField(mrgCount, mrgLine, &amp;lt;field value&amp;gt;);      &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160; // Terminate the line     &lt;br /&gt;&amp;#160; mrgLine.ADDTEXT(CRLF);      &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160; mrgLine.WRITE(OutStream);     &lt;br /&gt;&amp;#160; CLEAR(mrgLine);      &lt;br /&gt;UNTIL &amp;lt;No more records&amp;gt;      &lt;br /&gt;// Close the file      &lt;br /&gt;wrdMergeFile.Close();      &lt;br /&gt;MergeFileName := WordManagement.DownloadAndDeleteTempFile(MergeFileName);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;As you can see – no COM components, all server side. A couple of helper functions are used here, but no rocket science and not too different from the code that was.&lt;/p&gt;  &lt;p&gt;CreateMergeFile creates a server side temporary file.&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;&lt;u&gt;CreateMergeFile(VAR wrdMergefile : File) MergeFileName : Text[260]           &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;wrdMergefile.CREATETEMPFILE;        &lt;br /&gt;MergeFileName := wrdMergefile.NAME + '.csv';        &lt;br /&gt;wrdMergefile.CLOSE;        &lt;br /&gt;wrdMergefile.TEXTMODE := TRUE;        &lt;br /&gt;wrdMergefile.WRITEMODE := TRUE;        &lt;br /&gt;wrdMergefile.CREATE(MergeFileName); &lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;AddField adds a field to the BigText. Using AddString, which again uses DupQuotes to ensure that “ inside of the merge field are doubled.&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;AddField(VAR count : Integer;VAR mrgLine : BigText;value : Text[1024])         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;IF mrgLine.LENGTH = 0 THEN      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; count := 1;      &lt;br /&gt;END ELSE      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; count := count + 1;      &lt;br /&gt;&amp;#160; mrgLine.ADDTEXT(',');      &lt;br /&gt;END;      &lt;br /&gt;mrgLine.ADDTEXT('&amp;quot;');      &lt;br /&gt;AddString(mrgLine, value);      &lt;br /&gt;mrgLine.ADDTEXT('&amp;quot;'); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;AddString(VAR mrgLine : BigText;str : Text[1024])         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;IF STRLEN(str) &amp;gt; 512 THEN      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; mrgLine.ADDTEXT(DupQuotes(COPYSTR(str,1,512)));      &lt;br /&gt;&amp;#160; str := DELSTR(str,1,512);      &lt;br /&gt;END;      &lt;br /&gt;mrgLine.ADDTEXT(DupQuotes(str)); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;DupQuotes(str : Text[512]) result : Text[1024]         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;result := '';      &lt;br /&gt;REPEAT      &lt;br /&gt;&amp;#160; i := STRPOS(str, '&amp;quot;');      &lt;br /&gt;&amp;#160; IF i &amp;lt;&amp;gt; 0 THEN      &lt;br /&gt;&amp;#160; BEGIN      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; result := result + COPYSTR(str,1,i) + '&amp;quot;';      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; str := DELSTR(str,1,i);      &lt;br /&gt;&amp;#160; END;      &lt;br /&gt;UNTIL i = 0;      &lt;br /&gt;result := result + str; &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and a small function to return CRLF (line termination for a merge line)&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;CRLF() result : Text[2]         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;result[1] := 13;      &lt;br /&gt;result[2] := 10;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;When doing this I did run into some strange errors when writing both BigTexts and normal Text variables to a stream – that is the reason for building everything into a BigText and writing once pr. line.&lt;/p&gt;  &lt;p&gt;and last, but not least – a function to Download a file to the Client Tier and delete it from the Service Tier:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;DownloadAndDeleteTempFile(ServerFileName : Text[1024]) : Text[1024]         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;IF NOT ISSERVICETIER THEN      &lt;br /&gt;&amp;#160; EXIT(ServerFileName); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;FileName := RBAutoMgt.DownloadTempFile(ServerFileName);     &lt;br /&gt;FILE.ERASE(ServerFileName);      &lt;br /&gt;EXIT(FileName);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;It doesn’t take much more than that… (beside of course integrating this new method in the various functions in WordManagement). The fix doesn’t require anything else than just replacing codeunit 5054 and the new WordManagement can be downloaded &lt;a href="http://www.freddy.dk/NewWordManagement.zip" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Question is now, whether there are localization issues with this. I tried changing all kinds of things on my machine and didn’t run into any problems – but if anybody out there does run into problems with this method – please let me know so.&lt;/p&gt;  &lt;h3&gt;What about backwards compatibility&lt;/h3&gt;  &lt;p&gt;So what if you install this codeunit into a system, where some of these merge files already have been created – and are indeed stored as HTML in blob fields?&lt;/p&gt;  &lt;p&gt;Well – for that case, I created a function that was able to convert them - called&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;ConvertContentFromHTML(VAR MergeContent : BigText) : Boolean&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;It isn’t pretty – but it seems to work.&lt;/p&gt;  &lt;h3&gt;Feedback is welcome&lt;/h3&gt;  &lt;p&gt;I realize that by posting this, I am entering a domain where I am the newbie and a lot of other people are experts. I do welcome feedback on ways to do coding, things I can do better or things I could have done differently.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;font color="#0080ff"&gt;Freddy Kristiansen&lt;/font&gt;&lt;/strong&gt;      &lt;br /&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9897291" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/Service+Tier/default.aspx">Service Tier</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Client+Tier/default.aspx">Client Tier</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Performance/default.aspx">Performance</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/BigText/default.aspx">BigText</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Temporary+File/default.aspx">Temporary File</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/TAP/default.aspx">TAP</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Merge/default.aspx">Merge</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Word/default.aspx">Word</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/AL/default.aspx">AL</category></item><item><title>Auto deployment of Client Side Components</title><link>http://blogs.msdn.com/freddyk/archive/2009/09/19/auto-deployment-of-client-side-components.aspx</link><pubDate>Sat, 19 Sep 2009 22:27:17 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9897164</guid><dc:creator>freddyk</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9897164.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9897164</wfw:commentRss><description>&lt;p&gt;When you install the RoleTailored Client on a number of clients, you might need to install a number of Client side components as well. This might not sound as too much of a problem when you need to install the client anyway – but lets say you install an ISV Add-on with a live customer, who already have 100 clients install – and now you need to install the objects to the database – AND you need to run to 100 computers and install Client side components.&lt;/p&gt;  &lt;p&gt;Yes, you can do this with system management policies, but not all customers are running SMS and it would just be way easier if everything could be handled from the ISV Add-On and the Client Components could be auto deployed.&lt;/p&gt;  &lt;p&gt;When doing this – it is still important, that IF the customer is running SMS and decide to deploy the Client Side components through system policies – then the auto deployment should just pick this up and accept that things are Ok.&lt;/p&gt;  &lt;h3&gt;Two kinds of Client Side Components&lt;/h3&gt;  &lt;p&gt;NAV 2009 SP1 supports Add-Ins (Client Extensibility Controls) and Client side COM components (as NAV 2009) and the way these components are installed is very different.&lt;/p&gt;  &lt;p&gt;Add-Ins needs to be placed in the &lt;strong&gt;Add-Ins&lt;/strong&gt; folder under the RoleTailored Client folder on the Client and COM components can be installed wherever on the Client, but needs to registered in the registry with &lt;strong&gt;regasm&lt;/strong&gt;.&lt;/p&gt;  &lt;p&gt;Both Add-Ins and COM components might rely on other client side components, so it is important that we don’t just create a way of copying files to the Client – but we should instead create a way of launching a setup program on the client, which then installs the components. In my samples, I have one Setup program for every component, but an ISV could easily package all components together in one installation program and install them all in one go.&lt;/p&gt;  &lt;p&gt;To install a client side component really isn’t that difficult – use FILE.DOWNLOAD with an MSI and that’s it. But how do we detect whether or not the component is installed already?&lt;/p&gt;  &lt;p&gt;We cannot keep a list on the server side, since the computer might get re-installed or restored – we need a way of discovering whether a component is installed.&lt;/p&gt;  &lt;h3&gt;Detecting whether a Client side COM component is installed&lt;/h3&gt;  &lt;p&gt;I will start with the COM component (since it will take a COM component to check whether an Add-In is installed). The COM component needs a CREATE statement to be initialized and if you check the return value of the CREATE statement – you know whether or not the COM component is executable. If not we launch a FILE.DOWNLOAD&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;IF NOT CREATE(mycomponent, TRUE, TRUE) THEN     &lt;br /&gt;&amp;#160; FILE.DOWNLOAD(mycomponentinstaller);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Almost too simple right?&lt;/p&gt;  &lt;p&gt;Now – I know that some people will say – well, what if I have an updated version of the COM component and it needs to be deployed?&lt;/p&gt;  &lt;p&gt;My answer to that would be to change the COM signature, in effect making it a different COM component and allow them to be installed side-by-side. This would in effect mean that you might have multiple versions of COM components installed on a client, but they typically don’t take up a lot of space, and they don’t run if nobody uses them.&lt;/p&gt;  &lt;p&gt;You could also create a function for checking the version number of the component like:&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;IF NOT CREATE(mycomponent, TRUE, TRUE) THEN     &lt;br /&gt;&amp;#160; FILE.DOWNLOAD(mycomponentinstaller)      &lt;br /&gt;ELSE IF NOT mycomponent.CheckVersion(100) THEN      &lt;br /&gt;&amp;#160; FILE.DOWNLOAD(mycomponentinstaller);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font face="ver"&gt;problem with this approach is, that NAV keeps a lock on the Client side component (event if you CLEAR(mycomponent)) due to performance reasons and your mycomponentinstaller will have to close the NAV client in order to update the component.&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font face="ver"&gt;I like the solution better, where you just create a new GUID and thus a new component – so that is what I will describe here.&lt;/font&gt;&lt;/p&gt;  &lt;h3&gt;&lt;font face="ver"&gt;Detecting whether an Add-In is installed on the Client&lt;/font&gt;&lt;/h3&gt;  &lt;p&gt;If you have installed the Server pieces of the Virtual Earth Integration (look here), but have a Client without the VEControl Add-In, this is how the FactBox will look:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/AutodeploymentofClientSideComponents_72DD/image_2.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/AutodeploymentofClientSideComponents_72DD/image_thumb.png" width="329" height="126" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Not very informative when you were expecting this:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/AutodeploymentofClientSideComponents_72DD/image_4.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/AutodeploymentofClientSideComponents_72DD/image_thumb_1.png" width="319" height="281" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;But as you might know, we actually didn’t write any code to plugin the control and the Control Add-In error above is handled by the Client without actually notifying the Service Tier that anything is missing.&lt;/p&gt;  &lt;p&gt;What we need to do, is to create one line of code in the INIT trigger of all pages, which uses an Add-In:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;ComponentHelper.CheckAddInNameKey('FreddyK.LargeVEControl','1c9f7ad47dba024b');&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and then of course create a function that actually checks that the Add-In is there and does a FILE.DOWNLOAD(addininstaller) if it isn’t.&lt;/p&gt;  &lt;p&gt;Problem here is that we need a COM component in order to check the existence of an Add-In, and this COM component will have to run Client side (how else could it inspect the Add-ins folder – doh).&lt;/p&gt;  &lt;p&gt;The INIT trigger is executed before anything is sent off to the Client and thus we can install the component and continue opening the page after we have done that. BTW the FILE.DOWNLOAD is NOT going to wait until the user actually finishes the setup program, so we will have to bring up a modal dialog telling the user to confirm that he has completed the setup.&lt;/p&gt;  &lt;p&gt;BTW as you probably have figured out by now, the above line requires a registration of Add-Ins like:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;ComponentHelper.RegisterAddIn('FreddyK.LargeVEControl','1c9f7ad47dba024b','NAV Large Virtual Earth Control', 'NavVEControl.msi');&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;In order to specify what file to download. Now I could have added this to the Check function to avoid a table – but I actually don’t think it belongs there.&lt;/p&gt;  &lt;h3&gt;The ComponentHelper&lt;/h3&gt;  &lt;p&gt;So, what I have done is to collect some functionality that I find I use all the time in various samples in a Component called the ComponentHelper.&lt;/p&gt;  &lt;p&gt;The functions are:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Installation of Client side COM components (used by the majority of samples)&lt;/li&gt;    &lt;li&gt;Installation of Client side Add-Ins (used by all samples with Add-ins)&lt;/li&gt;    &lt;li&gt;Ability to Escape and Unescape strings (the method Web Services uses for encoding of company name – used in the Virtual Earth Integration)&lt;/li&gt;    &lt;li&gt;Ability to register a codeunit or page as Web Service from code (used by all samples using Web Services)&lt;/li&gt;    &lt;li&gt;Global information about the URL to my IIS and Web Service tier (used in Edit In Excel and Virtual Earth Integration)&lt;/li&gt;    &lt;li&gt;Modify metadata programmatically (all samples)&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;In fact I am hoping that these basic pieces of functionality will find their way into the base product in the future, where they IMO belong.&lt;/p&gt;  &lt;h3&gt;Installation of Client side COM components&lt;/h3&gt;  &lt;p&gt;Every time you use a self built COM component (in this case the NAVAddInHelper), which you want to auto-deploy, you should create a function like this:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;LoadAddInHelper(VAR NAVAddInHelper : Automation &amp;quot;'NAVAddInHelper'.NAVAddInHelper&amp;quot;) Ok : Boolean         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;Ok := FALSE;      &lt;br /&gt;WHILE NOT CREATE(NAVAddInHelper,TRUE,TRUE) DO      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; IF NOT AskAndInstallCOMComponent('NAV AddIn Helper', 'NAVAddInHelper.msi') THEN      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; EXIT;      &lt;br /&gt;END;      &lt;br /&gt;Ok := TRUE;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and always invoke this when you want to create an instance of the Component (instead of having CREATE(NAVAddInHelper,TRUE,TRUE) scattered around the code.&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;AskAndInstallCOMComponent(Description : Text[80];InstallableName : Text[80]) Retry : Boolean         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;Retry := FALSE;      &lt;br /&gt;IF CONFIRM(STRSUBSTNO(TXT_InstallCOMComponent, Description)) THEN      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; Retry := InstallComponent(InstallableName);      &lt;br /&gt;END;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;u&gt;&lt;strong&gt;InstallComponent(InstallableName : Text[80]) Retry : Boolean         &lt;br /&gt;&lt;/strong&gt;&lt;/u&gt;Retry := TRUE;      &lt;br /&gt;toFile := InstallableName;      &lt;br /&gt;fromFile := APPLICATIONPATH + '\ClientSetup\'+InstallableName;      &lt;br /&gt;IF NOT FILE.EXISTS(fromFile) THEN      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; fromFile := APPLICATIONPATH + '\..\ClientSetup\'+InstallableName;      &lt;br /&gt;END;      &lt;br /&gt;IF FILE.DOWNLOAD(fromFile, InstallableName, '', '', toFile) THEN      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; Retry := CONFIRM(TXT_PleaseConfirmComplete);      &lt;br /&gt;END;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;as you can see from the code, the function will try to create the component until it succeeds or the user says No, I do not want to install the component. At this time I would like to mention a small bug in NAV 2009 SP1 – when you try to CREATE a COM component client side and it isn’t there, the Client will still ask you whether or not you want to run a client side component, but since the Control isn’t installed – it doesn’t know what to call it, meaning that you will get:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/AutodeploymentofClientSideComponents_72DD/image_8.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/AutodeploymentofClientSideComponents_72DD/image_thumb_3.png" width="410" height="214" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Now it is OK for the user to cancel this window because he doesn’t know what it is, but if he says &lt;strong&gt;Never Allow&lt;/strong&gt; (silly choice to give the user:-)), he will have to delete personalization settings for automation objects to get this working again.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/AutodeploymentofClientSideComponents_72DD/image_12.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/AutodeploymentofClientSideComponents_72DD/image_thumb_5.png" width="554" height="360" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;BTW If the user declines running a COM component – our code will see this as the component is not installed and ask him to install it.&lt;/p&gt;  &lt;h3&gt;Installation of Client side Add-Ins &lt;/h3&gt;  &lt;p&gt;To check whether an Add-in is installed, we first check whether it is registered in the Client’s add-in table.&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;CheckAddInNameKey(AddInName : Text[220];PublicKeyToken : Text[20]) Found : Boolean         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;Found := FALSE;      &lt;br /&gt;IF NOT AddIn.GET(AddInName,PublicKeyToken) THEN      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; MESSAGE(STRSUBSTNO(TXT_AddInNotRegisterd, AddInName, PublicKeyToken));      &lt;br /&gt;&amp;#160; EXIT;      &lt;br /&gt;END;      &lt;br /&gt;Found := CheckAddIn(AddIn.&amp;quot;Control Add-in Name&amp;quot;, AddIn.&amp;quot;Public Key Token&amp;quot;, AddIn.Description);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Without anything here – nothing works. After this we check our own table (in which we have information about what executable to download to the client)&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;CheckAddIn(AddInName : Text[220];PublicKeyToken : Text[20];Description : Text[250]) Found : Boolean         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;IF Description = '' THEN      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; Description := AddInName;      &lt;br /&gt;END;      &lt;br /&gt;Found := FALSE;      &lt;br /&gt;IF LoadAddInHelper(NAVAddInHelper) THEN      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; WHILE NOT NAVAddInHelper.CheckAddIn(AddInName, PublicKeyToken) DO      &lt;br /&gt;&amp;#160; BEGIN      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; IF NOT InstallableAddIn.GET(AddInName, PublicKeyToken) THEN      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; BEGIN      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; IF NOT CONFIRM(STRSUBSTNO(TXT_AddInNotFound, Description)) THEN      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; BEGIN      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; EXIT(FALSE);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; END       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ELSE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; EXIT(AskAndInstallAddIn(Description, InstallableAddIn.InstallableName));      &lt;br /&gt;&amp;#160; END;      &lt;br /&gt;&amp;#160; Found := TRUE;      &lt;br /&gt;END;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and last but not least – the method that installs the Add-In&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;AskAndInstallAddIn(Description : Text[80];InstallableName : Text[80]) Retry : Boolean         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;Retry := FALSE;      &lt;br /&gt;IF CONFIRM(STRSUBSTNO(TXT_InstallAddIn, Description)) THEN      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; Retry := InstallComponent(InstallableName);      &lt;br /&gt;END;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;BTW, the method to register Add-Ins to this subsystem is&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;strong&gt;&lt;u&gt;RegisterAddIn(&amp;quot;Control Name&amp;quot; : Text[220];&amp;quot;Public Key Token&amp;quot; : Text[20];Description : Text[128];InstallableName : Text[80])         &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;IF NOT AddIn.GET(&amp;quot;Control Name&amp;quot;, &amp;quot;Public Key Token&amp;quot;) THEN      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; AddIn.INIT();      &lt;br /&gt;&amp;#160; AddIn.&amp;quot;Control Add-in Name&amp;quot; := &amp;quot;Control Name&amp;quot;;      &lt;br /&gt;&amp;#160; AddIn.&amp;quot;Public Key Token&amp;quot; := &amp;quot;Public Key Token&amp;quot;;      &lt;br /&gt;&amp;#160; AddIn.Description := Description;      &lt;br /&gt;&amp;#160; AddIn.INSERT(TRUE);      &lt;br /&gt;END;      &lt;br /&gt;IF NOT InstallableAddIn.GET(&amp;quot;Control Name&amp;quot;, &amp;quot;Public Key Token&amp;quot;) THEN      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; InstallableAddIn.INIT();      &lt;br /&gt;&amp;#160; InstallableAddIn.&amp;quot;Control Add-in Name&amp;quot; := &amp;quot;Control Name&amp;quot;;      &lt;br /&gt;&amp;#160; InstallableAddIn.&amp;quot;Public Key Token&amp;quot; := &amp;quot;Public Key Token&amp;quot;;      &lt;br /&gt;&amp;#160; InstallableAddIn.InstallableName := InstallableName;      &lt;br /&gt;&amp;#160; InstallableAddIn.INSERT(TRUE);      &lt;br /&gt;END;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;As you can see I could have extended the AddIn table – but I decided to go for adding a table instead, it doesn’t really matter.&lt;/p&gt;  &lt;h3&gt;Ability to Escape and Unescape strings &lt;/h3&gt;  &lt;p&gt;In the Virtual Earth sample, I need to construct a URL, which contains the company name from NAV. Now with NAV 2009SP1 we use standard Escape and Unescape of strings in the URL, so I have added functions to ComponentHelper to do this. In fact, they just call a function in the C# COM component, which contains these functions.&lt;/p&gt;  &lt;h3&gt;Ability to register a codeunit or page as Web Service from code &lt;/h3&gt;  &lt;p&gt;Instead of having to ask partners and/or users to register web services in the Web Service table or form, I have created this small function in the ComponentHelper to do this.&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;&lt;font color="#0000ff"&gt;&lt;strong&gt;&lt;u&gt;RegisterWebService(isPage : Boolean;&amp;quot;Object ID&amp;quot; : Integer;&amp;quot;Service Name&amp;quot; : Text[80];Published : Boolean)           &lt;br /&gt;&lt;/u&gt;&lt;/strong&gt;IF isPage THEN        &lt;br /&gt;BEGIN        &lt;br /&gt;&amp;#160; ObjType := WebService.&amp;quot;Object Type&amp;quot;::Page;        &lt;br /&gt;END ELSE        &lt;br /&gt;BEGIN        &lt;br /&gt;&amp;#160; ObjType := WebService.&amp;quot;Object Type&amp;quot;::Codeunit;        &lt;br /&gt;END; &lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;IF NOT WebService.GET(ObjType, &amp;quot;Service Name&amp;quot;) THEN     &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; WebService.INIT();      &lt;br /&gt;&amp;#160; WebService.&amp;quot;Object Type&amp;quot; := ObjType;      &lt;br /&gt;&amp;#160; WebService.&amp;quot;Object ID&amp;quot; := &amp;quot;Object ID&amp;quot;;      &lt;br /&gt;&amp;#160; WebService.&amp;quot;Service Name&amp;quot; := &amp;quot;Service Name&amp;quot;;      &lt;br /&gt;&amp;#160; WebService.Published := Published;      &lt;br /&gt;&amp;#160; WebService.INSERT();      &lt;br /&gt;&amp;#160; COMMIT;      &lt;br /&gt;END ELSE      &lt;br /&gt;BEGIN      &lt;br /&gt;&amp;#160; IF (WebService.&amp;quot;Object ID&amp;quot; &amp;lt;&amp;gt; &amp;quot;Object ID&amp;quot;) OR (WebService.Published&amp;lt;&amp;gt;Published)&amp;#160; THEN      &lt;br /&gt;&amp;#160; BEGIN      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WebService.&amp;quot;Object ID&amp;quot; := &amp;quot;Object ID&amp;quot;;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WebService.Published := Published;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WebService.MODIFY();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; COMMIT;      &lt;br /&gt;&amp;#160; END;      &lt;br /&gt;END;&lt;/font&gt;&lt;/p&gt;  &lt;h3&gt;Global information about the URL to my IIS and Web Service tier&lt;/h3&gt;  &lt;p&gt;Again – a number of the samples I create will integrate from the RoleTailored Client to an application or a web site, which then again uses Web Services. I found out, that I needed a central way to find the URL of the right Web Service listener and the best way was to create a table in which I store the base URL (which would be &lt;a href="http://&amp;lt;server&amp;gt;:&amp;lt;port&amp;gt;/&amp;lt;instance&amp;gt;/WS/"&gt;://WS/&amp;quot;&amp;gt;http://&amp;lt;server&amp;gt;:&amp;lt;port&amp;gt;/&amp;lt;instance&amp;gt;/WS/&lt;/a&gt; (default &lt;a href="http://localhost:7047/DynamicsNAV/WS/"&gt;http://localhost:7047/DynamicsNAV/WS/&lt;/a&gt;).&lt;/p&gt;  &lt;p&gt;Also in the Virtual Earth I spawn up a browser (with HYPERLINK) and I need a location for the intranet server on which an application like the MAP would reside.&lt;/p&gt;  &lt;h3&gt;Modify Metadata programmatically&lt;/h3&gt;  &lt;p&gt;I found that all my samples worked fine in the W1 version of NAV 2009 SP1, but as soon as I started to install them on other localized version, the pages on which I added actions etc. had been modified by local functionality and since there is no auto merge of pages, people would have to merge page metadata or find themselves loosing local functionality when they installed my samples.&lt;/p&gt;  &lt;p&gt;I have added 4 functions:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;GetPageMetadata(Id : Integer;VAR Metadata : BigText)&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;SetPageMetadata(Id : Integer;Metadata : BigText)&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;AddToMetadata(Id : Integer;VAR Metadata : BigText;Before : Text[80];Identifier : Text[80];Properties : Text[800]) result : Boolean&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;AddToPage(Id : Integer;VersionList : Text[30];Before : Text[80];Identifier : Text[80];Properties : Text[800]&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#000000"&gt;where the last function just call the three other (Get, Add, Set metadata).&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#000000"&gt;I am not very proud of the way these functions are made – they just search for a line in the exported text file and inserts some metadata but they meet the needs.&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;font color="#000000" face="Verdana"&gt;As an example on how these functions are used you will find:&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Read Page Metadata     &lt;br /&gt;ComponentHelper.GetPageMetadata(PAGE::&amp;quot;Customer Card&amp;quot;, Metadata);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Add Map Factbox     &lt;br /&gt;ComponentHelper.AddToMetadata(PAGE::&amp;quot;Customer Card&amp;quot;, Metadata, '&amp;#160;&amp;#160;&amp;#160; { 1900383207;1;Part&amp;#160;&amp;#160; ;',      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; '&amp;#160;&amp;#160;&amp;#160; { 66031;1&amp;#160; ;Part&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ;',      &lt;br /&gt;' SubFormLink=No.=FIELD(No.); PagePartID=Page66030 }')      &lt;br /&gt;OR &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Add View Area Map Action     &lt;br /&gt;ComponentHelper.AddToMetadata(PAGE::&amp;quot;Customer Card&amp;quot;, Metadata, '&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; { 82&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ;1&amp;#160;&amp;#160; ;ActionGroup;', '&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; { 66030&amp;#160;&amp;#160; ;2&amp;#160;&amp;#160; ;Action&amp;#160;&amp;#160;&amp;#160; ;',      &lt;br /&gt;' CaptionML=[ENU=View Area Map]; OnAction=VAR MAP : Codeunit 66032; BEGIN MAP.OpenCustomerMAPInBrowser(Rec); END; }');&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Write Page Metadata back     &lt;br /&gt;ComponentHelper.SetPageMetadata(PAGE::&amp;quot;Customer Card&amp;quot;, Metadata);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#000000" face="verda"&gt;So basically – it reads the metadata for the page, checks whether the action already has been added (the string &lt;font color="#ff0000"&gt;'&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; { 66030&amp;#160;&amp;#160; ;2&amp;#160;&amp;#160; ;Action&amp;#160;&amp;#160;&amp;#160; ;'&lt;/font&gt; exists already). If not it searches for the string &lt;font color="#ff0000"&gt;'&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; { 82&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ;1&amp;#160;&amp;#160; ;ActionGroup;'&lt;/font&gt;&lt;font color="#000000"&gt; and inserts the action below that. Not pretty – but it works.&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;h3&gt;&lt;font color="#000000"&gt;The Visual Studio piece&lt;/font&gt;&lt;/h3&gt;  &lt;p&gt;&lt;font color="#000000"&gt;As mentioned earlier a couple of functions are needed in a client side COM component.&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#000000"&gt;The Escape and Unescape functions really doesn’t do anything:&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;public string EscapeDataString(string str)     &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; return Uri.EscapeDataString(str);      &lt;br /&gt;} &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;public string UnescapeDataString(string str)     &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; return Uri.UnescapeDataString(str);      &lt;br /&gt;} &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#000000"&gt;and the essence of the CheckAddIn is the code found in the LoadAddIn function of the AddIn class:&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;Assembly assembly = Assembly.LoadFrom(dll); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;this.publicKey = &amp;quot;&amp;quot;;     &lt;br /&gt;foreach (byte b in assembly.GetName().GetPublicKeyToken())      &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; this.publicKey += string.Format(&amp;quot;{0:x2}&amp;quot;, b);      &lt;br /&gt;} &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;Type[] types = assembly.GetTypes();     &lt;br /&gt;foreach (Type type in types)      &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; foreach (System.Attribute att in System.Attribute.GetCustomAttributes(type))      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ControlAddInExportAttribute expAtt = att as ControlAddInExportAttribute;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (expAtt != null &amp;amp;&amp;amp; !string.IsNullOrEmpty(expAtt.Name))      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (!isAddIn)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.controlNames = new List&amp;lt;string&amp;gt;();      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; isAddIn = true;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.controlNames.Add(expAtt.Name);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }      &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Which loads an Add-In, finds the public key token and the registered controls. The rest is really simple – check whether one of the Add-Ins in fact is the one we are looking for – else install it…&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;font color="#333333" face="Verdana"&gt;The Visual Studio solution also contains a setup project for generating the .msi file which needs to be placed in the ClientSetup folder.&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;h3&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;font color="#333333" face="Verdana"&gt;Putting it all together&lt;/font&gt;&lt;/font&gt;&lt;/h3&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;font color="#333333" face="Verdana"&gt;So, now we have a .fob file and an .msi file which we need to install on the Service Tier – so why don’t we create a Setup project, which contains this .fob (install that in a ServerSetup folder) and the .msi (install that in the ClientSetup folder).&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Doing this makes installing the ComponentHelper a 3 step process:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;font color="#333333" face="Verdana"&gt;Install ComponentHelper.msi on the Service Tier&lt;/font&gt;&lt;/font&gt;&lt;/li&gt;    &lt;li&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;font color="#333333" face="Verdana"&gt;Import a .fob from the ServerSetup folder&lt;/font&gt;&lt;/font&gt;&lt;/li&gt;    &lt;li&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;font color="#333333" face="Verdana"&gt;Run a codeunit which registers the necessary stuff&lt;/font&gt;&lt;/font&gt;&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;font color="#333333" face="Verdana"&gt;In fact I am trying to make all the demos and samples installable like the ComponentHelper itself – so that anybody can download cool samples and get a sexy Microsoft Dynamics NAV 2009 SP1 – to work with.&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;ComponentHelper1.01.zip (which contains ComponentHelper1.01.msi) can be downloaded &lt;a href="http://www.freddy.dk/ComponentHelper1.01.zip" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;If you don’t fancy downloading the .msi (for whatever reason) - the source to NAVAddHelper can be downloaded &lt;a href="http://www.freddy.dk/NAVAddInHelper1.01.zip" target="_blank"&gt;here&lt;/a&gt; and the ComponentHelper objects can be downloaded &lt;a href="http://www.freddy.dk/ComponentHelperObjects1.01.zip" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;font color="#0080ff"&gt;&lt;strong&gt;Freddy Kristiansen&lt;/strong&gt;        &lt;br /&gt;&lt;/font&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9897164" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/Web+Services/default.aspx">Web Services</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Service+Tier/default.aspx">Service Tier</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Client+Tier/default.aspx">Client Tier</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/COM/default.aspx">COM</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Extensibility/default.aspx">Extensibility</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009+SP1/default.aspx">NAV 2009 SP1</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Add-Ins/default.aspx">Add-Ins</category></item><item><title>Microsoft Dynamics NAV 2009 SP1 launched!</title><link>http://blogs.msdn.com/freddyk/archive/2009/09/19/microsoft-dynamics-nav-2009-sp1-launched.aspx</link><pubDate>Sat, 19 Sep 2009 16:40:46 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9897121</guid><dc:creator>freddyk</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9897121.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9897121</wfw:commentRss><description>&lt;p&gt;I guess it is a little late to call this news, but nevertheless – on September 1st NAV 2009 SP1 launched.&lt;/p&gt;  &lt;p&gt;SP1 is big release for the user – the majority of the feedback we got from users on NAV 2009 was taken into consideration and I truly believe that we will see hard core classic client users shift and prefer the Role Tailored Client with these changes. Yes, they will have to get used to new ways of doing things – but I think we closed the gap on the pieces we missed out on in NAV 2009.&lt;/p&gt;  &lt;p&gt;All in all NAV 2009 SP1 just works more intuitively and it adds a feature called Client Extensibility.&lt;/p&gt;  &lt;p&gt;Client Extensibility is the ability to add custom controls to NAV and I am sure you will see a lot of blogs on this topic in the future (my first was done back in June based on NAV 2009 SP1 CTP2 – can be found &lt;a href="http://blogs.msdn.com/freddyk/archive/2009/06/07/integration-to-virtual-earth-part-4-of-4.aspx"&gt;here&lt;/a&gt;)&lt;/p&gt;  &lt;p&gt;Over the next weeks I will post updates to a number of my previous posts – what it takes to make them run under SP1.&lt;/p&gt;  &lt;p&gt;One thing I have heard from a number of ISV’s and partners is, that they are trying to minimize the number of Client side components, that could be COM components or Add-Ins (Client Extensibility Controls), and the very first thing I want to blog about, is a method to overcome this hurdle. A way to auto-deploy Client side components without having to run around and install anything on all clients.&lt;/p&gt;  &lt;p&gt;Stay tuned&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;font color="#0080ff"&gt;Freddy Kristiansen&lt;/font&gt;&lt;/strong&gt;      &lt;br /&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9897121" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/Launch/default.aspx">Launch</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009+SP1/default.aspx">NAV 2009 SP1</category></item><item><title>Multiple Service Tiers - SP1</title><link>http://blogs.msdn.com/freddyk/archive/2009/08/05/multiple-service-tiers-sp1.aspx</link><pubDate>Wed, 05 Aug 2009 22:55:35 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9858351</guid><dc:creator>freddyk</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9858351.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9858351</wfw:commentRss><description>&lt;p&gt;Right around the release of Microsoft Dynamics NAV 2009, I wrote a blog entry with some .bat files on how to create multiple Service Tiers when working with NAV 2009.&lt;/p&gt;  &lt;p&gt;The blog post is here: &lt;a title="http://blogs.msdn.com/freddyk/archive/2008/10/29/multiple-service-tiers.aspx" href="http://blogs.msdn.com/freddyk/archive/2008/10/29/multiple-service-tiers.aspx"&gt;http://blogs.msdn.com/freddyk/archive/2008/10/29/multiple-service-tiers.aspx&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Now NAV 2009 SP1 is about to be released, it is time for a small update. One of the files of the package is a CustomSettings.template file, which really is just the CustomSettings.config with a few values replaced with variable names, so that we can replace those automagically.&lt;/p&gt;  &lt;p&gt;Now in SP1, the CustomSettings.config has changed – new keys have been added and we also support named instances in the database.&lt;/p&gt;  &lt;p&gt;SP1 will actually run with the old config file, so we could just ignore the entire thing and continue as if nothing happened – the .bat files will still work in SP1.&lt;/p&gt;  &lt;p&gt;However – if we want to take advantage of the named instances in SQL Server or we want to have the additional keys available for modifying we need to change something.&lt;/p&gt;  &lt;p&gt;I have created a new CustomSettings.template based on the SP1 config file - copy the config file and change the following keys:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;add key=&amp;quot;DatabaseServer&amp;quot; value=&amp;quot;#DBSERVER#&amp;quot;&amp;gt;&amp;lt;/add&amp;gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;add key=&amp;quot;DatabaseInstance&amp;quot; value=&amp;quot;#DBINSTANCE#&amp;quot;&amp;gt;&amp;lt;/add&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;add key=&amp;quot;DatabaseName&amp;quot; value=&amp;quot;#DATABASE#&amp;quot;&amp;gt;&amp;lt;/add&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &amp;lt;add key=&amp;quot;ServerInstance&amp;quot; value=&amp;quot;#INSTANCE#&amp;quot;&amp;gt;&amp;lt;/add&amp;gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and extended the createservice.bat file to also allow a database instance to be specified, meaning that the usage is now:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;CreateService name [dbserver] [&amp;quot;dbinstance&amp;quot;] [&amp;quot;dbname&amp;quot;] [demand|auto|disabled] [both|servicetier|ws]&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;The new .zip file is available for download &lt;a href="http://www.freddy.dk/MultipleServiceTiers-SP1.zip" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;font color="#0080ff"&gt;Freddy Kristiansen       &lt;br /&gt;&lt;/font&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9858351" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/Web+Services/default.aspx">Web Services</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Service+Tier/default.aspx">Service Tier</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Service/default.aspx">Service</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Multiple/default.aspx">Multiple</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009+SP1/default.aspx">NAV 2009 SP1</category></item><item><title>Integration to Virtual Earth – Part 4 (out of 4)</title><link>http://blogs.msdn.com/freddyk/archive/2009/06/07/integration-to-virtual-earth-part-4-of-4.aspx</link><pubDate>Sun, 07 Jun 2009 08:18:43 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9703533</guid><dc:creator>freddyk</dc:creator><slash:comments>3</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9703533.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9703533</wfw:commentRss><description>&lt;p&gt;(a small change added that simplifies the SmallVEControl class definition)&lt;/p&gt;  &lt;p&gt;With the release of NAV 2009 SP1 CTP2 (to MVPs, TAP and BAP) and the official release of the statement of Direction, I can now write about the last part of the integration to Virtual Earth.&lt;/p&gt;  &lt;p&gt;People who hasn’t access to NAV 2009 SP1, will unfortunately have to wait until the official release until they can take advantage of this post.&lt;/p&gt;  &lt;p&gt;Please not that you should read &lt;a href="http://blogs.msdn.com/freddyk/archive/2009/03/18/integration-to-virtual-earth-part-1-out-of-4.aspx" target="_blank"&gt;Part 1&lt;/a&gt;, &lt;a href="http://blogs.msdn.com/freddyk/archive/2009/03/23/integration-to-virtual-earth-part-2-out-of-4.aspx" target="_blank"&gt;Part 2&lt;/a&gt; and &lt;a href="http://blogs.msdn.com/freddyk/archive/2009/03/23/integration-to-virtual-earth-part-3-out-of-4.aspx" target="_blank"&gt;Part 3&lt;/a&gt; of the Integration to Virtual Earth – and you would have to have the changes to the app. described in these posts in order to make this work.&lt;/p&gt;  &lt;p&gt;This post will take advantage of a functionality, which comes in NAV 2009 SP1 called Extensibility. Christian explains some basics about extensibility in a post, which you can find &lt;a href="http://blogs.msdn.com/cabeln/archive/2009/05/06/add-ins-for-the-roletailored-client-of-microsoft-dynamicsnav-2009-sp1-part1.aspx" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;h3&gt;The Goal&lt;/h3&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/IntegrationtoVirtualEarthPart4outof4_12A7B/image_2.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/IntegrationtoVirtualEarthPart4outof4_12A7B/image_thumb.png" width="1111" height="789" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;As you can see on the above picture, we have a control, which is able to show the map in NAV of the customer location, and as you select different customers in the list, the map changes.&lt;/p&gt;  &lt;p&gt;The changes in the map happens without any user interference, so that the user can walk up and down in the list without being irritated. In the Actions menu in the part, we will put an action called &lt;strong&gt;Open In Browser&lt;/strong&gt;, which will open up a map in a browser as explained in part 3.&amp;#160; &lt;/p&gt;  &lt;p&gt;Note that the Weather factbox is not shown here.&lt;/p&gt;  &lt;h3&gt;What is it?&lt;/h3&gt;  &lt;p&gt;The Control inside the Customer Map Factbox is basically just a browser control, in which we set a html document (pretty much like the one described in part 3) and leave it to the browser control to connect to Virtual Earth and retrieve the map. I do not connect to web services from the browser control, instead we transfer parameters of the current customer location to the control.&lt;/p&gt;  &lt;p&gt;Although the internal implementation is a browser control, we don’t do html in NAV and we don’t give the control any URL’s or other fancy stuff. The way we make this work is to have the control databind to a Text variable (CustomerLocation), which gets set in OnAfterGetRecord:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;CustomerLocation := 'latitude='+FORMAT(Latitude,0,9)+'&amp;amp;longitude='+FORMAT(Longitude,0,9)+'&amp;amp;zoom=15';&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;The factbox isn’t able to return any value and there isn’t any reason right now to trigger any events from the control.&lt;/p&gt;  &lt;p&gt;So now we just need to create a control, which shows the string “latitude=50&amp;amp;longitude=2&amp;amp;zoom=15” differently than a dumb text.&lt;/p&gt;  &lt;h3&gt;How is the control build?&lt;/h3&gt;  &lt;p&gt;Let’s just go through the creation of the VEControl step by step.&lt;/p&gt;  &lt;p&gt;1. Start Visual Studio 2008 SP1, create a new project of type Class Library and call it VEControl.&lt;/p&gt;  &lt;p&gt;2. Add a reference &lt;strong&gt;System.Windows.Forms&lt;/strong&gt; , &lt;strong&gt;System.Drawing&lt;/strong&gt; and to the file &lt;strong&gt;C:\Program Files\Microsoft Dynamics NAV\60\RoleTailored Client\Microsoft.Dynamics.Framework.UI.Extensibility.dll&lt;/strong&gt; – you need to browse and find it. Note that when you copy the VEControl.dll to it’s final location you don’t need to copy this DLL, since it will be loaded into memory from the Client before your DLL is called.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;3. Open Project Properties, go to the Signing tab, and sign your DLL with a new key.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/IntegrationtoVirtualEarthPart4outof4_12A7B/image_4.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/IntegrationtoVirtualEarthPart4outof4_12A7B/image_thumb_1.png" width="1002" height="624" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;4. In the Build Events Tab add the following command to the Post-Build Event window:&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;copy VEControl.dll &amp;quot;C:\Program Files\Microsoft Dynamics NAV\60\RoleTailored Client\Add-ins&amp;quot;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;this ensures that the Control gets installed in the right directory.&lt;/p&gt;  &lt;p&gt;5. Delete the automatically generated class1.cs and add another class file called VEControl.cs&lt;/p&gt;  &lt;p&gt;6. Add the following class to the file:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;/// &amp;lt;summary&amp;gt;      &lt;br /&gt;/// Native WinForms Control for Virtual Earth Integration       &lt;br /&gt;/// &amp;lt;/summary&amp;gt;       &lt;br /&gt;public class VEControl : WebBrowser       &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; private string template;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; private string text;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; private string html = &amp;quot;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;quot;;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Constructor for Virtual Earth Integration Control       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;param name=&amp;quot;template&amp;quot;&amp;gt;HTML template for Map content&amp;lt;/param&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public VEControl(string template)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.template = template;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(VEControl_DocumentCompleted);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; ///       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;param name=&amp;quot;sender&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;param name=&amp;quot;e&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; void VEControl_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (this.DocumentText != this.html)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.DocumentText = this.html;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Property for Data Binding       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; public override string Text       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; get       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return text;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; set       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (text != value)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; text = value;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (string.IsNullOrEmpty(value))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; html = &amp;quot;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;quot;;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; else       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; html = this.template;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; html = html.Replace(&amp;quot;%latitude%&amp;quot;, GetParameter(&amp;quot;latitude&amp;quot;, &amp;quot;0&amp;quot;));       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; html = html.Replace(&amp;quot;%longitude%&amp;quot;, GetParameter(&amp;quot;longitude&amp;quot;, &amp;quot;0&amp;quot;));       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; html = html.Replace(&amp;quot;%zoom%&amp;quot;, GetParameter(&amp;quot;zoom&amp;quot;, &amp;quot;1&amp;quot;));       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this.DocumentText = html;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// Get Parameter from databinding       &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;/summary&amp;gt;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;param name=&amp;quot;parm&amp;quot;&amp;gt;Parameter name&amp;lt;/param&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;param name=&amp;quot;defaultvalue&amp;quot;&amp;gt;Default Value if the parameter isn’t specified&amp;lt;/param&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; /// &amp;lt;returns&amp;gt;The value of the parameter (or default)&amp;lt;/returns&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; private string GetParameter(string parm, string defaultvalue)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; foreach (string parameter in text.Split('&amp;amp;'))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (parameter.StartsWith(parm + &amp;quot;=&amp;quot;))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return parameter.Substring(parm.Length + 1);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return defaultvalue;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;} &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Note, that you will need a using statement to System.Windows.Forms.&lt;/p&gt;  &lt;p&gt;This class gets initialized with a html template (our javascript code) and is able to get values like “latitude=50&amp;amp;longitude=2&amp;amp;zoom=15” set as the Text property and based on this render the right map through the template.&lt;/p&gt;  &lt;p&gt;The reason for the DocumentCompleted event handler is, that if we try to set the DocumentText property in the browser before it is done rendering the prior DocumentText, it will just ignore the new value. We handle this by hooking up to the event and if the DocumentText is different from the value we have – then this must have happened and we just set it again. We are actually pretty happy that the control works this way, because the javascript is run in a different thread than our main thread and fetching the map control from Virtual Earth etc. will not cause any delays for us.&lt;/p&gt;  &lt;p&gt;Now this is just a standard WinForms Control – how do we tell the Client that this is a control, that it can use inside the NAV Client?&lt;/p&gt;  &lt;p&gt;The way we chose to implement this is by creating a wrapper, which is the one we register with the NAV Client and this wrapper is responsible for creating the “real” control. This allows us to use 3rd party controls even if they are sealed and/or we don’t have the source for them.&lt;/p&gt;  &lt;p&gt;7. Add a html page called SmallVEMap.htm and add the following content&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;lt;html&amp;gt;      &lt;br /&gt;&amp;lt;head&amp;gt;       &lt;br /&gt;&amp;lt;title&amp;gt;&amp;lt;/title&amp;gt;       &lt;br /&gt;&amp;lt;meta http-equiv=&amp;quot;Content-Type&amp;quot; content=&amp;quot;text/html; charset=utf-8&amp;quot; /&amp;gt;       &lt;br /&gt;&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;&lt;/font&gt;&lt;a href="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2&amp;quot;"&gt;&lt;font color="#0000ff" face="Courier New"&gt;http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2&amp;quot;&lt;/font&gt;&lt;/a&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;gt;&amp;lt;/script&amp;gt;      &lt;br /&gt;&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; var map = null;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; var shape = null;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; function GetMap() {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; map = new VEMap('myMap'); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; var latitude = parseFloat(&amp;quot;%latitude%&amp;quot;);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; var longitude = parseFloat(&amp;quot;%longitude%&amp;quot;);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; var zoom = parseInt(&amp;quot;%zoom%&amp;quot;);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; map.SetDashboardSize(VEDashboardSize.Tiny); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; var position = new VELatLong(latitude, longitude);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; map.LoadMap(position, zoom, 'r', false);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; shape = new VEShape(VEShapeType.Pushpin, position);&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; map.AddShape(shape);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;lt;/script&amp;gt;       &lt;br /&gt;&amp;lt;/head&amp;gt;&amp;#160; &lt;br /&gt;&amp;lt;body onload=&amp;quot;GetMap();&amp;quot; style=&amp;quot;margin:0; position:absolute; width:100%; height:100%; overflow: hidden&amp;quot;&amp;gt;       &lt;br /&gt;&amp;lt;div id='myMap' style=&amp;quot;position: absolute; width:100%; height:100%&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;#160;&amp;#160; &lt;br /&gt;&amp;lt;/body&amp;gt;       &lt;br /&gt;&amp;lt;/html&amp;gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;8. Add a Resource file to the project called Resources.resx, open it and drag the SmallVEMap.htm into the resources file.&lt;/p&gt;  &lt;p&gt;9. Add a class called SmallVEControl.cs and add the following classes&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;font color="#0000ff" face="Courier New"&gt;[ControlAddInExport(&amp;quot;SmallVEControl&amp;quot;)] &lt;/font&gt;      &lt;br /&gt;public class SmallVEControl : StringControlAddInBase, IStringControlAddInDefinition       &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; protected override Control CreateControl()       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; var control = new VEControl(Resources.SmallVEMap);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.MinimumSize = new Size(200, 200);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.MaximumSize = new Size(500, 500);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.ScrollBarsEnabled = false;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.ScriptErrorsSuppressed = true;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; control.WebBrowserShortcutsEnabled = false;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return control;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; } &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&amp;#160;&amp;#160;&amp;#160; public override bool AllowCaptionControl      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; get       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return false;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;You need to add using statements to &lt;strong&gt;System.Drawing&lt;/strong&gt;, &lt;strong&gt;Microsoft.Dynamics.Framework.UI.Extensibility&lt;/strong&gt;, &lt;strong&gt;Microsoft.Dynamics.Framework.UI.Extensibility.WinForms&lt;/strong&gt; and &lt;strong&gt;System.Windows.Forms&lt;/strong&gt;.&lt;/p&gt;  &lt;p&gt;The CreateControl is the method called by the NAV Client when it needs to create the actual winforms control. We override this method and create the VEControl and give it the html template.&lt;/p&gt;  &lt;p&gt;The reason for overriding the AllowCaptionControl is to specify that our control will not need a caption (else the NAV Client will add a caption control in front of our control).&lt;/p&gt;  &lt;p&gt;There are various other methods that can be overridden, but we will touch upon these when needed.&lt;/p&gt;  &lt;p&gt;Build your solution and you should now have a VEControl.DLL in the Add-Ins directory under the RoleTailored Client.&lt;/p&gt;  &lt;h3&gt;And how do I put this control into use in the NAV Client?&lt;/h3&gt;  &lt;p&gt;First of all we need to tell the Client that the control is there!&lt;/p&gt;  &lt;p&gt;We do that by adding an entry to the &lt;strong&gt;Client Add-In&lt;/strong&gt; table (2000000069). You need to specify Control Add-In Name (which would be the name specified in the ControlAddInExport attribute above = SmallVEControl) and the public key token.&lt;/p&gt;  &lt;p&gt;But what is the public key token?&lt;/p&gt;  &lt;p&gt;Its is the public part of the key-file used to sign the assembly and as you remember, we just asked Visual Studio to create a new key-file so we need to query the key file for it’s public key and we do that by running &lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;sn –T VEControl.snk&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;in a Visual Studio command prompt.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/IntegrationtoVirtualEarthPart4outof4_12A7B/image_6.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/IntegrationtoVirtualEarthPart4outof4_12A7B/image_thumb_2.png" width="677" height="390" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;u&gt;Note that this public key is NOT the one you need to use, unless you download my solution below.&lt;/u&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/IntegrationtoVirtualEarthPart4outof4_12A7B/image_8.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/IntegrationtoVirtualEarthPart4outof4_12A7B/image_thumb_3.png" width="785" height="292" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;Having the Control Registered for usage we need to create a new page and call it Customer Map Factbox. This page has SourceTable set to the Customer table and is contains one control, bound to a variable called CustomerLocation, which gets set in the OnAfterGetRecord.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/IntegrationtoVirtualEarthPart4outof4_12A7B/image_12.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/IntegrationtoVirtualEarthPart4outof4_12A7B/image_thumb_5.png" width="794" height="186" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;The code in OnAfterGetRecord is&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;CustomerLocation := 'latitude='+FORMAT(Latitude,0,9)+'&amp;amp;longitude='+FORMAT(Longitude,0,9)+'&amp;amp;zoom=15';&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;The Customer Map Factbox is added as a part to the Customer Card and the Customer List and the SubFormLink is set to &lt;strong&gt;No.=FIELD(No.)&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;That’s it guys – I realize this is a little rough start on extensibility – I promise that there will be other and more entry level starter examples on extensibility – I just decided to create an end-to-end sample to show how to leverage the Virtual Earth functionality in a Factbox.&lt;/p&gt;  &lt;p&gt;As usual you can download the visual studio project &lt;a href="http://www.freddy.dk/VEControl.zip"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;font color="#0080ff"&gt;&lt;strong&gt;Freddy Kristiansen&lt;/strong&gt;         &lt;br /&gt;&lt;/font&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9703533" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/URL/default.aspx">URL</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Javascript/default.aspx">Javascript</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Virtual+Earth/default.aspx">Virtual Earth</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/MAP/default.aspx">MAP</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Latitude/default.aspx">Latitude</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Longitude/default.aspx">Longitude</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Extensibility/default.aspx">Extensibility</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009+SP1/default.aspx">NAV 2009 SP1</category></item><item><title>Microsoft SQL Server Management Studio Express</title><link>http://blogs.msdn.com/freddyk/archive/2009/05/29/microsoft-sql-server-management-studio-express.aspx</link><pubDate>Fri, 29 May 2009 20:20:34 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9655527</guid><dc:creator>freddyk</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9655527.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9655527</wfw:commentRss><description>&lt;p&gt;Most of you probably already know this, and I guess I am the last living nerd to discover that you actually can have SQL Server Management Studio for SQL Express…&lt;/p&gt;  &lt;p&gt;Anyway – I will showcase my lack of knowledge and admit that I just downloaded this from MS and installed next to my Demo install of NAV on my Laptop running Windows 7.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/MicrosoftSQLServerManagementStudioExpres_9063/image_2.png"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://blogs.msdn.com/blogfiles/freddyk/WindowsLiveWriter/MicrosoftSQLServerManagementStudioExpres_9063/image_thumb.png" width="856" height="542" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;You can download Microsoft SQL Server Management Studio Express &lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=C243A5AE-4BD1-4E3D-94B8-5A0F62BF7796&amp;amp;displaylang=en"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;font color="#0080ff"&gt;Freddy Kristiansen &lt;/font&gt;      &lt;br /&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9655527" width="1" height="1"&gt;</description></item><item><title>Handling Sales Orders from Page based Web Services – in NAV 2009SP1 (and RTM)</title><link>http://blogs.msdn.com/freddyk/archive/2009/05/28/handling-sales-orders-from-page-based-web-services-in-nav-2009sp1-and-rtm.aspx</link><pubDate>Thu, 28 May 2009 20:06:09 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9648012</guid><dc:creator>freddyk</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/freddyk/comments/9648012.aspx</comments><wfw:commentRss>http://blogs.msdn.com/freddyk/commentrss.aspx?PostID=9648012</wfw:commentRss><description>&lt;p&gt;First of all, there isn’t going to be a new post on every single record type on how to handle them from Web Services – but Sales Orders are special and the reason for the “(and SP1)” in the titel refers to the fact, that there are changes between RTM and SP1 or maybe a better way to state it is, that the way you could do it in RTM (that might lead to errors) is no longer possible – so you have to do it right.&lt;/p&gt;  &lt;p&gt;Secondly, please read the post about the Web Service Changes in SP1 before reading this post – you can find that post &lt;a href="http://blogs.msdn.com/freddyk/archive/2009/05/27/web-services-changes-in-nav-2009-sp1.aspx"&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;h3&gt;Working with the Sales Orders Page from Web Services in NAV 2009SP1&lt;/h3&gt;  &lt;p&gt;Just to recap a couple of facts about Page based Web Services – we are using the pages and the code on the pages to work with sales orders, this means that we need to mimic the way the RoleTailored Client works, and the RoleTailored Client doesn’t create the order header and all the lines in one go before writing anything to the database. Instead what really happens is that once you leave the primary key field (the Order No) it creates the Order Header in the table. The same with the lines, they are created and then you type data into them, after which they are updated.&lt;/p&gt;  &lt;p&gt;So what we need to do is to create the sales order in 4 steps:&lt;/p&gt;  &lt;p&gt;1. Create the Order Header    &lt;br /&gt;2. Update the Order Header     &lt;br /&gt;3. Create an Order Line     &lt;br /&gt;4. Update an Order Line     &lt;br /&gt;(repeat steps 3-4)&lt;/p&gt;  &lt;p&gt;Now this doesn’t mean that you have to do 2 + (no of Orderlines)*2 roundtrips to the server (fortunately) – but you always need 3 roundtrips.&lt;/p&gt; 1. Create the Order Header   &lt;br /&gt;2. Update the Order Header and Create all Order Lines   &lt;br /&gt;4. Update all Order Lines   &lt;br /&gt;  &lt;p&gt;meaning that you can create all order lines in one go (together with updating header info) and you can update them all in one go.&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;&lt;font color="#333333" face="Verdana"&gt;a code sample for doing this:&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Create Service Reference      &lt;br /&gt;var service = new SalesOrder_Service();       &lt;br /&gt;service.UseDefaultCredentials = true; &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Create the Order header      &lt;br /&gt;var newOrder = new SalesOrder();       &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;service.Create(ref newOrder); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Update Order header      &lt;br /&gt;newOrder.Sell_to_Customer_No = &amp;quot;10000&amp;quot;;       &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Create Order lines      &lt;br /&gt;newOrder.SalesLines = new Sales_Order_Line[2];       &lt;br /&gt;for (int idx = 0; idx &amp;lt; 2; idx++)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; newOrder.SalesLines[idx] = new Sales_Order_Line();       &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;service.Update(ref newOrder); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Update Order lines      &lt;br /&gt;var line1 = newOrder.SalesLines[0];       &lt;br /&gt;line1.Type = SalesOrderRef.Type.Item;       &lt;br /&gt;line1.No = &amp;quot;LS-75&amp;quot;;       &lt;br /&gt;line1.Quantity = 3;       &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;var line2 = newOrder.SalesLines[1];      &lt;br /&gt;line2.Type = NewSalesOrderRef.Type.Item;       &lt;br /&gt;line2.No = &amp;quot;LS-100&amp;quot;;       &lt;br /&gt;line2.Quantity = 3;       &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;service.Update(ref newOrder); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;After invoking Create(ref newOrder) or Update(ref newOrder) we get the updated sales order back from NAV, and we know that all the &amp;lt;field&amp;gt;Specified properties are set to true and all strings which has a value are not null – so we can just update the fields we want to update and call update(ref newOrder) again and utilize that SP1 only updates the fields that actually have changed.&lt;/p&gt;  &lt;p&gt;This behavior is pretty different from NAV 2009 RTM web services, where it will write all fields to the database if you don’t set strings fields to NULL or set the &amp;lt;field&amp;gt;Specified to false (as described in my previous post).&lt;/p&gt;  &lt;h3&gt;Making the above code run in NAV 2009RTM&lt;/h3&gt;  &lt;p&gt;What we really want to do here, is to mimic the behavior of NAV 2009 SP1 in RTM – without having to change the logic.&lt;/p&gt;  &lt;p&gt;So I went ahead and wrote two functions. One for making a copy of a record (to be invoked right after your Create(ref xx) or Update(ref xx)) – so we now have a copy of the object coming from NAV. Another function for preparing our object for Update (to be invoked right before calling Update(ref xx)) – to compare our object with the old copy and set all the unchanged fields &amp;lt;field&amp;gt;Specified to false and all unchanged string fields to null.&lt;/p&gt;  &lt;p&gt;The two functions are listed towards the end of this post.&lt;/p&gt;  &lt;p&gt;Our code from above would then look like:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Create Service Reference      &lt;br /&gt;var service = new SalesOrder_Service();       &lt;br /&gt;service.UseDefaultCredentials = true; &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Create the Order header      &lt;br /&gt;var newOrder = new SalesOrder();       &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;service.Create(ref newOrder);      &lt;br /&gt;SalesOrder copy = (SalesOrder)GetCopy(newOrder); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Update Order header      &lt;br /&gt;newOrder.Sell_to_Customer_No = &amp;quot;10000&amp;quot;;       &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Create Order lines      &lt;br /&gt;newOrder.SalesLines = new Sales_Order_Line[2];       &lt;br /&gt;for (int idx = 0; idx &amp;lt; 2; idx++)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; newOrder.SalesLines[idx] = new Sales_Order_Line();       &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;PrepareForUpdate(newOrder, copy);      &lt;br /&gt;service.Update(ref newOrder);       &lt;br /&gt;copy = (SalesOrder)GetCopy(newOrder);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Update Order lines      &lt;br /&gt;var line1 = newOrder.SalesLines[0];       &lt;br /&gt;line1.Type = SalesOrderRef.Type.Item;       &lt;br /&gt;line1.No = &amp;quot;LS-75&amp;quot;;       &lt;br /&gt;line1.Quantity = 3;       &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;var line2 = newOrder.SalesLines[1];      &lt;br /&gt;line2.Type = SalesOrderRef.Type.Item;       &lt;br /&gt;line2.No = &amp;quot;LS-100&amp;quot;;       &lt;br /&gt;line2.Quantity = 3;       &lt;br /&gt;PrepareForUpdate(newOrder, copy);       &lt;br /&gt;&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;service.Update(ref newOrder); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and this code would actually run on SP1 as well – and cause smaller packages to be sent over the wire (not that I think that is an issue).&lt;/p&gt;  &lt;h3&gt;Deleting a line from an existing Sales Order&lt;/h3&gt;  &lt;p&gt;Now we have seen how to create a Sales Order with a number of lines – but what if you want to delete a line after having saved the Sales Order. On the Service object you will find a method called Delete_SalesLines, which takes a key and delete that Sales Line.&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;service.Delete_SalesLines(line.Key);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;The only caveat to this is, that if you want to do any more work on the Sales Order, you will have to re-read the Sales Order, else you will get an information that somebody changed the record (and that would be you).&lt;/p&gt;  &lt;p&gt;So deleting all lines from a Sales Order could be done by:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;foreach (Sales_Order_Line line in so.SalesLines)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; service.Delete_SalesLines(line.Key);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;and then you would typically re-read the Sales Order with the following line:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;so = service.Read(so.No);&lt;/font&gt; &lt;/p&gt;  &lt;p&gt;That wasn’t so bad.&lt;/p&gt;  &lt;p&gt;My personal opinion is that we should change the Delete_SalesLines to be: &lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;service.Delete_SalesLines(ref so, line);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Which is why I created a function that does exactly that:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;void Delete_SalesLines(SalesOrder_Service service, &lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;ref SalesOrder so, Sales_Order_Line line)      &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Debug.Assert(so.SalesLines.Contains&amp;lt;Sales_Order_Line&amp;gt;(line));       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; service.Delete_SalesLines(line.Key);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; so = service.Read(so.No);       &lt;br /&gt;} &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Note, that Í just re-read the order, loosing any changes you have made to the order or order lines. Another approach here could be to remove the line deleted from the lines collection, but things becomes utterly complicated when we try to mimic a behavior in the consumer that IMO should be on the Server side.&lt;/p&gt;  &lt;p&gt;A different approach would be to create a codeunit for deleting lines and expose this as an extension to the page (functions added to the page), but we would gain anything, since we still would have to re-read the order afterwards.&lt;/p&gt;  &lt;h3&gt;Adding a line to an existing Sales Order&lt;/h3&gt;  &lt;p&gt;More complicated is it, when we want to add a line to an existing Sales Order through the Sales Order Page.&lt;/p&gt;  &lt;p&gt;Actually it isn’t complicated to add the line – but it is complicated to locate the newly added line after the fact to do modifications, because it is still true that you need to add the line first and then modify the line afterwards (and update the order).&lt;/p&gt;  &lt;p&gt;Adding the line is:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;// Create a new Lines array with only the new line and update (meaning create the line)      &lt;br /&gt;so.SalesLines = so.SalesLines.Concat(new [] { new Sales_Order_Line() }).ToArray();       &lt;br /&gt;service.Update(ref so); &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;This add’s a new line to the array of lines and update the order.&lt;/p&gt;  &lt;p&gt;After invoking update the newly added line is the last in the array (unless somebody messed around in the app-code and made this assumption false).&lt;/p&gt;  &lt;p&gt;My personal opinion is that we should add another method to the service called&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;service.Add_SalesLines(ref so, ref line);&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;so that we would have the newly added line available to modify and the so available for service.Update(ref so), which is why I created a function that does exactly that:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;Sales_Order_Line AddLine(SalesOrder_Service service, ref SalesOrder so)      &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; // Create a new Lines array with only the new line and update (meaning create the line)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; so.SalesLines = so.SalesLines.Concat(new [] { new Sales_Order_Line() }).ToArray();       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; service.Update(ref so);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; return so.SalesLines[so.SalesLines.Length-1];      &lt;br /&gt;}&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Again – If this method existed server side, automatically added by NAV WS, it would be able to do the right thing even though people had mangled in the application logic and change the line numbering sequence or whatever.&lt;/p&gt;  &lt;p&gt;A different approach would be to create a codeunit for adding lines and expose this as an extension to the page (functions added to the page). It wouldn’t make the consumers job much easier since we would still have to have any updates to the SalesOrder written before calling the function AND we would have to re-read the sales order after calling the function.&lt;/p&gt;  &lt;p&gt;Remember, that if you are using this function from NAV 2009 RTM you might want to consider using PrepareForUpdate before AddLine and GetCopy after AddLine, just as you would do with Update. You could even add another parameter and have that done by the function itself.&lt;/p&gt;  &lt;h3&gt;Working with other Header/Line objects&lt;/h3&gt;  &lt;p&gt;Although the samples in this post are using the Sales Orders, the same pattern can be reused for other occurrences of the Header/Line pattern. Just remember that the Header needs to be created first, then you can update the header and create the lines – and last (but not least) you can update the lines. &lt;/p&gt;  &lt;h3&gt;GetCopy and PrepareForUpdate&lt;/h3&gt;  &lt;p&gt;Here is a code-listing of GetCopy and PrepareForUpdate – I have tested these functions on a number of different record types and they should work generically&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;/// &amp;lt;summary&amp;gt;      &lt;br /&gt;/// Get a copy of a record for comparison use afterwards       &lt;br /&gt;/// &amp;lt;/summary&amp;gt;       &lt;br /&gt;/// &amp;lt;param name=&amp;quot;obj&amp;quot;&amp;gt;the record to copy&amp;lt;/param&amp;gt;       &lt;br /&gt;/// &amp;lt;returns&amp;gt;a copy of the record&amp;lt;/returns&amp;gt;       &lt;br /&gt;object GetCopy(object obj)       &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Type type = obj.GetType();       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; object copy = Activator.CreateInstance(type);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; foreach (PropertyInfo pi in type.GetProperties())       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (pi.PropertyType.IsArray)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Copy each object in an array of objects      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Array arr = (Array)pi.GetValue(obj, null);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Array arrCopy = Array.CreateInstance(arr.GetType().GetElementType(), arr.Length);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; for (int arrIdx = 0; arrIdx &amp;lt; arr.Length; arrIdx++)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; arrCopy.SetValue(GetCopy(arr.GetValue(arrIdx)), arrIdx);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; pi.SetValue(copy, arrCopy, null);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; else       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Copy each field      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; pi.SetValue(copy, pi.GetValue(obj, null), null);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; return copy;       &lt;br /&gt;} &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff" face="Courier New"&gt;/// &amp;lt;summary&amp;gt;      &lt;br /&gt;/// Prepare record for update       &lt;br /&gt;/// Set &amp;lt;field&amp;gt; to null if a string field hasn't been updated       &lt;br /&gt;/// Set &amp;lt;field&amp;gt;Specified to false if a non-string field hasn't been updated       &lt;br /&gt;/// &amp;lt;/summary&amp;gt;       &lt;br /&gt;/// &amp;lt;param name=&amp;quot;obj&amp;quot;&amp;gt;record to prepare for update&amp;lt;/param&amp;gt;       &lt;br /&gt;/// &amp;lt;param name=&amp;quot;copy&amp;quot;&amp;gt;copy of the record (a result of GetCopy)&amp;lt;/param&amp;gt;       &lt;br /&gt;void PrepareForUpdate(object obj, object copy)       &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Debug.Assert(obj.GetType() == copy.GetType());       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Type type = obj.GetType();       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; PropertyInfo[] properties = type.GetProperties();       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; for(int idx=0; idx&amp;lt;properties.Length; idx++)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; PropertyInfo pi = properties[idx];       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (pi.Name != &amp;quot;Key&amp;quot; &amp;amp;&amp;amp;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;pi.GetCustomAttributes(typeof(System.Xml.Serialization.XmlIgnoreAttribute), false).Length == 0)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (pi.PropertyType.IsArray)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Compare an array of objects - recursively      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Array objArr = (Array)pi.GetValue(obj, null);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Array copyArr = (Array)pi.GetValue(copy, null);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; for (int objArrIdx = 0; objArrIdx &amp;lt; objArr.Length; objArrIdx++)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; object arrObj = objArr.GetValue(objArrIdx);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; PropertyInfo keyPi = arrObj.GetType().GetProperty(&amp;quot;Key&amp;quot;);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; string objKey = (string)keyPi.GetValue(arrObj, null);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; for (int copyArrIdx = 0; copyArrIdx &amp;lt; copyArr.Length; copyArrIdx++)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; object arrCopy = copyArr.GetValue(copyArrIdx);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (objKey == (string)keyPi.GetValue(arrCopy, null))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; PrepareForUpdate(arrObj, arrCopy);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; else       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; object objValue = pi.GetValue(obj, null);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (objValue != null &amp;amp;&amp;amp; objValue.Equals(pi.GetValue(copy, null)))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Values are the same – signal no change      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; if (pi.PropertyType == typeof(string))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Strings doesn’t have a &amp;lt;field&amp;gt;Specified property – set the field to null      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; pi.SetValue(obj, null, null);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; else       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; {&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // The &amp;lt;field&amp;gt;Specified is autogenerated by Visual Studio as the next property&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; idx++;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; PropertyInfo specifiedPi = properties[idx];&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Exception if this assumption for some reason isn’t true&lt;/font&gt;&lt;font color="#0000ff" face="Courier New"&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Debug.Assert(specifiedPi.Name == pi.Name + &amp;quot;Specified&amp;quot;);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; specifiedPi.SetValue(obj, false, null);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }       &lt;br /&gt;} &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;That’s it, not as straightforward as you could have wished for, but SP1 definitely makes things easier once it comes out.&lt;/p&gt;  &lt;p&gt;And I will be pushing for a better programming model for this in v7.&lt;/p&gt;  &lt;p&gt;Enjoy&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;font color="#0080ff"&gt;Freddy Kristiansen       &lt;br /&gt;&lt;/font&gt;&lt;/em&gt;&lt;em&gt;PM Architect      &lt;br /&gt;Microsoft Dynamics NAV&lt;/em&gt;&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9648012" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/freddyk/archive/tags/Web+Services/default.aspx">Web Services</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009/default.aspx">NAV 2009</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/Sales+Order/default.aspx">Sales Order</category><category domain="http://blogs.msdn.com/freddyk/archive/tags/NAV+2009+SP1/default.aspx">NAV 2009 SP1</category></item></channel></rss>