Elegant way to serialize a MailMessage object in .NET - c#

I'm currently looking at serializing a MailMessage object in C# and although there are a couple of variations of an example on the net, they serialize to binary which kind of misses the point IMO.
My approach is that I'd like to serialize a MailMessage to an RFC2822 eml string and the code below is what I've come up with.
public string SerializeEmail(MailMessageArgs e)
{
string rfc822eml = "";
Guid g = Guid.NewGuid();
lock (g.ToString())
{
System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(#"C:\tmpspool");
di.CreateSubdirectory(g.ToString());
string spoolDir = #"C:\tmpspool\" + g.ToString();
SmtpClient Client = new SmtpClient("localhost");
Client.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
Client.PickupDirectoryLocation = spoolDir;
Client.Send(e.mailObj);
var files = from file in Directory.EnumerateFiles(spoolDir)
select file;
string serializedEml = files.First();
rfc822eml = File.ReadAllText(serializedEml);
File.Delete(serializedEml);
Directory.Delete(spoolDir);
}
return rfc822eml;
}
It's pretty nasty but it does work. Ideally though, I'd create a new SMTPClient and add in a Serialize function which would return the rfc822 string automatically without it ever hitting the file system.
As I don't seem to be able to trace into the SMTPClient.Send function with Visual Studio, this "ideal" way of doing things is a tad tricky.
I've already sorted out the deserialization of the RFC message by adding in some small changes to Peter Huber's POP3MimeClient and POP3MailClient classes so I can deserialize a file on the HDD or a string back into a MailMessage.
The thing that's nagging at me is that I really should be able to serialize the MailMessaage using a stream and writing the stream to a string -or- file of my choice instead of going around the houses to create GUID based folders as the code above does and reading the content of the file back into a string...
Any pointers as to how I could make this more elegant will be much appreciated.
Thanks.

This will be a hack. Don't forget, MS can change it in the next versions of .Net.
(With the help of ILSpy) you can write an extension method like below
public static class MailExtensions
{
public static string ToEml(this MailMessage mail)
{
var stream = new MemoryStream();
var mailWriterType = mail.GetType().Assembly.GetType("System.Net.Mail.MailWriter");
var mailWriter = Activator.CreateInstance(
type: mailWriterType,
bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic,
binder: null,
args: new object[] { stream },
culture: null,
activationAttributes: null);
mail.GetType().InvokeMember(
name: "Send",
invokeAttr: BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
binder: null,
target: mail,
args: new object[] { mailWriter, true, true });
return Encoding.UTF8.GetString(stream.ToArray());
}
}
and use as
string eml = mailMessage.ToEml();

I finally managed to deserialize the MailMessage after a lot of work:
To get it right, I created a dll which will be used in both the client application and the webservice. This needs to be the same for the deserialization to work :)
I picked the dll serialization classes here: https://bitbucket.org/burningice/compositec1contrib.email/src/0bba07df532c8134717cfb40757f4cb22f002b1d/Email/Serialization/?at=default
Many thanks for sharing!
Once compiled and added my new dll to the projects, the send and receive worked.
So I convert the MailMessage this way : string serializedMessage = SerializeAsBase64(mail);
and in the Webservice, I reconstruct it this way :
MailMessage Mm = DeserializeFromBase64(serializedMessage);
I hope this helps...

Related

BizTalk SMTP send mail with attachments (no orchestration, custom send pipeline component)

