Die UI5-Media-Entity Journey – Part 2

Die UI5-Media-Entity Journey – Part 2

In dieser 4-Teiligen Blogreihe erfahren Sie alles nötige, um Media-Entity-Handling in SAP UI5 laut gängigen Best-Practice-Erfahrungen zu implementieren.

Einen tieferen Einblick in verschiedenste Themen gibt es in unseren Kursen bei Die Entdecker, die offiziell über training.sap.com angeboten werden.

Für diesen Blog interessante Trainings:

  • HOFIO – Hands-on Deep Dive zur Entwicklung von SAP Fiori Oberflächen
  • WDECPS – SAP Cloud Platform Security Eine ganzheitliche Betrachtung

Inhalt

Im zweiten Schritt unserer OData-Journey erfahren wir alles nötige, um einen OData-Service für unsere Medien zu erstellen.

Wir werden einen SEGW-Projekt erstellen, einen OData-Service einrichten und die in Teil 1 erstellte CDS-View mappen. Außerdem werden wir noch die nötigen STREA-Methoden überschreiben und redefinieren. 

Voraussetzungen:

 

 

Schritt 1 – OData-Service erstelleln

Damit wir unsere Media-Entries auch aus einer UI5-App heraus auslesen können, brauchen wir einen OData-Service, der diese Daten freigibt.

Dazu gehen wir in die SEGWSAP Gateway Service Builder – und erstellen eine neues lokales Projekt. 

Struktur importieren

Sobald der Service nun erstellt wurde, können wir eine neue Entity basieren auf unserer File-Struktur anlegen. Dazu per Rechtsklick eine ABAP-Dictionary-Struktur importieren.

Wir nennen unsere Entity File und geben unsere File-Struktur an.

Im folgenden Screen müssen die Felder ausgewählt werden, die in die Entity importiert werden sollen. Hier selektieren wir alle Felder bis auf den Mandanten, da wir diesen nicht freigeben wollen.

Anschließend wählen wir noch das Schlüsselfeld für unsere Entity aus.

Damit wir in den Metadaten des Services noch die Info bekommen, welche Entity eine Media-Entity ist, setzten wir das Flag Medium auf true.

CDS-View mappen

Damit wir uns die Redefinitionen der GET_ENTITY und der GET_ENTITYSET sparen, können wir die in Teil 1 erstellte CDS-View importieren. Diese nimmt und die Funktionalität der beiden GET-Methoden ab und ermöglicht Filterung und Sortierung.

Wir wählen eine Business Entity aus und geben unseren DDL-View-Namen an.

Wenn nun unsere CDS-View gefunden wurde, können die Felder der View den Feldern der Entity zugeordnet werden. Falls Navigation-Properties/Associations existieren, können diese hier ebenfalls gemappt werden.

DEFINE redefinieren

Damit unser OData-Service weiß, welche Entity eine Media-Entity ist, bzw ob hier eine Stream-Methode dahinter liegt, muss die DEFINE-Methode der Model-Provider-Class (MPC_EXT) redefiniert werden. Hier sagen wir, welche Entity eine Media-Entity ist.

Wichtig für uns:

  • set_as_content_type
    • Hier wird das Feld angegeben, das den Mime-Type der Datei beinhaltet.
  • set_as_content_source
    • Hier wird das Feld angegeben, das den Inhalt der Datei beinhaltet.

  method DEFINE.
    DATA: lo_entity   TYPE REF TO /iwbep/if_mgw_odata_entity_typ.
    DATA: lo_property TYPE REF TO /iwbep/if_mgw_odata_property.

    super->define( ).

    lo_entity = model->get_entity_type( iv_entity_name = ‚File‘ ).

    IF lo_entity IS BOUND.

      lo_property = lo_entity->get_property( iv_property_name = ‚Mimetype‘ ).
      lo_property->set_as_content_type( ).

      lo_property = lo_entity->get_property( iv_property_name = ‚Content‘ ).
      lo_property->set_as_content_source( ).
    ENDIF.
  endmethod.

GET_STREAM redefinieren

Damit wir unsere Media-Entity als Datei geliefert bekommen, müssen wir die GET_STREAM-Methode der Data-Provider-Class (DPC_EXT) redefinieren. Hier selektieren wir mit dem übergebenen Schlüssel einen Eintrag unserer Tabelle und setzen die Response-Header.

Für uns wichtig:

  • it_key_tab
    • Tabelle mit den übergebenen Keys.
  • ls_stream
    • Struktur, in der der File-Stream mit Content und MimeType gespeichert werden.
  • ls_header
    • Struktur für den Response-Header

