Category Archives: Uncategorized

Microsoft Edge Chromium and ClickOnce

The new Microsoft Edge Chromium browser doesn’t provide native support for ClickOnce as of version 81.0.416.6.

It will by default NOT work to launch a Smart Office ClickOnce install point link in Edge Chromium.

To enable ClickOnce support in Edge Chromium:

  1. Enter edge://flags link in Edge Chromium browser.
  2. Scroll down to ClickOnce Support setting and select ‘Enable’ from the dropdown list.
  3. Restart the browser.
Note: This setting will be overridden if your organization configures the 
"Allow users to open files using the ClickOnce protocol" policy. – Windows

The Edge Chromium will always prompt the user before launch of a 
ClickOnce link because Chromium doesn't rely on the Windows Security Zones.

ScriptUtil.LoadAssemblyFromUrl() is Obsolete

The ScriptUtil.LoadAssemblyFromUrl method was marked obsolete in v10.2.1.0 HF32. A workaround for M3 scripts using the method is to replace with the following code:

var assembly = Assembly.Load(WebReader.GetRequestBinary(url));

In between version 10.2.1.0.333 (HF32) and 10.2.1.0.385 (HF42) the obsolete method will
fail in silence and just return null.

This has been changed in 10.2.1.0.389 (HF43) and an InvalidOperationException will be thrown when calling the method.

Calling a REST service from JScript

In this post we will take a look how to consume a REST service from the Smart Office JScript code. Related blog posts that focus on M3 APIs can be found at the following links.
Calling M3 APIs in JScript
Background workers in Smart Office scripts

Setting up the REST Data Service

Let’s start with the script code. It’s necessary to import the following three assemblies to gain access to the necessary classes and interfaces.

import MForms;
import Mango.Core.Services;
import System.ComponentModel;

The first step is to get a handle to the REST data service from the DataServiceManager.

var svc : IDataService = DataServiceManager.Current.Get("REST");

The REST data service is configured with a parameter set put in a DataItemCollection, so we start by setting the service address. I’m going to use a fake online REST service for the example to demonstrate how it’s done.

var params = new DataItemCollection();
params.Add(new DataItem(RestDataService.KeyBaseAddress, "https://jsonplaceholder.typicode.com"));

The available parameters can be found in the RestDataService class, the name starting with Key. It’s necessary to set KeyBaseAddress for the REST requests. KeyUri can contain a relative URI that is combined with KeyBaseAddress. Both parameters support variable substitution using names within curly braces {}. For example:

params.Add(new DataItem(RestDataService.KeyUri, "/users/{userId}"));
params.Add(new DataItem("userId", e.Argument));

or like this. Both will will result in the same endpoint URL.

params.Add(new DataItem(RestDataService.KeyUri, "/users/" + e.Argument));

If the userId variable equals to 7 the combined parameters results in the REST URL:

https://jsonplaceholder.typicode.com/users/7

The service request and response type headers must be specified unless the default values are required. The default values are the following two lines, that can be added for clarity but also omitted.

params.Add(new DataItem(RestDataService.KeyAcceptHeader, RestDataService.ContentTypeApplicationXml));
params.Add(new DataItem(RestDataService.KeyOutputType, RestDataService.OutputTypeDataItem));

 

Use the Background Worker to Prevent UI Freeze

Now we’re almost done, but the data service require that it’s running on a background thread. If not, it will throw an Exception to make you aware of that. This is done to prevent potential UI freeze and annoyed users. Let’s have a look at a simple solution by using the BackgroundWorker class.

var worker = new BackgroundWorker();
// Set the function that performs the service call
worker.add_DoWork(OnDoWork); 

// Set the function that performs update and clean-up after the call is done        
worker.add_RunWorkerCompleted(OnRunWorkerCompleted); 

// Launch the service call and provide start parameters
worker.RunWorkerAsync(serviceParameters);

The OnDoWork function will execute on the background  thread and call the data service. When done the OnRunWorkerCompleted function will execute on the UI thread to handle the response and do clean up after the call. Please, always handle Exceptions in your code!

public function OnDoWork(sender : Object, e : DoWorkEventArgs) 
{
    try 
    {
       var serviceParameters = e.Argument;
       // The data service call is done here (this is the background thread)
       e.Result = serverResponse
    } 
    catch(ex) 
    {
       // Exceptions must be handled
    }         
}   
public function OnRunWorkerCompleted(sender : Object, e : RunWorkerCompletedEventArgs) 
{
    try 
    {
       var worker = sender;
       worker.remove_DoWork(OnDoWork);
       worker.remove_RunWorkerCompleted(OnRunWorkerCompleted);
       var serviceResponse = e.Result;
       // The worker has completed its work (this is the UI thread) 
    } 
    catch(ex) 
    {
        // Exceptions must be handled 
    } 
}

The Full Script Example

Here is the full script example for you to test in the ISO Script Tool mforms://jscript:

import MForms;
import Mango.Core.Services;
import System.ComponentModel;