I hope someone can help me out with this since when I drop a JSON test file in my rcv-folder the only result is that in the BizTalk console (in 'Running instances'), the message only states 'Queued (Awaiting processing)'. I am not sure where my issue is in the code.
I am supposed to receive a JSON which will contain some info and possibly multiple attachments (in Base64 format) and then send the mail out (with attachments in correct format, i.e. PDF, txt, xls) to a certain email-address. One of the requirements is to not use an orchestration. But I am stuck and have no idea what I am doing anymore. What makes this question different from others is that I have no orchestration in my solution. Everything will be processed in a custom send pipeline component.
The sendpipeline (in encode stage) does contain my custom component and also the MIME/SMIME encoder. I am using the SMTP-adapter.
I have created the custom pipeline component with a wizard and my initial plan was in the form below:
Receive the file (in JSON-format. It will be transformed into XML for further processing). This will all be taken care of in my rcv-pipeline and this step already works for me.
Pick out all the necessary variables from XML needed to send in the e-mail. These variables are the ones I want to show in the E-mail text. Not sure how to explain it better.
Pick out all the attachments in base64, loop through them, convert to 'regular files' and then attach them to the mail (with correct filenames, extension etc.)
The XML looks like below:
ArchiveobjectsListErrands
- ArchiveobjectErrand
* UUID (Here are the variables I need to show in the E-mail. The plain text, so to say)
- ArchiveobjectListPaper
- Attachments
* Name
* Extension
* Size
* Base64String (Base64 string which will be needed to be fetched (in GetAttachments) and then processed in (ProcessAttachments))
The code I have is below:
public Microsoft.BizTalk.Message.Interop.IBaseMessage Execute(Microsoft.BizTalk.Component.Interop.IPipelineContext pContext, Microsoft.BizTalk.Message.Interop.IBaseMessage pInMsg)
{
// 1) Read file with XPathNavigator (https://learn.microsoft.com/en-us/dotnet/standard/data/xml/extract-xml-data-using-xpathnavigator)
XPathNavigator nav = ReadXmlFromMsgBox(pInMsg);
var outMsg = pContext.GetMessageFactory().CreateMessage();
outMsg.Context = PipelineUtil.CloneMessageContext(pInMsg.Context);
// 2) Pick out the necessary vars that the registrator requires
GetRegistratorProperties(nav, pContext, outMsg);
// 3) Read attachments
var attachments = GetAttachments(pInMsg, nav);
// 4) Processa attachments
ProcessAttachments(pContext, outMsg, attachments);
// 5) Send message along for further processing in the send pipeline
return outMsg;
}
private void GetRegistratorProperties(XPathNavigator _nav, IPipelineContext _pContext, IBaseMessage _msg)
{
var bodyPart = _pContext.GetMessageFactory().CreateMessagePart();
bodyPart.ContentType = "text/application";
bodyPart.PartProperties.Write("EmailBodyText", "http://schemas.microsoft.com/BizTalk/2003/smtp-properties", "EmailBodyText.");
bodyPart.PartProperties.Write("Subject", "http://schemas.microsoft.com/BizTalk/2003/smtp-properties", "Registratorsubject - Create errand");
_msg.AddPart("Body", bodyPart, true); // True for body but false for attachments
}
private void ProcessAttachments(IPipelineContext _pContext, IBaseMessage _msg, IList<Attachment> _attachments)
{
var msgPart = _pContext.GetMessageFactory().CreateMessagePart();
//outMsg.Context = PipelineUtil.CloneMessageContext(_msg.Context);
int i = 0;
foreach (var item in _attachments)
{
msgPart.PartProperties.Write("FileName", "http://schemas.microsoft.com/BizTalk/2003/mime-properties", item.filnamn+item.extension);
msgPart.PartProperties.Write("ContentDescription", "http://schemas.microsoft.com/BizTalk/2003/mime-properties", item.contentType);
msgPart.Data = new MemoryStream(BytesFromBase64String(item.base64));
//bodyPart.Charset = "utf-8";
msgPart.ContentType = item.contentType;
//_pInMsg.AddPart("Attachment part " + i.ToString(), bodyPart, false);
_msg.AddPart("Attachment part " + i.ToString(), msgPart, false);
i++;
}
}
private IList<Attachment> GetAttachments(IBaseMessage pInMsg, XPathNavigator _nav)
{
XPathNodeIterator iterator = _nav.Select("Path to attachments in xml");
IList<Attachment> myList = new List<Attachment>();
while (iterator.MoveNext())
{
XPathNavigator node = iterator.Current;
Attachment atttachments = new Attachment();
atttachments.filenamne = node.SelectSingleNode("Name").Value;
atttachments.extension = node.SelectSingleNode("Extension").Value;
atttachments.contentType = node.SelectSingleNode("Mimetype").Value;
atttachments.base64 = node.SelectSingleNode("Base64String").Value;
myList.Add(atttachments);
}
return myList;
}
private XPathNavigator ReadXmlFromMsgBox(IBaseMessage pInMsg)
{
// Using XPathNavigator to avoid creating a XMLDoc in memory
Stream originalMessage = pInMsg.BodyPart.GetOriginalDataStream();
XPathNavigator _navigator = new XPathDocument(originalMessage).CreateNavigator();
return _navigator;
}
[Serializable]
private class FileStreamFactory : IStreamFactory
{
byte[] _data;
public FileStreamFactory(byte[] data)
{
_data = data;
}
public Stream CreateStream()
{
return new MemoryStream(_data);
}
}
private static byte[] BytesFromBase64String(string msg)
{
return Convert.FromBase64String(msg);
}
#endregion
}
I can show some example file of the XML if deemed necessary. I avoided it due to brevity and also since it is quite large.
I would greatly appreciate if anyone could help out with how the code is supposed to look to achieve what is needed, a mail with some text and attachments named correctly regarding filename and extension.
Body text and Attachments are just different segments in a MIME encoded email, usually a plain text one is the first one.
If it has a status of 'Queued (Awaiting processing)', then it sounds like either
the host instance that is expecting to process it is not in a running state. Fix: Start the host instance.
the send port is not in a Started sate. Fix: set the send port to Started
or the send port has a service window set on it. Fix: disable the service window.

