Customer Engagement & Dynamics CRM Forum

Expand all | Collapse all

Copying PDF attachment mangles it

  • 1.  Copying PDF attachment mangles it

    SILVER CONTRIBUTOR
    Posted Mar 26, 2020 05:10 PM
    I wrote a C# plugin that's supposed to copy all of the notes, and their attachments, from one entity to another.  It seems to work fine, but when I copy a PDF attachment this way, then attempt to open the new copy, it gets an error and says the PDF is damaged and won't open.  The original PDF attachment opens fine, so it has something to do with how I am copying it.  Any ideas?

    This code:
    - Starts with a parameter entity reference to the target entity (tgtRef)
    - Uses fetch XML to get an entity reference to the parent source entity (srcRef), meaning, a quote's OpportunityId or a salesorder's QuoteId
    - Uses fetch XML to get what I thought was all the relevant attributes of every annotation belonging to srcRef
    - One at a time, copies the attributes of each annotation into a new annotation belonging to the tgtRef

    public void CopyAttachments(EntityReference tgtRef)
    {
    try
    {
    // Get an entity reference to the source entity that has the notes and attachments.

    string srcIdName = null;
    string tgtIdName = null;
    switch (tgtRef.LogicalName)
    {
    case Quote.EntityLogicalName:
    srcIdName = "opportunityid";
    tgtIdName = "quoteid";
    break;
    case SalesOrder.EntityLogicalName:
    srcIdName = "quoteid";
    tgtIdName = "salesorderid";
    break;
    default:
    throw new Exception($"Unsupported entity type: {tgtRef.LogicalName}");
    }
    string fetchXml = $@"
    <fetch top='1' mapping='logical' distinct='true'>
    <entity name='{tgtRef.LogicalName}'>
    <filter type='and'>
    <condition attribute='{tgtIdName}' operator='eq' value='{tgtRef.Id}' />
    </filter>
    <attribute name='{srcIdName}' />
    </entity>
    </fetch>";
    Entity srcEntity = _orgSvc.RetrieveMultiple(new FetchExpression(fetchXml)).Entities.FirstOrDefault();
    if (srcEntity?.Contains(srcIdName) != true || srcEntity[srcIdName].GetType() != typeof(EntityReference))
    {
    _log.Add($"Exiting CopyAttachments because the {tgtRef.LogicalName} doesn't have a {srcIdName}.");
    return;
    }
    EntityReference srcRef = (EntityReference)srcEntity[srcIdName];

    // Copy the notes and attachments attached to the source entity.

    fetchXml = $@"
    <fetch mapping='logical' distinct='true'>
    <entity name='{Annotation.EntityLogicalName}'>
    <filter type='and'>
    <condition attribute='objectid' operator='eq' value='{srcRef.Id}' />
    </filter>
    <attribute name='documentbody' />
    <attribute name='filename' />
    <attribute name='isdocument' />
    <attribute name='mimetype' />
    <attribute name='notetext' />
    <attribute name='ownerid' />
    <attribute name='subject' />
    </entity>
    </fetch>";
    EntityCollection ec = _orgSvc.RetrieveMultiple(new FetchExpression(fetchXml));
    if (ec?.Entities?.Count > 0)
    {
    _log.Add($"The source entity has {ec.Entities.Count} notes and attachments.");
    foreach (Annotation a in ec.Entities)
    {
    _orgSvc.Create(new Annotation()
    {
    DocumentBody = a.DocumentBody,
    FileName = a.FileName,
    IsDocument = a.IsDocument,
    MimeType = a.MimeType,
    NoteText = a.NoteText,
    ObjectId = tgtRef,
    ObjectTypeCode = tgtRef.LogicalName,
    OwnerId = a.OwnerId,
    Subject = a.Subject,
    });
    }
    }
    else
    {
    _log.Add($"The source entity has no notes and attachments.");
    }
    }
    catch (Exception ex)
    {
    _log.Add(ex.Message);
    _log.Add("(CopyAttachmentsLogic.cs CopyAttachments)");
    throw;
    }
    }

    ------------------------------
    Joseph Thiel
    Software Engineer
    Per Mar Security
    Davenport IA
    ------------------------------
    Academy - Online Interactive Learning from Experts


  • 2.  RE: Copying PDF attachment mangles it

    TOP CONTRIBUTOR
    Posted Mar 26, 2020 05:27 PM
    Joseph,
    I blame early binding. Try to use late binding instead:
    foreach (Entity a in ec.Entities)
    {
    _orgSvc.Create(new Entity("annotation")
    {
    ["documentbody"] = a.GetAttributeValue<string>("documentbody"),
    ["filename"] = a.GetAttributeValue<string>("filename"),
    ["isdocument"] = a.GetAttributeValue<bool>("isdocument"),
    ["mimetype"] = a.GetAttributeValue<string>("mimetype"),
    ["notetext"] = a.GetAttributeValue<string>("notetext"),
    ["objectid"] = tgtRef,
    ["objecttypecode"] = tgtRef.LogicalName,
    ["ownerid"] = a["ownerid"],
    ["subject"] = a.GetAttributeValue<string>("subject")
    });
    }
    }
    my theory that when you cast to Annotation some kind of truncation happens on documentbody field.

    ------------------------------
    Andrew Butenko
    ------------------------------

    Academy - Online Interactive Learning from Experts


  • 3.  RE: Copying PDF attachment mangles it

    SILVER CONTRIBUTOR
    Posted Mar 26, 2020 05:53 PM
    That made so much sense that I rushed out to the code and tried it.  Sadly, it didn't work.  I made 2 changes that shouldn't have affected it:  (1) used Annotation.EntityLogicalName instead of "annotation"; (2) a.GetAttributeValue<bool?>("isdocument") instead of "bool", because I noticed the early-bound entity allowed it to be nullable.  I made sure that both the foreach object and the newly-created object were Entities and not Annotations.  Do you have any more ideas?

    ------------------------------
    Joseph Thiel
    Software Engineer
    Per Mar Security
    Davenport IA
    ------------------------------

    Academy - Online Interactive Learning from Experts


  • 4.  RE: Copying PDF attachment mangles it

    TOP CONTRIBUTOR
    Posted Mar 26, 2020 05:58 PM
    Unfortunately I'm out of other ideas.

    ------------------------------
    Andrew Butenko
    ------------------------------

    Academy - Online Interactive Learning from Experts


  • 5.  RE: Copying PDF attachment mangles it

    TOP CONTRIBUTOR
    Posted Mar 27, 2020 06:08 AM
    Are you able to compare the original and new, invalid PDF documents to see what the differences look like? Is one smaller than the other? If you open them in a text editor, is the contents of one obviously different to the other?

    ------------------------------
    Mark Carrington
    Chief Technologist
    Data8
    Chester
    ------------------------------

    Academy - Online Interactive Learning from Experts


  • 6.  RE: Copying PDF attachment mangles it

    SILVER CONTRIBUTOR
    Posted Mar 27, 2020 10:51 AM

    Thank you for the idea!  The files were very, very different, not the least of which was the fact that the copied file was a lot smaller than the original.  From there, I went in to the FetchXML and told it to give me all the Annotation attributes and their .Lengths.  Before it blew up due to being unable to convert an entity reference to a string :) it told me the DocumentBody was exactly 2000 characters long, which was very suspicious.

    Thankfully, I found the solution...and wouldn't you know, sometimes you have to just keep googling til you use the right search term! "C# FetchXML Base64" led me here, but I'll save you the trip:  Including "distinct='true'" in the FetchXML causes the DocumentBody to truncate to 2000 characters.  Took that out and away we go...copied PDF attachments are openable now!

    Thanks everybody for your help!



    ------------------------------
    Joseph Thiel
    Software Engineer
    Per Mar Security
    Davenport IA
    ------------------------------

    Academy - Online Interactive Learning from Experts


  • 7.  RE: Copying PDF attachment mangles it

    GOLD CONTRIBUTOR
    Posted Mar 27, 2020 10:54 AM
      |   view attached
    Hi Joseph,

    I have confirmed that this is possible via early bound with an XrmToolkit proxy class.

    This code works for me, and the cloned PDF opens up fine (PDF attached in case you want to test with it).

        public class App_NoteCopy 
        {
            private IOrganizationService svc;
    
            public App_NoteCopy(IOrganizationService svc) => this.svc = svc;
    
            public void Run()
            {
                using(var ctx = new OrganizationServiceContext(svc))
                {
                    var q = from n in ctx.CreateQuery<Annotation>()
                            orderby n.CreatedOn descending
                            select n;
                    var note = q.First();
                    
                    var clone = new Annotation
                    {
                        Subject = $"{note.Subject} cloned",
                        FileName = note.FileName,
                        IsDocument = note.IsDocument,
                        DocumentBody = note.DocumentBody,
                        MimeType = note.MimeType,
                        ObjectId = note.ObjectId,
                        ObjectTypeCode2 = note.ObjectTypeCode2,
                        OwnerId = note.OwnerId
                    };
    
                    clone.Create(svc);
                }
            }
        }





    ------------------------------
    Aron Fischman
    Senior Consultant
    xRM Edge LLC
    Montclair, NJ
    ------------------------------

    Academy - Online Interactive Learning from Experts


If you've found this thread useful, dive deeper into User Group community content by role