Skip to Content
0

Fiori, OData: Testing Create/Update from Gateway Client, consuming in Fiori

Jul 21, 2017 at 01:49 PM

881

avatar image

What I have done so far for my first OData service / Fiori application (the part that is working):

I have an ABAP RFC function for reading and (combined) creating/updating records on the database. The records are two user settings - a line consist of FioriUser (key field), LegalEntity and SourceSystem.

I created the entity, entity set and service implementation/mapping for Read and Update in SEGW. (I didn't do Create separately, I just create the record if none exists, yet, in the ABAP code.) I made FioriUser a key field and the other two not.

The read works, I can test it in Gateway Client (/IWFND/GW_CLIENT) with "GET" and the URL /sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='D012345')

Now where I have problems, probably a general understanding problem (of HTTP? of OData/REST?):

How to test the Update (or Create) in Gateway Client, if possible with all the inputs in the URL?

/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='D012345',LegalEntity='PERF50',SourceSystem='ERSTE01') doesn't work with GET, PUT or UPDATE, the error in /n/IWFND/ERROR_LOG is "Invalid key predicate". I found on this forum that this is because only FioriUser is a key field and the other two are not.

I can use /sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='D012345') (same URL as for Read) + method PUT (POST didn't work) + clicking the button "Use as Request" in the Gateway Client after having executed a Read (for one entity, not query for the entity set). This method is from https://blogs.sap.com/2014/03/06/let-s-code-crudq-and-function-import-operations-in-odata-service/ .

That tutorial doesn't go to the point of consuming the OData service that needs a body from Fiori and so far I only know how to call a URL with settingsModel.loadData(URI) where settingsModel = new JSONModel() and URI = "/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='"+userId+"')" and I encode any variables in the URI with encodeURIComponent(...) first. This other tutorial https://blogs.sap.com/2015/02/05/simple-exercise-on-odata-and-sap-ui5-application-for-the-basic-crud-operation/ does include the part how to consume it from Fiori, but when the writer gets to the Create and Update is where things get confusing for me and I can't really follow, e.g. do I really need these X-CSRF tokens? Apart from that I *think* he might be constructing a body for the Put request ... but maybe not, not sure. Do I need to follow this tutorial, is this the only way? Is there no way to do it with GET and appropriate URL parameters?

On this forum I found some threads for the "Invalid key predicate" error that all suggest to use ?$filter (e.g. https://archive.sap.com/discussions/thread/3800034 ) with = replaced with EQ (maybe only for the non-key fields?), but I'm not getting anywhere with that, I tried e.g.

/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='D012345')?$filter=LegalEntity EQ 'SLE0' AND SourceSystem EQ 'ERSTE01' /sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet?$filter=FioriUser EQ 'D012345' AND LegalEntity EQ 'SLE0' AND SourceSystem EQ 'ERSTE01'

and I tried replacing the spaces with %20 and changing some other details, but no luck. Maybe this is only for Read? Or for Query but not wanting all entities in the entity set? But not for Create / Update? (The asks in those threads were around Read, so it could be.)

I tried making all three fields key fields in SEGW so that /sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='D012345',LegalEntity='PERF50',SourceSystem='ERSTE01') would work without an "Invalid key predicate" error, but then the Read operation complains that the key fields are not mapped to inputs, and if I make them input fields then the Read operation complains that there is not at least one output field.

10 |10000 characters needed characters left characters exceeded
* Please Login or Register to Answer, Follow or Comment.

3 Answers

Best Answer
Monika Eggers
Oct 23, 2017 at 12:20 PM
0

I finally got a working version, with help:

sap.ui.define([
		"zmc_cust/controller/BaseController",
		"zmc_cust/model/formatter",
		"sap/ui/model/json/JSONModel",
		"sap/ui/model/odata/v2/ODataModel",
		"sap/ui/model/Filter",
		"sap/ui/model/FilterOperator",
		"sap/m/MessageToast",
		"sap/m/MessageBox"
	], function (BaseController, formatter, JSONModel, ODataModel, Filter, FilterOperator, MessageToast, MessageBox) {
		"use strict";


		return BaseController.extend("zmc_cust.controller.Worklist", {


			formatter: formatter,


			onInit : function () {
				var that = this;


				var settingsModel = new JSONModel();
				//attach function for end of asynchronous call
				settingsModel.attachRequestCompleted(function() {
					var legalEntity = settingsModel.getProperty("/d/LegalEntity");
					var legalEntityComboBox = that.getView().byId("LegalEntityComboBox");
					legalEntityComboBox.setSelectedKey(legalEntity);
					legalEntityComboBox.setValue(legalEntity);


					var sourceSystem = settingsModel.getProperty("/d/SourceSystem");
					var sourceSystemComboBox = that.getView().byId("SourceSystemComboBox");
					sourceSystemComboBox.setSelectedKey(sourceSystem);
					sourceSystemComboBox.setValue(sourceSystem);
			      });
				var userId = sap.ushell.Container.getService("UserInfo").getId();
				var URI = "/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='"+userId+"')";
				settingsModel.loadData(URI);
			},


			onUpdateFinished : function (oEvent) {
				// update the worklist's object counter after the table update
				this.getModel("worklistView").setProperty("/busy", false);
			},
			


			onNavBack : function() {
				var sPreviousHash = sap.ui.core.routing.History.getInstance().getPreviousHash(),
					oCrossAppNavigator = sap.ushell.Container.getService("CrossApplicationNavigation");


				if (sPreviousHash !== undefined || !oCrossAppNavigator.isInitialNavigation()) {
					history.go(-1);
				} else {
					oCrossAppNavigator.toExternal({
						target: {shellHash: "#Shell-home"}
					});
				}
			},


			onSavePress: function (event) {
				var that = this;
				
				var userId = sap.ushell.Container.getService("UserInfo").getId();
				var keyLegalEntity = this.getView().byId("LegalEntityComboBox").getSelectedKey();
				var keySourceSystem = this.getView().byId("SourceSystemComboBox").getSelectedKey();


				// parameter for model for updating the entity set
				var URI = "/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV";				
				var settingsModel = new sap.ui.model.odata.v2.ODataModel(URI);
				
				// update the entity set
				settingsModel.update("/ZMCSettingsSet(FioriUser='"+userId+"')", 
						             { "FioriUser" : userId, "LegalEntity" : keyLegalEntity, "SourceSystem" : keySourceSystem },
						             {
						            	 success : function() {
						            		 
						            		settingsModel.read("/ZMCSettingsSet(FioriUser='"+userId+"')", {
						     					success: function(){ 


						     						var legalEntity = settingsModel.getProperty("/ZMCSettingsSet('"+userId+"')/LegalEntity");
								 					var sourceSystem = settingsModel.getProperty("/ZMCSettingsSet('"+userId+"')/SourceSystem");								 					
								 				
								 					  if (legalEntity != keyLegalEntity || sourceSystem != keySourceSystem) {
								 						  MessageBox.error("Saving failed even though the request was successful", {styleClass: "sapUiSizeCompact"}); 
								 					  } else {
								 						  MessageToast.show("Settings saved");
								 						  that.onNavBack(); 
								 					  }
								 				
						     						
						     					},
						     					error: function() {
						     						MessageBox.error("Error reading data");
						     					}
						     				});
						            		 
						            	 },
						            	 error : function() {
						            		 MessageBox.error("Error updating data");
						            	 }
						             }
				                    );


			},


			onCancelPress: function (event) {
				var that = this;
				this.onNavBack();
				MessageToast.show("Action cancelled...");
			}


		});
	}
);

I guess it's not optimal because I have two different models, a JSONModel and a ODataModel (v2), but it seems the first is better for reading data and the second for writing data - while I did manage to send a PUT or MERGE message with the JSONModel, too, I didn't find a way to get and send the X-CSRF token and the ODataModel did this by itself. Anyway usually models should be defined "outside", not in the onInit and onSavePress methods - but as they are only used once it's okay. It works and we are not anymore "faking" our way to updating the database with GET requests with parameters that indicate to the underlying ABAP code that it's really an update, so I'm happy.

Two other things I learned: I need to use the success: function ... and error: function ... instead of attachRequestCompleted and attachRequestFailed in the update, because I did both the update and then a read to check if the update really worked and the attached code was of course executed for both requests. And when weird stuff happens and objects/classes supplied by framework/API suddenly don't have some of the methods they should have according to the documentation one should first look at the part in the beginning:

sap.ui.define([
		"zmc_cust/controller/BaseController",
		"zmc_cust/model/formatter",
		"sap/ui/model/json/JSONModel",
		"sap/ui/model/odata/v2/ODataModel",
		"sap/ui/model/Filter",
		"sap/ui/model/FilterOperator",
		"sap/m/MessageToast",
		"sap/m/MessageBox"
	], function (BaseController, formatter, JSONModel, ODataModel, Filter, FilterOperator, MessageToast, MessageBox)

and make sure they are in the same order both times and none is missing in either list.

Share
10 |10000 characters needed characters left characters exceeded
Monika Eggers
Aug 01, 2017 at 07:52 PM
0

I'm making a little bit of progress in understanding some things but not in actually getting it to work. I get now that updating an entity always requires a PUT or MERGE request with the parameters in the body, not in the URL, but I should not build the request directly (and therefore ignore the tutorial above where odata.request(...) is called), the model will take care of building the request. I found that in v2 ODataModel one calls the method submitChanges(...) but there is no method with submit in the name in the JSONModel, so not sure what to call there. I tried if just calling setData(...) might be enough but either it's not or something else is wrong.

A sample request that works with method PUT in SAP Gateway Client /n/IWFND/GW_CLIENT:

/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='D012345')

<?xml version="1.0" encoding="utf-8"?>
<entry xml:base="https://ldcidpv.mo.sap.corp:44378/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/" xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
 <id>https://ldcidpv.mo.sap.corp:44378/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet('D012345')</id>
 <title type="text">ZMCSettingsSet('D012345')</title>
 <updated>2017-08-01T19:03:47Z</updated>
 <category term="ZMC_SAFI_GWPROJECT_SRV.ZMCSettings" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
 <link href="ZMCSettingsSet('D012345')" rel="self" title="ZMCSettings"/>
 <content type="application/xml">
 <m:properties>
 <d:FioriUser>D012345</d:FioriUser>
 <d:LegalEntity>SLE0</d:LegalEntity>
 <d:SourceSystem>ERSTE01</d:SourceSystem>
 </m:properties>
 </content>
</entry>

My old code where I incorrectly tried to call getData(...) with all three parameters in the URL, which resulted in the "Invalid key predicate" error in /n/IWFND/ERROR_LOG, because it's calling getEntity / read again, not updateEntity, and it supplies more than the key parameter:

