Generowanie treści wiadomości e-mail w formacie HTML w C #

114

Czy istnieje lepszy sposób generowania wiadomości e-mail w formacie HTML w C # (do wysyłania za pośrednictwem System.Net.Mail) niż użycie Stringbuildera do wykonania następujących czynności:

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>");

i tak dalej i tak dalej?

Obrabować
źródło
Cóż, to naprawdę zależy od rozwiązania, jakie widzę. Zrobiłem wszystko, od przechwytywania danych wejściowych użytkownika i automatycznego formatowania z różnych wzorów. Najlepszym rozwiązaniem, jakie zrobiłem z wiadomościami html, było formatowanie xml + xslt, ponieważ znaliśmy z góry dane wejściowe poczty.
cyberzed
To zależy od tego, jak złożone są Twoje wymagania. Miałem kiedyś aplikację, która renderowała tabelę w wiadomości e-mail w formacie HTML i użyłem ASP.NET Gridview do renderowania ciągów łączących HTML w celu wygenerowania tabeli.
RichardOD

Odpowiedzi:

181

Możesz użyć klasy MailDefinition .

Oto jak tego używasz:

MailDefinition md = new MailDefinition();
md.From = "[email protected]";
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("[email protected]", replacements, body, new System.Web.UI.Control());

Napisałem również post na blogu o tym, jak generować treść wiadomości e-mail w formacie HTML w C # przy użyciu szablonów wykorzystujących klasę MailDefinition .

MartinHN
źródło
11
Nawet nie wiedziałem, że to istnieje, po prostu genialny to jest ... +1 i zaakceptowano
Rob
5
+1. Ładne, choć ograniczone, prawdopodobnie obejmuje wiele zastosowań. Niezbyt przydatne, jeśli chcesz programowo dołączyć sekcje HTML i / lub zapętlić zestaw elementów, które wymagają renderowania.
AnthonyWJones
Dopiero niedawno zdałem sobie z tego sprawę. To jest super. Wydaje mi się, że mówi ci, jak ważne jest zapoznanie się z dokumentacją MSDN przed samodzielnym napisaniem zajęć dotyczących jakiegokolwiek problemu. Napisałem własną klasę, która działała prawie tak samo jak MailDefinition. Szkoda dla mnie. Strata czasu.
MartinHN
4
Używanie klasy MailDefinition było dla mnie niewygodne ze względu na ograniczone opcje określania pól od, do, cc i bcc. Opiera się również na przestrzeni nazw dla kontrolek interfejsu WWW - to nie ma dla mnie sensu. Zobacz moją odpowiedź poniżej ...
Seth,
+1, ponieważ nie wiedziałem, że ta klasa istnieje, chociaż podstawowa implementacja polega po prostu na iteracji listy zamienników i uruchomieniu Regex.Replace(body, pattern, replacement, RegexOptions.IgnoreCase);dla każdej pary klucz / wartość ... w świetle tych szczegółów ta klasa nie zapewnia dużej wartości, jak użyto powyżej chyba że pracujesz z istniejącym odniesieniem do System.Web.UI.Controls.
Chris Baxter,
27

Użyj klasy System.Web.UI.HtmlTextWriter.

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();

W przypadku obszernego kodu HTML, który obejmuje tworzenie atrybutów stylu, prawdopodobnie najlepszym rozwiązaniem jest HtmlTextWriter. Jednak może to być trochę niezgrabne w użyciu, a niektórzy programiści lubią, aby sam znacznik był łatwy do odczytania, ale perwersyjne wybory HtmlTextWriter dotyczące wcięć są nieco dziwne.

W tym przykładzie możesz również dość skutecznie używać XmlTextWriter: -

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();
AnthonyWJones
źródło
2
Wydaje się to bardzo przestarzałe
Daniel
1
To była dla mnie najlepsza odpowiedź. Prosty i na temat (choć wprawdzie dość niezgrabny). MailDefinition był fajny, ale nie to, czego szukałem.
thehelix
Cześć, jak można dodać wbudowane style, na przykład do h1tagu?
Izzy,
kiedy wyskakujące okienko e-mail zostało otwarte, używam znacznika DIV w moim kontekście, który był renderowany jako <25>. czy możesz pomóc w ostatnim kroku, jak zrobić poprawne renderowanie
wyjaśnienie
17

Zaktualizowana odpowiedź :

Dokumentacja SmtpClientklasy użytej w tej odpowiedzi brzmi teraz „Przestarzałe („ SmtpClient i jego sieć typów są źle zaprojektowane, zdecydowanie zalecamy użycie https://github.com/jstedfast/MailKit i https: // github .com / jstedfast / MimeKit zamiast ") '.

Źródło: https://www.infoq.com/news/2017/04/MailKit-MimeKit-Official

Oryginalna odpowiedź :

Używanie klasy MailDefinition jest niewłaściwym podejściem. Tak, jest poręczny, ale jest również prymitywny i zależy od elementów sterujących interfejsu użytkownika sieci Web - to nie ma sensu w przypadku czegoś, co jest zwykle zadaniem po stronie serwera.

Przedstawione poniżej podejście jest oparte na dokumentacji MSDN i poście Qureshi na CodeProject.com .

UWAGA: Ten przykład wyodrębnia plik HTML, obrazy i załączniki z osadzonych zasobów, ale użycie innych alternatyw w celu uzyskania strumieni dla tych elementów jest w porządku, np. Zakodowane ciągi, pliki lokalne i tak dalej.

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();
}
Seth
źródło
9
Nie publikuj odpowiedzi, która jest po prostu linkiem do Twojego posta na blogu. Jeśli / kiedy Twój blog zniknie, Twoja odpowiedź tutaj stanie się prawie bezużyteczna. Rozważ uwzględnienie w swojej odpowiedzi „kluczowych” części posta.
Rob
1
Tutaj przeniesiono treść artykułu na blogu. Dzięki, @Rob.
Seth
1
FWIW ten kod został przetestowany i jest używany w aplikacji produkcyjnej.
Seth
1
Jest to trochę zagmatwane, ponieważ w niektórych innych bibliotekach HTML jest wysyłany jako załącznik. Proponuję usunąć część załącznika PDF z tej próbki, aby była jaśniejsza. Co więcej, msg.Body nigdy nie jest ustawiony w tym przykładowym kodzie, zakładam, że należy mu przypisać zmienną body?
Gudlaugur Egilsson
1
Brakuje kluczowego kroku, którego ustawienie msg.IsBodyHtml = true. Zobacz tę odpowiedź tutaj: stackoverflow.com/questions/7873155/…
Gudlaugur Egilsson
6

