¿Puedo configurar plantillas HTML / de correo electrónico con ASP.NET?

97

Estoy trabajando en un sitio que enviará una cantidad significativa de correos electrónicos. Quiero configurar tanto el texto del encabezado como el pie de página, o tal vez incluso plantillas para permitir a los usuarios editar fácilmente estos correos electrónicos si lo necesitan.

Si incrusto el HTML dentro de los literales de cadena C #, es feo y tendrían que preocuparse por escapar. La inclusión de archivos planos para el encabezado y el pie de página podría funcionar, pero algo al respecto simplemente no se siente bien.

Lo ideal sería usar una .ASPXpágina como plantilla de alguna manera, luego decirle a mi código que sirva esa página y usar el HTML devuelto para el correo electrónico.

¿Existe una manera fácil y agradable de hacer esto? ¿Existe una mejor manera de resolver este problema?

Actualizado:
agregué una respuesta que le permite usar una página .aspx estándar como plantilla de correo electrónico. Simplemente reemplace todas las variables como lo haría normalmente, use enlace de datos, etc. Luego solo capture la salida de la página, ¡y listo! ¡Tienes tu correo electrónico HTML!

ACTUALIZADO CON AVISO !!!:
Estaba usando la clase MailDefinition en algunas páginas aspx bien, pero al intentar usar esta clase durante un proceso de servidor que se estaba ejecutando, falló. Creo que fue porque el método MailDefinition.CreateMailMessage () requiere un control válido para hacer referencia, aunque no siempre hace algo. Debido a esto, recomendaría mi enfoque usando una página aspx, o el enfoque de Mun usando una página ascx, que parece un poco mejor.

John Bubriski
fuente
Otra solución sería usar AlphaMail para crear y enviar sus correos electrónicos usando C # y el lenguaje de plantilla Comlang .
Timothy E. Johansson
1
@JohnBubriski: Yo trabajo en torno a un problema de control que usted ha mencionado en "Actualizado con emptor" utilizando new System.Web.UI.Control()como en: mailDefinition.CreateMailMessage("[email protected]", iDictionaryReplacements, new System.Web.UI.Control()).
Theophilus
Sí, yo también lo he hecho, pero dada la llegada de Razor, esto se está volviendo menos una buena idea.
John Bubriski

Respuestas:

73

Ya hay un montón de respuestas aquí, pero me encontré con un gran artículo sobre cómo usar Razor con plantillas de correo electrónico. Razor se introdujo con ASP.NET MVC 3, pero no se requiere que MVC use Razor. Este es un procesamiento bastante hábil de hacer plantillas de correo electrónico

Como identifica el artículo, "Lo mejor de Razor es que, a diferencia de su predecesor (formularios web), no está vinculado con el entorno web, podemos alojarlo fácilmente fuera de la web y usarlo como motor de plantillas para diversos fines".

Generación de correos electrónicos HTML con RazorEngine - Parte 01 - Introducción

Aprovechando las plantillas de Razor fuera de ASP.NET: ¡Ya no son solo para HTML!

Plantillas de correo electrónico más inteligentes en ASP.NET con RazorEngine

Control de calidad de Stackoverflow similar

Creación de plantillas con la nueva API RazorEngine

Usando Razor sin MVC

¿Es posible usar Razor View Engine fuera de asp.net?

Mike Barlow - BarDev
fuente
1
+1, pero tenga cuidado si los usuarios proporcionan las plantillas, ya que pueden ejecutar código C # desde la plantilla, lo que les da mucho más poder en su sistema de lo que probablemente desearía.
AaronLS
¿Qué opinas de Security? El uso de este motor de plantillas hace que sea posible formatear todo el sistema de archivos. Me gusta el motor, pero eso me obliga a echar un vistazo a otros motores.
der_chirurg
55

También puede intentar cargar un control y luego convertirlo en una cadena y configurarlo como el cuerpo HTML:

// Declare stringbuilder to render control to
StringBuilder sb = new StringBuilder();

// Load the control
UserControl ctrl = (UserControl) LoadControl("~/Controls/UserControl.ascx");

// Do stuff with ctrl here

// Render the control into the stringbuilder
StringWriter sw = new StringWriter(sb);
Html32TextWriter htw = new Html32TextWriter(sw);
ctrl.RenderControl(htw);

// Get full body text
string body = sb.ToString();

Luego puede construir su correo electrónico como de costumbre:

MailMessage message = new MailMessage();
message.From = new MailAddress("[email protected]", "from name");
message.Subject = "Email Subject";
message.Body = body;
message.BodyEncoding = Encoding.ASCII;
message.IsBodyHtml = true;

SmtpClient smtp = new SmtpClient("server");
smtp.Send(message);

Su control de usuario podría contener otros controles, como un encabezado y pie de página, y también aprovechar la funcionalidad como el enlace de datos.

Mun
fuente
De alguna manera me perdí esta respuesta la primera vez ... buena. Similar a mi solución, pero con un ascx en lugar de un aspx. Sigo pensando que aspx sería mejor, ya que ofrecería una página completa, en lugar de un control, pero eso es lo que creo.
John Bubriski
Sí, podrías usar cualquiera de las soluciones ... Funcionan de la misma manera. Un beneficio de este enfoque es la coherencia. Por ejemplo, puede mostrarle a un usuario un resumen de pedido e incluir exactamente lo mismo en el correo electrónico de confirmación reutilizando el mismo control.
Mun
Punto menor, pero le falta una línea para declarar un StringBuilder en el primer bloque de código.
Kirschstein
9
El ejemplo no explica dónde reside el código, ¿es una página ?, porque LoadControl es un método de página / control.
Shrage Smilowitz
@Mun, cargas el control de usuario en una variable llamada ctrl y nunca vuelves a hacer referencia a ella en tu código. ¿Cómo se supone que esto funcione?
The Muffin Man
35

Podrías probar la clase MailDefinition

John Sheehan
fuente
4
Solo quiero señalar que esto es bueno para correos electrónicos básicos, pero no nada complejo. La clase MailDefinition no admite el enlace de datos. Lo único que realmente hace es ofrecer reemplazos de cadenas. Aunque, también está integrado en el Asistente de creación de cuentas de membresía.
John Bubriski
4
La clase MailDefinition debe obtener un Control para representar contenido con plantilla. No tan bien.
Yuki
17

Si desea pasar parámetros como nombres de usuario, nombres de productos, ... etc., puede usar el motor de plantillas de código abierto NVelocity para producir su correo electrónico / HTML final.

Un ejemplo de plantilla NVelocity ( MailTemplate.vm ):

A sample email template by <b>$name</b>.
<br />

Foreach example :
<br />    
#foreach ($item in $itemList)

[Date: $item.Date] Name: $item.Name, Value: $itemValue.Value
<br /><br />

#end

Generando el cuerpo del correo por MailTemplate.vm en su aplicación:

VelocityContext context = new VelocityContext();
context.Put("name", "ScarletGarden");
context.Put("itemList", itemList);

StringWriter writer = new StringWriter();

Velocity.MergeTemplate("MailTemplate.vm", context, writer);

string mailBody = writer.GetStringBuilder().ToString();

El cuerpo del correo resultante es:

Una plantilla de correo electrónico de muestra de ScarletGarden .

Para cada ejemplo:

[Fecha: 12.02.2009] Nombre: Elemento 1, Valor: 09

[Fecha: 21.02.2009] Nombre: Elemento 4, Valor: 52

[Fecha: 01.03.2009] Nombre: Elemento 2, Valor: 21

[Fecha: 23.03.2009] Nombre: Elemento 6, Valor: 24

Para editar las plantillas, tal vez pueda usar FCKEditor y guardar sus plantillas en archivos.

Canavar
fuente
7