sap.ui.define([
    "zmc_cust/controller/BaseController",
    "zmc_cust/model/formatter",
    "sap/ui/model/json/JSONModel",
    "sap/ui/model/Filter",
    "sap/ui/model/FilterOperator",
    "sap/m/MessageToast",
    "sap/m/MessageBox"
  ], function (BaseController, formatter, JSONModel, Filter, FilterOperator, MessageToast, MessageBox) {
    "use strict";
    return BaseController.extend("zmc_cust.controller.Worklist", {
      formatter: formatter,
      onInit : function () {
        var that = this;
        var settingsModel = new JSONModel();
        //attach function for end of asynchronous call
        settingsModel.attachRequestCompleted(function() {
          var legalEntity = settingsModel.getProperty("/d/LegalEntity");
          var legalEntityComboBox = that.getView().byId("LegalEntityComboBox");
          legalEntityComboBox.setSelectedKey(legalEntity);
          legalEntityComboBox.setValue(legalEntity);


          var sourceSystem = settingsModel.getProperty("/d/SourceSystem");
          var sourceSystemComboBox = that.getView().byId("SourceSystemComboBox");
          sourceSystemComboBox.setSelectedKey(sourceSystem);
          sourceSystemComboBox.setValue(sourceSystem);
            });
        var userId = sap.ushell.Container.getService("UserInfo").getId();
        var URI = "/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='"+userId+"')";
        settingsModel.loadData(URI);
      },




      onUpdateFinished : function (oEvent) {
        // update the worklist's object counter after the table update
        this.getModel("worklistView").setProperty("/busy", false);
      },
      
      onNavBack : function() {
        var sPreviousHash = sap.ui.core.routing.History.getInstance().getPreviousHash(),
          oCrossAppNavigator = sap.ushell.Container.getService("CrossApplicationNavigation");
        if (sPreviousHash !== undefined || !oCrossAppNavigator.isInitialNavigation()) {
          history.go(-1);
        } else {
          oCrossAppNavigator.toExternal({
            target: {shellHash: "#Shell-home"}
          });
        }
      },




      onSavePress: function (event) {
        var that = this;
        
        var userId = sap.ushell.Container.getService("UserInfo").getId();
        var keyLegalEntity = this.getView().byId("LegalEntityComboBox").getSelectedKey();
        var keySourceSystem = this.getView().byId("SourceSystemComboBox").getSelectedKey();
        var settingsModel = new JSONModel();


        //attach function for end of asynchronous call
        settingsModel.attachRequestCompleted(function() {
          var legalEntity = settingsModel.getProperty("/d/LegalEntity");
          var sourceSystem = settingsModel.getProperty("/d/SourceSystem");
          if (event.getParameters().success) {
            if (legalEntity != keyLegalEntity || keySourceSystem != sourceSystem) {
              MessageBox.error("Saving failed even though the request was successful", {styleClass: "sapUiSizeCompact"}); 
            } else {
              MessageToast.show("Settings saved");
              that.onNavBack(); 
            }
          }
        });
        
        settingsModel.attachRequestFailed(function() {
          MessageBox.error("Saving failed due to connection or ABAP system problem", {styleClass: "sapUiSizeCompact"});         
        });


        
        var URIkeyUserId       = encodeURIComponent(userId);
        var URIkeyLegalEntity  = encodeURIComponent(keyLegalEntity); 
        var URIkeySourceSystem = encodeURIComponent(keySourceSystem);
        var URI = "/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='"+URIkeyUserId+"',LegalEntity='" + URIkeyLegalEntity + "',SourceSystem='" + URIkeySourceSystem + "')";
        settingsModel.loadData(URI);      
      },


      onCancelPress: function (event) {
        var that = this;
        this.onNavBack();
        MessageToast.show("Action cancelled...");
      }
    });
  }
);

I changed it like this (see the places with the comments in the beginning of the line), which in my understanding is coming closer to being correct ... but it's not working anyway. attachRequestCompleted() is called, but nothing was saved (error "Saving failed even though the request was successful" is shown and I can see on the database it was not updated):