Saving email to the directory with specified file name is not working in .NET 5.0

I am trying to follow the approach suggested in a post by Allan Eagle in code-project. This same approach was working fine up until .NET Core 3.1, but not with .NET 5.0 . Here is the save method I created,
private void Save(MailMessage message, string filePath)
{
var assembly = typeof(SmtpClient).Assembly;
var mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
var mailWriterContructor = mailWriterType.GetConstructors(bindingFlags)[0];
var mailWriter = mailWriterContructor.Invoke(new object[] { fileStream });//<-- This line throws error saying parameter mismatch
var sendMethod = typeof(MailMessage).GetMethod("Send", bindingFlags);
sendMethod.Invoke(message, bindingFlags, null, new[] { mailWriter, true, true }, null);
var closeMethod = mailWriter.GetType().GetMethod("Close", bindingFlags);
closeMethod.Invoke(mailWriter, bindingFlags, null, new object[] { }, null);
}
}
I checked all the available underlying invoke methods and tried working with them passing needed parameters but non of them worked for me.
Error Message: "Parameter count mismatch."
Inner Exception: null
Stack Trace:
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.ConstructorInfo.Invoke(Object[] parameters)
at..//user written line info
Any help to solve this issue or new approach to achieve same thing using System.Net.Mail will be highly appreciated.
To explicitly give you an answer based on first comment,
var encodeForTransport = false;
var mailWriter = mailWriterContructor.Invoke(new object[] { fileStream, encodeForTransport });
As I mentioned in the comments, this article is obsolete in 2020. Despite the date, it was actually written in 2009 and stopped working in 2014, when some of the internal methods changed signature. In any case, the System.Net.Mail namespace shouldn't be used because, as Microsoft strongly warns:
Important
We don't recommend that you use the SmtpClient class for new development because SmtpClient doesn't support many modern protocols. Use MailKit or other libraries instead. For more information, see SmtpClient shouldn't be used on GitHub.
MailKit (or rather the MimeKit library it's built upon) already supports saving and loading Mail messages. From Q: How do I save messages? the answer is a simple:
message.WriteTo("message.eml");
You can use MailKit's POP3 or IMAP4 clients to retrieve messages. The DownloadMessages example show how to download messages from GMail and save them:
using (var client = new Pop3Client (new ProtocolLogger ("pop3.log"))) {
client.Connect ("pop.gmail.com", 995, SecureSocketOptions.SslOnConnect);
client.Authenticate ("username", "password");
for (int i = 0; i < client.Count; i++) {
var message = client.GetMessage (i);
// write the message to a file
message.WriteTo (string.Format ("{0}.msg", i));
// mark the message for deletion
client.DeleteMessage (i);
}
client.Disconnect (true);
}

Need help to write web api wrapper for Silverlight project

Please bear with me as I am new to Silverlight. I need to write a web api wrapper (I've named it WebClientWrapper) which is supposed to consume rest services. The project uses Silverlight 5. I am facing many problems while writing such a wrapper. There are a lot of examples demonstrating rest service consumption in C#. But unfortunately none of them worked for me. It's been a challenge for me to get done with what I need. Below are my requirements:
1) UI should not freeze while I make any GET request,
2) Calling methods from WebClientWrapper should be as easier as possible.
3) .NET framework version of project should not be changed.
I tried following things so far:
1) Use of HttpClient. I referred this link: http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client. The problem with this approach is I cannot call ReadAsAsync method. Calling this method requires change in framework (i.e. replacing dlls or changing framework version) which is not feasible.
2) Use of WebClient. http://www.kastory.net/index.php?option=com_content&view=article&id=25:rest-web-service-in-c-with-silverlight-and-asp-net-web-api&catid=32:web-services&Itemid=130.
The problem with this is I will probably (not sure!) have to make changes in the way I call methods from WebClientWrapper. I am trying to accomplish calling method from WebClientWrapper something like this:
var appointments = WebClientWrapper.Get<Appointments>(new Dictionary<string, string>()
{
{"hospitalId", "19465654546"}
}, "appointment");
Below is the code snippet I tried in WebClientWrapper.
private const string BaseUrl = "http://localhost:63455/api";
private static WebClient GetClient()
{
int leadingInt = new Random().Next(10000, 99999);
int trailingInt = new Random().Next(1000, 9999);
string date = DateTime.Now.ToString("ddHHmmMMssMMyyyyyss");
string ticketString = string.Format("{0}{1}{2}", leadingInt, date, trailingInt);
var client = new WebClient();
client.Headers["Accept"] = ticketString;
client.Headers["UserAgent"] = "ReceptionistApp";
return client;
}
private static void DownloadCompletionHandler<T>(object sender, DownloadStringCompletedEventArgs e)
{
Encoding messageEncoding = Encoding.UTF8;
var serializer = new DataContractJsonSerializer(typeof (T));
var memoryStream = new MemoryStream(messageEncoding.GetBytes(e.Result));
var objectToReturn = (T) serializer.ReadObject(memoryStream);
}
public static T Get<T>(Dictionary<string, string> paramDictionary, string controller)
{
string absoluteUrl = BaseUrl + controller + "?";
absoluteUrl = paramDictionary.Aggregate(absoluteUrl,
(current, keyValuePair) => current + (keyValuePair.Key + "=" + keyValuePair.Value + "&"));
absoluteUrl = absoluteUrl.TrimEnd('&');
WebClient client = GetClient();
client.DownloadStringCompleted += DownloadCompletionHandler<T>;
client.DownloadStringAsync(new Uri(absoluteUrl));
}
Below are the things I want to mention regarding the code above:
1) As obvious, compiler throws error for the method Get<T> since I've not returned object of type T. How shall I get that object from DownloadStringAsync? I know I can use DownloadStringTaskAsync. But it is not available with the current framework. What changes do I have to make in my code so that I get appointments as shown in the Get<Appointments> method call?
2) DownloadCompletionHandler<T> is bound to return void but actually I want to return objectToReturn as shown in code.
Help will be greatly appreciated. Any new code snippets that will fulfill my requirements are welcome.
RestSharp helped me instead of WebClient.

