microsoftgraph/aspnet-snippets-sample

Get Attachment from Outlook using the HasAttachment Bool?

twcarnahan opened this issue · 16 comments

While attempting to get the attachment from Outlook, I have a problem using the message.hasattachement while checking to see which message has an attachment before getting the attachment; can you show example or further your example getting an attachment.
This works from the graph.microsoft.io
https://graph.microsoft.com/v1.0/me/MailFolders/Inbox/Messages?$select=webLink,subject,hasAttachments just can't get it into the Razor page --- ;(

added behavior notes:
a and b below work using the tool at graph.microsoft.io using a message with one attachment
a) //graph.microsoft.com/v1.0/me/mailFolders/inbox/messages/[message.id]/attachments?$select=name,size
b) //graph.microsoft.com/v1.0/me/mailFolders/inbox/messages/[message.id]/attachments/$count
the below seems to work and downloads to the page without the page having the capability to view so it's blank - or at least I didn't get an error.
c) //graph.microsoft.com/v1.0/me/mailFolders/inbox/messages/[message.id]/attachments

My problem exist while using the razor and "results", I'm trying to get that page to show "getmessage" the attachment (I've added gif for this) and then click the link (attachment name) which I have successfully got to the __ResultsPartial.cshtml page.
I had to use the below:
IMessageAttachmentsCollectionPage attachments = await graphClient.Me.Messages[message.Id].Attachments.Request().Select("Name,Size").GetAsync();

This is all a bit messy and any direction would be appreciated :)
Vr, Tim:
graph.microsoft.io

Hi Tim. This sample uses the Graph SDK, so the equivalent request is this:

IMailFolderMessagesCollectionPage messages = await graphClient.Me.MailFolders.Inbox.Messages.Request().Select("webLink,subject,hasAttachments").GetAsync();

You can use Expand to retrieve attachments in the same request:

IMailFolderMessagesCollectionPage messages = await graphClient.Me.MailFolders.Inbox.Messages.Request().Select("webLink,subject,hasAttachments").Expand("attachments").GetAsync();

What is it that you're trying to get into the Razor page? Are you extending this sample app to show attachment data?

Sorry, I didn't see your additional info until I refreshed the page. The sample isn't currently designed to show binary results. To indicate successful file download, it displays some stream info, like in this example that shows the stream size:

        public async Task<ActionResult> GetMyInboxMessagesThatHaveAttachments()
        {
            ResultsViewModel results = new ResultsViewModel();
            List<ResultsItem> items = new List<ResultsItem>();

            try
            {

                // Initialize the GraphServiceClient.
                GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient();

                // Get messages in the Inbox folder.
                IMailFolderMessagesCollectionPage messages = await graphClient.Me.MailFolders.Inbox.Messages.Request().Filter("hasAttachments eq true").Expand("attachments").GetAsync();
                
                if (messages?.Count > 0)
                {
                    foreach (Message message in messages)
                    {
                        items.Add(new ResultsItem
                        {
                            Display = message.Subject,
                            Id = message.Id,
                            Properties = new Dictionary<string, object>
                            {
                                { "Attachment count", message.Attachments.Count },
                                { "First attachment name", message.Attachments[0].Name },
                                { "First attachment type", message.Attachments[0].ODataType },
                                { "First attachment size", message.Attachments[0].Size }
                            }
                        });
                    }
                }
                results.Items = items;
            }
            catch (ServiceException se)
            {
                if (se.Error.Message == Resource.Error_AuthChallengeNeeded) return new EmptyResult();

                // Personal accounts that aren't enabled for the Outlook REST API get a "MailboxNotEnabledForRESTAPI" or "MailboxNotSupportedForRESTAPI" error.
                return RedirectToAction("Index", "Error", new { message = string.Format(Resource.Error_Message, Request.RawUrl, se.Error.Code, se.Error.Message) });
            }
            return View("Test", results);
        }

Hi Tim. The attachment name and size should be on the items in the attachments collection, for example: message.Attachments[0].Name

I'm getting the stream too, but the ContentLocation property is null. This sounds like what you're looking for. I'll try to find out how to get it.

contentLocation: The Uniform Resource Identifier (URI) that corresponds to the location of the content of the attachment.

Hi Tim. No news on ContentLocation yet.

I did confirm that it's by design that hasAttachments does not apply to inline attachments. I also learned this: If the message has inline attachments, they're indicated in the HTML of the message's content property with a src attribute like: src=\"cid:image001.jpg@01D26CD8.6C05F070\

To avoid a single expensive request, you could send requests to the messages/{id}/attachments endpoint (like in your first comment) as needed. Then you could select the attachment properties you want.

The binary contents should be in the attachment.ContentBytes property. It's a byte[], not a stream.

image

Re: side notes.

  1. There should be a Close issue button at the bottom of the page.
  2. Maybe you pasted the text from a formatted doc?

Hi Tim. I could see the ContentBytes property in the local variable, but I had to cast it as a FileAttachment in order to access it. For example:

IMessageAttachmentsCollectionPage attachments = await graphClient.Me.Messages[message.Id].Attachments.Request().GetAsync();
foreach (Attachment attachment in attachments)
{
    if (attachment.ODataType == "#microsoft.graph.fileAttachment")
    {
        FileAttachment fileAttachment = attachment as FileAttachment;
        byte[] contentBytes = fileAttachment.ContentBytes;
    }
}

Note I'm querying the Attachments endpoint directly, which differs from your previous example.

I added the check for attachment type because only attachments of type FileAttachment have the ContentBytes property. This includes FileAttachments that are inline.

I'll check whether this is expected behavior (the explicit cast).