METHOD /iwbep/if_mgw_appl_srv_runtime~get_stream.
    DATA: ls_key      TYPE /iwbep/s_mgw_name_value_pair,
          lv_fileid   TYPE sysuuid_c,
          ls_stream   TYPE ty_s_media_resource,
          ls_header   TYPE ihttpnvp,
          lv_filename TYPE string,
          ls_file     TYPE zdemo_file_s.

*   Keys lesen
    READ TABLE it_key_tab WITH KEY name = ‚Fileid‘ INTO ls_key.
    lv_fileid = ls_key-value.

*   File aus Datenbank lesen
    SELECT SINGLE * FROM zdemo_file INTO @ls_file WHERE fileid = @lv_fileid.

    IF sy-subrc <> 0.
      mo_context->get_message_container( )->add_message_text_only(
        iv_msg_type = ‚E‘
        iv_msg_text = TEXT-003
      ).

      RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
        EXPORTING
          textid            = /iwbep/cx_mgw_busi_exception=>business_error
          message_container = mo_context->get_message_container( ).
    ENDIF.

*   Werte zuweißen
    ls_stream-value = ls_file-content.
    ls_stream-mime_type = ls_file-mimetype.
    lv_filename = ls_file-filename.
    lv_filename = escape(
      val = lv_filename
      format = cl_abap_format=>e_url
    ).

*   Header setzen
    ls_header-name = |content-disposition|.
    ls_header-value = |inline; filename={ lv_filename }|.
    set_header( is_header = ls_header ).

    copy_data_to_ref(
       EXPORTING
         is_data = ls_stream
       CHANGING
         cr_data = er_stream ).
  ENDMETHOD.

CREATE_STREAM überschreiben

Damit wir auch Files speichern können, überschreiben wir die CREATE_STREAM-Methode. In dieser erstellen wir einen neuen Eintrag in unserer Tabelle. Dazu benötigen wir eine GUID, die wir ebenfalls erstellen.

Für uns wichtig:

  • create_uuid_c32_static
    • Erstellt uns eine gültige UUID als Key für unseren Datensatz.
  • copy_data_to_ref
    • Kopiert die erstellten Daten in die zurückliefernde Struktur.
  • iv_slug
    • In dem sogenannten Slug-Header steht der Dateiname.

  METHOD /iwbep/if_mgw_appl_srv_runtime~create_stream.
    DATA: ls_file           TYPE zdemo_file_s,
          ls_key_tab        TYPE /iwbep/s_mgw_name_value_pair,
          lo_facade         TYPE REF TO /iwbep/if_mgw_dp_int_facade,
          lt_client_headers TYPE tihttpnvp,
          ls_client_header  LIKE LINE OF lt_client_headers,
          lv_file_id        TYPE sysuuid_c.

    lo_facade ?= /iwbep/if_mgw_conv_srv_runtime~get_dp_facade( ).
    lt_client_headers = lo_facade->get_request_header( ).

*   UUID erstellen
    TRY.
        lv_file_id = cl_system_uuid=>create_uuid_c32_static( ).
      CATCH cx_uuid_error INTO DATA(e_txt).
        mo_context->get_message_container( )->add_message_text_only(
          iv_msg_type = ‚E‘
          iv_msg_text = TEXT-002
        ).

        RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
          EXPORTING
            textid            = /iwbep/cx_mgw_busi_exception=>business_error
            message_container = mo_context->get_message_container( ).
    ENDTRY.

*   Entity erstellen und mit Werten füllen
    ls_file-fileid = lv_file_id.
    ls_file-filename = iv_slug.
    ls_file-content = is_media_resource-value.
    ls_file-mimetype = is_media_resource-mime_type.
    ls_file-creation_date = sy-datum.
    ls_file-creation_time = sy-uzeit.
    ls_file-created_by = sy-uname.

*   Neuer Eintrag in die Datenbank
    INSERT INTO zdemo_file VALUES ls_file.

    IF sy-subrc <> 0.
      mo_context->get_message_container( )->add_message_text_only(
         iv_msg_type = ‚E‘
         iv_msg_text = TEXT-001
       ).
      RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
        EXPORTING
          textid            = /iwbep/cx_mgw_busi_exception=>business_error
          message_container = mo_context->get_message_container( ).
    ENDIF.

*   Daten zurückgeben
    copy_data_to_ref(
       EXPORTING
         is_data = ls_file
       CHANGING
         cr_data = er_entity ).
  ENDMETHOD.

