SAP BTP, ABAP Environment, ABAP Extensibility, ABAP RESTful Application Programming Model, SAP Business Technology Platform

Streams in RAP: Uploading PDF, Excel and Other Files in RAP Application

Uploading Large Object and media such as Excel or Image through your application is a common business requirement and the only way to do it through a RAP application was by extending the application and using UI5 tooling to upload the file.

With the latest SAP BTP ABAP 2208 release the RAP framework now supports OData streams. It is now possible to enable your RAP application to maintain and handle Large Objects(LOBs).This feature provides end users an option to upload external files of different file formats such as PDF, XLSX, binary file format and other types hence allowing media handling.

In this Blog we will explore how to upload and handle Large Object such as PDF or Binary files without the need to extend the RAP application in BAS.

Large objects are modeled by means of the following fields:

  • Attachment
  • Mimetype
  • Filename

The field Attachment contains the LOB itself in a RAWSTRING format and is technically bound to the field Mimetype and Filename using semantics annotation.

Mimetype represents the content type of the attachment uploaded and the values for the fields Mimetype and Filename are derived from the field Attachment by the RAP framework based on the maintained CDS annotations. No attachment can exist without its mimetype and vice versa.

For example, when a PDF is uploaded the Mimetype field will be derived and populated with ‘APPLICATION/PDF’.

PDF File Uploaded from RAP application

To try this feature out I have built a simple RAP application to upload files directly using RAP Framework.

Database Table

A Database table was built as per code snippet below.

The field attachment has a data type of RAWSTRING. In BTP ABAP environment you cannot use RAWSTRING domain directly so create a custom domain with data type as RAWSTRING and Length as ‘0’ . This is important as length being ‘0’ would indicate that the RAWSTRING has No length restriction and can accommodate file of larger size. ZMIMETYPE and ZFILENAME are both of type Character and length 128.

@EndUserText.label : 'Invoice Table'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zinvoicetable {
  key client            : abap.clnt not null;
  key invoice           : ebeln not null;
  comments              : char30;
  attachment            : zattachment;
  mimetype              : zmimetype;
  filename              : zfilename;
  local_created_by      : abp_creation_user;
  local_created_at      : abp_creation_tstmpl;
  local_last_changed_by : abp_locinst_lastchange_user;
  local_last_changed_at : abp_locinst_lastchange_tstmpl;
  last_changed_at       : abp_lastchange_tstmpl;

}

Interface View

CDS annotation @Semantics.largeObject technically binds the MimeType and Filename to the Attachment.

The annotation contentDispositionPreference can be used to define whether, depending on the browser settings, the file attachment is either displayed in the browser (setting #INLINE) or downloaded when selected (option #ATTACHMENT).

Annotation @Semantics.largeObject.acceptableMimeTypes can be used to restrict the Media types which can be uploaded. The validation and Error handling on upload of unsupported media type is handled by the RAP framework.

CDS annotation @Semantics.mimeType: true was used to define the field MimeType as such.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Invoice Table'
define root view entity ZI_INVOICETABLE
  as select from zinvoicetable
{
  key invoice               as Invoice,
      comments              as Comments,
      @Semantics.largeObject:
      { mimeType: 'MimeType',
      fileName: 'Filename',
      contentDispositionPreference: #INLINE }
      attachment            as Attachment,
      @Semantics.mimeType: true
      mimetype              as MimeType,
      filename              as Filename,
      @Semantics.user.createdBy: true
      local_created_by      as LocalCreatedBy,
      @Semantics.systemDateTime.createdAt: true
      local_created_at      as LocalCreatedAt,
      @Semantics.user.lastChangedBy: true
      local_last_changed_by as LocalLastChangedBy,
      //local ETag field --> OData ETag
      @Semantics.systemDateTime.localInstanceLastChangedAt: true
      local_last_changed_at as LocalLastChangedAt,

      //total ETag field
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at       as LastChangedAt
}

Consumption View

@EndUserText.label: 'Invvoice Table'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
define root view entity ZC_INVOICE_TABLE
  provider contract transactional_query
  as projection on ZI_INVOICETABLE
{
  key Invoice,
      Comments,
      Attachment,
      MimeType,
      Filename,
      LocalLastChangedAt
}

Metadata Extension

From an UI perspective the User only needs to interact with the Attachment and hence Mimetype and Filename is hidden.

@Metadata.layer: #CORE
@UI: { headerInfo: {
typeName: 'Invoice',
typeNamePlural: 'Invoices',
title: { type: #STANDARD, value: 'Invoice' },
         description: { type: #STANDARD, value: 'Invoice' } },
         presentationVariant: [{
         sortOrder: [{ by: 'Invoice', direction: #ASC }],
         visualizations: [{type: #AS_LINEITEM}] }] }
annotate entity ZC_INVOICE_TABLE with
{
  @UI.facet: [    {
                label: 'General Information',
                id: 'GeneralInfo',
                type: #COLLECTION,
                position: 10
                },
                     { id:            'Invoicedet',
                    purpose:       #STANDARD,
                    type:          #IDENTIFICATION_REFERENCE,
                    label:         'Invoice Details',
                    parentId: 'GeneralInfo',
                    position:      10 },
                  {
                      id: 'Upload',
                      purpose: #STANDARD,
                      type: #FIELDGROUP_REFERENCE,
                      parentId: 'GeneralInfo',
                      label: 'Upload Invoice',
                      position: 20,
                      targetQualifier: 'Upload'
                  } ]

  @UI: { lineItem:       [ { position: 10, importance: #HIGH , label: 'Invoice Number'} ] ,
          identification: [ { position: 10 , label: 'Invoice Number' } ] }
  Invoice;
  @UI: { lineItem:       [ { position: 20, importance: #HIGH , label: 'Comments'} ] ,
           identification: [ { position: 20 , label: 'Comments' } ] }
  Comments;
  @UI:
  { fieldGroup:     [ { position: 50, qualifier: 'Upload' , label: 'Attachment'} ]}
  Attachment;

  @UI.hidden: true
  MimeType;

  @UI.hidden: true
  Filename;

}

I have created a managed Behavior definition with Draft and Created a Service definition and Service binding to expose this as a V4 UI .

managed implementation in class zbp_i_invoicetable unique;
strict ( 2 );
with draft;

define behavior for ZI_INVOICETABLE alias Invoice
persistent table ZINVOICETABLE
draft table zinvoicetdraft
lock master
total etag LocalLastChangedAt
authorization master ( instance )
etag master LastChangedAt
{

 // administrative fields: read only
  field ( readonly ) LastChangedAt, LocalLastChangedBy, LocalLastChangedAt , LocalCreatedBy ,
                      LocalCreatedAt;

  create;
  update;
  delete;

  draft action Edit ;
  draft action Activate;
  draft action Discard;
  draft action Resume;

  draft determine action Prepare ;
}

Once the OData is published through service binding, we can preview the application.

List Page

You can click on create to Create a new Instance.

Object page With Upload Option

On “Upload File” the File Open Dialog comes up to select the file from Presentation Server .

File Selection Dialog

Once the File is uploaded the Hyperlink can be used to access the file and based on annotation contentDispositionPreference the file would either open in a new window or downloaded when selected.

After File Upload

Once the Instance is saved we can see the new file encoded in RAWSTRING format along with its Mimetype and Name saved in the database.

Database table after Upload

In Conclusion, with the Support of OData streams, we can now handle LOBs directly using RAP framework, this really caters to a lot of missing features for which extensions were needed before. The above application is a very simple example of how this feature can be used.