El componente de correo electrónico Mail.dll incluye el motor de plantillas de correo electrónico:

Aquí está la descripción general de la sintaxis:

<html>
<body>
Hi {FirstName} {LastName},

Here are your orders: 
{foreach Orders}
    Order '{Name}' sent to <strong>{Street}</strong>. 
{end}

</body>
</html>

Y el código que carga la plantilla, llena los datos del objeto c # y envía un correo electrónico:

Mail.Html(Template
              .FromFile("template.txt")
              .DataFrom(_contact)
              .Render())
    .Text("This is text version of the message.")
    .From(new MailBox("[email protected]", "Alice"))
    .To(new MailBox("[email protected]", "Bob"))
    .Subject("Your order")
    .UsingNewSmtp()
    .WithCredentials("[email protected]", "password")
    .Server("mail.com")
    .WithSSL()
    .Send();

Puede obtener más información en la publicación de blog del motor de plantillas de correo electrónico .

O simplemente descargue el componente de correo electrónico Mail.dll y pruébelo.

Tenga en cuenta que este es un producto comercial que he creado.

Pawel Lesnikowski
fuente
6

Si la flexibilidad es uno de sus requisitos previos, XSLT podría ser una buena opción, que es completamente compatible con .NET framework y podría incluso permitir que el usuario edite esos archivos. Este artículo ( http://www.aspfree.com/c/a/XML/XSL-Transformations-using-ASP-NET/ ) puede ser útil para empezar (msdn tiene más información al respecto). Como dijo ScarletGarden, NVelocity es otra buena opción, pero prefiero XSLT por su compatibilidad con el marco .NET "integrado" y su plataforma independiente.

Everton
fuente
Nunca había pensado en esto antes, pero después de probar muchos otros métodos descubrí que esto funcionaba muy bien en combinación con la adición de la IXmlSerializableinterfaz a mis clases. En unas pocas líneas puedo hacer que mi clase entregue un correo electrónico.
cjbarth
Urgh, he soñado con pesadillas sobre XSLT. Probablemente el lenguaje de programación / marcado menos intuitivo con el que he trabajado. E imposible de mantener para otros e incluso para usted mismo 1 mes después de haber codificado por primera vez su XSLT.
PussInBoots
5

Creo que también podrías hacer algo como esto:

Cree una página .aspx y colóquela al final del método OnLoad o llámela manualmente.

    StringBuilder sb = new StringBuilder();
    StringWriter sw = new StringWriter(sb);
    HtmlTextWriter htmlTW = new HtmlTextWriter(sw);
    this.Render(htmlTW);

No estoy seguro de si hay problemas potenciales con esto, pero parece que funcionaría. De esta manera, podría usar una página .aspx con todas las funciones, en lugar de la clase MailDefinition que solo admite reemplazos de texto.

John Bubriski
fuente
Si bien la clase MailDefinition es un buen comienzo, es un poco rudimentario. Este método debería admitir muchas más funciones como el enlace de datos y tal vez incluso el seguimiento. ¿Alguna idea sobre esto o posibles problemas?
John Bubriski
¡Excelente! ¿Tuviste algún problema con eso?
John Bubriski
Entonces, ¿permitirá que sus usuarios editen los archivos .aspx cuando necesiten realizar cambios en la plantilla de correo? Yo llamaría a eso un problema potencial.
Bryan
No lo creo, al menos, no es más riesgoso que otras plantillas que puedan editar. Por supuesto, si supieran lo que estaban haciendo, podrían causar daño, pero al menos en este caso, es poco probable. No sería una página .aspx compleja, más bien una plantilla con marcadores de posición.
John Bubriski
Ha pasado un tiempo, lo sé, pero ¿recuerdas tu solución final? No pude hacer que este enfoque particular funcionara con a Page, al menos cuando usé un método de extensión genérico para la representación. Así cambié a UserControl; mira mi respuesta a continuación. Hasta ahora parece estar funcionando bien ... Me interesaría saber cómo lo resolvió en ese momento.
InteXX
4

Seguro que puedes crear una plantilla html y te recomendaría también una plantilla de texto. En la plantilla, puede poner [BODY] en el lugar donde se colocaría el cuerpo y luego puede leer la plantilla y reemplazar el cuerpo con el nuevo contenido. Puede enviar el correo electrónico utilizando .Nets Mail Class. Solo tiene que repetir el envío del correo electrónico a todos los destinatarios después de crear el correo electrónico inicialmente. Funcionó como un encanto para mí.

using System.Net.Mail;

// Email content
string HTMLTemplatePath = @"path";
string TextTemplatePath = @"path";
string HTMLBody = "";
string TextBody = "";

HTMLBody = File.ReadAllText(HTMLTemplatePath);
TextBody = File.ReadAllText(TextTemplatePath);

HTMLBody = HTMLBody.Replace(["[BODY]", content);
TextBody = HTMLBody.Replace(["[BODY]", content);

// Create email code
MailMessage m = new MailMessage();

m.From = new MailAddress("[email protected]", "display name");
m.To.Add("[email protected]");
m.Subject = "subject";

AlternateView plain = AlternateView.CreateAlternateViewFromString(_EmailBody + text, new System.Net.Mime.ContentType("text/plain"));
AlternateView html = AlternateView.CreateAlternateViewFromString(_EmailBody + body, new System.Net.Mime.ContentType("text/html"));
mail.AlternateViews.Add(plain);
mail.AlternateViews.Add(html);

SmtpClient smtp = new SmtpClient("server");
smtp.Send(m);
Josh Mein
fuente
Puede cortar el material de StreamReader y reemplazarlo con File.ReadAllText (ruta)
John Sheehan
Este es un buen comienzo, pero solo proporciona funcionalidad para un encabezado y pie de página. Esto realmente no ayuda con el cuerpo en sí.
John Bubriski
El cuerpo, todo lo que tiene que hacer es ingresar el contenido del cuerpo que desea en los campos HTMLBody y TextBody o, por supuesto, también puede almacenarlos en archivos
Josh Mein
4

Aquí hay una alternativa más que usa transformaciones XSL para plantillas de correo electrónico más complejas: Envío de correo electrónico basado en HTML desde aplicaciones .NET .

Alek Davis
fuente
2
Me gusta el enlace. ¡Gracias! Mi cerebro comenzó a girar y me di cuenta de que podía dar un paso más y tener una plantilla XSLT que toma un objeto XML serializable o un contrato de datos WCF directamente en formato de correo electrónico html. De repente, ¡tendría plantillas de correo electrónico de tipo fuerte a través de clases serializables reales!
CodingWithSpike
2

Tenga cuidado al hacer esto, los filtros de SPAM parecen bloquear el html generado por ASP.net, aparentemente debido a ViewState, así que si va a hacer esto, asegúrese de que el Html producido esté limpio.

Personalmente, consideraría el uso de Asp.net MVC para lograr los resultados deseados. o NVelocity es bastante bueno en esto

contramaestre
fuente
1

Lo ideal sería usar una página .ASPX como plantilla de alguna manera, luego decirle a mi código que sirva esa página y usar el HTML devuelto para el correo electrónico.

Fácilmente podría construir una WebRequest para acceder a una página ASPX y obtener el HTML resultante. Con un poco más de trabajo, probablemente pueda hacerlo sin WebRequest. Un PageParser y un Response.Filter le permitirían ejecutar la página y capturar la salida ... aunque puede haber algunas formas más elegantes.

Mark Brackett
fuente
1

Tenía un requisito similar en 1 de los proyectos en los que tenía que enviar una gran cantidad de correos electrónicos cada día, y el cliente quería un control completo sobre las plantillas html para diferentes tipos de correos electrónicos.

Debido a la gran cantidad de correos electrónicos que se enviarán, el rendimiento fue una preocupación principal.

lo que se nos ocurrió fue contenido estático en el servidor sql donde se guarda el marcado completo de la plantilla html (junto con marcadores de posición, como [UserFirstName], [UserLastName] que se reemplazan con datos reales en tiempo de ejecución) para diferentes tipos de correos electrónicos

luego cargamos estos datos en la caché de asp.net, por lo que no leemos las plantillas html una y otra vez, sino solo cuando realmente se modifican

le dimos al cliente un editor WYSIWYG para modificar estas plantillas a través de un formulario web de administración. cada vez que se realizan actualizaciones, restablecemos la caché de asp.net.

y luego teníamos una tabla separada para los registros de correo electrónico, donde se registraban todos los correos electrónicos que se enviarían. esta tabla tenía campos llamados emailType, emailSent y numberOfTries.

simplemente ejecutamos un trabajo cada 5 minutos para tipos de correo electrónico importantes (como registro de nuevo miembro, contraseña olvidada) que deben enviarse lo antes posible

ejecutamos otro trabajo cada 15 minutos para tipos de correo electrónico menos importantes (como correo electrónico de promoción, correo electrónico de noticias, etc.)

de esta manera no bloquea su servidor enviando correos electrónicos sin parar y procesa los correos por lotes. una vez que se envía un correo electrónico, configura el campo emailSent en 1.

Raj
fuente
Pero, ¿cómo manejaste las colecciones?
Riri
1
También hice esto y funcionó bien. Además, históricamente puede volver atrás y ver los registros de los correos electrónicos enviados, si los informes son lo suyo.
Mark Glorie
1

Tenga en cuenta que las soluciones aspx y ascx requieren un HttpContext actual, por lo que no se pueden usar de forma asincrónica (por ejemplo, en subprocesos) sin mucho trabajo.

Rosco
fuente
1

Creo que la respuesta fácil es MvcMailer. Es el paquete NuGet que le permite usar su motor de visualización favorito para generar correos electrónicos. Vea el paquete NuGet aquí y la documentación del proyecto

¡Espero eso ayude!

Sohan
fuente
Mmmm, ¿extraño que esta respuesta no haya recibido tanta atención?
PussInBoots
1

DotLiquid es otra opción. Usted especifica valores de un modelo de clase como {{ user.name }}y luego, en tiempo de ejecución, proporciona los datos en esa clase y la plantilla con el marcado, y fusionará los valores por usted. Es similar a usar el motor de plantillas de Razor de muchas maneras. Admite cosas más complejas como bucles y varias funciones como ToUpper. Lo bueno es que son "seguros" para que los usuarios que crean las plantillas no puedan bloquear su sistema o escribir código inseguro como lo haría en razor: http://dotliquidmarkup.org/try-online

AaronLS
fuente
0

Si puede permitir que ASPNET y los usuarios asociados tengan permiso para leer y escribir un archivo, puede usar fácilmente un archivo HTML con String.Format()marcadores de posición estándar ( {0},{1:C} , etc.) para lograr esto.

Simplemente lea el archivo, como una cadena, usando clases del System.IOespacio de nombres. Una vez que tengas esa cadena, pásala como primer argumento aString.Format() y proporcione los parámetros.

Mantenga esa cadena alrededor y úsela como el cuerpo del correo electrónico, y básicamente ya está. Hacemos esto en docenas de sitios (ciertamente pequeños) hoy, y no hemos tenido problemas.

Debo señalar que esto funciona mejor si (a) no está enviando millones de correos electrónicos a la vez, (b) no está personalizando cada correo electrónico (de lo contrario, consume una tonelada de cadenas) y (c ) el archivo HTML en sí es relativamente pequeño.

John Rudy
fuente
0

Configure el mensaje de correo electrónico IsBodyHtml = true

Tome su objeto que contiene el contenido de su correo electrónico. Serialice el objeto y use xml / xslt para generar el contenido html.

Si desea hacer AlternateViews, haga lo mismo que jmein solo use una plantilla xslt diferente para crear el contenido de texto sin formato.

una de las principales ventajas de esto es que si desea cambiar su diseño, todo lo que tiene que hacer es actualizar la plantilla xslt.

Bob el conserje
fuente
0

Mire SubSonic (www.subsonicproject.com). Están haciendo exactamente esto para generar código: la plantilla es ASPX estándar y genera c #. El mismo método sería reutilizable para su escenario.

jvenema
fuente
0

Usaría una biblioteca de plantillas como TemplateMachine . esto le permite en su mayoría poner su plantilla de correo electrónico junto con el texto normal y luego usar reglas para inyectar / reemplazar valores según sea necesario. Muy similar a ERB en Ruby. Esto le permite separar la generación del contenido del correo sin vincularlo demasiado a algo como ASPX, etc., luego, una vez que se genera el contenido con esto, puede enviarlo por correo electrónico.

MikeJ
fuente
0

Me gusta la respuesta de Raj. Programas como ListManager y frameworks como DNN hacen cosas similares, y si se requiere una edición fácil por parte de usuarios no técnicos, los editores WYSIWYG para modificar HTML almacenado en SQL es una forma sencilla y sencilla de realizar y puede acomodar fácilmente la edición de encabezados independientemente de los pies de página. etc, además de usar tokens para insertar valores dinámicamente.

Una cosa a tener en cuenta si usa el método anterior (o cualquiera, en realidad) es ser estricto y cuidadoso con los tipos de estilos y etiquetas que permite que los editores inserten. Si cree que los navegadores son quisquillosos, espere hasta que vea cuán diferente los clientes de correo electrónico hacen lo mismo ...

Mella
fuente
0

Similar a la respuesta de Canavar, pero en lugar de NVelocity, siempre uso " StringTemplate ", que cargo la plantilla desde un archivo de configuración, o cargo un archivo externo usando File.ReadAllText () y establezco los valores.

Es un proyecto de Java, pero el puerto C # es sólido y lo he usado en varios proyectos (solo lo usé para plantillas de correo electrónico usando la plantilla en un archivo externo).

Las alternativas siempre son buenas.

Bryan Bailliache
fuente
0

A continuación, se muestra una forma sencilla de utilizar la clase WebClient :

public static string GetHTMLBody(string url)
{
    string htmlBody;

    using (WebClient client = new WebClient ())
    {
        htmlBody = client.DownloadString(url);
    }

    return htmlBody;
}

Entonces llámalo así:

string url = "http://www.yourwebsite.com";
message.Body = GetHTMLBody(url);

Por supuesto, su CSS deberá estar alineado para mostrar los estilos de la página web en la mayoría de los clientes de correo electrónico (como Outlook). Si su correo electrónico muestra contenido dinámico (por ejemplo, nombre del cliente), le recomendaría utilizar QueryStrings en su sitio web para completar los datos. (por ejemplo, http://www.yourwebsite.com?CustomerName=Bob )

ROFLwTIME
fuente
Genial, aunque creo que la mayoría de las otras respuestas hacen esto sin hacer una solicitud web de regreso al sitio, es decir, tener que alojar el cuerpo del correo electrónico en su sitio.
Rup
@Rup Comprensible, pero tenga en cuenta que muchas veces la gente quiere ver una "versión web" del correo electrónico de todos modos. Esta solución funciona perfectamente para ese escenario.
ROFLwTIME
0

@bardev proporciona una buena solución, pero desafortunadamente no es ideal en todos los casos. El mío fue uno de ellos.

Estoy usando WebForms en un sitio web (juro que nunca volveré a usar un sitio web, qué PITA) en VS 2013.

Probé la sugerencia de Razor, pero siendo el mío un sitio web no obtuve el importante IntelliSense que ofrece el IDE en un proyecto MVC. También me gusta usar el diseñador para mis plantillas, un lugar perfecto para un UserControl.

Nix en Razor de nuevo.

En su lugar, se me ocurrió este pequeño marco (consejos de sombrero para @mun para UserControl y @imatoria para Strong Typing). Casi el único problema potencial que puedo ver es que debe tener cuidado de mantener su nombre de archivo .ASCX sincronizado con su nombre de clase. Si te desvías, obtendrás un error de tiempo de ejecución.

FWIW: En mis pruebas, al menos a la llamada RenderControl () no le gusta un control de página, así que fui con UserControl.

Estoy bastante seguro de que he incluido todo aquí; avíseme si dejé algo fuera.

HTH

Uso:

Partial Class Purchase
  Inherits UserControl

  Private Sub SendReceipt()
    Dim oTemplate As MailTemplates.PurchaseReceipt

    oTemplate = MailTemplates.Templates.PurchaseReceipt(Me)
    oTemplate.Name = "James Bond"
    oTemplate.OrderTotal = 3500000
    oTemplate.OrderDescription = "Q-Stuff"
    oTemplate.InjectCss("PurchaseReceipt")

    Utils.SendMail("{0} <[email protected]>".ToFormat(oTemplate.Name), "Purchase Receipt", oTemplate.ToHtml)
  End Sub
End Class

Clase base:

Namespace MailTemplates
  Public MustInherit Class BaseTemplate
    Inherits UserControl

    Public Shared Function GetTemplate(Caller As TemplateControl, Template As Type) As BaseTemplate
      Return Caller.LoadControl("~/MailTemplates/{0}.ascx".ToFormat(Template.Name))
    End Function



    Public Sub InjectCss(FileName As String)
      If Me.Styler IsNot Nothing Then
        Me.Styler.Controls.Add(New Controls.Styler(FileName))
      End If
    End Sub



    Private ReadOnly Property Styler As PlaceHolder
      Get
        If _Styler Is Nothing Then
          _Styler = Me.FindNestedControl(GetType(PlaceHolder))
        End If

        Return _Styler
      End Get
    End Property
    Private _Styler As PlaceHolder
  End Class
End Namespace

Clase "Fábrica":

Namespace MailTemplates
  Public Class Templates
    Public Shared ReadOnly Property PurchaseReceipt(Caller As TemplateControl) As PurchaseReceipt
      Get
        Return BaseTemplate.GetTemplate(Caller, GetType(PurchaseReceipt))
      End Get
    End Property
  End Class
End Namespace

Clase de plantilla:

Namespace MailTemplates
  Public MustInherit Class PurchaseReceipt
    Inherits BaseTemplate

    Public MustOverride WriteOnly Property Name As String
    Public MustOverride WriteOnly Property OrderTotal As Decimal
    Public MustOverride WriteOnly Property OrderDescription As String
  End Class
End Namespace

Encabezado ASCX:

<%@ Control Language="VB" ClassName="_Header" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<!--
  See https://www.campaignmonitor.com/blog/post/3317/ for discussion of DocType in HTML Email
-->

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title></title>
  <asp:PlaceHolder ID="plcStyler" runat="server"></asp:PlaceHolder>
</head>
<body>

Pie de página ASCX:

<%@ Control Language="VB" ClassName="_Footer" %>

</body>
</html>

Plantilla ASCX:

<%@ Control Language="VB" AutoEventWireup="false" CodeFile="PurchaseReceipt.ascx.vb" Inherits="PurchaseReceipt" %>

<%@ Register Src="_Header.ascx" TagName="Header" TagPrefix="uc" %>
<%@ Register Src="_Footer.ascx" TagName="Footer" TagPrefix="uc" %>

<uc:Header ID="ctlHeader" runat="server" />

  <p>Name: <asp:Label ID="lblName" runat="server"></asp:Label></p>
  <p>Order Total: <asp:Label ID="lblOrderTotal" runat="server"></asp:Label></p>
  <p>Order Description: <asp:Label ID="lblOrderDescription" runat="server"></asp:Label></p>

<uc:Footer ID="ctlFooter" runat="server" />

Código de plantilla ASCX Archivo:

Partial Class PurchaseReceipt
  Inherits MailTemplates.PurchaseReceipt

  Public Overrides WriteOnly Property Name As String
    Set(Value As String)
      lblName.Text = Value
    End Set
  End Property



  Public Overrides WriteOnly Property OrderTotal As Decimal
    Set(Value As Boolean)
      lblOrderTotal.Text = Value
    End Set
  End Property



  Public Overrides WriteOnly Property OrderDescription As Decimal
    Set(Value As Boolean)
      lblOrderDescription.Text = Value
    End Set
  End Property
End Class

Ayudantes:

'
' FindNestedControl helpers based on tip by @andleer
' at http://stackoverflow.com/questions/619449/
'

Public Module Helpers
  <Extension>
  Public Function AllControls(Control As Control) As List(Of Control)
    Return Control.Controls.Flatten
  End Function



  <Extension>
  Public Function FindNestedControl(Control As Control, Id As String) As Control
    Return Control.Controls.Flatten(Function(C) C.ID = Id).SingleOrDefault
  End Function



  <Extension>
  Public Function FindNestedControl(Control As Control, Type As Type) As Control
    Return Control.Controls.Flatten(Function(C) C.GetType = Type).SingleOrDefault
  End Function



  <Extension>
  Public Function Flatten(Controls As ControlCollection) As List(Of Control)
    Flatten = New List(Of Control)

    Controls.Traverse(Sub(Control) Flatten.Add(Control))
  End Function


  <Extension>
  Public Function Flatten(Controls As ControlCollection, Predicate As Func(Of Control, Boolean)) As List(Of Control)
    Flatten = New List(Of Control)

    Controls.Traverse(Sub(Control)
                        If Predicate(Control) Then
                          Flatten.Add(Control)
                        End If
                      End Sub)
  End Function



  <Extension>
  Public Sub Traverse(Controls As ControlCollection, Action As Action(Of Control))
    Controls.Cast(Of Control).ToList.ForEach(Sub(Control As Control)
                                               Action(Control)

                                               If Control.HasControls Then
                                                 Control.Controls.Traverse(Action)
                                               End If
                                             End Sub)
  End Sub



  <Extension()>
  Public Function ToFormat(Template As String, ParamArray Values As Object()) As String
    Return String.Format(Template, Values)
  End Function



  <Extension()>
  Public Function ToHtml(Control As Control) As String
    Dim oSb As StringBuilder

    oSb = New StringBuilder

    Using oSw As New StringWriter(oSb)
      Using oTw As New HtmlTextWriter(oSw)
        Control.RenderControl(oTw)
        Return oSb.ToString
      End Using
    End Using
  End Function
End Module



Namespace Controls
  Public Class Styler
    Inherits LiteralControl

    Public Sub New(FileName As String)
      Dim _
        sFileName,
        sFilePath As String

      sFileName = Path.GetFileNameWithoutExtension(FileName)
      sFilePath = HttpContext.Current.Server.MapPath("~/Styles/{0}.css".ToFormat(sFileName))

      If File.Exists(sFilePath) Then
        Me.Text = "{0}<style type=""text/css"">{0}{1}</style>{0}".ToFormat(vbCrLf, File.ReadAllText(sFilePath))
      Else
        Me.Text = String.Empty
      End If
    End Sub
  End Class
End Namespace



Public Class Utils
  Public Shared Sub SendMail(Recipient As MailAddress, Subject As String, HtmlBody As String)
    Using oMessage As New MailMessage
      oMessage.To.Add(Recipient)
      oMessage.IsBodyHtml = True
      oMessage.Subject = Subject.Trim
      oMessage.Body = HtmlBody.Trim

      Using oClient As New SmtpClient
        oClient.Send(oMessage)
      End Using
    End Using
  End Sub
End Class
InteXX
fuente