Używam dotLiquid właśnie do tego zadania.

Pobiera szablon i wypełnia specjalne identyfikatory treścią anonimowego obiektu.

//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));

Działa również z kolekcjami, zobacz kilka przykładów online .

Marcel
źródło
czy templateSource może być plikiem .html? czy jeszcze lepiej plik brzytwy .cshtml?
ozzy432836
1
@Ozzy W rzeczywistości może to być dowolny plik (tekstowy). DotLiquid umożliwia nawet zmianę składni szablonu w przypadku, gdy koliduje z plikiem szablonu.
Marcel
5

Poleciłbym używanie jakiegoś rodzaju szablonów. Istnieje wiele różnych sposobów podejścia do tego problemu, ale zasadniczo należy przechowywać szablon wiadomości e-mail w jakimś miejscu (na dysku, w bazie danych itp.) I po prostu wstawić kluczowe dane (tj. Nazwa odbiorcy itp.) Do szablonu.

Jest to o wiele bardziej elastyczne, ponieważ oznacza, że ​​możesz zmienić szablon zgodnie z wymaganiami bez konieczności zmiany kodu. Z mojego doświadczenia wynika, że ​​prawdopodobnie otrzymasz prośby o zmiany w szablonach od użytkowników końcowych. Jeśli chcesz iść na całość, możesz dołączyć edytor szablonów.

Skórzany
źródło
Zgoda, wszystkie odpowiedzi robią prawie to samo, używając różnych metod. String.Format to wszystko, czego potrzebujesz i możesz użyć dowolnego z nich, aby utworzyć własny szablon.
user1040975
4

Jako alternatywę dla MailDefinition, spójrz na RazorEngine https://github.com/Antaris/RazorEngine .

To wygląda na lepsze rozwiązanie.

Przypisywane ...

jak wysłać wiadomość e-mail z szablonem wiadomości e-mail c #

Na przykład

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);
        }
    }
}

Które wyjścia ...

<h1>Heading Here</h1>
Dear Fred,
<br />
<p>First part of the email body goes here</p>

Jadę tutaj

Drogi Fredie,

Tutaj znajduje się pierwsza część treści wiadomości e-mail

Mick
źródło
da to najwięcej opcji, gdy istnieje potrzeba pętli i ifów
CMS
3

Emitowanie ręcznie utworzonego kodu HTML w ten sposób jest prawdopodobnie najlepszym sposobem, o ile znaczniki nie są zbyt skomplikowane. Program budujący ciągi zaczyna zwracać się w kategoriach wydajności dopiero po około trzech połączeniach, więc dla naprawdę prostych rzeczy wystarczy ciąg + ciąg.

Poza tym możesz zacząć używać kontrolek html (System.Web.UI.HtmlControls) i renderować je, w ten sposób czasami możesz je dziedziczyć i tworzyć własne klasy dla złożonego układu warunkowego.

Mark Dickinson
źródło
1

Możesz rzucić okiem na niektóre z dostępnych obecnie struktur szablonów. Niektóre z nich powstały w wyniku MVC, ale nie jest to wymagane. Spark jest dobry.

Simon Farrow
źródło
Tylko do Twojej wiadomości - odniesienie do adresu URL w Twojej odpowiedzi nie ma już znaczenia; prowadzi do witryny z wiadomościami.
Geovani Martinez
1

Jeśli nie chcesz zależności od pełnego .NET Framework, istnieje również biblioteka, która sprawia, że ​​Twój kod wygląda następująco:

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();

Jest to open source, możesz go pobrać z http://sourceforge.net/projects/htmlplusplus/

Zastrzeżenie: Jestem autorem tej biblioteki, została napisana, aby dokładnie rozwiązać ten sam problem - wysłać e-mail w formacie HTML z aplikacji.

Vladislav Zorov
źródło
1

Wersja komercyjna, której używam na produkcji i pozwala na łatwą konserwację, to LimiLabs Template Engine , używam go od ponad 3 lat i pozwala mi na dokonywanie zmian w szablonie tekstowym bez konieczności aktualizacji kodu (zastrzeżenia, linki itp.) - to może być tak proste, jak

Contact templateData = ...; 
string html = Template
     .FromFile("template.txt")
     .DataFrom(templateData )
     .Render();

Warto się temu przyjrzeć, tak jak ja; po wypróbowaniu różnych odpowiedzi wymienionych tutaj.

Geovani Martinez
źródło