Mailkit - Get HTML and Text parts without having full mime message - c#

I want to download text and html parts from mime message and store it in the database and later if needed to download attachments. I need this because I don't want to store the attachments in my database to save disk space and bandwidth. They will be downloaded on demand later. I am not sure if I can do that and still be able to use MimeParser from MimeKit
I am planning to do that:
Get message body structure and find the text and html parts
Download text and html body parts using ImapFolder.GetStream and store in database preserving mime tree structure by saving section names along with headers. Attachment will not be downloaded
Later I want to show the message in the UI but I want to delay parsing until the mail message needs to be shown in the UI.
This is my progress so far
var msgSummaries = remoteFolder.Fetch(new int[] { remoteMessage.Index }, MessageSummaryItems.BodyStructure);
var stream = remoteFolder.GetStream(remoteMessage.Index, msgSummaries[0].HtmlBody.PartSpecifier);
//at this point i am saving the stream to the database and later i am trying to convert it to mime entity like that
var parser = new MimeParser(ParserOptions.Default, stream, true);
var mimeEntity = parser.ParseEntity(cancellationToken);
Unfortunatelly the stream doesn't contain mime part headers and cannot be parsed and I don't see an option to request headers inside GetStream method like this
FETCH 1 (BODY.PEEK[2.MIME] BODY.PEEK[2])
Any suggestions?

Well, first of all, have you tried:
var mimeEntity = remoteFolder.GetBodyPart (remoteMessage.Index, msgSummaries[0].HtmlBody);
or, if you really want to use streams:
var headerStream = remoteFolder.GetStream (remoteMessage.Index, msgSummaries[0].HtmlBody.PartSpecifier + ".MIME");
var contentStream = remoteFolder.GetStream (remoteMessage.Index, msgSummaries[0].HtmlBody.PartSpecifier);
var stream = new MemoryStream ();
headerStream.CopyTo (stream);
headerStream.Dispose ();
contentStream.CopyTo (stream);
contentStream.Dispose ();
stream.Position = 0;
//at this point i am saving the stream to the database and later i am trying to convert it to mime entity like that
var parser = new MimeParser(ParserOptions.Default, stream, true);
var mimeEntity = parser.ParseEntity(cancellationToken);

Related

Merging Crystal Reports with itext7 in MVC produces unmerged pdf from stream

I'm trying to merge two (or more) Crystal Reports in an ASP.net MVC project and I downloaded the itext7 NuGet package to do so. I'm trying to put together a simple proof-of-concept in which I concatenate a pdf with itself in a single method:
var rpt1 = new CrystalDecisions.CrystalReports.Engine.ReportDocument();
var rpt2 = new CrystalDecisions.CrystalReports.Engine.ReportDocument();
rpt1.Load(Server.MapPath("~/Reports/MyReport.rpt");
rpt2.Load(Server.MapPath("~/Reports/MyReport.rpt");
DataTable table = GetDataMethod();
rpt1.SetDataSource(table);
rpt2.SetDataSource(table);
Stream stream = rpt.ExportToStream(ExportFormatType.PortableDocFormat);
var write = new PdfWriter(stream);
var doc = new PdfDocument(write);
var merger = new PdfMerger(doc);
var doc1 = new PdfDocument(new PdfReader(rpt1.ExportToStream(ExportFormatType.PortableDocFormat)));
var doc2 = new PdfDocument(new PdfReader(rpt2.ExportToStream(ExportFormatType.PortableDocFormat)));
merger.Merge(doc1, 1, doc1.GetNumberOfPages());
merger.Merge(doc2, 1, doc2.GetNumberOfPages());
doc.CopyPagesTo(1, doc2.GetNumberOfPages(), doc2);
stream.Flush();
stream.Position = 0;
return this.File(stream, "application/pdf", "DownloadName.pdf");
You can see I'm sort of throwing everything at the wall and seeing what sticks insofar as I'm using both PdfMerger.Merger() and PdfDocument.CopyPagesTo(), and I think either of those should be sufficient to do the job by itself? (And, of course, I ran the code trying each of those by themselves as well as together.) But when I run the above code the PDF which downloads is unmerged, i.e. the report only appears once. (If I run it with two different reports, then only the first report appears.)
Now, I'm returning the stream while I'm doing all the interesting stuff with the PdfMerger and PdfDocument objects, so it makes sense to me that the stream would be unchanged. But all the examples of using iText 7 I've found return either the stream or a byte array (e.g., this StackOverflow question), so that seems to be the way it is supposed to work.
Any changes I've made to the code either have no effect, throw an error, or result in the downloaded file being unreadable by the browser (i.e. not recognized as a PDF). For example, I tried converting the stream to a byte array and returning that:
using (var ms = new MemoryStream()) {
stream.CopyTo(ms);
byte[] bytes = ms.ToArray();
return new FileContentResult(bytes, "application/pdf");
}
but the browser couldn't open the download then. The same thing happened when I tried closing the PdfDocument before returning the stream (trying it to force it to write the merge to the stream).
There is a lot of confusion with streams in your code. Normally a stream is used either for input or for output. MemoryStream can be used for both, but you need to make sure to not close it to be able to reuse it. It's often simpler and cleaner to create a new instance with the underlying bytes than reusing existing ones, especially taking into account that it does not affect the performance much as the underlying heavy array structures will be reused by new instances anyway. Here is an example of how yo distinguish between the streams. ExportToStream returns you a stream from which you can obtain the byte array with the bytes of your PDF files, then you load those documents into iText and you also create the third document that you will merge the two source documents into. Then you have to make sure to call PdfDocument#Close() to tell iText to finalize your documents and then you can fetch the resultant bytes of the merged document and pass them along, wrapping them into a stream if necessary
var rpt1 = new CrystalDecisions.CrystalReports.Engine.ReportDocument();
var rpt2 = new CrystalDecisions.CrystalReports.Engine.ReportDocument();
rpt1.Load(Server.MapPath("~/Reports/MyReport.rpt");
rpt2.Load(Server.MapPath("~/Reports/MyReport.rpt");
DataTable table = GetDataMethod();
rpt1.SetDataSource(table);
rpt2.SetDataSource(table);
var report1Stream = (MemoryStream)rpt1.ExportToStream(ExportFormatType.PortableDocFormat);
var report2Stream = (MemoryStream)rpt2.ExportToStream(ExportFormatType.PortableDocFormat);
var doc1 = new PdfDocument(new PdfReader(new MemoryStream(report1Stream.ToArray())));
var doc2 = new PdfDocument(new PdfReader(new MemoryStream(report2Stream.ToArray())));
var outStream = new MemoryStream();
var write = new PdfWriter(outStream);
var doc = new PdfDocument(write);
var merger = new PdfMerger(doc);
merger.Merge(doc1, 1, doc1.GetNumberOfPages());
merger.Merge(doc2, 1, doc2.GetNumberOfPages());
doc.Close();
doc1.Close();
doc2.Close();
return this.File(new MemoryStream(outStream.ToArray()), "application/pdf", "DownloadName.pdf");

Getting empty pdf attachments when trying to create "activitymimeattachment"

I want to attach pdf from url into CRM "activitymimeattachment". Everything works fine but im getting empty pdf attachments (even the size is the same as original pdf). Could it be some problem with Encoding or converting? Maybe somebody could help me with this?
I see that email is created with attachment which is empty, but the size is the same as for original pdf.
Entity attachment = new Entity("activitymimeattachment");
attachment["subject"] = "Attachment";
string fileName = "Invoice.pdf";
attachment["filename"] = fileName;
WebClient wc = new WebClient();
string theTextFile = wc.DownloadString(url);
byte[] fileStream = Encoding.ASCII.GetBytes(theTextFile);
attachment["body"] = Convert.ToBase64String(fileStream);
attachment["mimetype"] = "application/pdf";
attachment["attachmentnumber"] = 1;
attachment["objectid"] = new EntityReference("email", emailguid);
attachment["objecttypecode"] = "email";
service.Create(attachment);
I believe the encoding is issue. The PDF is neither a string nor an ASCII file so these lines are at fault:
string theTextFile = wc.DownloadString(url);
byte[] fileStream = Encoding.ASCII.GetBytes(theTextFile);
I would suggest changing your web client to download a binary file i.e. application/pdf and save the file locally e.g to d:\temp\invoice.pdf.
Then you can do this:
var bytes = File.ReadAllBytes(#"d:\temp\invoice.pdf");
var body = Convert.ToBase64String(bytes);
In short, avoid trying to put the PDF into a string, or using the ASCII encoding to get its byte array. It's a binary file until you convert it to Base64.
Of course you can also probably get your web client to download the file into memory and convert it to Base64 without writing the file locally. I just wanted the simplest example to make the point about the encoding.
Thank You for advice #Aron. I actually have found a solution which is very simnply. I just used another method from WebClient class. The main thing I needed to change DownloadString(url) method into DownloadData(url) method:
WebClient wc = new WebClient();
byte[] theTextFile = wc.DownloadData(url);
attachment["body"] = Convert.ToBase64String(theTextFile);enter code here

Send Image in Json : 400 Bad request

From my android I am trying to send an image with a class of data, to an IIS webservice. (C#)
The problem is I get 400 Bad request.
The image is being encoded to Base64. And then placed into json with the rest of the class elements.
My guess is that Base64 is not valid inside a Json. So the server does not understand it.
If I set the string to be "", the post is accepted fine.
So the question is, how do I make my Base64 valid from within a Json array? ( I tried URL.Encode with no success).
Or How should you send an image from android to webservice?
Gson gson = new Gson();
String json = gson.toJson(record); // record has param { String base64Photo }
How big is the image? I'm pretty sure you are surpassing the IIS Json size limit (default is almost 4 MB).
Check this http://geekswithblogs.net/frankw/archive/2008/08/05/how-to-configure-maxjsonlength-in-asp.net-ajax-applications.aspx or this http://www.webtrenches.com/post.cfm/iis7-file-upload-size-limits
Good luck!
I'll be honest - I've never uploaded an image from Android to an IIS webservice but in every other context I've always just used a File. It's easy to create a file and upload it as a MultipartEntity. Plus you avoid having to use Base64 all together, which is good because it saves you the roughly 33% increased overhead that comes with using Base64.
private File createFileFromBm(Bitmap pic){
File f = new File(context.getCacheDir(), "image");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
pic.compress(CompressFormat.JPEG, 100, bos);
byte[] data = bos.toByteArray();
try{
FileOutputStream fos = new FileOutputStream(f);
fos.write(data);
fos.close();
} catch (IOException e){
Log.e(TAG, e.toString());
}
return f;
}
Here's how you create a MultipartEntity
MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE;
entity.addPart("photo", new FileBody(file, "image/jpeg"));
httpPost.setEntity(entity);
return httpClient.execute(httpPost, responseHandler);
I used an HttpPost here and a BasicResponseHandler to receive the JSON output from the server for processing but you can do whatever you like.

Modifying Word Document XML using Packages

I am attempting to modify a simple MS word templates XML. I realize there are SDK's available that could make this process easier but what I am tasked with maintaining uses packages and I was told to do the same.
I have a basic test document with two placeholders mapped to the following XML:
<root>
<element>
Fubar
</element>
<second>
This is the second placeholder
</second>
</root>
What I am doing is creating a stream using the word doc, removing the existing XML, getting some hard coded test XML and trying to write that to the stream.
Here is the code I am using:
string strRelRoot = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
byte[] buffer = File.ReadAllBytes("dev.docx");
//stream with the template
MemoryStream stream = new MemoryStream(buffer, true);
//create a package using the stream
Package package = Package.Open(stream, FileMode.Open, FileAccess.ReadWrite);
PackageRelationshipCollection pkgrcOfficeDocument = package.GetRelationshipsByType(strRelRoot);
foreach (PackageRelationship pkgr in pkgrcOfficeDocument)
{
if (pkgr.SourceUri.OriginalString == "/")
{
Uri uriData = new Uri("/customXML/item1.xml", UriKind.Relative);
//remove the existing part
if (package.PartExists(uriData))
{
// Delete template "/customXML/item1.xml" part
package.DeletePart(uriData);
}
//create a new part
PackagePart pkgprtData = package.CreatePart(uriData, "application/xml");
//test data
string xml = #"<root>
<element>
Changed
</element>
<second>
The second placeholder changed
</second>
</root>";
//stream created from the xml string
MemoryStream fromStream = new MemoryStream();
UnicodeEncoding uniEncoding = new UnicodeEncoding();
byte[] fromBuffer = uniEncoding.GetBytes(xml);
fromStream.Write(fromBuffer, 0, fromBuffer.Length);
fromStream.Seek(0L, SeekOrigin.Begin);
Stream toStream = pkgprtData.GetStream();
//copy the xml to the part stream
fromStream.CopyTo(toStream);
//copy part stream to the byte stream
toStream.CopyTo(stream);
}
}
This is currently not modifying the document although I feel like I am close to a solution. Any advice would be very much appreciated. Thanks!
Edit: To clairify, the result I am getting is the document is unchanged. I get no exceptions or the like, but the documents XML is not modified.
OK, so not quite the timely response I promised, but here goes!
There are several aspects to the problem. Sample code is from memory and documentation, not necessarily compiled and tested.
Read the template XML
Before you delete the package part containing the template XML, you need to open its stream and read the XML. How you get the XML if the part doesn't exist to begin with is up to you.
My example code uses classes from the LINQ to XML API, though you could use whichever set of XML APIs you prefer.
XElement templateXml = null;
using (Stream stream = package.GetPart(uriData))
templateXml = XElement.Load(stream);
// Now you can delete the part.
At this point you have an in-memory representation of the template XML in templateXml.
Substitute values into the placeholders
templateXml.SetElementValue("element", "Replacement value of first placeholder");
templateXml.SetElementValue("second", "Replacement value of second placeholder");
Check out the methods on XElement if you need to do anything more advanced than this, e.g. read the original content in order to determine the replacement value.
Save the document
This is your original code, modified and annotated.
// The very first thing to do is create the Package in a using statement.
// This makes sure it's saved and closed when you're done.
using (Package package = Package.Open(...))
{
// XML reading, substituting etc. goes here.
// Eventually...
//create a new part
PackagePart pkgprtData = package.CreatePart(uriData, "application/xml");
// Don't need the test data anymore.
// Assuming you need UnicodeEncoding, set it up like this.
var writerSettings = new XmlWriterSettings
{
Encoding = Encoding.Unicode,
};
// Shouldn't need a MemoryStream at all; write straight to the part stream.
// Note using statements to ensure streams are flushed and closed.
using (Stream toStream = pkgprtData.GetStream())
using (XmlWriter writer = XmlWriter.Create(toStream, writerSettings))
templateXml.Save(writer);
// No other copying should be necessary.
// In particular, your toStream.CopyTo(stream) appeared
// to be appending the part's data to the package's stream
// (the physical file), which is a bug.
} // This closes the using statement for the package, which saves the file.

How to attach an XElement to a SMTP message as an attachment in C#

I'm trying to attach an XElement to an SMTP message i'm sending.
My code looks like this:
XElement xmlMsg = new XElement("Test",new XElement("TestSon", "DummyValue"),new XElement("TestSon2","DummyValue"));
using (MemoryStream memoryStream = new MemoryStream())
{
byte[] contentAsBytes = Encoding.Default.GetBytes(xmlMsg.ToString());
memoryStream.Write(contentAsBytes, 0, contentAsBytes.Length);
// Set the position to the beginning of the stream.
memoryStream.Seek(0, SeekOrigin.Begin);
// Create attachment
ContentType contentType = new ContentType();
contentType.MediaType = MediaTypeNames.Text.Plain;
contentType.Name = "Conversation.xml";
Attachment attachment = new Attachment(memoryStream, contentType);
mail.Attachments.Add(attachment);
Server.Send(mail);
}
However, my email is received with the XML file clipped at the end, without the last 2 chars...
Am i missing something here?
Thanks
What encoding is Encoding.Default on your system?
If it is UTF-16 I expect the two bytes are a BOM which is (for some reason) not being included in the byte count.
Suggestions:
Put xmlMsg.ToString() into a local variable, and check it in the debugger.
Have a look at the bytes in memoryStream
Look at the content of the email message in the raw, possibly copying the unencoded attachment so you can manually decode as a binary file.
Ie. check each step with as little automatic re-interpretation (eg. by an XML viewer) as possible.

Categories

Resources