web
You’re offline. This is a read only version of the page.
close
Skip to main content

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Finance | Project Operations, Human Resources, ...
Answered

Export/Download attachment using OData Entity or custom service API

(0) ShareShare
ReportReport
Posted on by 129

Hello community.

i am trying to Export a PDF attachment from D365.
Our client would like to provide their vendors a file download link from their portal (not a D365 session).
For information, our storage local is set to Azure Blob.
We tried two approaches and both failed:

Via OData:
we built a new Entity with DocuRefEntity as primary DataSource and added my table as secondary (for Record Identity purpose).
The problem is the field DocuRefEntity.Attachment always returns "null".
Maybe because of the following standard code:

DocuRefEntity.defineFileContents()
private static str defineFileContents()
{
    // This is a workaround to get import/export working since it does not recognize a virtual container field.
    return SysComputedColumn::nullExpression();
}

Via Custom Service (API):
we built a Class that receives Record Identity and retrieves the following Download URL:
docuURL = "filemanagement/bc36fd9f-8d45-4b01-83af-6f3f9446c3a5?access_token=eyJDb21wYW55IjoiMSIsIkhhc2hlZFRva2VuIjoiYmRVVHZTR1VINU1rSERVNWlaNGVUbDRtY0xMQnVhVnpOT0o2eHNhVGVJaW1NdlpqNlNERGxNTTFpeW1UMU9SWXkxeEhlaHNCcXVWb052VStZclwvRWZ3PT0iLCJQYXJ0aXRpb24iOjU2MzcxNDQ1NzYsIlVzZXJJZCI6NTYzNzE0NDU3NiwiVmFsaWRVbnRpbCI6IlwvRGF0ZSgxNjM2MDUxMjk3NTExKVwvIiwiRmlsZUlkIjoiYmMzNmZkOWYtOGQ0NS00YjAxLTgzYWYtNmYzZjk0NDZjM2E1In0%3D"

our attempt to download from this url was to browse for:
>> [our_D365_environment_url] / [docuURL]
But this did not work, unless an Admin user is logged to a D365 session, which of course is not our scenario.

Any help?! We do appreciate.

