ews-managed-api icon indicating copy to clipboard operation
ews-managed-api copied to clipboard

Unable to download FileAttachment

Open bstoinev opened this issue 4 years ago • 18 comments

Hello! I'm having a hard time downloading attachments.

The below code never downloads an attachment:

        private Dictionary<string, Stream> DownloadAttachments(EmailMessage message)
        {
            var result = new Dictionary<string, Stream>();

            var pdfAttachments = message.Attachments.Where(a => a.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase));

            Log.Debug($"{pdfAttachments.Count()} attachment(s) found.");

            foreach (FileAttachment attachment in pdfAttachments)
            {
                var ms = new MemoryStream(attachment.Size);
                attachment.Load(ms);
                result.Add(attachment.Name, ms);
            }

            return result;
        }

No error is thrown; the attachment.Content property is null, and the length of the MemoryStream is 0. The metadata for the attachment is downloaded correctly though. I can obtain attachment filename, size, and content type. Please note, that the EmailMessage message argument is already bound to an ExchangeService by the calling method using the ItemSchema.Attachments amongst other schemas.

Any help/insights would be greatly appreciated!

bstoinev avatar Mar 08 '21 22:03 bstoinev

Try load message first then try to find attachments.

private Dictionary<string, Stream> DownloadAttachments(EmailMessage message)
{
    var result = new Dictionary<string, Stream>();

    message.Load();

    var pdfAttachments = message.Attachments.Where(a => a.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase));

    Log.Debug($"{pdfAttachments.Count()} attachment(s) found.");

    foreach (FileAttachment attachment in pdfAttachments)
    {
        var ms = new MemoryStream(attachment.Size);
        attachment.Load(ms);
        result.Add(attachment.Name, ms);
    }

    return result;
}

LubosVoska avatar Mar 09 '21 05:03 LubosVoska

Yes, I'm already doing this in the calling method like this (code is shortened for brevity):


var newMail = await EmailMessage.Bind(Server, email.ItemId, EmailSchema);
await newMail.Load();

var attachments = DownloadAttachents(newMail);

bstoinev avatar Mar 09 '21 05:03 bstoinev

This works for me

var message = await EmailMessage.Bind(_exchange, item.Id, new PropertySet(ItemSchema.Attachments));

await message.Load();

var attachment = attachments[0] as FileAttachment;
string file = Path.Combine(FolderPath, attachment.FileName);
await attachment.Load(file);

LubosVoska avatar Mar 09 '21 05:03 LubosVoska

Thanks for your assistance @LubosVoska! The funny thing is I was thinking the same thing because that approach is all over the Internet. However, it doesn't work for me :(

I'm using the NuGet package 1.1.3 Do you see anything wrong with it? Should I integrate EWS by some other means?

bstoinev avatar Mar 09 '21 06:03 bstoinev

Try to use version 2.0.0. I am using 2.0.0-beta1 but there is newer version 2.0.0-beta2.

LubosVoska avatar Mar 09 '21 06:03 LubosVoska

FYI, 2.0.0-beta2 leads to another issue: opening a StreamingSubscriptionConnection hangs till the HttpClient timeout then throws. I'm trying 2.0.0-beta1 and get back with some news...

bstoinev avatar Mar 09 '21 06:03 bstoinev

No joy. 2.0.0-beta1 has the same issue as 2.0.0-beta2.

