.NET-es kódból e-mailt küldeni nem éppen atomfizikusnak való feladat, mindenki hamar rájön, hogy milyen egyszerű spam gyárat írni a System.Net.Mail névtér segítségével:
MailMessage msg = new MailMessage(
"felado@example.com",
"cimzett@example.com",
"Próba levél tárgysora kódból",
"Ide jön az érdekes tartalom..." );
SmtpClient smtp = new SmtpClient( "mail.example.com" );
smtp.Send( msg );
Ez eddig ment .NET 1.0-n is, ami viszont macerás volt, az a feladó és a címzettek megadása, amire a megoldást a .NET 2.0-ban megjelent MailAddress osztály hozta meg. Így már barátságosabban címezhetünk levelet:
MailMessage msg = new MailMessage();
msg.From = new MailAddress( "felado@example.com", "Feladó szép neve" );
msg.To.Add( new MailAddress( "cimzett@example.com", "Címzett szép neve" ) );
msg.Subject = "Próba levél tárgysora";
msg.Body = "A tartalom helye...";
Ez mind szép, de a címzettel kár sokat erőlködni, egy Exchange Serverre kötött Outlook ragaszkodik az AD-ben lévő Display Name-hez. Egyébként pedig a webes levelező kliensek közül egyik-másik (pl. a Freemail biztosan) nem tud megbírkózni a címmezőkben lévő ékezetekkel, hiába adunk meg a MailAddress konstruktornak harmadik paraméterben egy Encodingot. Érdekes módon a Gmailnek meg se kottyan...
Ha már a címzetteknél járunk, bizony mindenkinek akadnak olyan levelezőpartnerei, akiknél nem ártana a kézbesítési és az olvasási nyugta használata. Örüljünk, mert kézbesítési nyugtát kérhetünk a DeliveryNotificationOptions tulajdonság használatával. Mivel ez flages enum bátran összevagyolhatjuk az egyes elemeit, például:
msg.DeliveryNotificationOptions =
DeliveryNotificationOptions.OnFailure | DeliveryNotificationOptions.OnSuccess;
Az olvasási nyugtára viszont nincs közvetlenül property, így nincs más lehetőségünk, mint kézzel beletúrni a fejléc mezőkbe:
msg.Headers.Add( "Disposition-Notification-To", "felado@example.com" );
Ennyit a címzésről, lépjünk tovább a tartalomra, például legyen inkább HTML:
msg.Body = "<html><body><h1>Hello Szivi!</h1>Hoztál <i>nekem</i> kismajmot?</body></html>";
msg.IsBodyHtml = true;
Na ezt már nem sikerült elküldeni az Exchange-en keresztül:
Unhandled Exception: System.Net.Mail.SmtpException: Mailbox unavailable. The server response was: 5.7.1 Requested action not taken: message refused
A finnyás mindenit, túl spam gyanúsnak találta a levelet, ezért kénytelen voltam a lokális SMTP szerveremen keresztül elküldeni. Mert XP-n még van olyan, Vistán nincs, de majd a Windows Server 2008-ban lesz.
Az AlternateView osztály segítségével még arra is van lehetőségünk, hogy egyetlen levélben különböző formátumokban küldjük el a mondandónkat.
string htmlBody =
"<html><body><h1>Hello Szivi!</h1>Hoztál <i>nekem</i> kismajmot?</body></html>";
string textBody = "Szia Szivi! Hoztál nekem kismajmot?";
AlternateView htmlView = AlternateView.CreateAlternateViewFromString(
htmlBody, Encoding.Default, MediaTypeNames.Text.Html );
msg.AlternateViews.Add( htmlView );
AlternateView textView = AlternateView.CreateAlternateViewFromString(
textBody, Encoding.Default, MediaTypeNames.Text.Plain );
msg.AlternateViews.Add( textView );
Ez (legalább) két ok miatt érdekes:
- Az AlternateView osztálynak van olyan konstruktora, amelyet fájl név vagy stream megadásával használhatunk, ha tehát nem változik a szöveg, akkor egyszerűen felnyalhatjuk fájlrendszerből.
- Így van lehetőségünk arra, hogy a HTML változathoz - és csak ahhoz - képet fűzzünk. Nosza!
string htmlBody = @"<html><body><h1>Képes</h1><img src=""cid:Kep1""></body></html>";
AlternateView htmlView = AlternateView.CreateAlternateViewFromString(
htmlBody, Encoding.Default, MediaTypeNames.Text.Html );
LinkedResource pic = new LinkedResource(
@"C:\WINDOWS\Web\Wallpaper\Crystal.jpg", MediaTypeNames.Image.Jpeg );
pic.ContentId = "Kep1";
htmlView.LinkedResources.Add( pic );
msg.AlternateViews.Add( htmlView );
Aláhúztam a trükkös részeket, a ContentId tulajdonságra kell figyelni, és az IMG elem SRC attribútumában cid:ContentId formátumban hivatkozhatunk a képre.
Így már persze jó nagy leveleket fogunk küldözgetni, nem is fog gyorsan menni. Küldjük hát aszinkron módon, mert 2.0-ban már azt is lehet:
smtp.SendCompleted += new SendCompletedEventHandler( OnSendCompleted );
smtp.SendAsync( msg, "Levél a Kedvesnek" );
A küldés eredményét pedig egy eseménykezelővel tudhatjuk meg:
static void OnSendCompleted( object sender, AsyncCompletedEventArgs e )
{
string state = e.UserState as string;
if( e.Cancelled )
{
Console.WriteLine( @"""{0}"" küldése megszakítva.", state );
}
if( e.Error != null )
{
Console.WriteLine( @"Hiba a ""{0}"" küldése közben: {1}", state, e.Error.Message );
}
else
{
Console.WriteLine( @"A ""{0}"" elküldve.", state );
}
}
A teljességhez hozzátartozik, hogy az SmtpClient.SendAsyncCancel() metódus hívásával leállíthatjuk a levél elküldését. Ezt jelenlegi formájában a valóságban aligha fogjuk használni, hiszen nem tudunk hivatkozni egy konkrét MailMessage objektumra.
Ha valami miatt mégis ragaszkodnánk a szinkron küldéshez, ne feledkezzünk meg a Timeout tulajdonságról, ami alapértelmezés szerint 100 másodperc, addig próbálkozik a küldéssel. Apropó, ha már a küldésnél tartunk, igen örvendetes, hogy már van lehetőségünk a kapcsolat titkosítására (SmtpClient.EnableSsl) és hitelesítésre is (SmtpClient.Credentials).
Aki pedig arra kíváncsi, hogy mi történik a háttérben, ne habozzon felhúzni néhány trace listenert az app.config fájlban, egészen részletes olvasnivalóban lehet része:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
<trace autoflush="true" />
<sources>
<source name="System.Net" >
<listeners>
<add name="MyTraceFile"/>
</listeners>
</source>
<source name="System.Net.Sockets">
<listeners>
<add name="MyTraceFile"/>
</listeners>
</source>
</sources>
<sharedListeners>
<add
name="MyTraceFile"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="System.Net.trace.log" />
</sharedListeners>
<switches>
<add name="System.Net" value="Verbose" />
<add name="System.Net.Sockets" value="Verbose" />
</switches>
</system.diagnostics>
</configuration>
És hogy valami stílszerű maradjon a végére, ezt soha ne felejtsük el:
msg.Dispose();