Introduction
Uploading large files like Excel sheets or images is a frequent requirement in many business applications. Previously, in RAP-based applications, the only way to handle such uploads was by extending the application and using UI5 tooling to support file uploads manually.
However, with the release of SAP BTP ABAP environment version 2208, the RAP framework has introduced support for OData streams. This enhancement now allows developers to natively manage and process Large Objects (LOBs) directly within RAP applications. It enables end users to upload various external file types — including PDFs, XLSX files, binary formats, and other media — without custom extensions or additional tooling.
In this blog, we’ll walk through how to implement file upload functionality for large objects (like PDFs or binary files) within a standard RAP application, making full use of the new stream handling capabilities — and all without extending the UI manually.
LOB's Model
Large objects are modeled by means of the following fields:
- Attachment
- Mimetype
- Filename
The filed 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 attachemnt 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'.
Create DB Model for Attachment
A Database table was built as per code snippet below.
The field attachment has a datatype of RAWSTRING. In BTP ABAP environment we 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 header table'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zsb_inv_hdr {
key client : abap.clnt not null;
key invoice : ebeln not null;
comments : char30;
vendor_id : /dmo/customer_id;
local_created_by : abp_creation_user;
local_last_changed_by : abp_locinst_lastchange_user;
local_last_changed_at : abp_locinst_lastchange_tstmpl;
last_changed_at : abp_lastchange_tstmpl;
}
@EndUserText.label : 'Table for invoice attachment'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zsb_inv_att {
key client : abap.clnt not null;
key attach_id : char32 not null;
invoice : ebeln not null;
comments : char30;
attachment : zsb_attachement;
mimetype : char128;
filename : char128;
}
Design CDS Entities
CDS annotations @Semantics.LargeObject technically binds the Mimetype and Filename to the Attachment.
The annotations 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 RAP framework.
CDS annotation @Semantics.mimetype: true was used to define the field Mimetype as such.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Invoice header entity'
@Metadata.ignorePropagatedAnnotations: true
define root view entity zi_inv_hdr_sb as select from zsb_inv_hdr
composition[1..*] of zi_inv_att_sb as _Attachemnt
association[1] to /DMO/I_Customer as _Customers on
$projection.VendorId = _Customers.CustomerID
{
key zsb_inv_hdr.invoice as Invoice,
@EndUserText.label: 'Comments'
zsb_inv_hdr.comments as Comments,
@Consumption.valueHelpDefinition: [{ entity: {name: '/DMO/I_Customer',
element: 'CustomerID' } }]
@ObjectModel.text.element: [ 'CustomerName' ]
zsb_inv_hdr.vendor_id as VendorId,
_Customers.FirstName as CustomerName,
@Semantics.user.createdBy: true
zsb_inv_hdr.local_created_by as LocalCreatedBy,
@Semantics.user.lastChangedBy: true
zsb_inv_hdr.local_last_changed_by as LocalLastChangedBy,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
zsb_inv_hdr.local_last_changed_at as LocalLastChangedAt,
@Semantics.systemDateTime.lastChangedAt: true
zsb_inv_hdr.last_changed_at as LastChangedAt,
_Attachemnt, // Make association public
_Customers
}
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Invoice attachments'
@Metadata.ignorePropagatedAnnotations: true
define view entity zi_inv_att_sb as select from zsb_inv_att
association to parent zi_inv_hdr_sb as _Invoice
on $projection.Invoice = _Invoice.Invoice
{
key zsb_inv_att.attach_id as AttachId,
@EndUserText.label: 'Invoice'
zsb_inv_att.invoice as Invoice,
@EndUserText.label: 'Comments'
zsb_inv_att.comments as Comments,
@EndUserText.label: 'Attachments'
@Semantics.largeObject : {
mimeType: 'Mimetype',
fileName: 'Filename',
contentDispositionPreference: #INLINE
}
zsb_inv_att.attachment as Attachment,
@EndUserText.label: 'Type of Document'
zsb_inv_att.mimetype as Mimetype,
@EndUserText.label: 'File Type'
zsb_inv_att.filename as Filename,
_Invoice.LastChangedAt as LastChangedAt,
_Invoice // Make association public
}
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Invoice header projection'
@UI.headerInfo :{ typeName: 'Invoice',
typeNamePlural: 'Invoices',
title.value: 'Invoice',
description.value: 'Comments'
}
define root view entity ZC_INV_HDR_SB as projection on zi_inv_hdr_sb
{
@UI.facet: [{ id: 'InvoiceData',
purpose: #STANDARD,
label: 'Invoice Data',
type: #IDENTIFICATION_REFERENCE,
position: 10
},
{ id: 'Upload',
purpose: #STANDARD,
label: 'Upload your Attachments',
type: #LINEITEM_REFERENCE,
targetElement: '_Attachemnt',
position: 20
} ]
: {
selectionField: [{ position: 10 }],
lineItem: [{position: 10}],
identification: [{position: 10}]
}
key Invoice,
: {
lineItem: [{position: 20}],
identification: [{position: 20}]
}
Comments,
: {
selectionField: [{ position: 30 }],
lineItem: [{position: 30}],
identification: [{position: 30}]
}
VendorId,
CustomerName,
: {
identification: [{position: 40}]
}
LocalCreatedBy,
: {
identification: [{position: 50}]
}
LocalLastChangedBy,
LocalLastChangedAt,
LastChangedAt,
/* Associations */
_Attachemnt: redirected to composition child ZC_INV_ATT_SB,
_Customers
}
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Consumption view Attachements'
@UI.headerInfo :{ typeName: 'Attachment',
typeNamePlural: 'Attachments',
title.value: 'Filename'
}
define view entity ZC_INV_ATT_SB as projection on ZI_INV_ATT_SB
{
@UI.facet: [{ id: 'AttachmentData',
purpose: #STANDARD,
label: 'Attachment Info',
type: #IDENTIFICATION_REFERENCE,
position: 10
}]
: {
lineItem: [{position: 10}],
identification: [{position: 10}]
}
key AttachId,
: {
lineItem: [{position: 20}],
identification: [{position: 20}]
}
Invoice,
: {
lineItem: [{position: 30}],
identification: [{position: 30}]
}
Comments,
: {
lineItem: [{position: 40}],
identification: [{position: 40}]
}
Attachment,
: {
lineItem: [{position: 50}],
identification: [{position: 50}]
}
Mimetype,
: {
lineItem: [{position: 60}],
identification: [{position: 60}]
}
Filename,
/* Associations */
_Invoice: redirected to parent ZC_INV_HDR_SB
}
managed implementation in class zbp_i_inv_hdr_sb unique;
strict ;
with draft;
define behavior for zi_inv_hdr_sb alias Invoice
persistent table zsb_inv_hdr
draft table zdr_sb_inv_hdr
lock master
total etag LocalLastChangedAt
authorization master ( global )
etag master LastChangedAt
{
create ( authorization : global );
update;
delete;
field ( readonly ) Invoice;
association _Attachemnt { create; with draft; }
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare;
mapping for zsb_inv_hdr {
Comments = comments;
Invoice = invoice;
VendorId = vendor_id;
LastChangedAt = last_changed_at;
LocalCreatedBy = local_created_by;
LocalLastChangedAt = local_last_changed_at;
LocalLastChangedBy = local_last_changed_by;
}
}
define behavior for zi_inv_att_sb alias InvoiceAttachment
persistent table zsb_inv_att
draft table zdr_sb_inv_att
lock dependent by _Invoice
authorization dependent by _Invoice
etag master LastChangedAt
{
update;
delete;
field ( readonly ) AttachId, Invoice;
association _Invoice { with draft; }
mapping for zsb_inv_att {
AttachId = attach_id;
Attachment = attachment;
Comments = comments;
Filename = filename;
Invoice = invoice;
Mimetype = mimetype;
}
}
projection;
strict ( 1 );
use draft;
define behavior for ZC_INV_HDR_SB //alias
{
use create;
use update;
use delete;
use action Edit;
use action Activate;
use action Discard;
use action Resume;
use action Prepare;
use association _Attachemnt { create; with draft; }
}
define behavior for ZC_INV_ATT_SB //alias
{
use update;
use delete;
use association _Invoice { with draft; }
}
@EndUserText.label: 'Service Definition for Invoice attachment'
define service ZSD_SB_INV_ATT_SRV {
expose ZC_INV_HDR_SB;
expose ZC_INV_ATT_SB;
expose /DMO/I_Customer;
}
After Creating and activating the service definition ZSD_SB_INV_ATT_SRV, create the service binding of the type OData V4 – UI ZSB_INV_SB_SRV for the service definition and activate the same.
Then use this service SAP BAS (Business Application Studio). We will get below screen. Here click on create button. You will get pop up to enter Document number and provide the document number and continue.
In Object page provide Invoice number and Customer Id. Then click on create button you will get a pop up to enter attachment Id. This field can be made read only and we can provide internal numbering also.
We will get below page and we can see upload icon. By clicking on this we will be able to upload the respective file.
For this we can do validation and handle the error for unsupported format and so.
Once the upload is complete click on Apply button. Then our PDF will be uploaded.
Conclusion
In this blog we learnt how to upload a Large Object PDF file to our application using ABAP Restful Programing.



