2014-11-14

Could not load file or assembly Microsoft.Office.BusinessApplications.Fba VSTO error

Here is an error that plagued me (occasionally) for months.  Since Office 2013 was rolled out, we would sometimes get a call from a user trying to install an older Visual Studio Tools for Office add-in. Not sure how much of the specifics are at play here, but these were the facts in this situation:

Windows version: Windows 7
New Office version: Office 2013
VSTO Add-In version: Office 2007
VSTO Add-In application: Excel

The full text of the error was:

The value of the property 'type' cannot be parsed.  The error is: Could not load file or assembly 'Microsoft.Office.BusinessApplications.Fba, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9r29c' or one of its dependencies. The system cannot find the file specified. (C:\Program Files (x86)\Common Files\Microsoft Shared\VSTO\10.0\VSTOInstaller.exe.Config line 10)

clip_image002

For months, all I could find was this post suggesting to repair Office.  Unfortunately, it failed to correct the issue and we were forced to uninstall and reinstall Office 2013 on a number of machines.  After a successful reinstall of Office, the VSTO installation ran to completion. This is a pain for both the support personnel and the users.

Today I found and successfully used the post at the end of this thread.  My experience wasn’t quite as clean as described, but it worked.  Here’s what I did:

  1. I right-clicked on setup.exe (installer for VSTO application) and chose ‘Run as administrator’. Answered [Yes] at the UAC prompt.
  2. I received a message informing that I already had the VSTO application installed.  The application was not showing in Excel, however this was already a step further than previous installation attempts.
  3. I pushed forward, found the VSTO application in Programs and Features and uninstalled it.
  4. Finally, I ran setup.exe again - this time not using ‘Run as administrator’.  It installed successfully and runs as expected. (Plan was to use Run as admin again if it failed, but it wasn’t required.)

Hope this helps others, too.

Later.

2014-01-06

Improvement to SAP JSON RFC service

In my last post, I described an essential piece of the SAP UI5 development as being the JSON HTTP handler.  This week I enhanced the service to allow more complex data types to be passed into the RFC.

The issue I encountered was this: the function module I was calling had input values that were more than simple data types.  It appears that the JSON HTTP handler as designed by Cesar would allow me to pass the JSON data directly in from HTTP.  However, since my SAP UI5 in JavaScript skills are currently much weaker than my ABAP I wanted a simple way to pass this data as a URL parameter to the SAP HTTP handler and let it do the work of converting the input to JSON.  Here’s what I did. 

I added two new private methods to the HTTP hander class:

MAP_QS2JSON: A recursive routine that reads the query string and determines whether the name is a table/structure/simple type.  This determination is made by appending [], {}, or nothing to the name of the query parameter.

METHOD map_qs2json.
*****************************************************************zkegm**
* Purpose: Map query string name/values to JSON format.  Rewrote this  *
*          to handle table data input also.                            *
*----------------------------------------------------------------------*
* -Changed by-       -Date-     -Req #-  -Action-                      *
* Jeff Woehler       2014.01.01 PLM8772  Initial.                      *
************************************************************************
 
* We map the query string to a simple JSON input. Handy for REST style queries.
* The query string may come from GET requests in the url and content data in
* POST request in x-www-form-urlencoded. ICF handles this perfectly and mixes both!! Great!!
 
  DATA: lv_lines                      TYPE i,
        lv_idx                        TYPE i.
  DATA: ls_save_nvp                   TYPE ihttpnvp,
        lt_nvp                        TYPE tihttpnvp,
        lt_tr_nvp                     TYPE tihttpnvp,
        lv_tr_json                    TYPE string,
        lv_name                       TYPE string,
        lv_len                        TYPE i,
        lv_table                      TYPE xfeld.
  FIELD-SYMBOLS <ls_nvp> TYPE ihttpnvp.
 
  lt_nvp[] = it_qs_nvp[].
  SORT lt_nvp[] BY name.
 
  lv_lines = lines( lt_nvp[] ).
  CLEAR: lv_idx, lv_table.
 
  LOOP AT lt_nvp ASSIGNING <ls_nvp>.
    ADD 1 TO lv_idx.
 
    " ABAP is upper case internally anyway.
    TRANSLATE <ls_nvp>-name TO UPPER CASE.
 
    IF lv_table = abap_true.
      IF <ls_nvp>-name NE ls_save_nvp-name.
        CONCATENATE rv_json ']'
               INTO rv_json RESPECTING BLANKS.
        lv_table = abap_false.
      ENDIF.
    ENDIF.
 
    IF lv_idx NE 1.
      CONCATENATE rv_json ','
             INTO rv_json RESPECTING BLANKS.
    ENDIF.
 
    IF <ls_nvp>-name CS '[]'.
      lv_len = strlen( <ls_nvp>-name ) - 2.
      lv_name = <ls_nvp>-name(lv_len).
 
      IF <ls_nvp>-name NE ls_save_nvp-name.
        lv_table = abap_true.
        CONCATENATE rv_json '"' lv_name '":['
               INTO rv_json RESPECTING BLANKS.
      ENDIF.
 
      lt_tr_nvp = map_tblrow2nvp( iv_value      = <ls_nvp>-value
                                  iv_delimiter  = ';' ).
      lv_tr_json = map_qs2json( lt_tr_nvp ).
      CONCATENATE rv_json lv_tr_json
             INTO rv_json RESPECTING BLANKS.
    ELSEIF <ls_nvp>-name CS '{}'.
      lv_len = strlen( <ls_nvp>-name ) - 2.
      lv_name = <ls_nvp>-name(lv_len).
      lt_tr_nvp = map_tblrow2nvp( iv_value      = <ls_nvp>-value
                                  iv_delimiter  = ';' ).
      lv_tr_json = map_qs2json( lt_tr_nvp ).
      CONCATENATE rv_json '"' lv_name '":' lv_tr_json
             INTO rv_json RESPECTING BLANKS.
    ELSE.
      CONCATENATE rv_json '"' <ls_nvp>-name '":"' <ls_nvp>-value '"'
             INTO rv_json RESPECTING BLANKS.
    ENDIF.
 
    IF lv_idx < lv_lines.
    ENDIF.
 
    ls_save_nvp = <ls_nvp>.
  ENDLOOP.
 
  CONCATENATE '{' rv_json '}' INTO rv_json.
 
