1

I'm currently following the UI5 tutorial and am stuck on step 27: Mock Server Configuration.

The problem is the rootUri configuration of the mock server. I am using the Northwind OData service as per tutorial and have configured a dataSource for Invoices in the manifest.json.

Now, to use the local mock data instead of the online service, I created the necessary files as stated by the tutorial. When I then run the mockServer.html, the server doesn't redirect the service request to the local mock data, but instead makes the request and fails because of web security issues.

If I use the following rootUri, the mock server doesn't redirect and the service request fails:

// Snippet from mockserver.js
var oMockServer = new MockServer({
    rootUri: "/destinations/northwind/V2/Northwind/Northwind.svc/"
});

Failed to load https://services.odata.org/V2/Northwind/Northwind.svc/$metadata?sap-language=DE: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://webidetesting9515320-s0015250556trial.dispatcher.hanatrial.ondemand.com' is therefore not allowed access. The response had HTTP status code 501.

Another question on Stackoverflow shows the mock server using a "wildcard" root-uri, but this also fails:

// Snippet from mockserver.js
var oMockServer = new MockServer({
    rootUri: "/"
});

The only way I can make the mock server configuration work is to use the exact same URL as the rootUri that is written in the manifest.json as the URI of the dataSource I want to mock:

// Snippet from mockserver.js
var oMockServer = new MockServer({
    rootUri: "https://services.odata.org/V2/Northwind/Northwind.svc/"
});

The above code works, but the Web IDE states that this is a bad practice:

error: Fiori Architectural Guidelines: ESLint(sap-no-hardcoded-url): Hardcoded (non relative) URL found. (img)

My question now is: How can I make the mock server run the intended way with a relative rootUri?


webapp/manifest.json (excerpt)

{
  "_version": "1.1.0",
  "sap.app": {
    "_version": "1.1.0",
    "id": "sap.ui.tutorial.walkthrough",
    "type": "application",
    "i18n": "i18n/i18n.properties",
    "title": "{{appTitle}}",
    "description": "{{appDescription}}",
    "applicationVersion": {
      "version": "1.0.0"
    },
    "dataSources": {
      "invoiceRemote": {
        "uri": "https://services.odata.org/V2/Northwind/Northwind.svc/",
        "type": "OData",
        "settings": {
          "odataVersion": "2.0"
        }
      }
    }
  },
...

webapp/test/mockServer.html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta charset="utf-8">
        <title>Hello World App - Test Page</title>
        <script src="/resources/sap-ui-core.js"
                id="sap-ui-bootstrap"
                data-sap-ui-theme="sap_belize"
                data-sap-ui-libs="sap.m"
                data-sap-ui-compatVersion="edge"
                data-sap-ui-preload="async"
                data-sap-ui-resourceroots='{
                    "sap.ui.tutorial.walkthrough": "../"
                }'></script>

        <script type="text/javascript">
            sap.ui.getCore().attachInit(function() {
                sap.ui.require([
                    "sap/ui/tutorial/walkthrough/localService/mockserver",
                    "sap/m/Shell",
                    "sap/ui/core/ComponentContainer"
                ], function(mockserver, Shell, ComponentContainer) {
                    mockserver.init();

                    new Shell({
                        app: new ComponentContainer({
                            name: "sap.ui.tutorial.walkthrough",
                            height: "100%"
                        })
                    }).placeAt("content")
                });
            });
        </script>
    </head>
    <body class="sapUiBody" id="content">
    </body>
</html>

webapp/localService/mockserver.js

sap.ui.define([
    "sap/ui/core/util/MockServer"
], function (MockServer) {
    "use strict";

    return {
        init: function () {
            // create
            var oMockServer = new MockServer({
                rootUri: "https://services.odata.org/V2/Northwind/Northwind.svc/"
            });
            var oUriParameters = jQuery.sap.getUriParameters();
            // configure
            MockServer.config({
                autoRespond: true,
                autoRespondAfter: oUriParameters.get("serverDelay") || 1000
            });
            // simulate
            var sPath = jQuery.sap.getModulePath("sap.ui.tutorial.walkthrough.localService");
            oMockServer.simulate(sPath + "/metadata.xml", sPath + "/mockdata");
            // start
            oMockServer.start();
        }
    };
});

webapp/localService/metadata.xml

<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
    <edmx:DataServices m:DataServiceVersion="1.0" m:MaxDataServiceVersion="3.0"
                       xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
        <Schema Namespace="NorthwindModel" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
            <EntityType Name="Invoice">
                <Key>
                    <PropertyRef Name="ProductName"/>
                    <PropertyRef Name="Quantity"/>
                    <PropertyRef Name="ShipperName"/>
                </Key>
                <Property Name="ShipperName" Type="Edm.String" Nullable="false" MaxLength="40" FixedLength="false"
                          Unicode="true"/>
                <Property Name="ProductName" Type="Edm.String" Nullable="false" MaxLength="40" FixedLength="false"
                          Unicode="true"/>
                <Property Name="Quantity" Type="Edm.Int16" Nullable="false"/>
                <Property Name="ExtendedPrice" Type="Edm.Decimal" Precision="19" Scale="4"/>
            </EntityType>
        </Schema>
        <Schema Namespace="ODataWebV2.Northwind.Model" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
            <EntityContainer Name="NorthwindEntities" m:IsDefaultEntityContainer="true" p6:LazyLoadingEnabled="true"
                             xmlns:p6="http://schemas.microsoft.com/ado/2009/02/edm/annotation">
                <EntitySet Name="Invoices" EntityType="NorthwindModel.Invoice"/>
            </EntityContainer>
        </Schema>
    </edmx:DataServices>