Service hinzufügen

Nachdem nun unser Servce fertig ist, müssen wir ihn in der Transation /IWFND/MAINT_SERVICE zu dem Service-Catalog hinzufügen.

Hier filtern wir nach dem Systemalias LOCAL und dem Technischen Servicenamen unserers Services.

Sobald er gefunden wurden, fügen wir den Service zu dem Service-Catalog hinzu.

Wir erstellen einen lokal laufenden Service.

Service testen

Sobald der Service im Service-Catalog aufscheint, können wir in im SAP Gateway Client  testen.

Upload testen

Um ein File per OData-Service hochzuladen, müssen wir einen POST-Request auswählen.

Anschließend wählen wir unser FileSet aus.

Per Datei hinzufügen fügen wir ein Test-File zu unserem Request hinzu. Zusätzlich müssen wir noch den SLUG-Header vergeben.

Sobald wir den Request versendet haben und der mit dem HTTP-Status 201 durchgelaufen ist, sehen wir eine Response, die das gespeicherte/hochgeladene File enthält.

Download testen

Wenn wir einen GET-Request mit einer URI auf eine existierende Entity mit dem Parameter $value versenden, bekommen wir das gespeicherte File zurückgeliefert.

Der Parameter $value signalisiert, dass die GET_STREAM-Methode dieser Entity aufgerufen wird. Das Resultat können wir uns per Antwort in Browser im Browser anzeigen lassen.

Testen per OData-Plugin

Das Testen von OData-Services geht ebenfalls mit dem CloudDNA OData-Plugin bequem aus der WebIDE.

Hier kann die unsere File-Entity getestet werden. Nach unserem Request bekommen wir auch noch Zusatzinformationen zu dem Request wie Header, Parameters etc.

Das Testen von Upload-Funktionalitäten geht ebenfalls einfach per File-Uploader. Sollten wir etwas im Backend nicht richtig implementiert haben, bekommen wir hier eine Fehlermeldung. Außerdem brauchen wir uns nicht um das Setzen von Slug und XCSRF-Token kümmern. 

Zusammenfassung

Die Hälfte ist geschafft.

In Teil 2 haben wir nun einen OData-Service basierend auf den Daten von Teil 1 erstellt. Diesen Odata-Service werden wir in Teil 3 benötigen, um die UploadCollection ins Laufen zu bringen.

Falls Fragen zu diesen einzelnen Schritten auftreten, können sie gerne in den Kommentaren gestellt werden.

 

 

Die UI5-Media-Entity Journey – Part 1

In dieser 4-Teiligen Blogreihe erfahren Sie alles nötige, um Media-Entity-Handling in SAP UI5 laut gängigen Best-Practice-Erfahrungen zu implementieren.

Einen tieferen Einblick in verschiedenste Themen gibt es in unseren Kursen bei Die Entdecker, die offiziell über training.sap.com angeboten werden.

Für diesen Blog interessante Trainings:

  • HOFIO – Hands-on Deep Dive zur Entwicklung von SAP Fiori Oberflächen
  • WDECPS – SAP Cloud Platform Security Eine ganzheitliche Betrachtung

Inhalt

Die Reihe befasst sich mit folgenden 4 Schritten zur kompletten UI5-App mit Media-Handling inklusive OData-Service per SEGW.

  1. onPremise-Vorarbeit
  2. OData-Service
  3. File Upload
  4. File Download

 

In Schritt 1 legen wir den Grundstein unseres File-Handlings. Wir legen eine Tabelle zur Speicherung unserer Files und die dazugehörige Struktur und CDS-View an.

In Schritt 2 implementieren wir über ein SEGW-Projekt einen OData-Service, der uns unser File an unsere UI5-App liefert. Hier werden die nötigen Methoden der Data-Provider-Class (DPC) redefiniert und korrekt implementiert.

In Schritt 3 legen wir ein neues UI5-Projekt an und verbinden uns mit dem erstellten Service. Außerdem implementieren wir eine UploadCollection, um Files von unserer App per OData-Service ins Backend zu uploaden.

In Schritt 4 erweitern wir die UploadCollection um eine Download-Funktion. Der Schritt 4 ist der abschließende Part der UI5-Media-Entity-Journey.

 

Schritt 1 – onPremise-Vorarbeit

Damit der Dokumentenupload in unserer UI5-App funktioniert, müssen wir auf unserem onPremise-System Vorarbeit leisten, sofern noch nicht geeignete Tabellen als Media-Entity über eine OData-Service freigegeben wurden.

 

 

