Are there any open source/free frameworks available that take some of the pain out of building HTML e-mails in C#?
I maintain a number of standalone ASP.NET web forms whose main function is to send an e-mail. Most of these are in plain text format right now, because doing a nice HTML presentation is just too tedious.
I'd also be interested in other approaches to tackling this same problem.
EDIT: To be clear, I'm interested in taking plain text form input (name, address, phone number) and dropping it into an HTML e-mail template. That way the receipient would see a nicely formatted message instead of the primitive text output we're currently giving them.
EDIT 2: As I'm thinking more about this and about the answers the question has generated so far, I'm getting a clearer picture of what I'm looking for. Ideally I'd like a new class that would allow me to go:
HtmlMessage body = new HtmlMessage();
body.Header(imageLink);
body.Title("Some Text That Will Display as a Header");
body.Rows.Add("First Name", FirstName.Text);
The HtmlMessage class builds out a table, drops the images in place and adds new rows for each field that I add. It doesn't seem like it would be that hard to write, so if there's nothing out there, maybe I'll go that route
Andrew Davey created Postal which lets you do templated emails using any of the ASP.NET MVC view engines. Here's a video where he talks about how to use it.
His examples:
public class HomeController : Controller {
public ActionResult Index() {
dynamic email = new Email("Example");
email.To = "webninja#example.com";
email.FunnyLink = DB.GetRandomLolcatLink();
email.Send();
return View();
}
}
And the template using Razor:
To: #ViewBag.To From: lolcats#website.com Subject: Important Message
Hello, You wanted important web links right? Check out this:
#ViewBag.FunnyLink
<3
The C# port of StringTemplate worked well for me. I highly recommend it. The template file can have a number of named tokens like this:
...
<b>
Your information to login is as follows:<br />
Username: $username$<br />
Password: $password$<br />
</b>
...
...and you can load this template and populate it like this:
notificationTemplate.SetAttribute("username", Username);
notificationTemplate.SetAttribute("password", Password);
At the end, you get the ToString() of the template and assign it to the MailMessage.Body property.
I recently implemented what you're describing using MarkDownSharp. It was pretty much painless.
It's the same framework (minus a few tweaks) that StackOverflow uses to take plain-text-formatted posts and make them look like nice HTML.
Another option would be to use something like TinyMCE to give your users a WYWIWYG HTML editor. This would give them more power over the look and feel of their emails, but it might just overcomplicate things.
Bear in mind that there are also some security issues with user-generated HTML. Regardless of which strategy you use, you need to make sure you sanitize the user's input so they can't include scary things like script tags in their input.
Edit
Sorry, I didn't realize you were looking for an email templating solution. The simplest solution I've come up with is to enable text "macros" in user-generated content emails. So, for example, the user could input:
Dear {RecipientFirstName},
Thank you for your interest in {ClientCompanyName}. The position you applied for has the following minimum requirements:
- B.S. or greater in Computer Science or related field
- ...
And then we'd do some simple parsing to break this down to:
Dear {0},
Thank you for your interest in {1}. The position you applied for has the following minimum requirements:
- B.S. or greater in Computer Science or related field
- ...
... and ...
0 = "RecipientFirstName"
1 = "ClientCompanyName"
...
We store these two components in our database, and whenever we're ready to create a new instance from this template, we evaluate the values of the given property names, and use a standard format string call to generate the actual content.
string.Format(s, macroCodes.Select(c => EvaluateMacroCode(c, obj)).ToArray());
Then I use MarkdownSharp, along with some HTML sanitizing methods, to produce a nicely-formatted HTML email message:
Dear John,
Thank you for your interest in Microsoft. The position you applied for has the following minimum requirements:
B.S. or greater in Computer Science or related field
...
I'd be curious to know if there's something better out there, but I haven't found anything yet.
Related
I want to give my users the possibility to create document templates (contracts, emails, etc.)
The best option I figured out would be to store these document templates in mongo (maybe I'm wrong...)
I've been searching for a couple of hours now but I can't figure out how to render these document template with their data context.
Example:
Template stored in Mongo: "Dear {{firstname}}"
data context: {firstname: "Tom"}
On Tom's website, He should read: "Dear Tom"
How can I do this?
EDIT
After some researches, I discovered a package called spacebars-compiler that brings the option to compile to the client:
meteor add spacebars-compiler
I then tried something like this:
Template.doctypesList.rendered = ->
content = "<div>" + this.data.content + "</div>"
template = Spacebars.compile content
rendered = UI.dynamic(template,{name:"nicolas"})
UI.insert(rendered, $(this).closest(".widget-body"))
but it doesn't work.
the template gets compiled but then, I don't know how to interpret it with its data context and to send it back to the web page.
EDIT 2
I'm getting closer thanks to Tom.
This is what I did:
Template.doctypesList.rendered = ->
content = this.data.content
console.log content
templateName = "template_#{this.data._id}"
Template.__define__(templateName, () -> content)
rendered = UI.renderWithData(eval("Template.#{templateName}"),{name:"nicolas"})
UI.insert(rendered, $("#content_" + this.data._id).get(0))
This works excepted the fact that the name is not injected into the template. UI.renderWithData renders the template but without the data context...
The thing your are missing is the call to (undocumented!) Template.__define__ which requires the template name (pick something unique and clever) as the first argument and the render function which you get from your space bars compiler. When it is done you can use {{> UI.dynamic}} as #Slava suggested.
There is also another way to do it, by using UI.Component API, but I guess it's pretty unstable at the moment, so maybe I will skip this, at least for now.
Use UI.dynamic: https://www.discovermeteor.com/blog/blaze-dynamic-template-includes/
It is fairly new and didn't make its way to docs for some reason.
There are few ways to achieve what you want, but I would do it like this:
You're probably already using underscore.js, if not Meteor has core package for it.
You could use underscore templates (http://underscorejs.org/#template) like this:
var templateString = 'Dear <%= firstname %>'
and later compile it using
_.template(templateString, {firstname: "Tom"})
to get Dear Tom.
Of course you can store templateString in MongoDB in the meantime.
You can set delimiters to whatever you want, <%= %> is just the default.
Compiled template is essentially htmljs notation Meteor uses (or so I suppose) and it uses Template.template_name.lookup to render correct data. Check in console if Template.template_name.lookup("data_helper")() returns the correct data.
I recently had to solve this exact (or similar) problem of compiling templates client side. You need to make sure the order of things is like this:
Compiled template is present on client
Template data is present (verify with Template.template_name.lookup("data_name")() )
Render the template on page now
To compile the template, as #apendua have suggested, use (this is how I use it and it works for me)
Template.__define__(name, eval(Spacebars.compile(
newHtml, {
isTemplate: true,
sourceName: 'Template "' + name + '"'
}
)));
After this you need to make sure the data you want to render in template is available before you actually render the template on page. This is what I use for rendering template on page:
UI.DomRange.insert(UI.render(Template.template_name).dom, document.body);
Although my use case for rendering templates client side is somewhat different (my task was to live update the changed template overriding meteor's hot code push), but this worked best among different methods of rendering the template.
You can check my very early stage package which does this here: https://github.com/channikhabra/meteor-live-update/blob/master/js/live-update.js
I am fairly new to real-world programming so my code might be ugly, but may be it'll give you some pointers to solve your problem. (If you find me doing something stupid in there, or see something which is better done some other way, please feel free to drop a comment. That's the only way I get feedback for improvement as I am new and essentially code alone sitting in my dark corner).
once again I have to ask you for a help.
I have a problem with creating public variable and using it in formula.
The details of this issue is:
1, right after starting a Lotus Notes Application i have to create public variable EmpNum which will be contain an employee number (the number will be imported from IBM Blue Pages)
2, this EmpNum variable will be used to filter the data in a view, so I have to use it in formula in View Selection.
I'll be very grateful if you help with this issue.
Maybe there is possible any simplest solution of this problem?
Thanks in advance,
Tomasz (td2003)
EDIT:
Torsten, Panu, thank you.
I've decided to try write an EmpNum variable in notes.ini using #SetEnvironment and read it by #GetEnvironment, and it works.
BUT (!) there is a very strange thing:
1) when I entered formula "SELECT((form="ITForm") & (Status="Completed") & (TX_EmployeeNumber=#Environment("EmpNum")))" the view shows me all document where "TX_EmployeeNumber" is EMPTY;
2) otherwise when I entered formula "SELECT((form="ITForm") & (Status="Completed") & (TX_EmployeeNumber!=#Environment("EmpNum")))" (not equal) the view shows me all document where "TX_EmployeeNumber" is NOT EMPTY and no matter if TX_EmployeeNumber have exactly the same value as EmpNum.
For example: if EmpNum="P11" the view with 2nd formula shows the documents where TX_EmployeeNumber fields contains "P11", "P22", "A32" and so on.
I'm sure that #Environment("EmpNum") retrieves correct data from notes.ini.
I'm totally confused and completely don't know what's going on.
Do you have any idea about this?
First of all: There are NO public variables in Lotus Notes as you request it. Neither in LotusScript nor in Formula. Every variable just lives in its context, never in the complete client.
There are two places to put such a variable:
in the notes.ini, where you can set / read it using Formula (#Environment, #SetEnvironment, #getEnvironent) or LotusScript (NotesSession.GetEnvironmentString, NotesSession.SetEnvironmentVar).
In a Profile document in a database, where you can get it using #GetProfileField (Formula) or NotesDatabase.GetprofileDocument() (LotusScript)
Both of these approches will NOT help you for your number 2.
There is only ONE View- Selection- Formula for ALL Users. Putting something "userspecific" in there will let it render correctly for ONE value (the one, that the server uses when building the view- index), but not for all the others.
To solve your problem you can use:
a) An embedded View
A view that is categorized by empNum
A Form that has this view embedded and a Formula for "Show single category" for this embedded view set
b) A SPOFU view
SPOFU is "Shared, private on first use" and means, that every user has his own copy of the view. These views have some caveats and are hard to maintain. You should NOT use them, if you are not totally aware of the implications (getting the ACL right is one very important thing for these views)...
EDIT (due to change in question): SPOFU will neither work with Environment nor with Profile- documents, as the methods to read them are not supported in Views... So b) is not really an option... Sorry...
It is possible to use the #SetViewInfo formula in the QueryOpen and/or PostOpen of a view to get the view to only present data from one category. Be careful with this because you will need to touch EVERY view so that the value is cleared if the user navigates to a view which is not categorized by the employee num...
FWIW: I only have done this using #UserName not with another piece of data. I suggest you do that by having the import process add the fully qualified Notes name of the user to the documents as they are imported.
You can modify view selection formula with NotesView class. In this case you would have to use private views.
A better solution would be to use an XPage to show the view and use category filter. Or if you want to use traditional style then embed the view to a form or page and use "Show Single Category" feature.
I built a site using the starter site template in Webmatrix that uses a log in. After a user logs in, it displays near the top "Hello, (email address)!"
Looking at the html code, I see
Hello, <a id="logname" class="email" href="~/Account/Manage" title="Manage">#WebSecurity.CurrentUserName</a>!
I'm just a beginner with asp.net but can work things out following logic, so what I have done is added a 'Name' field to the database where the email and password is stored and edited the registration page so users can enter their name as well, it writes to the database no problems.
Now I assumed it's just a matter of finding where #WebSecurity.CurrentUserName is referring to the user's email address and make it point to the Name field instead. However I've searched high and low and can not seem to find it anywhere.
So my next approach was to use code from a Bakery database tutorial that fetches data from a field and modify it to grab the name and display it where I want, the code I tried to use here:
#foreach(var row in db.Query(selectQueryString))
This appeared to work at first, It displayed "Hello, Ben!" but then if I made any more accounts it started to display them too, so after a while I was getting "Hello, Ben, Steve, Grace, etc..."
Even though I'm a beginner I do realize that this code isn't quite what I need, but something similar that takes that single Name field but only from the currently logged in user... I've googled and searched and seen many people wanting the same thing, except it's using PHP or VB or something that isn't Webmatrix.
If it's a quick answer I'd appreciate the code I need, or otherwise pointed in the right direction, any help would be greatly appreciated. Thanks in advance..
OK ironically I find the solution moments after posting the question, so here goes..
This is the code needed to be put at the very top of the file _SiteLayout.chtml
#{
var authenticatedUser = "";
if (WebSecurity.IsAuthenticated) {
authenticatedUser = WebSecurity.CurrentUserName;
var db = Database.Open("StarterSite");
var UserData = db.QuerySingle("SELECT Name FROM UserProfile WHERE LOWER(Email) = LOWER(#0)", authenticatedUser);
authenticatedUser = UserData.Name;
}
}
StarterSite is the name of the database, and UserProfile is the name of the Table, and I labeled the name field Name (explained for the purpose of beginners like me who might be reading this)
Then in the html markup I changed #WebSecurity.CurrentUserName to #authenticatedUser so now it reads:
Hello, <a id="logname" class="email" href="~/Account/Manage" title="Manage">#authenticatedUser</a>!
resulting in the desired outcome of it now displaying the user's name instead of their email address.
I am working with a historical PayPal system in vb.net. I am struggling to add individual item descriptions or names for the products that the user is paying for. It is using the NVPSetExpressCheckout and the data is meant to display on the PayPal website when the user is about to pay. Instead however I am getting constant issues which I assume must be due to syntax or just the way I am trying to do it.
Here is the current code which works:
Dim ppSet As New NvpSetExpressCheckout()
ppSet.Add(NvpSetExpressCheckout.Request._AMT, Decimal.Parse(litTotal.Text))
ppSet.Add(NvpSetExpressCheckout.Request.CURRENCYCODE, "GBP")
Dim basePath As String = Request.Url.AbsoluteUri.Replace(Request.Url.PathAndQuery, String.Empty) + Request.ApplicationPath
ppSet.Add(NvpSetExpressCheckout.Request._RETURNURL, basePath & "paypal.aspx")
ppSet.Add(NvpSetExpressCheckout.Request._CANCELURL, basePath & "cancel.aspx")
I have then tried to add the a description using many methods such as:
ppSet.Add(NvpSetExpressCheckout.Request.L_DESC0, "First Item")
I however am simply getting errors like these:
'L_DESC0' is not a member of 'Encore.PayPal.Nvp.NvpSetExpressCheckout.Request'.
This issue is driving me mad and I can not find a fix. All documentation including the XML says that this is the correct way. I did try to just use the DESC field which worked however all the items just got displayed as one paragraph instead of being on separate lines. Help extremely appreciated.
Found the solution. It was due to the L_AMTn request not being submitted which is basically the item amounts. Once this adds up to the total cost it submits perfectly.
...or, in other words, how to create a simple join as I would do in SQL?
Suppose I want the following information:
Just as an example:
a person's full name
a person's hobbies.
His full name is in a (content profile) node type 'name_and_address' and his hobbies are in 'hobbies'.
In SQL, I would link them together by node.uid.
I've seen a bit about using relationships, but that goes with user-node-refs.
I just want the same user from one content-type and the other.
Now how could I get his name and his hobbies in 1 view?
There is a how to here does this do the job?
If not...
Views can be extended with custom joins, filters etc. If you are lucky there will be a module for this already. Some modules even provide their own views plugins.
You can write your own views plugins, although the documentation is a little fragmented.
The other thing that should be noted is that views isn't always the answer. Sometimes writing a custom query and display handler will do what you want with much less hassle.
Look at the relationships section of the view. This allows you to relate (ie join) different types of content (ie tables). It's not especially intuitive to someone used to SQL, but this video explains much of it. http://www.drupalove.com/drupal-video/demonstration-how-use-views-2s-relationships
You could use views_embed_view() in your template files to manually specify where they appear (and by extension render one view right below another).
You could override this function in a custom module (modulename_embed_view($name, $display_id)) in order to selectively edit what data is allowed out to the page.
Ex):
function modulename_embed_view($name, $display_id) {
if (strcmp($_GET['q'], 'node/123') === 0) {
$view = views_get_view($name);
$view2 = views_get_view('second view');
$output = $view['some element'] . $view2['element'];
}
return $output;
}
I know that this is very much a hack - I just wanted to show how one might use php to manually render and modify views in your template files.