Skip to Content
0
6 days ago

No payload deserializer available for resource kind 'ENTITY' and mime type 'text/csv'

250 Views Last edit 6 days ago 3 rev

Greetings.

I am working on SAP CAP + SAP Fiori Element app (currently in a local development setup with SQLite).

I am implementing functionality that allows you to upload a CSV file from the UI, save the file in a table column, but before (or as part of that operation) process the file, extract data and write it to another table (then maybe possibly, in several tables).

The definition of the entity where the raw file must be saved is as follows:

entity SystemTxnUsersFiles {
    key system   : Association to Systems;
        filename : String;
        content  : LargeBinary @Core.MediaType : 'text/csv'
                               @Core.ContentDisposition.Filename: filename;
};

The entity is exposed in service as follows:

@(path:'RA/prepare/structures/app00/v1')
service app00{
    // other service definitions
    // ...

    entity SystemTxnUsersFiles as projection on ORG.SystemTxnUsersFiles;
}

At the UI level, to do the CSV file upload, I implemented a Fiori Elements extension with a FileUploader in a snippet like this:

<core:FragmentDefinition
    xmlns="sap.m" xmlns:form="sap.ui.layout.form" xmlns:core="sap.ui.core" xmlns:comm="sap.suite.ui.commons"
    xmlns:u="sap.ui.unified">
    <Dialog
        title="Transacction Users Upload" resizable="false" draggable="false" showHeader="true"
        horizontalScrolling="false" contentWidth="450px">
        <content>
            <form:SimpleForm title="Upload File">
                <form:content>
                    <u:FileUploader
                        id="txnUsrFileUploader"
                        name="TxnUsersFileUploader"
                        httpRequestMethod="Post"
                        uploadUrl="/RA/prepare/structures/app00/v1/SystemTxnUsersFiles"
                        uploadOnChange="false"
                        uploadComplete="onUploadComplete"
                        change="onValueChange"
                        typeMissmatch="onTypeMissmatch"
                        tooltip="Upload transacction users data file (.csv)"
                        style="Emphasized"
                        fileType="txt,csv"
                        placeholder="Choose a file (.csv)"
                        class="sapUiSmallMarginBegin"
                        mimeType="text/csv"
                        maximumFileSize="500.0" />
                </form:content>
            </form:SimpleForm>
        </content>
        <buttons>
            <Button text="Upload" type="Emphasized" id="btnUpload" press="onUploadPress" class="sapUiSmallMarginBegin"/>
            <Button text="Cancel" type="Emphasized" id="btnCancel" press="onCancelPress"/>
        </buttons>
    </Dialog>
</core:FragmentDefinition>

The attached image shows what the file upload dialog looks like.

As you can see, the path for uploading the file in the FileUploader control is the path of the service that exposes the SystemTxnUsersFiles table (the table where the file must be stored). Here is my first question: Understanding what I want to do, Is it correct to use that route? What should be the upload path of the file if not?


However, with the configuration as described, I get a 400 (Bad Request) error from the UI side when trying to load a CSV file, and I get the following message from the CAP side:

[cds] - POST /RA/prepare/structures/app00/v1/SystemTxnUsersFiles

[cds] - DeserializationError: No payload deserializer available for resource kind 'ENTITY' and mime type 'text/csv'

Given that drawback, I've improvised a workaround using $.ajax() as follows:

				var oFileData = {
                                    "system_id" : _that._sysID,
                                    "content"   : _that._content,
                                    "filename"  : _that._file.name
                                };
                                
                                $.ajax({
                                    url: "/RA/prepare/structures/app00/v1/SystemTxnUsersFiles",
                                    type: "POST",
                                    contentType: "application/json",
                                    data: JSON.stringify(oFileData),
                                    beforeSend: function() {
                                        _that._busyDialog.open();
                                    }, 
                    
                                    success: function(oData, oRes) {
                                        _that._busyDialog.close();
                                        _that._uploadDialog.close();
                                        _that._uploadDialog.destroy();
                                        _that._uploadDialog = null;
                                        MessageBox.success("File Uploaded Successfully");
                                    },
                                    error: function (jqXHR, textStatus) {
                                        _that._busyDialog.close();
                                        MessageBox.alert('Error uploading file');
                                    }
                                });

When the "upload" button is pressed the selected CSV file is read (as text) and put into the variable _that._content. The above code is then executed. The variable _that._uploadDialog contains the instantiated fragment.

Although using $.ajax in the way I indicated I have managed to solve the functionality, I would like to know the expert opinion of the members of the community regarding the "correct" way or the "best" way to implement functionality like the one I have described, as well as any inconveniences or disadvantages of my solution.

As a final note I want to add a couple of things:

1. The logic of processing the CSV file, and writing it to another table, I implemented using a custom CAP handler, which is executed before saving the file to the SystemTxnUsersFiles table. It is similar to this:

    srv.before('CREATE', SystemTxnUsersFiles, async (req) => {
        console.log("[>>[BEFORE-CREATE] SystemTxnUsersFiles]")
        let aFileContent = String(req.data.content).split(/\r?\n/).map((str) => {
            return [req.data.system_id].concat( _CSVStrtoArray(str) );
        });
	// rest of file processing
       	// ...
    });

2. To a large extent, the difficulty that I have found to implement this CSV file upload functionality is that I have had to put together several things: at first the idea was to use CAP actions, with the file as a parameter, but I could not get it to work (that's why I got to make the file a column of a table). On the other hand, there was the processing of the CSV data itself with regular expressions (the test file weighs more than 70 MB and has certain peculiarities to validate). There was also the issue of implementing an SAP Fiori Element extension, and making the call to the endpoint to upload the file have to be (or so I thought at first) using the attributes of the SAPUI5 FileUploader control (the main part of the fragment in my SFE extension).

I greatly appreciate any response and/or comment that may shed light on this approach, or any alternative approach.