help in using HttpWebResponse c#

I need your help in finding better way in downloading a url using HttpWebResponse
I used next code
HttpWebResponse myboot = HttpWebRequest.Create("http://www.wwenews.us/m1.php?id=441229").GetResponse() as HttpWebResponse;
StreamReader myboot_content = new StreamReader(myboot.GetResponseStream(), Encoding.GetEncoding("windows-1256"));
string temp_data = myboot_content.ReadToEnd();
but a problem says
The server committed a protocol violation. Section=ResponseHeader Detail=CR must be followed by LF
appears to me when trying parsing http://www.wwenews.us/m1.php?id=441229
please help me to download string of this site
note: test your solution code before present it as I had tested several solutions and no one solve the problem
Add a reference to System.Configuration to your project and add the following method.
public static bool SetUnsafeHeaderParsing()
{
Assembly oAssembly = Assembly.GetAssembly(typeof(System.Net.Configuration.SettingsSection));
if (oAssembly != null)
{
Type oAssemblyType = oAssembly.GetType("System.Net.Configuration.SettingsSectionInternal");
if (oAssemblyType != null)
{
object oInstance = oAssemblyType.InvokeMember("Section",
BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.NonPublic, null, null, new object[] { });
if (oInstance != null)
{
FieldInfo objFeild = oAssemblyType.GetField("useUnsafeHeaderParsing", BindingFlags.NonPublic | BindingFlags.Instance);
if (objFeild != null)
{
objFeild.SetValue(oInstance, true);
return true;
}
}
}
}
return false;
}
Call the method SetUnsafeHeaderParsing() before you use the HttpWebRequest.Create method call.
Its actually a problem with the server. The server is not following the HTTP specifications. However your .NET client by default would adhere to the specs and flags it as a potential problem.
Skype installed on the same machine as the web server can cause such problems, as it's using the same port (80) by default.
To change that port look here:
http://forum.skype.com/index.php?showtopic=31024