ENDMETHOD.

MAP_TBLROW2NVP: This routine splits the input structure value at a ‘;’ and creates a new table of name/value pairs for which to generate a JSON string.



METHOD map_tblrow2nvp.
*****************************************************************zkegm**
* Purpose: Map table-row to name/value pair.                           *
*----------------------------------------------------------------------*
* -Changed by-       -Date-     -Req #-  -Action-                      *
* Jeff Woehler       2014.01.01 PLM8772  Initial.                      *
************************************************************************
 
  DATA: lt_params                     TYPE TABLE OF string,
        lv_param                      TYPE string,
        ls_nvp                        TYPE ihttpnvp.
 
  SPLIT iv_value AT iv_delimiter INTO TABLE lt_params[].
 
  LOOP AT lt_params INTO lv_param.
    SPLIT lv_param AT '=' INTO ls_nvp-name ls_nvp-value.
    APPEND ls_nvp TO rt_nvp[].
  ENDLOOP.
 
ENDMETHOD.

Caveat: this improvement obviously wouldn’t handle nested levels of structure - example: a structure containing a field that is a structure.  But this is (I hope) a tiny improvement to the the wonderful work that Cesar Martin put together.


Now I can have an SAP UI5 refresh routine like this and ABAP will handle the JSON. 



  • I_TEMPORARY_FLAG: simple value
  • I_SEARCH_DATE: structure of fields – single row
  • IT_WERKS: table of data – multiple rows


refresh: function(event) {
    sap.ui.getCore().setModel(undefined);
    
        var url = 'http://kww-h10s.kimball.com:8002/keg/rfc/Z_E_RFC_GCS_AVG_TURN_TIME/lc?I_TEMPLATE=00001';
      
        var cbTemp = this.byId('cbTemp');
        if (cbTemp.getChecked() == true) {
            url += '&I_TEMPORARY_FLAG=X';
        } 
        
        var date1 = this.byId('date1');
        var date2 = this.byId('date2');
        url += '&I_SEARCH_DATE{}=SIGN=I;OPTION=BT;LOW=' + date1.getYyyymmdd() + ';HIGH=' + date2.getYyyymmdd();
        
        var lstPlant = this.byId('lstPlant');
        var plants = lstPlant.getSelectedKeys();
        for (var idx = 0; idx < plants.length; idx++) {
            url += '&IT_WERKS[]=SIGN=I;OPTION=EQ;WERKS_LOW=' + plants[idx];
        }
      
      var oModel4 = new sap.ui.model.json.JSONModel(url);
      sap.ui.getCore().setModel(oModel4);
}

Get my update to the handler here.  Be sure to read the spec and documentation from the original developer here.


Enjoy!

2013-07-18

Develop an SAP UI5 Application

Overview

SAP has built a great library of controls for generating colorful, animated, snappy reports for the business.

Here’s a simple example of an SAP UI5 application built with Eclipse and published to an SAP BSP application. The report displays the results of an ‘Average Turn Time’ report in a pie chart. It offers inputs to filter the data to only Temporary changes or all changes and also lets the user adjust the date range.