Tabelle anlegen

 Zu aller Erst begeben wir uns in die SE80 unseres onPrem-Systems.

In diesem Beispiel arbeiten wir mit lokalen Objekten. Diese befinden sich in der $TMP-Entwicklungsklasse und werden nicht weitertransportiert.

Nun legen wir eine neue Datenbanktabelle an. Diese wird bei unseren Lokalen Objekten in dem Dictionary-Folder gespeichert.

Als Name der Tabelle vergeben wir ZDEMO_FILE. Nach dem Names-Dialog fügen wir eine Beschreibung hinzu und konfigurieren Auslieferungsklasse und die Pflegeerlaubnis. Anschließend speichern wir die Tabelle als lokales Objekt.

Jetzt müssen wir Tabellenfelder vergeben. Die von uns in diesem Beispiel verwendeten können selbstverständlich anders benannt werden, die letzten 3 Felder sind sogar optional, da sie nicht so trivial für die Funktionalität sind, jedoch nützliche Informationen beinhalten.

 

  • MANDT
  • FILEID – Zur eindeutigen Identifikation unseres Files verwenden wir hier eineUUID.
  • FILENAME – Speichert unseren Dokumentennamen, diesen bekommen wir später als Request-Header.
  • MIMETYPE – Hier wird der Datentyp unseres Files gespeichert.
  • CONTENT – Der Inhalt unseres Files.
  • CREATION_DATE – Erstelldatum – Optional
  • CREATION_TIME – Erstellzeit – Optional
  • CREATED_BY – Ersteller – Optional

Anschließend speichern wir die Tabelle, aktivieren sie und ergänzen die Erweiterungskategorie und konfigurieren die Technischen Einstellungen.

 

Struktur anlegen

Sobald die Tabelle fertig konfiguriert ist, legen wir eine dazugeörige Struktur namens ZDEMO_FILE_S an.  Diese wird gespeichert, aktiviert und zusätzlich wird noch die Erweiterungskategorie definiert.

View anlegen

Um den Inhalt der File-Tabelle über OData anzuzeigen, könne wir eine CDS-View erstellen. Diese wird mit unserer OData-Entity gemapped, somit bekommen wir die Get-Entity und die Get-Entity-Set Funktionen zusätzliche mit Filter und Sorter geschenkt.

Erforderlich sind hier:

Nüzliche Links:

Wir erstellen eine CDS-View namens ZDEMO_FILE_V.  Laut Best-Practice-Regeln sollte man für jede Tabelle, die man jedenfalls freigeben möchte, eine Basic-View erstellen. Diese gibt alle Felder der Tabelle frei. Und für jede Anwendung die nun die Daten der Tabelle/Basic-View benutzen möchte, sollte nun eine eigene Consumption-View erstellt werden, die genau auf den Anwendungsfall zugeschnitten wird. Für unser Beispiel reicht jedoch nur eine Consumption-View ohne Basic-View.

 

Sobald unsere View angelegt ist, können wir sie konfigurieren.

Wir benötigen den SQL-View-Namen, den View-Type und die zu selektierende Tabelle.

Der SQL-View-Name muss sich von dem DDL-Name unterscheiden. Als View-Type verwenden wir eine Consumption-View und wir möchten die Werte unserer zuvor angelegten ZDEMO_FILE-Tabelle selektieren.

Anschließend speichern wir unsere View und aktivieren sie. Falls Fehler auftreten, könnte es sein, dass der SQL- oder der DDL-Name schon vergeben ist, oder dass die ZDEMO_FILE-Tabelle nicht richtig aktiviert worden ist.

Zusammenfassung

In Teil 1 haben wir die Vorarbeit für unseren OData-Service gelegt, den wir im nächsten Schritt anlegen werden. Dazu haben wir eine Tabelle angelegt, die dazugehörige Struktur definiert und eine CDS-View erstellt.

Falls Fragen zu diesen einzelnen Schritten auftreten, können sie gerne in den Kommentaren gestellt werden.

 

 

OData ABAP Backend Exception lernen

OData ABAP Backend Exception lernen

Lernen sie mit dem Team der CloudDNA unteranderem  einen ordentlichen Umgang im Error Handling bei SAPUI5 Entwicklungen:

Inhaltsverzeichnis im OData:

 

 

 

State-of-the-Art SAPUI5 / Fiori Apps erfordern ein sauber implementiertes Error Handling. Sind OData Services in einem SAP NetWeaver ABAP Stack implementiert, gibt es zwei ABAP Klassen die für Exceptions verwendet werden: 

 

 

 

  • /IWBEP/CX_MGW_BUSI_EXCEPTION für Business / Logische Fehler
  • /IWBEP/CX_MGW_TECH_EXCEPTION für Technische Fehler

 