package MForms.JScript 
{
   class RESTDemo 
   {
      var debug;
      var controller;     
      public function Init(element: Object, args: Object, controller : Object, debug : Object) 
      {
         this.debug = debug;
         this.controller = controller;        
         debug.WriteLine("Script Initializing.");            
         DoRestCallOnBackgroundWorker();        
      }
    
      public function DoRestCallOnBackgroundWorker()
      { 
         debug.WriteLine("The REST service call is made on a background worker");          
         var worker = new BackgroundWorker();
         worker.add_DoWork(OnDoWork);
         worker.add_RunWorkerCompleted(OnRunWorkerCompleted);
        
         var userId = 7;         
         worker.RunWorkerAsync(userId);
      }
      public function OnDoWork(sender : Object, e : DoWorkEventArgs) 
      {
         try 
         {
            debug.WriteLine("OnDoWork started");
            debug.WriteLine("Calling a REST service getting DataItem response data");

            var svc : IDataService = DataServiceManager.Current.Get("REST");
            var params = new DataItemCollection();
            params.Add(new DataItem(RestDataService.KeyBaseAddress, "https://jsonplaceholder.typicode.com/"));             
            params.Add(new DataItem(RestDataService.KeyUri, "users/{userId}"));                          
            params.Add(new DataItem(RestDataService.KeyAcceptHeader, RestDataService.ContentTypeTextXml)); // default value                                  
            params.Add(new DataItem(RestDataService.KeyOutputType, RestDataService.OutputTypeDataItem));   // default value
            params.Add(new DataItem("userId", e.Argument)); 
            
            var dataRequest = new DataRequest(params);
            dataRequest.OperationType = "GET";
            var dataResponse = svc.Execute(dataRequest.OperationType, dataRequest);
            e.Result = dataResponse.Data;            
            debug.WriteLine("OnDoWork done");    
         } 
         catch(ex) 
         {
           debug.WriteLine("Exception: " + ex.Message);
           e.Result = null;
         }         
      }
      public function OnRunWorkerCompleted(sender : Object, e : RunWorkerCompletedEventArgs) 
      {
         try 
         {
            debug.WriteLine("OnRunWorkerCompleted running on UI thread");    
            var worker = sender; 
            worker.remove_DoWork(OnDoWork);
            worker.remove_RunWorkerCompleted(OnRunWorkerCompleted);
            if(e.Error != null) 
            {
               debug.WriteLine("Error: " + e.Error.Message); 
               return;
            }
            debug.WriteLine("Fetched user data for:");           
            var dataItem = e.Result;
            debug.WriteLine(dataItem["name"] + " (" + dataItem["username"] + ")" + " Phone " + dataItem["phone"]);  
         } 
         catch(ex) 
         {
            debug.WriteLine("Exception: " + ex.Message);
         }
      }
   }
}

 

Debug output when running the sample in the Script Tool.

Script Initializing.
The REST service call is made on a background worker
OnDoWork started
Calling a REST service getting DataItem response data
OnDoWork done
OnRunWorkerCompleted running on UI thread
Fetched user data for:
Kurtis Weissnat (Elwyn.Skiles) Phone 210.067.6132

New Log Viewer – Part III

Part I – Overview of the Improved UI
Part II – How to Find Your Way
Part IV – ClickOnce Installation Log
Part V – M3 Transaction Time Measurements

Two of the new features in the improved UI are the side-panels with categories to the left and facts to the right.

The Basic Facts

Every time the selected session changes, relevant support information are extracted and presented in the Fact area, e g Smart Office version, launch method, Windows related version, feature details, languages timeouts etc. The basic facts often needed in a support case.

ISOLogViewerFactsEnv

To compare fact value changes between sessions in the log file, you select the full log file in the session selector.  This will list duplicate facts one for each session.

A click on a listed fact will scroll the originating log line into view and select it. The context menu on a selected fact reveals more options that may help you further.

ISOLogViewerFactsMenu
Copy Value

Copy puts the presented value into the Windows Clipboard.

Copy Matches to Clipboard

Copy the log entries from where the fact was extracted into the Windows Clipboard.

View External

Show the log entries from where the fact was extracted in the Windows associated text viewer.

Browse

Browse is enabled if the fact value is an URL and the user is allowed to launch external applications in Windows according to the Smart Office settings. It will launch the URL in the Windows associated browser application.

Open file location

Open file location is enabled if the fact value is an existing file path and the user is allowed to launch external applications in Windows according to the Smart Office settings. It will launch the Windows File Explorer and show the folder.

 

I’ve got a recurring set of log entries I often look for

You can create a category in the category area that will collect the log entries that match any of the aspects defined. If you’re a feature or SDK application developer you can create a category that matches the origin i e the namespace of your assembly. Some entries may appear more than once in the result if many aspects matches.

ISOLogViewerCreateCategoryfrom

Select one or many log entries that is representative for the category you wish to create and click on Create Category from in the context menu, it will open the category definition dialog with the values from the selection added as options for the category definition.

Select a category Section from the list of existing ones or type a new section name. Name the category in the Header field. Minimize the number of matching values to get fewer more relevant matches. All log entries that contains ANY of the aspects defined will be part of the category.

ISOLogViewerCategoryEditor

Press Save button and you will have your category in the left side-panel for quick content selection.

ISOLogViewerMyCategory.PNG

Launch the Log Viewer targeting a specific feature or SDK application

You can create a Shortcut with a parameterized link on the Canvas to launch the Log Viewer on specific content, for debugging scenarios or targeting a specific feature or SDK application. The base launch link for the Log Viewer is:

internal://log

Add a free-text search parameter to start with all entries matching the search criteria, in this example all entries related to user idle detection.

internal://log?search=idledetect

This can be extended with a session parameter to specify the initial session to select in the log. It can be a value [1..n], last will select the current session, prev will select the previous session and full selects the whole log file. Previous is useful if you examine something that happens during shutdown and you examine the log after a restart.

internal://log?search=idledetect&session=2
internal://log?search=idledetect&session=last
internal://log?search=idledetect&session=prev

and you can of course specify a category as initial selection e g your custom category

internal://log?cat=service unavailable
internal://log?cat=service%20unavailable
internal://log?cat=service unavailable&session=full

Finally you have a sessiontime parameter that may be useful in some special cases. If the sessiontime parameter is present, the session parameter is ignored.

internal://log?search=idledetect&sessiontime=13:10