image

Dependencies

SAP ERP software component UISAPUI5 (SAP UI5)

Eclipse (I’m using Version: 4.2.1)

SAP HTML5 SDK (I’m using version 1.12.1)

  • SAP core library (installed on a server)
  • client software (installed in Eclipse – helpful post)

JSON HTTP Handler

First I needed a way to convert the results from an SAP function module to JSON format to be consumed by the SAP UI5 components. I found a project on SAP SCN that converts function module results to JSON. I set it up in SICF.

https://cw.sdn.sap.com/cw/groups/json-adapter-for-abap-function-modules

clip_image002

I had to make one small change to use a different class for the actual JSON conversion.  In method SERIALIZE_ID of class ZCL_JSON_HANDLER, instead of calling the transformation, I’m using class CL_TREX_JSON_SERIALIZER.

   1: *          call transformation id options data_refs = 'embedded'
   2: *                                         initial_components = 'include'
   3: *                                 source (stab)
   4: *                                 result xml json_writer.
   5: *          o_string = cl_abap_codepage=>convert_from( json_writer->get_output( ) ).
   6:           CREATE OBJECT json_writer
   7:             EXPORTING
   8:               data = stab.
   9:           json_writer->serialize( ).
  10:           o_string = json_writer->get_data( ). 

SAP UI5 Application


In Eclipse, I created the SAP UI5 application zem_ui5_gcs01.  The application uses a very simple index.html.  However, be sure to include the additional library: sap.viz.



   1: <!DOCTYPE html>
   2: <html><head>
   3:     <meta http-equiv='X-UA-Compatible' content='IE=edge' />
   4:     <title>Average Turn Time</title>
   5:  
   6:     <script id='sap-ui-bootstrap' type='text/javascript'
   7:         src='http://[host-to-sap-ui5-core]/sapui5/resources/sap-ui-core-all.js'
   8:         data-sap-ui-theme='sap_goldreflection'
   9:         data-sap-ui-libs='sap.ui.commons, sap.viz'></script>
   1:  
   2:             
   3: <script>
   4:     sap.ui.localResources('avg_turn_time');
   5:     var view = sap.ui.view({
   6:         id: "main1",
   7:         viewName:"avg_turn_time.main",
   8:         type:sap.ui.core.mvc.ViewType.JS
   9:     });
  10:     view.placeAt('content');
</script>
  10:  
  11: </head>
  12: <body class='sapUiBody'>
  13:     <div id='content'></div>
  14: </body>
  15: </html>

It calls function module Z_E_RFC_GCS_AVG_TURN_TIME. This returns in the following format:


1:  {  
2: "et_actions": [
3: { "action":"00012", "display_order":"00002", "task":"Through Request",
4: "day_count":1, "record_count":99, "day_count_2":1, "record_count_2":99 },
5: { "action":"00046", "display_order":"00003", "task":"Through Analysis",
6: "day_count":1, "record_count":99, "day_count_2":0, "record_count_2":99 },
7: { "action":"00027", "display_order":"00004", "task":"Through Approval",
8: "day_count":2, "record_count":99, "day_count_2":1, "record_count_2":99 },
9: { "action":"00088", "display_order":"00005", "task":"Through Implementation",
10: "day_count":49, "record_count":91, "day_count_2":48, "record_count_2":91 },
11: { "action":"00000", "display_order":"00006", "task":"Through Archive",
12: "day_count":59, "record_count":91, "day_count_2":10, "record_count_2":91 }
13: ],
14: "e_record_count":100
15: }

The SAP UI5 javascript application contains just one view avg_turn_time/main.view.js, that contains the UI for the report.



   1: sap.ui.jsview("avg_turn_time.main", {
   2:  
   3:       getControllerName : function() {
   4:          return "avg_turn_time.main";
   5:       },
   6:  
   7:       createContent : function(oController) {
   8:           var controls = [];
   9:           
  10:           var btn1 = new sap.ui.commons.Button({
  11:               id: this.createId('btn1'),
  12:               text: 'press me', 
  13:               press: [ oController.button_press, oController ]
  14:           });
  15:                               
  16:           var lblTemp = new sap.ui.commons.Label(this.createId('lblTemp'), { 
  17:               text: "Temporary:" 
  18:           });
  19:           var cbTemp = new sap.ui.commons.CheckBox(this.createId('cbTemp'), { 
  20:               checked: true,
  21:               change: [ oController.checkbox_change, oController ]
  22:           });
  23:           
  24:           var lblDate = new sap.ui.commons.Label(this.createId('lblDate'), { 
  25:               text: "Date:"
  26:           });
  27:           var date1 = new sap.ui.commons.DatePicker(this.createId('date1'), { 
  28:               yyyymmdd: '20130101' ,
  29:               change: [ oController.date_change, oController ]
  30:           });
  31:           var date2 = new sap.ui.commons.DatePicker(this.createId('date2'), { 
  32:               yyyymmdd: '20130105',
  33:               change: [ oController.date_change, oController ]
  34:           });
  35:  
  36:           var dataset2 = new sap.viz.ui5.data.FlattenedDataset({
  37:              dimensions : [
  38:                { axis : 1, name : 'Task', value : '{task}' },
  39:              ],
  40:              measures : [
  41:                { name : 'Average', value : '{day_count_2}' }
  42:              ],
  43:              data : { path : '/et_actions'}
  44:           });
  45:           
  46:           var pie1 = new sap.viz.ui5.Pie(this.createId('pie1'), {
  47:               dataset: dataset2
  48:           });
  49:  
  50:           controls.push(
  51:                   lblTemp, cbTemp,
  52:                   lblDate, date1, date2,
  53:                   btn1,
  54:                   pie1
  55:                   );
  56:  
  57:  
  58:           return controls;
  59:       }
  60:  
  61: });