</edmx:Edmx>
Boghyon Hoffmann
  • 13,472
  • 7
  • 49
  • 114
Daniel Kneip
  • 13
  • 1
  • 5

2 Answers2

3

There are some rules to watch out for when it comes to defining the rootUri for mock server.

The rootUri

  • Should be relative
  • Has to end with a slash ("/")
  • Has to match with the URI that is assigned to your model.

It is mentioned in the step 27:

(The rootUri) matches the URL of our data source in the descriptor file.

As well as in the topic Mock Server: Frequently Asked Questions:

The root URI has to be relative and requires a trailing '/'. It also needs to match the URI set in OData/JSON models or simple XHR calls in order for the mock server to intercept them.


So it doesn't matter how your rootUri is defined as long as it fulfills those three requirements mentioned above. That's why some arbitrary URIs like rootUri: "/" works as well but only if the uri in the dataSource is the same.

In your case, changing the rootUri value like this below should make the mock server running:

var oMockServer = new MockServer({
  rootUri: "/destinations/northwind/V2/Northwind/Northwind.svc/"
});

And in your app descriptor (manifest.json) respectively..:

"dataSources": {
  "invoiceRemote": {
    "uri": "/destinations/northwind/V2/Northwind/Northwind.svc/",
    "type": "OData",
    "settings": {
      "odataVersion": "2.0"
    }
  }
}

To make the path work in a non-MockServer scenario, you'll need to register a corresponding destination in SAP Cloud Platform and edit neo-app.json in your project accordingly.
No need to change the application code.

Boghyon Hoffmann
  • 13,472
  • 7
  • 49
  • 114
1

Some details of chapter 27 of the tutorial are quite misleading.

  1. The rootUri of the MockServer must match the uri parameter of the datasource in manifest.json.

  2. Instead of changing datasource's uri to the (wrong) rootUri of MockServer given in the tutorial, you should actually change MockServer's rootUri to the URI of the external source. In webapp/localService/mockserver.js use this corrected block:

        var oMockServer = new MockServer({
            rootUri: "https://services.odata.org/V2/Northwind/Northwind.svc/"
        });
    

This will create a MockServer that intercepts all calls to that external URI and responds to them locally. And with this construct, it is actually possible to access the MockServer through /webapp/test/mockServer.html and the live data server through /webapp/index.html.

Hint:
You will most likely still have trouble accessing the original (external) data source using /webapp/index.html due to Same Origin Policy restrictions (SOP). With Google Chrome this can be nicely solved by running a second (!) instance of the browser without SOP. This is possible in parallel to other open browser windows of the standard instance WITH SOP, so you don't need to close all your open browser windows. See this answer for details.

Jpsy
  • 17,245
  • 6
  • 99
  • 99
  • I don't see any mistake in the tutorial step. The Walkthrough just assumes that the project is built on Web IDE / SCP in which you can [define destinations](https://stackoverflow.com/questions/46664672/ui5-mock-server-with-local-data-rooturi-not-working/47181847#comment82587427_47181847). And the question author also mentions that he uses Web IDE. So in that case, the URI `/destinations/northwind/V2/Northwind/Northwind.svc/` is totally valid and working even without starting the mock server. No need to bypass SOP. – Boghyon Hoffmann Jan 11 '18 at 13:21
  • @boghyon: Please correct me, if I'm wrong. I followed the Walkthrough myself and got trapped at the same chapter. Registering a destination is not explained anywhere in the tutorial. Chapter 27 just claims that with the given changes, the mockserver and the liveserver can be used alternatively by using the two different entry URLs. A destination is not defined/registered anywhere. – Jpsy Jan 11 '18 at 20:10
  • It's correct that the Walkthrough itself doesn't say much about registering the destination, however, it's incorrect that there is a mistake since [the same step](https://ui5.sap.com/#/topic/bae9d90d2e9c4206889368f04edab508) mentions that "The URI [...] points to our destination configured for SAP Web IDE (see previous step). We assume this destination to be available." The previous step then contains a link to [this](https://ui5.sap.com/#/topic/5bb388fc289d44dca886c8fa25da466e) which points to [this](https://ui5.sap.com/#/topic/3a16c7a2f1e944deb000db49e5ece6be) additaionally. It's all there – Boghyon Hoffmann Jan 11 '18 at 22:24
  • 1
    The tutorial is correct but misleading because it links to the [First Aid Kit](https://openui5.hana.ondemand.com/#/topic/5bb388fc289d44dca886c8fa25da466e.html) to create a proxy. It is not obvious because it is said that it is only for production environments. Instead they advise to disable CORS in Chrome. But then they assume that a proxy is created (with a relative uri). How to do this with OpenUI5 without SAP Web IDE? It was also not obvious that the mockserver.js rootUri needed to exactly match the real server's uri. So I think that this answer is helpful. It solved the problem for me. – thibautg Feb 12 '18 at 22:15
  • 2
    @thibautg I agree. The Walkthrough definitely needs to make the [rules](https://stackoverflow.com/a/47181847/5846045) more obvious and be less Web IDE oriented (at least the OpenUI5 version). I'll forward this feedback to SAP. – Boghyon Hoffmann Feb 13 '18 at 11:12