Fehler können auf verschieden Arten geworfen werden. Nachfolgend zeigen wir Ihnen unterschiedliche Optionen. 

 

 

Ein Möglichkeit ist es, über den MessageContainer den Fehlertext direkt zurückzuliefern. Diese Methode hat den Nachteil, dass die Fehlertexte hartkodiert sind und somit eine Übersetzung in unterschiedliche Sprachen nicht möglich ist. 

 

 

METHOD customerset_get_entity.
  SELECT SINGLE * FROM zcustomer where ...
  IF sy-subrc NE 0.
    mo_context->get_message_container( )->add_message_text_only( 
iv_msg_type = 'E'                                                       
iv_msg_text = |Der angeforderte Datensatz wurde nicht gefunden.| ).
  
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        message_container = mo_context->get_message_container( ).
  ENDIF.
ENDMETHOD.

 

Eine weitere Möglichkeit besteht in der Verwendung einer Nachrichtenklasse. Damit können alle bekannten ABAP Tools wie z.B. die SE91 verwendet werden. 

 

 

METHOD customerset_get_entity.
  SELECT SINGLE * FROM zcustomer where ...
  IF sy-subrc <> 0.
    mo_context->get_message_container( )->add_message( iv_msg_type   = 'E'
                                                       iv_msg_id     = 'SY'
                                                       iv_msg_number = '100'
                                                       iv_msg_text   = |Der angeforderte Datensatz wurde nicht gefunden.| ).
  
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        message_container = mo_context->get_message_container( ).
  ENDIF.
ENDMETHOD.

 

Eine weitere Möglichkeit in Kombination mit dem Message Container bietet sich, wenn Fehler in Form eine BABIRET2 Struktur vorliegen. 

 

METHOD xyz_get_entity.
  DATA: lt_return TYPE bapirettab.
   
  CALL FUNCTION 'BAPI_CALL_XYZ'
    ...
    TABLES
      return = lt_return.
   
  IF lt_return IS NOT INITIAL.
    mo_context->get_message_container( )->add_messages_from_bapi( it_bapi_messages = lt_return ).
 
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        message_container = mo_context->get_message_container( ).
  ENDIF.
ENDMETHOD.

 

Es gibt in diesem Szenario auch die Option die Leading Message zu bestimmen. In diesem Fall muss das Coding leicht abgeändert werden. 

 

METHOD xyz_get_entity.
  DATA: lt_return TYPE bapirettab.
   
  CALL FUNCTION 'BAPI_CALL_XYZ'
    ...
    TABLES
      return = lt_return.
   
  IF lt_return IS NOT INITIAL.
    mo_context->get_message_container( )->add_messages_from_bapi( it_bapi_messages = lt_return
                                                                  iv_determine_leading_msg = /iwbep/if_message_container=>gcs_leading_msg_search_option-first ).
 
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        message_container = mo_context->get_message_container( ).
  ENDIF.
ENDMETHOD.

 

Exceptions können auch direkt ohne Verwendung des Message Containers geworfen werden. 

 

METHOD customerset_get_entity.

  SELECT SINGLE * FROM zcustomer where ...
  IF sy-subrc NE 0.
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        textid  = /iwbep/cx_mgw_busi_exception=>business_error
        message = |Der angeforderte Datensatz wurde nicht gefunden.|.
  ENDIF.
ENDMETHOD.

 

Eine weitere Option besteht darin, der Exception eine Struktur vom Typ scx_t100key mitzugeben.

 

METHOD customerset_get_entity. 

 

  SELECT SINGLE * FROM zcustomer where ...
  IF sy-subrc NE 0.
    DATA(lv_message) = VALUE scx_t100key( msgid = 'SY'
                                          msgno = '001'
                                          attr1 = |Der angeforderte Datensatz wurde nicht gefunden.| ).
 
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        textid  = lv_message.
  ENDIF.
ENDMETHOD.

Die letzte in diesem Blog dargestellte Option ist es, eine Exception abzufangen, und den Exceptiontext durchzureichen.

METHOD customerset_get_entity.
  TRY.
      ...
    CATCH cx_root INTO DATA(lr_ex).
      RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
        EXPORTING
          textid  = /iwbep/cx_mgw_busi_exception=>business_error
          message = |{ lr_ex->get_text( ) }|.
  ENDTRY.
ENDMETHOD.