sap.ui.define([
    "zmc_cust/controller/BaseController",
    "zmc_cust/model/formatter",
    "sap/ui/model/json/JSONModel",
    "sap/ui/model/Filter",
    "sap/ui/model/FilterOperator",
    "sap/m/MessageToast",
    "sap/m/MessageBox"
  ], function (BaseController, formatter, JSONModel, Filter, FilterOperator, MessageToast, MessageBox) {
    "use strict";
    return BaseController.extend("zmc_cust.controller.Worklist", {
      formatter: formatter,


      onInit : function () {
        var that = this;
        var settingsModel = new JSONModel();
        //attach function for end of asynchronous call
        settingsModel.attachRequestCompleted(function() {
          var legalEntity = settingsModel.getProperty("/d/LegalEntity");
          var legalEntityComboBox = that.getView().byId("LegalEntityComboBox");
          legalEntityComboBox.setSelectedKey(legalEntity);
          legalEntityComboBox.setValue(legalEntity);


          var sourceSystem = settingsModel.getProperty("/d/SourceSystem");
          var sourceSystemComboBox = that.getView().byId("SourceSystemComboBox");
          sourceSystemComboBox.setSelectedKey(sourceSystem);
          sourceSystemComboBox.setValue(sourceSystem);
            });
        var userId = sap.ushell.Container.getService("UserInfo").getId();
        var URI = "/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='"+userId+"')";
        settingsModel.loadData(URI);
      },


      onUpdateFinished : function (oEvent) {
        // update the worklist's object counter after the table update
        this.getModel("worklistView").setProperty("/busy", false);
      },
      
      onNavBack : function() {
        var sPreviousHash = sap.ui.core.routing.History.getInstance().getPreviousHash(),
          oCrossAppNavigator = sap.ushell.Container.getService("CrossApplicationNavigation");
        if (sPreviousHash !== undefined || !oCrossAppNavigator.isInitialNavigation()) {
          history.go(-1);
        } else {
          oCrossAppNavigator.toExternal({
            target: {shellHash: "#Shell-home"}
          });
        }
      },


      onSavePress: function (event) {
        var that = this;
        
        var userId = sap.ushell.Container.getService("UserInfo").getId();
        var keyLegalEntity = this.getView().byId("LegalEntityComboBox").getSelectedKey();
        var keySourceSystem = this.getView().byId("SourceSystemComboBox").getSelectedKey();
// new parameter  
        var userId = sap.ushell.Container.getService("UserInfo").getId();
        var URI = "/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='"+userId+"')";        
        var settingsModel = new JSONModel(URI);


        //attach function for end of asynchronous call
        settingsModel.attachRequestCompleted(function() {
          var legalEntity = settingsModel.getProperty("/d/LegalEntity");
          var sourceSystem = settingsModel.getProperty("/d/SourceSystem");
          if (event.getParameters().success) {
            if (legalEntity != keyLegalEntity || keySourceSystem != sourceSystem) {
              MessageBox.error("Saving failed even though the request was successful", {styleClass: "sapUiSizeCompact"}); 
            } else {
              MessageToast.show("Settings saved");
              that.onNavBack(); 
            }
          }
        });
        
        settingsModel.attachRequestFailed(function() {
          MessageBox.error("Saving failed due to connection or ABAP system problem", {styleClass: "sapUiSizeCompact"});         
        });


// replacing loadData with setData        
        // var URIkeyUserId       = encodeURIComponent(userId);
        // var URIkeyLegalEntity  = encodeURIComponent(keyLegalEntity); 
        // var URIkeySourceSystem = encodeURIComponent(keySourceSystem);
        // var URI = "/sap/opu/odata/sap/ZMC_SAFI_GWPROJECT_SRV/ZMCSettingsSet(FioriUser='"+URIkeyUserId+"',LegalEntity='" + URIkeyLegalEntity + "',SourceSystem='" + URIkeySourceSystem + "')";
        // settingsModel.loadData(URI);      
        settingsModel.setData({"FioriUser" : userId, "LegalEntity" : keyLegalEntity , "SourceSystem" : keySourceSystem} );
      },


      onCancelPress: function (event) {
        var that = this;
        this.onNavBack();
        MessageToast.show("Action cancelled...");
      }
    });
  }
);

There are no new errors in /n/IWFND/ERROR_LOG. The calls don't stop at external breakpoints in my update ABAP code or the ABAP code of the _DPC or _DPC_EXT classes (for the read they do). I tried to look at what happens with F12 in Chrome, but it doesn't tell me anything, it looks to my like setData() is called successfully and then, no idea why it doesn't work. Is some other call equivalent to submitChanges() in ODataModel needed?

Show 1 Share
10 |10000 characters needed characters left characters exceeded

I found in some code samples that additionally

sap.ui.getCore().setModel(settingsModel);

was called directly before or after the call to setData ... it didn't help, I get the same error as before. Also not sure if this is still necessary or was only in previous versions, as the code samples were from 2013.

0
Ram Burugu Aug 01, 2017 at 08:37 PM
0

Hello, I would like to share the official ODATA specification, SAP Gateway is an implementation of this spec.

http://www.odata.org/documentation/odata-version-2-0/operations/

This document has very clean instructions on how to perform ODATA operations - all CRUDQ( Creates, Reads, Updates,Deletes, Queries)

And here is the quick answer for your question on how to do creates/update.

For Create and Update, the parameters cannot be submitted on the Query string, they need to be passed in the request body. The Gateway framework tries to identify a REST Resource(Entity) based on the Query string, so passing non-key information is not helpful as the Resource was already uniquely identified by the Key properties.

Please see below screenshots for more information, make note of the difference in URL for POST and PUT

Create

Update


create.jpg (95.7 kB)
upadte.jpg (96.9 kB)
Show 1 Share
10 |10000 characters needed characters left characters exceeded

Thanks for helping with the Gateway Client part of the problem!

0