How to send an XDocument via MSMQ (using WCF)?

I have an order as an XDocument and I simply want to stick it in the body of a message and send it to an MSMQ queue. I've effectively serialized the order object already and now I just want to send it. Is this possible?
I'm using WCF here but I'd happy with a plain old msmq solution. I'm getting an error here indicating that an XDocument can't be serialized ... obviously can't do that, but how do I get my XDocument into the message body? Do I need to roll my own Serializer?
public void SendOrder(XDocument order)
{
var address = new EndpointAddress(#"msmq.formatname:DIRECT=OS:myServer\private$\myQueue");
var binding = new MsmqIntegrationBinding();
binding.Security.Mode = MsmqIntegrationSecurityMode.None;
binding.ExactlyOnce = false;
binding.Durable = false;
var channelFactory = new ChannelFactory<IOrderSubmitter>(binding, address);
var channel = channelFactory.CreateChannel();
var message = new MsmqMessage<XDocument>(order);
message.Label = "Really Big Order with lots of profit";
message.BodyType = (int)System.Runtime.InteropServices.VarEnum.VT_ARRAY;
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
channel.SubmitOrder(message);
scope.Complete();
}
}
[ServiceContractAttribute(Namespace = "http://my.namespace.com", Name = "Hello")]
public interface IOrderSubmitter
{
[OperationContract(IsOneWay = true)]
void SubmitOrder(MsmqMessage<XDocument> message);
}
An XDocument is a convenient wrapper over XML data. There is no need to serialize the XDocument, just send the XML data as a string, using XDocument.ToString()
I'm having the same problem developing on a Windows 7 box. It's putting my XML string inside another xml. Everything works just fine in server 2003.
I was finally able to fix this. There seem to be two ways to do this. Both involve setting the Formatter to XmlMessageFormatter. You can either set the Formatter on the MessageQueue or you can set it on the message before you send and after you peek/receive.
messageQueue.Formatter = new System.Messaging.XmlMessageFormatter(new Type[] { typeof(System.String) });

Categories