Programming would be much easier without users...
What I really need to be able to do is:
Put the content of a web page (including styles) into the body of an email and also set the subject.
OR
Send the current user an email containing the body of a web page.
I really don't care how this is implemented -- server or client side. I've not come up with any good way of client side besides trying to push the web page into the clipboard for the users to then paste into their email.
App Background
I wrote a web site using c#, ts, angular. The site manages xml documents.
The users can select a document and click the "Human Readable" button or the "XML" button. The "Human Readable" is xml with xsl to make it look pretty for the humans. The XML button is apparently for non-humans.
The "Human Readable" version opens in another browser tab.
The users want a new "email" button for emailing the human readable. The person clicking the email button has access to my web site but the recipient may not.
I've attempted educating my users to do Ctrl+A, Ctrl+C, open email, Ctrl+V but this is beyond most of their capabilities.
I have tried so many different ways to accomplish this and all have failed.
I currently do a mailto link which opens their email and the body contains a link to the Human Readable.
Here's what I've tried so far -- this may not be a conclusive list of my attempts as I've been at this for a few days now.
I've tried putting a button in the human readable (xsl with javascript) in an attempt to copy the resulting html into the clipboard for the users to paste.
A button on the web site to scrape an iFrame into the clipboard
Many iterations of javascript copy/paste techniques
a c# controller that does a ReadAsStringAsync().Result function (which I will post below because I like that solution the best so far...
Option #4 I'm partial to and I got almost working -- if it weren't for that pesky xsl not getting formatted it would probably work. My results are the data being presented without xml tags and no styles.
[ActionName("PostEmailHumanReadable")]
public void EmailHumanReadable(List<DocumentVM> documents)
{
foreach (var document in documents)
{
var docId = document.document.DocId;
var docTypeId = document.document.DocTypeId;
var co = string.Empty;
var order = string.Empty;
var name = string.Empty;
var po = string.Empty;
// get the data for the subject line
using (var efUoW = new EFUnitOfWork(EDIEnvironment.EDIEnvironment.Instance.ConnectionString))
{
var doc = efUoW.DocumentRepository.GetById(docId);
co = doc.CompanyId.ToString();
var orders = efUoW.DocumentOrderRepository.GetByDocumentID(docId).ToList();
if (!string.IsNullOrEmpty(doc.Source_Order))
order = doc.Source_Order;
else
order = "n/a";
foreach (var o in orders)
order += string.Format("{0} ", o.OrderId);
//order = string.Join(",", doc.DocumentOrders.Select(q => q.OrderId).ToList());
name = doc.BillToName;
name = doc.PurchaseOrder;
}
var subject = string.Format("{0}, {1}, {2}, {3}", co, order, name, po);
// get the human readable
var hrResponse = GetFile(docId, docTypeId);
var hrText = hrResponse.Content.ReadAsStringAsync().Result;
// format the url
var url = string.Format("<a href='/api/Documents/Getfile?DocId={0}&DocTypeId={1}'>click here to open the jEDI Human Readable</a><br><br>", docId, docTypeId);
// find the current user's email address
var users = new List<string>();
users.Add(AppUser.ADUserName);
//var to = EmailUtility.GetEmailID(users);
// and finally send the email
EmailUtility.SendEmail(EmailUtility.GetEmailID(users), null, subject, url + hrText);
}
}
[ActionName("GetFile")]
public HttpResponseMessage GetFile(int DocId, int DocTypeId)
{
if (DocTypeId == (int)DocumentTypeEnum.EDI850)
{
using (var efUoW = new Factory_UOW().EF_UOW())
{
var doc = efUoW.DocumentRepository.GetById(DocId);
var xdoc = XDocument.Parse(doc.Message);
var proc = new XProcessingInstruction("xml-stylesheet", "type='text/xsl' href='/EDI850.xsl'");
xdoc.Root.AddBeforeSelf(proc);
var response = new HttpResponseMessage();
response.Content = new System.Net.Http.StringContent(xdoc.ToString());
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
response.Content.Headers.Add("X-UA-Compatible", "IE=edge");
return response;
}
I would be very grateful for any assistance in getting this to work.
And, yes, I know I shouldn't do the Async().Result -- blocking and all that... Let's just get this working first, shall we?
Related
I am having the hardest time figuring out how to create a pdf and attach it to an automated email. I thought it wouldn't be that hard but have quickly found out that it is much more complex than I imagined. Anyways, what I am doing is I have an order form, and when the customer fills out an order and submits it, I want it to generate a PDF for that order and attach it to an automated confirmation email. I am currently using Rotativa to generate the pdf and if I just ran the action result for generating it, it works perfectly fine which looks something like this:
public ActionResult ProcessOrder(int? id)
{
string orderNum = "FulfillmentOrder" + orderDetail.OrderID + ".pdf";
var pdf = new ActionAsPdf("OrderReportPDF", new { id = orderDetail.OrderID }) { FileName = orderNum };
return pdf;
}
When I added the function to send an automated email after the order form was submitted, that works perfectly fine with just the email by itself. But I can't seem to figure out how to generate the pdf and attach it to the email. The report view that gets the data from the order form is called "OrderReportPDF". My order form is called "Checkout", but the action result I use for this is called "Process Order". I've taken out the code in this function that is for the order form as it is not applicable. The section of my code for sending the email after the form is submitted is:
public ActionResult ProcessOrder(int? id)
{
//Send a confirmation email
var msgTitle = "Order Confirmation #" + orderDetail.OrderID;
var recipient = JohnDoe;
var fileAttach = //This is where I can't figure out how to attach//
var fileList = new string[] { fileAttach };
var errorMessage = "";
var OrderConfirmation = "Your order has been placed and is pending review." +
" Attached is a copy of your order.";
try
{
// Initialize WebMail helper
WebMail.SmtpServer = "abc.net";
WebMail.SmtpPort = 555;
WebMail.UserName = recipient;
WebMail.Password = "12345";
WebMail.From = "orders#samplecode.com";
// Send email
WebMail.Send(to: "orders#samplecode.com",
subject: msgTitle,
body: OrderConfirmation,
filesToAttach: fileList
);
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
//5. Remove item cart session
Session.Remove(strCart);
return View("OrderSuccess");
}
I have been able to find very few articles on this issue using Rotativa and the ones I have found don't work for what I'm doing. Maybe Rotativa won't work for this, I'm hoping it does because I've designed my pdf report it generates from it, and not sure if doing it another way will screw up my formatting of it or not. As you can see at the bottom of the code, I return the view to an "OrderSuccess" page. When I try to implement the first code into the second code, it won't let me "return pdf" to execute the ActionAsPdf that generates the pdf when I do the "return View("OrderSuccess")". It only lets me do one or the other. If anybody knows how I can accomplish this, I need some help. I appreciate any suggestions or feedback. I'm pretty new to this so please be patient with me if I don't understand something.
Here is the updated code that fixed my problem and created the pdf, then attached it to an automated email once the order form was submitted:
public ActionResult ProcessOrder(int? id)
{
//1. Generate pdf file of order placed
string orderNum = "FulfillmentOrder" + orderDetail.OrderID + ".pdf";
var actionResult = new Rotativa.ActionAsPdf("OrderReportPDF", new { id = orderDetail.OrderID }) { FileName = orderNum };
var PdfAsBytes = actionResult.BuildFile(this.ControllerContext);
//2. Send confirmation email
var msgTitle = "Order Confirmation #" + orderDetail.OrderID;
var OrderConfirmation = "Your order has been placed and is pending review.<br />" +
" Attached is a copy of your order." +
using (MailMessage mail = new MailMessage())
{
mail.From = new MailAddress("orders#samplecode.com");
mail.To.Add("orders#samplecode.com");
mail.Subject = msgTitle;
mail.Body = OrderConfirmation;
mail.IsBodyHtml = true;
//STREAM THE CONVERTED BYTES AS ATTACHMENT HERE
mail.Attachments.Add(new Attachment(new MemoryStream(PdfAsBytes), orderNum));
using (SmtpClient smtp = new SmtpClient("abc.net", 555))
{
smtp.Credentials = new NetworkCredential("orders#samplecode.com", "password!");
smtp.EnableSsl = true;
smtp.Send(mail);
}
}
//3. Remove item cart session
Session.Remove(strCart);
return View("OrderSuccess");
}
Thank you again to the people that helped and got me pointed in the right direction! Also, a quick shout out to #Drewskis who posted this answer in convert Rotativa.ViewAsPdf to System.Mail.Attachment where I was able to use it to solve my issue!
That's how I've added a regular link to the bottom of email if the customer would like to unsubscribe from newsletter, but Sendgrid has turned it into an incredibly long email.
Then my question is: How can I change it to my own link or link that is smaller? - I'd just like to make it shorter or less so customers do not think it's spam
It's like I can not upload image so I can show it here.
Code:
var viewModel = new EmailModel
{
getUrl = link, //link to remove user from newsletterlist here
Title = title, //title for email
FullName = item.Navn, //name on the user
text = text.ToHtmlString() //text value
};
var resultMail = await _viewRenderService.RenderToStringAsync("~/Views/Templates/newMail.cshtml", viewModel);//return Null here
MailMessageControl mail = new MailMessageControl();
mail.SetCredentials(m.azureName(), m.password());
mail.SetSender(m.mailFrom());
mail.AddAddressSee(item.Brugernavn);
mail.SetSubject(title);
mail.SetBody(Regex.Replace(resultMail, "<[^>]*>", ""));
mail.SendEmail();
await Task.Delay(2200);
Is there a better way to generate HTML email in C# (for sending via System.Net.Mail), than using a Stringbuilder to do the following:
string userName = "John Doe";
StringBuilder mailBody = new StringBuilder();
mailBody.AppendFormat("<h1>Heading Here</h1>");
mailBody.AppendFormat("Dear {0}," userName);
mailBody.AppendFormat("<br />");
mailBody.AppendFormat("<p>First part of the email body goes here</p>");
and so on, and so forth?
You can use the MailDefinition class.
This is how you use it:
MailDefinition md = new MailDefinition();
md.From = "test#domain.example";
md.IsBodyHtml = true;
md.Subject = "Test of MailDefinition";
ListDictionary replacements = new ListDictionary();
replacements.Add("{name}", "Martin");
replacements.Add("{country}", "Denmark");
string body = "<div>Hello {name} You're from {country}.</div>";
MailMessage msg = md.CreateMailMessage("you#anywhere.example", replacements, body, new System.Web.UI.Control());
Also, I've written a blog post on how to generate HTML e-mail body in C# using templates using the MailDefinition class.
Use the System.Web.UI.HtmlTextWriter class.
StringWriter writer = new StringWriter();
HtmlTextWriter html = new HtmlTextWriter(writer);
html.RenderBeginTag(HtmlTextWriterTag.H1);
html.WriteEncodedText("Heading Here");
html.RenderEndTag();
html.WriteEncodedText(String.Format("Dear {0}", userName));
html.WriteBreak();
html.RenderBeginTag(HtmlTextWriterTag.P);
html.WriteEncodedText("First part of the email body goes here");
html.RenderEndTag();
html.Flush();
string htmlString = writer.ToString();
For extensive HTML that includes the creation of style attributes HtmlTextWriter is probably the best way to go. However it can be a bit clunky to use and some developers like the markup itself to be easily read but perversly HtmlTextWriter's choices with regard indentation is a bit wierd.
In this example you can also use XmlTextWriter quite effectively:-
writer = new StringWriter();
XmlTextWriter xml = new XmlTextWriter(writer);
xml.Formatting = Formatting.Indented;
xml.WriteElementString("h1", "Heading Here");
xml.WriteString(String.Format("Dear {0}", userName));
xml.WriteStartElement("br");
xml.WriteEndElement();
xml.WriteElementString("p", "First part of the email body goes here");
xml.Flush();
Updated Answer:
The documentation for SmtpClient, the class used in this answer, now reads, 'Obsolete("SmtpClient and its network of types are poorly designed, we strongly recommend you use https://github.com/jstedfast/MailKit and https://github.com/jstedfast/MimeKit instead")'.
Source: https://www.infoq.com/news/2017/04/MailKit-MimeKit-Official
Original Answer:
Using the MailDefinition class is the wrong approach. Yes, it's handy, but it's also primitive and depends on web UI controls--that doesn't make sense for something that is typically a server-side task.
The approach presented below is based on MSDN documentation and Qureshi's post on CodeProject.com.
NOTE: This example extracts the HTML file, images, and attachments from embedded resources, but using other alternatives to get streams for these elements are fine, e.g. hard-coded strings, local files, and so on.
Stream htmlStream = null;
Stream imageStream = null;
Stream fileStream = null;
try
{
// Create the message.
var from = new MailAddress(FROM_EMAIL, FROM_NAME);
var to = new MailAddress(TO_EMAIL, TO_NAME);
var msg = new MailMessage(from, to);
msg.Subject = SUBJECT;
msg.SubjectEncoding = Encoding.UTF8;
// Get the HTML from an embedded resource.
var assembly = Assembly.GetExecutingAssembly();
htmlStream = assembly.GetManifestResourceStream(HTML_RESOURCE_PATH);
// Perform replacements on the HTML file (if you're using it as a template).
var reader = new StreamReader(htmlStream);
var body = reader
.ReadToEnd()
.Replace("%TEMPLATE_TOKEN1%", TOKEN1_VALUE)
.Replace("%TEMPLATE_TOKEN2%", TOKEN2_VALUE); // and so on...
// Create an alternate view and add it to the email.
var altView = AlternateView.CreateAlternateViewFromString(body, null, MediaTypeNames.Text.Html);
msg.AlternateViews.Add(altView);
// Get the image from an embedded resource. The <img> tag in the HTML is:
// <img src="pid:IMAGE.PNG">
imageStream = assembly.GetManifestResourceStream(IMAGE_RESOURCE_PATH);
var linkedImage = new LinkedResource(imageStream, "image/png");
linkedImage.ContentId = "IMAGE.PNG";
altView.LinkedResources.Add(linkedImage);
// Get the attachment from an embedded resource.
fileStream = assembly.GetManifestResourceStream(FILE_RESOURCE_PATH);
var file = new Attachment(fileStream, MediaTypeNames.Application.Pdf);
file.Name = "FILE.PDF";
msg.Attachments.Add(file);
// Send the email
var client = new SmtpClient(...);
client.Credentials = new NetworkCredential(...);
client.Send(msg);
}
finally
{
if (fileStream != null) fileStream.Dispose();
if (imageStream != null) imageStream.Dispose();
if (htmlStream != null) htmlStream.Dispose();
}
I use dotLiquid for exactly this task.
It takes a template, and fills special identifiers with the content of an anonymous object.
//define template
String templateSource = "<h1>{{Heading}}</h1>Dear {{UserName}},<br/><p>First part of the email body goes here");
Template bodyTemplate = Template.Parse(templateSource); // Parses and compiles the template source
//Create DTO for the renderer
var bodyDto = new {
Heading = "Heading Here",
UserName = userName
};
String bodyText = bodyTemplate.Render(Hash.FromAnonymousObject(bodyDto));
It also works with collections, see some online examples.
I would recomend using templates of some sort. There are various different ways to approach this but essentially hold a template of the Email some where (on disk, in a database etc) and simply insert the key data (IE: Recipients name etc) into the template.
This is far more flexible because it means you can alter the template as required without having to alter your code. In my experience your likely to get requests for changes to the templates from end users. If you want to go the whole hog you could include a template editor.
As an alternative to MailDefinition, have a look at RazorEngine https://github.com/Antaris/RazorEngine.
This looks like a better solution.
Attributted to...
how to send email wth email template c#
E.g
using RazorEngine;
using RazorEngine.Templating;
using System;
namespace RazorEngineTest
{
class Program
{
static void Main(string[] args)
{
string template =
#"<h1>Heading Here</h1>
Dear #Model.UserName,
<br />
<p>First part of the email body goes here</p>";
const string templateKey = "tpl";
// Better to compile once
Engine.Razor.AddTemplate(templateKey, template);
Engine.Razor.Compile(templateKey);
// Run is quicker than compile and run
string output = Engine.Razor.Run(
templateKey,
model: new
{
UserName = "Fred"
});
Console.WriteLine(output);
}
}
}
Which outputs...
<h1>Heading Here</h1>
Dear Fred,
<br />
<p>First part of the email body goes here</p>
Heading Here Dear Fred, First part of the email
body goes here
Emitting handbuilt html like this is probably the best way so long as the markup isn't too complicated. The stringbuilder only starts to pay you back in terms of efficiency after about three concatenations, so for really simple stuff string + string will do.
Other than that you can start to use the html controls (System.Web.UI.HtmlControls) and render them, that way you can sometimes inherit them and make your own clasess for complex conditional layout.
If you don't want a dependency on the full .NET Framework, there's also a library that makes your code look like:
string userName = "John Doe";
var mailBody = new HTML {
new H(1) {
"Heading Here"
},
new P {
string.Format("Dear {0},", userName),
new Br()
},
new P {
"First part of the email body goes here"
}
};
string htmlString = mailBody.Render();
It's open source, you can download it from http://sourceforge.net/projects/htmlplusplus/
Disclaimer: I'm the author of this library, it was written to solve the same issue exactly - send an HTML email from an application.
You might want to have a look at some of the template frameworks that are available at the moment. Some of them are spin offs as a result of MVC but that isn't required. Spark is a good one.
A commercial version which I use in production and allows for easy maintenance is LimiLabs Template Engine, been using it for 3+ years and allows me to make changes to the text template without having to update code (disclaimers, links etc..) - it could be as simple as
Contact templateData = ...;
string html = Template
.FromFile("template.txt")
.DataFrom(templateData )
.Render();
Worth taking a look at, like I did; after attempting various answers mentioned here.
We are working on implementing some custom code on a workflow in a Sitecore 6.2 site. Our workflow currently looks something like the following:
Our goal is simple: email the submitter whether their content revision was approved or rejected in the "Awaiting Approval" step along with the comments that the reviewer made. To accomplish this we are adding an action under the "Approve" and "Reject" steps like so:
We are having two big issues in trying to write this code
There doesn't seem to be any easy way to determine which Command was chosen (the workaround would be to pass an argument in the action step but I'd much rather detect which was chosen)
I can't seem to get the comments within this workflow state (I can get them is the next state though)
For further context, here is the code that I have so far:
var contentItem = args.DataItem;
var contentDatabase = contentItem.Database;
var contentWorkflow = contentDatabase.WorkflowProvider.GetWorkflow(contentItem);
var contentHistory = contentWorkflow.GetHistory(contentItem);
//Get the workflow history so that we can email the last person in that chain.
if (contentHistory.Length > 0)
{
//contentWorkflow.GetCommands
var status = contentWorkflow.GetState(contentHistory[contentHistory.Length - 1].NewState);
//submitting user (string)
string lastUser = contentHistory[contentHistory.Length - 1].User;
//approve/reject comments
var message = contentHistory[contentHistory.Length - 1].Text;
//sitecore user (so we can get email address)
var submittingUser = sc.Security.Accounts.User.FromName(lastUser, false);
}
I ended up with the following code. I still see no good way to differentiate between commands but have instead implemented two separate classes (one for approve, one for reject):
public void Process(WorkflowPipelineArgs args)
{
//all variables get initialized
string contentPath = args.DataItem.Paths.ContentPath;
var contentItem = args.DataItem;
var contentWorkflow = contentItem.Database.WorkflowProvider.GetWorkflow(contentItem);
var contentHistory = contentWorkflow.GetHistory(contentItem);
var status = "Approved";
var subject = "Item approved in workflow: ";
var message = "The above item was approved in workflow.";
var comments = args.Comments;
//Get the workflow history so that we can email the last person in that chain.
if (contentHistory.Length > 0)
{
//submitting user (string)
string lastUser = contentHistory[contentHistory.Length - 1].User;
var submittingUser = Sitecore.Security.Accounts.User.FromName(lastUser, false);
//send email however you like (we use postmark, for example)
//submittingUser.Profile.Email
}
}
I have answered a very similar question.
Basically you need to get the Mail Workflow Action and then you need to further extend it to use the original's submitter's email.
Easiest way to get the command item itself is ProcessorItem.InnerItem.Parent
This will give you the GUID for commands like submit, reject etc.
args.CommandItem.ID
This will give you the GUID for states like Draft, approved etc.
args.CommandItem.ParentID
Is there a better way to generate HTML email in C# (for sending via System.Net.Mail), than using a Stringbuilder to do the following:
string userName = "John Doe";
StringBuilder mailBody = new StringBuilder();
mailBody.AppendFormat("<h1>Heading Here</h1>");
mailBody.AppendFormat("Dear {0}," userName);
mailBody.AppendFormat("<br />");
mailBody.AppendFormat("<p>First part of the email body goes here</p>");
and so on, and so forth?
You can use the MailDefinition class.
This is how you use it:
MailDefinition md = new MailDefinition();
md.From = "test#domain.example";
md.IsBodyHtml = true;
md.Subject = "Test of MailDefinition";
ListDictionary replacements = new ListDictionary();
replacements.Add("{name}", "Martin");
replacements.Add("{country}", "Denmark");
string body = "<div>Hello {name} You're from {country}.</div>";
MailMessage msg = md.CreateMailMessage("you#anywhere.example", replacements, body, new System.Web.UI.Control());
Also, I've written a blog post on how to generate HTML e-mail body in C# using templates using the MailDefinition class.
Use the System.Web.UI.HtmlTextWriter class.
StringWriter writer = new StringWriter();
HtmlTextWriter html = new HtmlTextWriter(writer);
html.RenderBeginTag(HtmlTextWriterTag.H1);
html.WriteEncodedText("Heading Here");
html.RenderEndTag();
html.WriteEncodedText(String.Format("Dear {0}", userName));
html.WriteBreak();
html.RenderBeginTag(HtmlTextWriterTag.P);
html.WriteEncodedText("First part of the email body goes here");
html.RenderEndTag();
html.Flush();
string htmlString = writer.ToString();
For extensive HTML that includes the creation of style attributes HtmlTextWriter is probably the best way to go. However it can be a bit clunky to use and some developers like the markup itself to be easily read but perversly HtmlTextWriter's choices with regard indentation is a bit wierd.
In this example you can also use XmlTextWriter quite effectively:-
writer = new StringWriter();
XmlTextWriter xml = new XmlTextWriter(writer);
xml.Formatting = Formatting.Indented;
xml.WriteElementString("h1", "Heading Here");
xml.WriteString(String.Format("Dear {0}", userName));
xml.WriteStartElement("br");
xml.WriteEndElement();
xml.WriteElementString("p", "First part of the email body goes here");
xml.Flush();
Updated Answer:
The documentation for SmtpClient, the class used in this answer, now reads, 'Obsolete("SmtpClient and its network of types are poorly designed, we strongly recommend you use https://github.com/jstedfast/MailKit and https://github.com/jstedfast/MimeKit instead")'.
Source: https://www.infoq.com/news/2017/04/MailKit-MimeKit-Official
Original Answer:
Using the MailDefinition class is the wrong approach. Yes, it's handy, but it's also primitive and depends on web UI controls--that doesn't make sense for something that is typically a server-side task.
The approach presented below is based on MSDN documentation and Qureshi's post on CodeProject.com.
NOTE: This example extracts the HTML file, images, and attachments from embedded resources, but using other alternatives to get streams for these elements are fine, e.g. hard-coded strings, local files, and so on.
Stream htmlStream = null;
Stream imageStream = null;
Stream fileStream = null;
try
{
// Create the message.
var from = new MailAddress(FROM_EMAIL, FROM_NAME);
var to = new MailAddress(TO_EMAIL, TO_NAME);
var msg = new MailMessage(from, to);
msg.Subject = SUBJECT;
msg.SubjectEncoding = Encoding.UTF8;
// Get the HTML from an embedded resource.
var assembly = Assembly.GetExecutingAssembly();
htmlStream = assembly.GetManifestResourceStream(HTML_RESOURCE_PATH);
// Perform replacements on the HTML file (if you're using it as a template).
var reader = new StreamReader(htmlStream);
var body = reader
.ReadToEnd()
.Replace("%TEMPLATE_TOKEN1%", TOKEN1_VALUE)
.Replace("%TEMPLATE_TOKEN2%", TOKEN2_VALUE); // and so on...
// Create an alternate view and add it to the email.
var altView = AlternateView.CreateAlternateViewFromString(body, null, MediaTypeNames.Text.Html);
msg.AlternateViews.Add(altView);
// Get the image from an embedded resource. The <img> tag in the HTML is:
// <img src="pid:IMAGE.PNG">
imageStream = assembly.GetManifestResourceStream(IMAGE_RESOURCE_PATH);
var linkedImage = new LinkedResource(imageStream, "image/png");
linkedImage.ContentId = "IMAGE.PNG";
altView.LinkedResources.Add(linkedImage);
// Get the attachment from an embedded resource.
fileStream = assembly.GetManifestResourceStream(FILE_RESOURCE_PATH);
var file = new Attachment(fileStream, MediaTypeNames.Application.Pdf);
file.Name = "FILE.PDF";
msg.Attachments.Add(file);
// Send the email
var client = new SmtpClient(...);
client.Credentials = new NetworkCredential(...);
client.Send(msg);
}
finally
{
if (fileStream != null) fileStream.Dispose();
if (imageStream != null) imageStream.Dispose();
if (htmlStream != null) htmlStream.Dispose();
}
I use dotLiquid for exactly this task.
It takes a template, and fills special identifiers with the content of an anonymous object.
//define template
String templateSource = "<h1>{{Heading}}</h1>Dear {{UserName}},<br/><p>First part of the email body goes here");
Template bodyTemplate = Template.Parse(templateSource); // Parses and compiles the template source
//Create DTO for the renderer
var bodyDto = new {
Heading = "Heading Here",
UserName = userName
};
String bodyText = bodyTemplate.Render(Hash.FromAnonymousObject(bodyDto));
It also works with collections, see some online examples.
I would recomend using templates of some sort. There are various different ways to approach this but essentially hold a template of the Email some where (on disk, in a database etc) and simply insert the key data (IE: Recipients name etc) into the template.
This is far more flexible because it means you can alter the template as required without having to alter your code. In my experience your likely to get requests for changes to the templates from end users. If you want to go the whole hog you could include a template editor.
As an alternative to MailDefinition, have a look at RazorEngine https://github.com/Antaris/RazorEngine.
This looks like a better solution.
Attributted to...
how to send email wth email template c#
E.g
using RazorEngine;
using RazorEngine.Templating;
using System;
namespace RazorEngineTest
{
class Program
{
static void Main(string[] args)
{
string template =
#"<h1>Heading Here</h1>
Dear #Model.UserName,
<br />
<p>First part of the email body goes here</p>";
const string templateKey = "tpl";
// Better to compile once
Engine.Razor.AddTemplate(templateKey, template);
Engine.Razor.Compile(templateKey);
// Run is quicker than compile and run
string output = Engine.Razor.Run(
templateKey,
model: new
{
UserName = "Fred"
});
Console.WriteLine(output);
}
}
}
Which outputs...
<h1>Heading Here</h1>
Dear Fred,
<br />
<p>First part of the email body goes here</p>
Heading Here Dear Fred, First part of the email
body goes here
Emitting handbuilt html like this is probably the best way so long as the markup isn't too complicated. The stringbuilder only starts to pay you back in terms of efficiency after about three concatenations, so for really simple stuff string + string will do.
Other than that you can start to use the html controls (System.Web.UI.HtmlControls) and render them, that way you can sometimes inherit them and make your own clasess for complex conditional layout.
If you don't want a dependency on the full .NET Framework, there's also a library that makes your code look like:
string userName = "John Doe";
var mailBody = new HTML {
new H(1) {
"Heading Here"
},
new P {
string.Format("Dear {0},", userName),
new Br()
},
new P {
"First part of the email body goes here"
}
};
string htmlString = mailBody.Render();
It's open source, you can download it from http://sourceforge.net/projects/htmlplusplus/
Disclaimer: I'm the author of this library, it was written to solve the same issue exactly - send an HTML email from an application.
You might want to have a look at some of the template frameworks that are available at the moment. Some of them are spin offs as a result of MVC but that isn't required. Spark is a good one.
A commercial version which I use in production and allows for easy maintenance is LimiLabs Template Engine, been using it for 3+ years and allows me to make changes to the text template without having to update code (disclaimers, links etc..) - it could be as simple as
Contact templateData = ...;
string html = Template
.FromFile("template.txt")
.DataFrom(templateData )
.Render();
Worth taking a look at, like I did; after attempting various answers mentioned here.