System.AggregateException: One or more errors occurred. (The request failed. The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.)
 ---> Microsoft.Exchange.WebServices.Data.ServiceRequestException: The request failed. The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.
 ---> Microsoft.Exchange.WebServices.Data.EwsHttpClientException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.
   at Microsoft.Exchange.WebServices.Data.EwsHttpWebRequest.GetResponse()
   at Microsoft.Exchange.WebServices.Data.ServiceRequestBase.GetEwsHttpWebResponse(IEwsHttpWebRequest request)
   --- End of inner exception stack trace ---
   at Microsoft.Exchange.WebServices.Data.ServiceRequestBase.GetEwsHttpWebResponse(IEwsHttpWebRequest request)
   at Microsoft.Exchange.WebServices.Data.ServiceRequestBase.ValidateAndEmitRequest()
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   at System.Threading.Tasks.Task`1.get_Result()
   at Microsoft.Exchange.WebServices.Data.HangingServiceRequestBase.InternalExecute()
   at Microsoft.Exchange.WebServices.Data.StreamingSubscriptionConnection.Open()
   at Lantech.Salechase.Business.MsExchangeMailboxMonitor.Watch(Object folder, Boolean skipInitialSync) in C:\Users\bstoinev...\MsExchangeMailboxMonitor.cs:line 217
   at Lantech.Salechase.WindowsService.SalechaseService.StartMonitoringWithRetry() in C:\Users\bstoinev...\SalechaseService.cs:line 106

And this is the offending code:

            var streamIsClosed = !Stream.CurrentSubscriptions.Any();

            var subscription = await Server.SubscribeToStreamingNotifications(new FolderId[] { targetFolderId }, EventType.NewMail);
            Stream.AddSubscription(subscription);

            if (streamIsClosed)
            {
                // This next line hangs for 100 seconds then throws the above error.
                Stream.Open();
            }

bstoinev avatar Mar 09 '21 06:03 bstoinev

Unfortunately i don't have Exchange to test currrently. If you can give me (privately) a sample account + code that doesn't work I can check it.

sherlock1982 avatar Mar 09 '21 07:03 sherlock1982

I'm in the same shoes; I'm using Office 365 but am initializing with ExchangeVersion.Exchange2010_SP1 like this:

            Server = new ExchangeService(ExchangeVersion.Exchange2010_SP1)
            {
                Credentials = new WebCredentials(Config.Mailbox, Config.Password),
                TraceEnabled = true,
                TraceFlags = TraceFlags.All,
                Url = new Uri(Config.Endpoint)
            };

Is this the issue? Do I need a real MS Exchange, in my case version 2010 to accomplish what I'm after? Otherwise, I have no issues sharing the test mailbox details in private...

FYI, I have to use 2010 as this is the production requirement. So far I'm developing in a lab environment, using Office 365.

bstoinev avatar Mar 09 '21 08:03 bstoinev

Try this

public ExchangeService ConnectToExchange(string username, string password, string email)
{
        var exchange = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
        exchange.TraceEnabled = true;
        exchange.Credentials = new WebCredentials(email, password);
        exchange.UseDefaultCredentials = false;
        exchange.PreAuthenticate = false;
        exchange.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
        exchange.AutodiscoverUrl(email, (url) =>
        {
            // The default for the validation callback is to reject the URL.
            bool result = false;

            Uri redirectionUri = new Uri(url);

            // Validate the contents of the redirection URL. In this simple validation
            // callback, the redirection URL is considered valid if it is using HTTPS
            // to encrypt the authentication credentials. 
            if (redirectionUri.Scheme == "https")
            {
                result = true;
            }
            return result;
        });

        return exchange;
}

LubosVoska avatar Mar 09 '21 08:03 LubosVoska

Ah, sorry @LubosVoska but you're getting out of context...I have no issues connecting with the mailbox and actually am receiving streaming notifications in real-time, searching items, moving stuff around, etc. All the email attributes received are as expected with the exception of the attachments which is the current issue...

However, I just tried the above suggestion for ExchangeVersion.Exchange2013_SP1 and it doesn't make a difference.

bstoinev avatar Mar 09 '21 08:03 bstoinev

This does not work in 1.1.3, because of missing await in FileAttachment.Load function. It works in 2.0.0-beta2, where FileAttachment.Load is made async. Same problem is discussed in #40

mirecad avatar Mar 18 '21 10:03 mirecad

Hi @mirecad, where to get the v2 version of the NuGet Package? I cannot even find the repo and who has made it...

Related to the missing await for the Load functions: it has not been the only bug, if you take a look at on the souce code in the master branch, there are only dummy implementations for both Load method overrides: https://github.com/sherlock1982/ews-managed-api/blob/master/ComplexProperties/FileAttachment.cs

Should be the OfficeDev the official repo for the implementation? They neither seem to have an implemenation for the Load(Stream stream) and Load(string) methods.

Has somebody already a fork, which has made some meaningful steps to solve the problem?

StrictLine avatar Mar 18 '21 20:03 StrictLine

For those who stay with the stable v1.1.3 from nuget.org: ` string emailId = "xxxx";

        // sorry, these 2 lines reference to my external code, but simple calls the EmailMessage.Bind with the unique id
        EmailMessage emailMsg = exchService.GetEmailMessage(emailId);
        await emailMsg.Load();

        if (emailMsg.HasAttachments)
            foreach (FileAttachment fileAttachment in emailMsg.Attachments.Where(att => att is FileAttachment))
            {
                await fileAttachment.Load();
                
                    //fileAttachment.Load($"./{fileAttachment.Name}"); // not working due to dummy implemenation

                using (var fileOutput = new FileStream($"./{fileAttachment.Name}", FileMode.Create, FileAccess.Write))
                    fileOutput.Write(fileAttachment.Content, 0, fileAttachment.Content.Length);
            }

`

I simply use the async Load method of the base class, which works and loads the Content property with bytes of the file.

StrictLine avatar Mar 18 '21 21:03 StrictLine

hi @sherlock1982 , at first many thanks for your fork! I'm a little bit sceptic, what you want to do with a real account: in order to replace the dummy implementation a mock is fairly enough and should be faster than playing with a real EWS.

StrictLine avatar Mar 18 '21 21:03 StrictLine

Hi @mirecad, where to get the v2 version of the NuGet Package? I cannot even find the repo and who has made it...

Look for prerelease packages: https://www.nuget.org/packages/Microsoft.Exchange.WebServices.NETStandard/2.0.0-beta2

mirecad avatar Mar 19 '21 08:03 mirecad

According to AssemblyInfoOpenSource.cs the master branch is on the version 2.2.1 - still with missing/dummy implementation of Load(Stream stream) and Load(string fileName), that means, the upgrade to 2.0.0-beta2 cannot really solve the problem right?

Has somebody already a fork, which has made some meaningful steps to solve the problem? If not, than I might have some time at the weekend to create fork with the implementation.

StrictLine avatar Mar 19 '21 08:03 StrictLine

I've started to fix the missing implementation, but I miss the information/documentation on the private member 'loadToStream'. I have the strong assumption, that this class member has nothing to do with referential integrity. I hope, nobody will have bad feelings about the removal of it from the method (it's simply useless).

StrictLine avatar Apr 18 '21 17:04 StrictLine