I have a method which accepts a ExchangeWebServices.MessageType parameter. I am using this variable to get the attachments from the message. When I get the contents of the attachments, it is always null. But file name is being read correctly. Below is the code I am using:
public void StripAttachments(MessageType fullMessage)
{
AttachmentType[] attachments = fullMessage.Attachments;
if (attachments != null && attachments.Length > 0)
{
foreach (AttachmentType attachment in attachments)
{
if (attachment is FileAttachmentType)
{
FileAttachmentType file = (FileAttachmentType)attachment;
byte[] contents = file.Content; //Always null
try
{
if(contents != null)
{
System.IO.File.WriteAllBytes(#"C:\TestLocation" + file.Name, contents);
}
}
catch (Exception ex)
{
}
}
}
}
}
Is there a better method to get the attachments from a specific message?
Try using EmailMessage instead of MessageType
You will need to use EmailMessage.Bind(ExchangeService, ItemId) to populate the list of attachments and work with them, otherwise an exception will be thrown.
public void StripAttachments(ItemId id)
{
EmailMessage email = EmailMessage.Bind(service, id)
foreach (Attachment a in email.Attachments)
{
if (a is FileAttachment)
{
// do your thing
}
}
}
Also check out Getting attachments by using the EWS Managed API for more of an idea.
Related
I want to create an attribute which will perform following tasks.
Validate the file type as per the file extension.
Validate the file type as per magic number/signature of file.
Validate the file length/size.
If file is validated then I want to store the file into database as as Base 64 string.
I tried it, but in my solution I have to read file twice first in attribute to check extension, magic number and size and secondly to convert the file stream into base 64 string. But Request.Content is forward only reader so it is throwing error when I tried to read the file again.
Please see the code below
File Validator Filter
public class ValidateFileAttribute : ActionFilterAttribute
{
public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(async () => {
if (Request.Content.IsMimeMultipartContent())
{
var provider = actionContext.Request.Content.ReadAsMultipartAsync(cancellationToken).Result;
foreach (var content in provider.Contents)
{
//Here logic to check extension, magic number and length.
//If any error occurred then throw exception with HttpStatusCode
var fileName = content.Headers.ContentDisposition == null ? string.Empty : content.Headers.ContentDisposition.FileName;
var fileInBytes = content.ReadAsByteArrayAsync().Result;
var extention = fileName.Substring(fileName.LastIndexOf('.') + 1);
var validExtensions = new List<string>() { "pdf", "doc", "docx" };
if (!validExtensions.Contains(extention, StringComparer.OrdinalIgnoreCase))
{
//Return Exception
}
if (fileInBytes != null && fileInBytes.Any())
{
var magicNumber = BitConverter.ToString(fileInBytes).Substring(0, 11);
var validMagicNumbers = new List<string>() { "25-50-44-46", "D0-CF-11-E0", "50-4B-03-04" };
if (!validMagicNumbers.Contains(magicNumber, StringComparer.OrdinalIgnoreCase))
{
// Return Exception
}
}
if(fileInBytes != null && fileInBytes.Any() && fileInBytes.Length >= 3000000)
{
// Return Exception
}
}
}
}, cancellationToken);
}
}
Upload Action Method
[ValidateFile]
[Route("upload")]
[HttpPost]
public DocumentUploadResponse Upload()
{
if (Request.Content.IsMimeMultipartContent())
{
var provider = Request.Content.ReadAsMultipartAsync().Result;
// Getting error here..
foreach (var content in provider.Contents)
{
//Here logic to convert file stream into base 64 string.
//And store that string into Database.
var fileInBytes = content.ReadAsByteArrayAsync().Result;
var fileToStore = Convert.ToBase64String(fileInBytes);
/// Here goes Database code.....
}
}
}
Your help will be appreciated.
I'm developing an addin application for Outlook using Redemption. I'm trying to get a list of addresses to add to the email as addresses and then show the email.
session = new RDOSession();
session.Logon();
var contacts = session.GetDefaultFolder
(rdoDefaultFolders.olFolderContacts);
var mailItem = contacts.Items.Add("New message");
AddinModule.CurrentInstance.Session.MAPIOBJECT = mailItem.Session.MAPIOBJECT;
mailItem.Attachments.Add(file, OlAttachmentType.olByValue, Type.Missing, Type.Missing);
RDOFolder folder = session.GetDefaultFolder(rdoDefaultFolders.olFolderContacts);
foreach (RDOFolder subFolder in folder.Folders)
{
if (subFolder.Name == "CAS_Notifications")
{
foreach (var rdoItem in subFolder.Items)
{
RDOContactItem contactItem = rdoItem as RDOContactItem;
RDODistListItem distList = rdoItem as RDODistListItem;
if (distList != null)
{
foreach (RDOAddressEntry rdoAddressEntry in distList.OneOffMembers)
{
mailItem.Recipients.Add(rdoAddressEntry.SMTPAddress);
}
}
else if (contactItem != null)
{
mailItem.Recipients.Add(contactItem.Email1Address);
}
}
}
}
mailItem.Display();
Threw exception
System.Runtime.InteropServices.COMException: "Error in IMAPIFormMgr.LoadForm: MAPI_E_NOT_FOUND"
How to debug it?
The following line is the problem:
var mailItem = contacts.Items.Add("New message");
Add takes either a message class (e.g. "IPM.Note" or one of the olItemType / rdoItemType enums (such as olMailItem = 0).
The error you get essentially tells you that MAPI cannot find a form for the specified message class ("New message").
I have looked at other examples online, but I am unable to figure out how to download and store ALL the attachments from a MimeMessage object.
I did look into the WriteTo(), but I could not get it to work.
Also wondering whether attachments will be saved according to the original file name, and type inside the email.
Here is what I have so far:
using (var client = new ImapClient())
{
client.Connect(Constant.GoogleImapHost, Constant.ImapPort, SecureSocketOptions.SslOnConnect);
client.AuthenticationMechanisms.Remove(Constant.GoogleOAuth);
client.Authenticate(Constant.GoogleUserName, Constant.GenericPassword);
if (client.IsConnected == true)
{
FolderAccess inboxAccess = client.Inbox.Open(FolderAccess.ReadWrite);
IMailFolder inboxFolder = client.GetFolder(Constant.InboxFolder);
IList<UniqueId> uids = client.Inbox.Search(SearchQuery.All);
if (inboxFolder != null & inboxFolder.Unread > 0)
{
foreach (UniqueId msgId in uids)
{
MimeMessage message = inboxFolder.GetMessage(msgId);
foreach (MimeEntity attachment in message.Attachments)
{
//need to save all the attachments locally
}
}
}
}
}
This is all explained in the FAQ in the "How do I save attachments?" section.
Here is a fixed version of the code you posted in your question:
using (var client = new ImapClient ()) {
client.Connect (Constant.GoogleImapHost, Constant.ImapPort, SecureSocketOptions.SslOnConnect);
client.AuthenticationMechanisms.Remove (Constant.GoogleOAuth);
client.Authenticate (Constant.GoogleUserName, Constant.GenericPassword);
client.Inbox.Open (FolderAccess.ReadWrite);
IList<UniqueId> uids = client.Inbox.Search (SearchQuery.All);
foreach (UniqueId uid in uids) {
MimeMessage message = client.Inbox.GetMessage (uid);
foreach (MimeEntity attachment in message.Attachments) {
var fileName = attachment.ContentDisposition?.FileName ?? attachment.ContentType.Name;
using (var stream = File.Create (fileName)) {
if (attachment is MessagePart) {
var rfc822 = (MessagePart) attachment;
rfc822.Message.WriteTo (stream);
} else {
var part = (MimePart) attachment;
part.Content.DecodeTo (stream);
}
}
}
}
}
A few notes:
There's no need to check if client.IsConnected after authenticating. If it wasn't connected, it would have thrown an exception in the Authenticate() method. It would have thrown an exception in the Connect() method as well if it didn't succeed. There is no need to check the IsConnected state if you literally just called Connect() 2 lines up.
Why are you checking inboxFolder.Unread if you don't even use it anywhere? If you just want to download unread messages, change your search to be SearchQuery.NotSeen which will give you only the message UIDs that have not been read.
I removed your IMailFolder inboxFolder = client.GetFolder(Constant.InboxFolder); logic because you don't need it. If you are going to do the SEARCH using client.Inbox, then don't iterate over the results with a different folder object.
This problem is happening for one of our customers and I have been unable to replicate on my side using the same version of Outlook. My customer and I are using Office 365 with Outlook 2016 installed. When he sends an email in our program via Outlook Redemption (a third party program used for Outlook integration), the mail gets stuck in his outbox.
If he double clicks the message (so it pops up in Outlook) he can hit the send button and it will sucessfuly send. If they use an old version of Outlook (2010) this is not a problem. I upgraded them to the the newest version of Outlook Redmeption at the time (released May 07, 2016), though it looks like they just came out with a new version a few days ago. I'll try that soon, but the changelog doesn't mention mail getting stuck in the Outbox.
I also noticed that the emails in his outbox have what appears to be the 'draft' symbol on them, while in my outbox they have a 'sending' symbol on them. This seems important, but I'm not sure what I can do about that.
Also, hitting Send/Receive All Folders does not help.
My code is below. Thank you for any assistance.
public static bool SendMessage(Recipients recipients, string[] addressListReplyTo, string subject, string body, string[] attachments, bool requestReadReceipt, Log log, bool isHtmlBody = false)
{
RDOSession session = null;
RDOMail mail;
RDOFolder folder;
bool result = true;
session = GetSessionAndLogon(log);
if (session == null)
return false;
folder = session.GetDefaultFolder(rdoDefaultFolders.olFolderOutbox);
mail = folder.Items.Add();
if (isHtmlBody)
mail.HTMLBody = body;
else
mail.Body = body;
mail.Subject = subject;
mail.ReadReceiptRequested = requestReadReceipt;
foreach (string attachment in attachments)
{
if (attachment != "")
mail.Attachments.Add(attachment);
}
foreach (string address in addressListReplyTo)
{
if (address != "")
mail.ReplyRecipients.Add(address);
}
foreach (string address in recipients.To)
{
if (address != "")
mail.Recipients.Add(address).Type = 1;
}
foreach (string address in recipients.Cc)
{
if (address != "")
mail.Recipients.Add(address).Type = 2;
}
foreach (string address in recipients.Bcc)
{
if (address != "")
mail.Recipients.Add(address).Type = 3;
}
foreach (RDORecipient recipient in mail.Recipients)
{
if (!OutlookMailEngine64.existsName(recipient.Name, session, log == null ? null : log))
result = false;
}
if (result)
{
try
{
mail.Send();
result = true;
}
catch (System.Runtime.InteropServices.COMException ex)
{
string message = "Error while sending email: " + ex.Message;
if (log != null)
log.Message(message);
if (OutlookMailEngine64.DiagnosticMode)
MessageBox.Show(message);
throw new EmailLibraryException(EmailLibraryException.ErrorType.InvalidRecipient, "One or more recipients are invalid (use OutlookMailEngine64.ValidateAddresses first)", ex);
}
}
if (session.LoggedOn)
session.Logoff();
return result;
}
Keep in mind that message submission is asynchronous, and it will nto be automatically triggered unless you are using the online Exchange store (where store and transport provider are tightly coupled).
You can force send/receive by calling Namespace.SendAndReceive in the Outlook Object Model.
Dmitry worked with me via email. The solution for me was to swap out RDO for the SafeMailItem object. Here is the updated version of my method so you can see the changes:
private static bool SendSafeMessage(Recipients recipients, string[] addressListReplyTo, string subject, string body, string[] attachments, bool requestReadReceipt, Log log, bool isHtmlBody = false)
{
//This method was added because sometimes messages were getting stuck in the Outlook Outbox and this seems to solve that
bool result = true;
Microsoft.Office.Interop.Outlook.Application application = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.NameSpace namespaceMAPI = application.GetNamespace("MAPI");
namespaceMAPI.Logon();
RDOSession session = null;
session = GetSessionAndLogon(log); //TODO: I'm creating a 2nd session here which is wasteful
SafeMailItem safeMail = Redemption.RedemptionLoader.new_SafeMailItem();
Microsoft.Office.Interop.Outlook.MailItem outlookMailItem = (Microsoft.Office.Interop.Outlook.MailItem)application.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
safeMail.Item = outlookMailItem;
if (isHtmlBody)
outlookMailItem.HTMLBody = body;
else
safeMail.Body = body;
outlookMailItem.Subject = subject;
outlookMailItem.ReadReceiptRequested = requestReadReceipt;
foreach (string attachment in attachments)
{
if (attachment != "")
safeMail.Attachments.Add(attachment);
}
foreach (string address in addressListReplyTo)
{
if (address != "")
safeMail.ReplyRecipients.Add(address);
}
foreach (string address in recipients.To)
{
if (address != "")
safeMail.Recipients.Add(address).Type = 1;
}
foreach (string address in recipients.Cc)
{
if (address != "")
safeMail.Recipients.Add(address).Type = 2;
}
foreach (string address in recipients.Bcc)
{
if (address != "")
safeMail.Recipients.Add(address).Type = 3;
}
foreach (Microsoft.Office.Interop.Outlook.Recipient recipient in outlookMailItem.Recipients)
{
if (!OutlookMailEngine64.existsName(recipient.Name, session, log == null ? null : log))
result = false;
}
if (result)
{
try
{
safeMail.Send();
result = true;
}
catch (System.Runtime.InteropServices.COMException ex)
{
string message = "Error while sending email: " + ex.Message;
if (log != null)
log.Message(message);
if (OutlookMailEngine64.DiagnosticMode)
MessageBox.Show(message);
throw new EmailLibraryException(EmailLibraryException.ErrorType.InvalidRecipient, "One or more recipients are invalid (use OutlookMailEngine64.ValidateAddresses first)", ex);
}
}
if (session.LoggedOn)
session.Logoff();
namespaceMAPI.Logoff();
return result;
}
I have an SPEmailEventReceiver that receives an email message, adds a listitem to the list, loops through the email message attachments collection and adds them to the listitem attachment collection.
I also need to get the email message itself (.eml file) and add it to the ListItem attachments collection. I am able to get the email body, subject, sender...etc, but i cannot get the message itself. Any suggestions on what i can use? Here's what I have so far.
public class EInvoiceEmailEventReceiver : SPEmailEventReceiver
{
/// <summary>
/// The list received an e-mail message.
/// </summary>
public override void EmailReceived(SPList list, SPEmailMessage emailMessage, String receiverData)
{
if (list == null)
throw new ArgumentNullException("list", "null list parameter in EmailReceived");
if (emailMessage == null)
throw new ArgumentNullException("emailMessage", "null emailMessage parameter in EmailReceived");
try
{
AddItemFromEmail(list, emailMessage, receiverData);
}
catch (Exception ex)
{
DiagnosticsService.WriteToLocalLog("ERROR: Error while adding eInvoice item from email: " +
}
}
private void AddItemFromEmail(SPList list, SPEmailMessage emailMessage, String receiverData)
{
string subject;
try
{
subject = emailMessage.Headers["Subject"] ?? string.Empty;
}
catch
{
subject = string.Empty;
}
SPListItem item = list.Items.Add();
SetItemFieldValue(item, "Title", "Crreated from email on " + DateTime.Now.ToString("yyyy-MM-dd
HH:mm:ss.FFFFFFF"));
SetItemFieldValue(item, "EmailFromAddress", emailMessage.Sender);
SPAttachmentCollection itemAttachments = item.Attachments;
base.EmailReceived(list, emailMessage, receiverData);
SPEmailAttachmentCollection emailAttachments = emailMessage.Attachments;
if (emailAttachments != null)
{
foreach (SPEmailAttachment emailAttachment in emailAttachments)
{
try
{
byte[] emailAttachmentBytes = new byte[emailAttachment.ContentStream.Length];
emailAttachment.ContentStream.Read(emailAttachmentBytes, 0, emailAttachmentBytes.Length);
itemAttachments.Add(emailAttachment.FileName, emailAttachmentBytes);
}
catch (Exception ex)
{
DiagnosticsService.WriteToLocalLog("Error while moving attachment from email to eInvoice
item: " + ex.Message, LogLogLevel.Error, CategoryId.CustomAction);
}
}
}
item.Update();
}
private static void SetItemFieldValue(SPListItem item, string fieldName, string value)
{
try
{
item[fieldName] = value ?? string.Empty;
}
catch
{
DiagnosticsService.WriteToLocalLog(string.Format("Error while setting field {0} in eInvoice list.", fieldName), LogLogLevel.Error, CategoryId.CustomAction);
}
}
}
}
You can use GetMessageStream method of SPEmailMessage object to do that.
var emailStream = emailMessage.GetMessageStream();
var emailAsBytes = new byte[emailStream.Length];
emailStream.Read(emailAsBytes, 0, emailStream.Length);
itemAttachments.Add('Message.eml', emailAsBytes);