Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to download FileAttachment #55

Open
bstoinev opened this issue Mar 8, 2021 · 18 comments
Open

Unable to download FileAttachment #55

bstoinev opened this issue Mar 8, 2021 · 18 comments

Comments

@bstoinev
Copy link

bstoinev commented Mar 8, 2021

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!

@LubosVoska
Copy link

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;
}

@bstoinev
Copy link
Author

bstoinev commented Mar 9, 2021

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);

@LubosVoska
Copy link

LubosVoska commented Mar 9, 2021

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);

@bstoinev
Copy link
Author

bstoinev commented Mar 9, 2021

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?

@LubosVoska
Copy link

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

@bstoinev
Copy link
Author

bstoinev commented Mar 9, 2021

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
Copy link
Author

bstoinev commented Mar 9, 2021

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();
            }

@sherlock1982
Copy link
Owner

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.

@bstoinev
Copy link
Author

bstoinev commented Mar 9, 2021

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.

@LubosVoska
Copy link

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;
}

@bstoinev
Copy link
Author

bstoinev commented Mar 9, 2021

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.

@mirecad
Copy link

mirecad commented Mar 18, 2021

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

@StrictLine
Copy link

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
Copy link

StrictLine commented Mar 18, 2021

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
Copy link

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.

@mirecad
Copy link

mirecad commented Mar 19, 2021

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

@StrictLine
Copy link

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
Copy link

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants