I have a checkbox list for the products to selected, with another text entry for quantity. Upon clicking Submit, I need two actions to be performed:
1) I would like to get the customers selection and entry into an email (with a given email address) and emailed as a (create) PDF attachment with date-time stamp.
2) The next page after hitting submit button, should produce next/another page with email confirmation message and a print button of the PDF attachment with date-time stamp.
There are many other things that go into this webform, but this is the area that I am needing some guidance and any link or code example to help me find a proper solution you can reply me here would be much appreciated.
For PDF creation, there are several tools- I've used iTextSharp. You can download the nuget package on VS and example code can be found here. You will probably want to use tables to make it look nice, but to get used to it, maybe just throw some info in to get an idea of how it works.
The method may look something like this:
public byte[] CreateOrderPdf(OrderDetails details)
{
byte[] fileBytes;
using (MemoryStream PdfStream = new MemoryStream())
{
iTextSharp.text.Document doc = new iTextSharp.text.Document();
PdfWriter writer = PdfWriter.GetInstance(doc, PdfStream);
doc.Open();
doc.Add(new Paragraph(OrderDetails.Name));
doc.Add(new Paragraph(OrderDetails.Address));
//Add More Tables and Content Here (See Documentation for more)
doc.Close();
fileBytes = PdfStream.ToArray();
}
return fileBytes
}
For Email, .Net has the System.Net.Mail.MailMessage class. You can attach the pdf as a stream. You will need to define the to, from, subject, body, etc. That would look something like this:
string fileName = string.Format("OrderConfirmation-{0}.pdf", DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss"));
MailMessage message = new MailMessage(from, to);
message.Subject = subject;
message.Body = body;
MemoryStream stream = new MemoryStream(fileBytes);
stream.Position = 0;
Attachment attachment = new Attachment(stream, fileName, MediaTypeNames.Application.Octet);
message.Attachments.Add(attachment);
SmtpClient mailServer = new SmtpClient(example.stmphost.com);
mailserver.Send(message);
message.Dispose();
Hopefully this is a good starting point for you.
In my search of the solution and suggested starting point, I stumbled upon this and I will also MENTION this for anyone looking for the same answers to look into spire.pdf at www.e-iceblue.com/Introduce/pdf-for-net-introduce.html
Related
I have a RibbonXML which provide a context menu that takes an action on a received email. That email contains a spreadsheet attachment, and I want to update that spreadsheet (xlsx) and forward it to another recipient...
What is happening is that the recipient see two attachments, one being usually quite small (a few KB), and the other being the correct attachment. This does the same thing with pdf, or text files, so pretty sure it isn't the file type. It shows up in the attachments list in the email inspector, but if you try to do anything with it Outlook says the attachment cannot be found.
I built up the test from scratch and added in components of my solution until the error was found. It seems to be related to removing attachments from the new email item (resulting from the Forward() method).
public void OnTestAttachment(Office.IRibbonControl control)
{
if (control.Context is Selection)
{
Selection selection = control.Context as Selection;
if (selection.Count == 1)
{
object item = selection[1];
if (item is MailItem)
{
MailItem mailItem = item as MailItem;
var newItem = mailItem.Forward();
newItem.Recipients.Add("xxxxx#xxxxx.com");
var newAttachments = newItem.Attachments;
// remove the line below and I don't see the issue
for (int i = newAttachments.Count; i >= 1; i--) { newAttachments.Remove(i); }
{
var body = "Testing.....\r\n";
MSWord.Document document = (MSWord.Document)newItem.GetInspector.WordEditor;
MSWord.Paragraph paragraph = document.Paragraphs.Add(document.Range());
paragraph.Range.Text = body;
}
// do some things with a file..in prod I save the existing file, edit it via code and then save it back down....
// I tested using a byte stream in case that has something to do with the issue (as that's the closest
// match to what is actually going on in prod)
var fileName = #"C:\Users\<me>\Test.xlsx";
var temp = Path.Combine(Path.GetTempPath(), Path.GetFileName(fileName));
byte[] buffer = File.ReadAllBytes(fileName);
using (var stream = new FileStream(temp, FileMode.Create, FileAccess.Write))
{
stream.Write(buffer, 0, buffer.Length);
stream.Close();
}
newAttachments.Add(temp, Microsoft.Office.Interop.Outlook.OlAttachmentType.olByValue, Type.Missing, Type.Missing);
// the issues doesn't appear here...it is only once it is received that it appears
newItem.Display();
Marshal.ReleaseComObject(newItem);
Marshal.ReleaseComObject(newAttachments);
Marshal.ReleaseComObject(mailItem);
}
}
Marshal.ReleaseComObject(selection);
}
It shows up in the attachments list in the email inspector, but if you try to do anything with it Outlook says the attachment cannot be found.
The Outlook object model is not related to the issue described. The file can be overwritten with an empty data and then can be attached to the email:
using (var stream = new FileStream(temp, FileMode.Create, FileAccess.Write))
{
stream.Write(buffer, 0, buffer.Length);
stream.Close();
}
The following sentence confirms this:
What is happening is that the recipient see two attachments, one being usually quite small (a few KB), and the other being the correct attachment.
If you need to cope the file content I'd suggest saving the file using the SaveAsFile method of the Attachment class which saves the attachment to the specified path. Thus you can be sure the file content is not empty.
I was about to use iTextSharp to fill out PDF form, when I've found out, that there is new kid on the block: iText 7.0. Naturally I gave it a try and... it didn't go well, unfortunately. Here is an issue. I have editable PDF form, that's need to be filled out programmatically. I used following code to acomplished that:
string srcPdfFormPath = #"<Path to source pdf form>";
string destPdfFormPath = #"<Path to destination pdf form>";
using (PdfDocument pdfDocument = new PdfDocument(reader, new PdfWriter(destPdfFormPath)))
{
PdfAcroForm form = PdfAcroForm.GetAcroForm(pdfDocument, true);
var fieldName = "FullName";
var pdfField = form.GetField(fieldName);
if (pdfField != null)
pdfField.SetValue("John Doe", null);
else
Debug.WriteLine($"Cannot find following field {fieldName } on pdf form.");
pdfDocument.Close();
}
Though desired field has been populated, the form not only has been flattened(which, AFAIK is not expected behavior), but also when populated pdf would be opened in Acrobat reader it would produce following message:
"This document enabled extended features in Adobe Acrobat Reader DC.
The document has been changed since it was created and use of extended
features is no longer available. Please contact the author for the
original version of this document."
Those results are not what we have been shooting for. So in order to preserve the document editability and get rid of pesky message I've decided to open PDF form in "Append Mode". Here is a code I've used:
string srcPdfFormPath = #"<Path to source pdf form>";
string destPdfFormPath = #"<Path to destination pdf form>";
using (PdfDocument pdfDocument = new PdfDocument(reader, new PdfWriter(destPdfFormPath)
, new StampingProperties().UseAppendMode()))
{
PdfAcroForm form = PdfAcroForm.GetAcroForm(pdfDocument, true);
var fieldName = "FullName";
var pdfField = form.GetField(fieldName);
if (pdfField != null)
pdfField.SetValue("John Doe", null);
else
Debug.WriteLine($"Cannot find following field {fieldName } on pdf form.");
pdfDocument.Close();
}
And, alas, this attempt also failed on its mission. Though pesky message would no longer be seen, the form itself would not be populated, which is not acceptable. And for the life of me I could not figure out why those behaviors are persist and go together, like horse and carriage!
So question is: what am I missing here? Is there way to make it work using new version of iText?
I am trying to send an email with an attachment by accessing it directly after saving it in the database. To do so I am following this tutorial.
What works?
Storing the attachments in the database is correct as when I go to the details page I can see the image associated with the profile.
What doesn't?
Unfortunately there seems to be a problem with how retrieving files from database works as the attachments are damaged, e.g. image stored in database shows 153328 B, but when sent turns into 117B).
The solution that actually succeeds and sends an email (damaged email) is taken from this link, but when I try to send it using the commented out stream code, the code crashes on the indicated line:
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing); //this line
}
this is the controller code i use to save and retrieve the attachments:
public async Task<ActionResult> Create([Bind(Include = "ID,LastName,FirstMidName")] Person person, HttpPostedFileBase upload)
{
if (ModelState.IsValid)
{
if (upload != null && upload.ContentLength > 0)
{
var avatar = new File
{
FileName = System.IO.Path.GetFileName(upload.FileName),
FileType = FileType.Avatar,
ContentType = upload.ContentType
};
using (var reader = new System.IO.BinaryReader(upload.InputStream))
{
avatar.Content = reader.ReadBytes(upload.ContentLength);
}
person.Files = new List<File> { avatar };
}
db.People.Add(person);
db.SaveChanges();
//await SendEmail(person.ID);
var message = new MailMessage();
var file = db.Files.Find(person.ID);
Attachment attachment;
var stream = new MemoryStream();
try
{
stream.Write(file.Content, 0, file.Content.Length - 1);
attachment = new Attachment(stream, file.FileName);
}
catch
{
stream.Dispose();
throw;
}
//When i use this bit of code, I receive an error "Cannot access a closed stream
//using (var stream = new MemoryStream())
//{
// stream.Write(file.Content, 0, file.Content.Length - 1);
// attachment = new Attachment(stream, file.FileName);
//}
var fileSize = file.Content.Length;
message.Attachments.Add(attachment);
message.To.Add(new MailAddress("recipient#gmail.com")); // replace with valid value
message.From = new MailAddress("sender#outlook.com"); // replace with valid value
message.Subject = "Your email subject";
message.BodyEncoding = System.Text.Encoding.UTF8;
message.Body = "<p>file size: </p>" + "<p>" + fileSize + "</p>";
message.IsBodyHtml = true;
message.BodyEncoding = System.Text.Encoding.UTF8;
using (var smtp = new SmtpClient())
{
//when i try to send the mail asynchronously the view with the form just keeps showing "waiting for localhost"
//await smtp.SendMailAsync(message);
smtp.Send(message);
return RedirectToAction("Index");
}
}
return View(person);
}
Additional Question
Would it be a good idea to send the attachment inside of the save to database part?
EDIT
I have just tried sending the attachment with the below line of code:
message.Attachments.Add(new Attachment(upload.InputStream, Path.GetFileName(upload.FileName)));
added after:
person.Files = new List<File> { avatar };
But still receive damaged attachment..
EDIT 2:
I think this line
var file = db.Files.Find(person.ID)
should actually be (you were trying to get a file using a person id):
var file = db.Files.Find(avatar.ID)
but, in your case you don't need to retrieve it from the database. You already have the bytes there, so just wrap them in a MemoryStream, as you can't directly send the upload.InputStream without storing it in memory:
attachment = new Attachment(new MemoryStream(avatar.Content), file.FileName);
Looking at this quickly, I'd look at the obvious.
var file = db.Files.Find(person.ID);
Look at what this is returning. It may well be that after this object is being used, depending on what object it is, may have been disposed of already.
The reason being is you're attempting to read from the file.Content.Length which may be the very cause to the problem because it doesn't have a value or whatever.
Step through the logic, line by line. Break it down from the most simple, and build it up slowly until you get to the cause. Also, think about abstracting the logic from the controller and implementing a service that deals with this action. Check out repository pattern, unit of work and dependency injection as a side note.
Ultimately, your issue, I think it's just the fact that you're not checking all the "what if it wasn't the way you expected" type errors, which in all is why you should most probably also have some tests in place. :P
Deconstruct, start from basics and build your way up. Doing this, I'm sure you will find the problem. :)
I have a html page which is attached to the email as word document.
string body = String.Empty;
body = new StreamReader("execlude.html").ReadToEnd();
byte[] data = Encoding.ASCII.GetBytes(body);
MemoryStream ms = new new MemoryStream(data);
var message = new System.Net.Mail.MailMessage(email.From, email.To);
message.Attachments.Add(new Attachment(ms, "excluded.doc", "application/msword"));
Here the attachment becomes word format, but the margin are too big. Let me know a solution to remove margins.
Thanks in advance.
Brother try these links....
Changing the margins of a Word Document
Setting word document table cell margin programmatically using c#
https://social.msdn.microsoft.com/Forums/sharepoint/en-US/30377e45-6473-4385-a83d-664ad5cc7dea/how-to-set-margin-of-word-document-using-c?forum=worddev
I got the following code
protected override void Render(HtmlTextWriter writer)
{
// Write the HTML into this string builder
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
HtmlTextWriter hWriter = new HtmlTextWriter(sw);
base.Render(hWriter);
string pageHTML = sb.ToString();
// Write it back to the server
writer.Write(pageHTML);
if (Convert.ToBoolean(this.ViewState["SendEmail"]))
{
string HTML = "";
HTML = "<!DOCTYPE HTML PUBLIC '-//IETF//DTD HTML//EN'>";
HTML += "<html>";
HTML += "<head>";
HTML += "<meta http-equiv='Content-Type'";
HTML += "content='text/html; charset=iso-8859-1'>";
HTML += "<title>Order Information</title>";
HTML += "</head>";
HTML += "<body>";
HTML += "See attachment for information.";
HTML += "</body>";
HTML += "</html>";
MailMessage mail = new MailMessage("from#xxx.com", "to#xxx.com", "Subject", HTML);
mail.IsBodyHtml = true;
string path = #"d:\websites\plate.html";
using (StreamWriter sw11 = File.CreateText(path))
{
sw11.WriteLine(pageHTML);
}
mail.Attachments.Add(new Attachment(path));
SmtpClient client = new SmtpClient("192.168.1.127");
client.Send( mail );
Response.Write("<script>alert('Your information has been sent.')</script>");
this.ViewState["SendEmail"] = false;
}
}
After a fresh clean/build of my solution, when I press the send button, this function is called and the html page is sent in attachment by mail without a problem. But if I try to press again the send button, I'm getting "System.IO.IOException: The process cannot access the file 'd:\websites\plate.html' because it is being used by another process." The error occur when I'm trying to open the file. What's wrong?
SmtpClient implements IDisposable but you are not disposing the instance.
http://msdn.microsoft.com/en-us/library/system.net.mail.smtpclient.aspx
It may be holding on to the file for that reason.
Generally speaking, it is wise to wrap anything that implements IDisposable in a using statement unless you have a specific reason not to (e.g. your are explicitly managing the object lifetime via the class that holds an IDisposable instance).
I also want to draw attention to #DanPichelman's comment that you are using a constant file name, but this code may execute on separate threads in parallel. That would cause the output file to be locked for any user past the first user, until the code completes for the first user.
As Eric has pointed out, you should have the SmtpClient in a using statement - ditto MailMessage.
However, you'd still end up writing to the file system for no obvious reason. I'd strongly advise you to use one of the Attachment constructors which doesn't require a file to start with. You can write to a MemoryStream, rewind it, and then provide that to the Attachment for example.
Aside from anything else, that would mean you wouldn't have a problem if multiple threads (or processes) tried running this code at the same time.
I think you should close it:
using (StreamWriter sw11 = File.CreateText(path))
{
sw11.WriteLine(pageHTML);
sw11.Flush();
sw11.Close();
}