I have the same question (0)
  • Martin Dráb Profile Picture
    237,878 Most Valuable Professional on at

    Yes, the computed column is defined to always return null, but it serves just as a placeholder. The data is populated in postLoad() method.

    this.FileContents = DocumentManagement::getAttachmentAsContainer(record);

    On import, FileContents is handled in insertEntityDataSource().

    If you want to use use custom service (or an OData action), maybe you should return the file contents and not just a link.

  • Hugo Alves Profile Picture
    129 on at

    Thank you, Martin

    On Custom Service:

    i tried your suggestion to return file contents. Here is what i got:
    Return type: Container
    - Error: Type is not supported Container
    Return type: System.IO.Stream
    - Error: Unable to cast object of type 'Microsoft.WindowsAzure.Storage.Blob.BlobReadStream' to type 'Microsoft.Dynamics.Ax.Xpp.XppObjectBase'.

    On OData:

    From Postman, i've tested an existing Data Entity that works perfectly to retrieve file contents in Attachment field: SalesOrderHeaderDocumentAttachments
    "Attachment": "JVBERi0xLjcgCiXi48/TIAoxIDAgb2JqIAo8PCAKL1R5cGUgL0NhdGFsb2cgCi9QYWdlcyAyIDAgUiAKL1BhZ2VNb2RlIC9Vc2VOb25lIAovVmlld2VyUHJlZmVyZW5jZXMgPDwgCi9GaXRXaW5kb3cgdHJ1ZSAKL1BhZ2VMYXlvdXQgL1NpbmdsZVBhZ2UgCi9Ob25GdWxsU2NyZWV..."

    My new Data Entity though, still retrieves "null". I can't see any difference. Am i missing some step or procedure?
    "Attachment": null

    Even for the standard Entity (VendorDocumentAttachments) it is returning "Null"
    "Attachment": null

  • Verified answer
    Martin Dráb Profile Picture
    237,878 Most Valuable Professional on at

    With a custom service, I think the best approach would be returning a byte array (System.Byte[]). Give it a try. I got streaming working in AX 2012 (SOAP), but I've never tried it in F&O.

    Regardinbg your custom entity, it's very difficult to find a bug in your code if we don't know your code at all. Anyway, your first step should be reviewing and debugging your code populating the field (in postLoad()).

  • Hugo Alves Profile Picture
    129 on at

    I'll try the return type System.Byte[], for the API.

    About the OData approach, there is no code on specific Record Attachment Entities,
    because the code that populates the File Contents or Attachment field is set on standard DocuRefEntity which is our primary datasource.
    See standard entities like SalesOrderHeaderDocumentAttachments or VendorDocumentAttachments.

  • Martin Dráb Profile Picture
    237,878 Most Valuable Professional on at

    It's still impossible to review the design of your entity, and we can't debug it either. You'll need to do the debugging by yourself.

    By the way, I do see code SalesOrderHeaderDocumentAttachmentEntity related to the Attachment field - in postLoad(). Check out if it's not related to your case.

  • Hugo Alves Profile Picture
    129 on at

    Where did you find code for this entity, Martin?

    pastedimage1636720876727v1.png

  • Hugo Alves Profile Picture
    129 on at

    i will put some code from the API approach. It is not working cause i can't find a compatible return type.

    Return type / Error:

    Container -Type is not supported Container
    System.IO.MemoryStream - Unable to cast object of type 'System.IO.MemoryStream' to type 'Microsoft.Dynamics.Ax.Xpp.XppObjectBase'. at Dynamics.AX.Application.FormJsonSerializer
    System.Byte[ - Unable to cast object of type 'System.Byte[' to type 'Microsoft.Dynamics.Ax.Xpp.XppObjectBase'

    in general the error,is incompatible return type:
    "An exception occured when serializing a parameters - Exception occured when serializing the return value - Unrecognized AX collection type"

    [DataContractAttribute]
    class BiddingProcessDocFileResponse
    {
        BiddingProcessCode     		code;
        str                         docFileURL;
        System.Byte[]               fileBytes;
    
    	...
    
        [DataMemberAttribute('FileBytes'),
        SysOperationLabelAttribute(literalStr('@SYS93335'))]
        public System.Byte[] parmFileBytes(System.Byte[] _fileBytes = fileBytes)
        {
            fileBytes = _fileBytes;
    
            return fileBytes;
        }
    }

    ...
        [AifCollectionType('return', Types::Class, classStr(BiddingProcessDocFileResponse))]
        public BiddingProcessDocFileResponse docFileDetailJson(BiddingProcessDetailRequest _BiddingProcessDetailRequest)
        {
            BiddingProcessTable                biddingProcessTable;
            BiddingProcessDocFileResponse      response = new BiddingProcessDocFileResponse();
    
            if (_BiddingProcessDetailRequest.parmCode())
            {
                biddingProcessTable = BiddingProcessTable::find(_BiddingProcessDetailRequest.parmCode());
                if (biddingProcessTable)
                {
                    DocuRef         docuRef = biddingProcessTable.docuRefPublicEspecification();
    
                    response.parmCode(biddingProcessTable.Code);
                    if (docuRef)
                    {
                        DocuRefHelper      docuRefHelper = new DocuRefHelper(docuRef);
    
                        response.parmDocFileUrl(docuRefHelper.getDownloadUrl());
                        response.parmFileBytes(docuRefHelper.getFileByteArray());
                    }
                }
            }
    
            return response;
        }
    ...    

    using Microsoft.Dynamics.ApplicationPlatform.Services.Instrumentation;
    using Microsoft.DynamicsOnline.Infrastructure.Components.SharedServiceUnitStorage;
    using Microsoft.Dynamics.AX.Framework.FileManagement;
    using System.IO.Stream;
    
    class DocuRefHelper
    {
        DocuRef     docuRef;
        str         docfiletype;
        str         downloadUrl;
    
        
        public void new(DocuRef _docuRef)
        {
            docuRef = _docuRef;
        }
    
        public str getDownloadUrl()
        {
            if (!downloadUrl)
            {
                //if (docuRef.url())
                //{
                //    downloadUrl = docuRef.url();
                //}
                //else
                //if (docuRef.isValueAttached())
                //{
                //    var docuValueloc = docuRef.docuValue();
                //    downloadUrl = docuValueloc.Path;
    
                //    if (!downloadUrl || docuValueloc.Type == DocuValueType::Others)
                //    {
                //        str accessToken = DocumentManagement::createAccessToken(docuRef);
                //        downloadUrl = Microsoft.Dynamics.AX.Framework.FileManagement.URLBuilderUtilities::GetDownloadUrl(docuValueloc.FileId, accessToken);
                //    }     
                //}
    
                downloadUrl = DocumentManagement::getAttachmentPublicUrl(docuRef);
            }
    
            return downloadUrl;
        }
    
        public container getFileContainer()
        {
            return DocumentManagement::getAttachmentAsContainer(docuRef);
        }
    
        public System.IO.Stream getFileStream()
        {
            return DocumentManagement::getAttachmentStream(docuRef);
        }
    
        public System.Byte[] getFileByteArray()
        {
            System.Byte[] byteArray = null;
    
            ContainerClass containerClass = new ContainerClass(this.getFileContainer());
            container blobContainer = containerClass.toBlob();
    
            Binary binary = Binary::constructFromContainer(blobContainer);
            using (System.IO.MemoryStream stream = binary.getMemoryStream())
            {
                byteArray = stream.ToArray();
            }
        
            return byteArray;
        }
    
        public CLRObject getFileCLRStream()
        {
            ContainerClass containerClass = new ContainerClass(this.getFileContainer());
            container blobContainer = containerClass.toBlob();
    
            Binary binary = Binary::constructFromContainer(blobContainer);
        
            return binary.getMemoryStream();
        }
    }

  • Verified answer
    Martin Dráb Profile Picture
    237,878 Most Valuable Professional on at

    I have code in postLoad() of the entity. Maybe you're using a different (probably older) version of the application.

    Regarding the custom service, your code is very different from what I meant. I meant returning System.Byte[] from the service operation, while you're trying to use use System.Byte[ as data member of a contract class.

    By the way, you can also throw away AifCollectionTypeAttribute. It would be needed if you returned a List, which isn't your case.

    Try something like this:

    public System.Byte[] docFileDetail(BiddingProcessDetailRequest _biddingProcessDetailRequest)
    {
    	if (_biddingProcessDetailRequest.parmCode())
    	{
    		BiddingProcessTable biddingProcessTable = BiddingProcessTable::find(_BiddingProcessDetailRequest.parmCode());
    		if (biddingProcessTable)
    		{
    			DocuRef docuRef = biddingProcessTable.docuRefPublicEspecification();
    
    			if (docuRef)
    			{
    				DocuRefHelper docuRefHelper = new DocuRefHelper(docuRef);
    				return docuRefHelper.getFileByteArray();
    			}
    		}
    	}
    
    	return null;
    }

    Another remark - this is how you can simplify getFileByteArray():

    public System.Byte[] getFileByteArray()
    {
    	System.Byte[] byteArray;
    
    	Binary binary = Binary::constructFromContainer(this.getFileContainer());
    	using (System.IO.MemoryStream stream = binary.getMemoryStream())
    	{
    		byteArray = stream.ToArray();
    	}
    
    	return byteArray;
    }

  • Hugo Alves Profile Picture
    129 on at

    Thank you very much, Martin
    Finally i got the expected stream response.
    i confess i thought API should necessarily return a contract to be converted in json.

  • Martin Dráb Profile Picture
    237,878 Most Valuable Professional on at

    No, you can use primitive types, lists or some .NET types as well. And maybe other things - for example, AX 2012 supported tables and I guess that they can be used in F&O as well (although I don't think I've ever done it).

    Using a contract class a typical scenario, of course, but there are other options as well.

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Responsible AI policies

As AI tools become more common, we’re introducing a Responsible AI Use…

Neeraj Kumar – Community Spotlight

We are honored to recognize Neeraj Kumar as our Community Spotlight honoree for…

Leaderboard > Finance | Project Operations, Human Resources, AX, GP, SL

#1
Martin Dráb Profile Picture

Martin Dráb 646 Most Valuable Professional

#2
André Arnaud de Calavon Profile Picture

André Arnaud de Cal... 529 Super User 2025 Season 2

#3
Sohaib Cheema Profile Picture

Sohaib Cheema 285 User Group Leader

Last 30 days Overall leaderboard

Product updates

Dynamics 365 release plans