In the controller, we create a ‘refresh’ routine to call the web service with selected input parameters.



   1: sap.ui.controller("avg_turn_time.main", {
   2:  
   3:  
   4: /**
   5: * Called when a controller is instantiated and its View controls (if available) are already created.
   6: * Can be used to modify the View before it is displayed, to bind event handlers and do other one-time initialization.
   7: */
   8:    onInit: function() {
   9:    },
  10:  
  11: /**
  12: * Similar to onAfterRendering, but this hook is invoked before the controller's View is re-rendered
  13: * (NOT before the first rendering! onInit() is used for that one!).
  14: */
  15:    onBeforeRendering: function() {
  16:    },
  17:  
  18: /**
  19: * Called when the View has been rendered (so its HTML is part of the document). Post-rendering manipulations of the HTML could be done here.
  20: * This hook is the same one that SAPUI5 controls get after being rendered.
  21: */
  22:    onAfterRendering: function() {
  23:        //var txt1 = this.byId('txt1');
  24:        //txt1.focus();
  25:        this.refresh(undefined);
  26:    },
  27:  
  28: /**
  29: * Called when the Controller is destroyed. Use this one to free resources and finalize activities.
  30: */
  31: //   onExit: function() {
  32: //
  33: //   }
  34:  
  35:     button_press: function(event) {
  36:         this.refresh(event);
  37:     },
  38:    
  39:     date_change: function(event) {
  40:         this.refresh(event);
  41:     },
  42:     
  43:     checkbox_change: function(event) {
  44:         this.refresh(event);
  45:     },
  46:     
  47:     refresh: function(event) {
  48:         sap.ui.getCore().setModel(undefined);
  49:         
  50:             var url = 'http://<sap-host>:<sap-port>/keg/rfc/Z_E_RFC_GCS_AVG_TURN_TIME/lc?I_TEMPLATE=00001';
  51:           
  52:             var cbTemp = this.byId('cbTemp');
  53:             if (cbTemp.getChecked() == true) {
  54:                 url += '&I_TEMPORARY_FLAG=X';
  55:             } 
  56:             
  57:             var date1 = this.byId('date1');
  58:             var date2 = this.byId('date2');
  59:             url += '&I_SEARCH_DATE=IBT' + date1.getYyyymmdd() + date2.getYyyymmdd();
  60:           
  61:           var oModel4 = new sap.ui.model.json.JSONModel(url);
  62:           sap.ui.getCore().setModel(oModel4);
  63:     }
  64: });

Create the SAP BSP Application


I understand that Eclipse has the ability to connect to the SAP host and generate the BSP application automatically.  Unfortunately, due to our system release, that feature is not available to my team. 


However, SAP has provided a very nice compromise.  Report /UI5/UI5_REPOSITORY_LOAD can be used to upload the Eclipse JavaScript application to SAP as a new BSP application.  Some items of note:



  • Our system has no default code page.  Use Cp1252.
  • Activate node /default_host/sap/bc/ui5_ui5 (and subnodes).
  • During the upload, nodes are generated in two places.  Make sure they are all activated prior to execution.


/default_host/sap/bc/ui5_ui5/sap/[z_bsp_name]

/default_host/sap/bc/bsp/sap/[z_bsp_name]

Now, you’ll have access to an application at the following location.



http://[sap-host]:[sap-port]/sap/bc/ui5_ui5/sap/zem_ui5_gcs01/index.html

The result is what you see above – a cool pie chart of average time through each stage of a process.  With some more coding in events of the pie chart control, I’m pretty sure we could build some fancy drill-down capabilities… haven’t explored that yet.


Enjoy!