webentwicklung-frage-antwort-db.com.de

Der richtige Weg, asynchron eine E-Mail in ASP.NET zu senden ... (mache ich das richtig?)

Wenn sich ein Benutzer auf meiner Website registriert, sehe ich nicht, warum ich ihn dazu bringen muss, auf das SMTP zu warten, damit er eine Aktivierungs-E-Mail erhält.

Ich habe beschlossen, diesen Code asynchron zu starten, und es war ein Abenteuer.

Stellen wir uns vor, ich habe eine Methode wie:

private void SendTheMail() { // Stuff }

Mein erster Gedanke ... war das Einfädeln. Ich tat dies:

Emailer mailer = new Emailer();
Thread emailThread = new Thread(() => mailer.SendTheMail());
emailThread.Start();

Das funktioniert ... bis ich mich entschied, es auf Fehlerbehandlung zu testen. Ich habe die SMTP-Serveradresse in meiner web.config absichtlich abgebrochen und ausprobiert. Das beängstigende Ergebnis war, dass IIS im Grunde BARFED mit einem nicht behandelten Ausnahmefehler bei w3wp.exe war (es war ein Windows-Fehler! Wie extrem ...). ELMAH (mein Fehlerlogger) hat ihn NICHT UND IIS wurde neu gestartet, sodass die Sitzung von allen Benutzern der Website gelöscht wurde. Völlig inakzeptables Ergebnis!

Mein nächster Gedanke war, einige Nachforschungen zu asynchronen Delegierten durchzuführen. Dies scheint besser zu funktionieren, da Ausnahmen innerhalb des asynchronen Delegaten behandelt werden (im Gegensatz zum obigen Thread-Beispiel). Ich mache mir jedoch Sorgen, ob ich es falsch mache oder vielleicht Speicherlecks verursacht.

Folgendes mache ich:

Emailer mailer = new Emailer();
AsyncMethodCaller caller = new AsyncMethodCaller(mailer.SendMailInSeperateThread);
caller.BeginInvoke(message, email.EmailId, null, null);
// Never EndInvoke... 

Mache ich das richtig?

19
Ralph N

Es gab viele gute Ratschläge, die ich hier erfahren habe, zum Beispiel, dass ich mich daran erinnere, IDisposable zu verwenden (ich wusste es überhaupt nicht). Mir wurde auch klar, wie wichtig es ist, Fehler in einem anderen Thread manuell abzufangen, da es keinen Kontext gibt. Ich habe an der Theorie gearbeitet, dass ich ELMAH einfach alles erledigen lassen sollte. Nach weiteren Erkundigungen wurde mir klar, dass ich auch vergessen hatte, IDisposable für mailmessage zu verwenden.

Als Antwort auf Richard, obwohl ich sehe, dass die Threading-Lösung funktionieren kann (wie in meinem ersten Beispiel vorgeschlagen), solange ich die Fehler auffange ... es ist immer noch etwas beängstigend, dass IIS vollständig ist explodiert, wenn der Fehler nicht erfasst wird. Das sagt mir, dass ASP.NET/IIS niemals für Sie gedacht hat, dass Sie das tun sollten. Deshalb neige ich dazu, .BeginInvoke/delegates stattdessen zu verwenden, da dies IIS nicht durcheinander bringt Etwas geht schief und scheint in ASP.NET beliebter zu sein.

Als Antwort auf ASawyer war ich total überrascht, dass ein .SendAsync in den SMTP-Client integriert war. Ich habe eine Weile mit dieser Lösung gespielt, aber das scheint mir nicht zu helfen. Obwohl ich den Client von Code, der SendAsync ausführt, überspringen kann, "wartet" die Seite immer noch, bis das SendCompleted-Ereignis abgeschlossen ist. Mein Ziel war es, den Benutzer und die Seite vorwärts zu bewegen, während die E-Mail im Hintergrund gesendet wird. Ich habe das Gefühl, dass ich immer noch etwas falsch mache. Wenn also jemand vorbeikommt, möchte er es vielleicht selbst ausprobieren.

Hier ist meine vollständige Lösung für das Senden von E-Mails zu 100% asynchron mit ELMAH.MVC-Fehlerprotokollierung. Ich entschied mich für eine erweiterte Version von Beispiel 2:

public void SendThat(MailMessage message)
{
    AsyncMethodCaller caller = new AsyncMethodCaller(SendMailInSeperateThread);
    AsyncCallback callbackHandler = new AsyncCallback(AsyncCallback);
    caller.BeginInvoke(message, callbackHandler, null);
}

private delegate void AsyncMethodCaller(MailMessage message);

private void SendMailInSeperateThread(MailMessage message)
{
    try
    {
        SmtpClient client = new SmtpClient();
        client.Timeout = 20000; // 20 second timeout... why more?
        client.Send(message);
        client.Dispose();
        message.Dispose();

        // If you have a flag checking to see if an email was sent, set it here
        // Pass more parameters in the delegate if you need to...
    }
    catch (Exception e)
    {
         // This is very necessary to catch errors since we are in
         // a different context & thread
         Elmah.ErrorLog.GetDefault(null).Log(new Error(e));
    }
}

private void AsyncCallback(IAsyncResult ar)
{
    try
    {
        AsyncResult result = (AsyncResult)ar;
        AsyncMethodCaller caller = (AsyncMethodCaller)result.AsyncDelegate;
        caller.EndInvoke(ar);
    }
    catch (Exception e)
    {
        Elmah.ErrorLog.GetDefault(null).Log(new Error(e));
        Elmah.ErrorLog.GetDefault(null).Log(new Error(new Exception("Emailer - This hacky asynccallback thing is puking, serves you right.")));
    }
}
25
Ralph N

Ab .NET 4.5 implementiert SmtpClient die async awaitable-Methode SendMailAsync . E-Mail asynchron zu senden, lautet daher wie folgt:

public async Task SendEmail(string toEmailAddress, string emailSubject, string emailMessage)
{
    var message = new MailMessage();
    message.To.Add(toEmailAddress);

    message.Subject = emailSubject;
    message.Body = emailMessage;

    using (var smtpClient = new SmtpClient())
    {
        await smtpClient.SendMailAsync(message);
    }
} 
6
Boris Lipschitz

Verwenden Sie den .Net SmtpClient zum Senden von E-Mails? Es kann bereits asynchrone Nachrichten senden .

Bearbeiten - Wenn Emailer mailer = new Emailer(); kein Wrapper über SmtpClient ist, ist dies nicht so nützlich, denke ich.

3
asawyer

Wenn Sie die Klassen SmtpClient und MailMessage von .Net verwenden, sollten Sie einige Punkte beachten. Erwarten Sie zunächst Fehler beim Senden, also fangen Sie sie an und behandeln Sie sie. Zweitens wurden in .Net 4 einige Änderungen an diesen Klassen vorgenommen, und beide implementieren jetzt IDisposable (MailMessage seit 3.5, SmtpClient neu in 4.0). Aus diesem Grund sollten Sie Ihre Erstellung des SmtpClient und der MailMessage mithilfe von Blöcken einschließen oder explizit verwerfen. Dies ist ein bahnbrechender Wandel, von dem manche Menschen nichts wissen.

In dieser SO -Frage finden Sie weitere Informationen zur Entsorgung bei Verwendung von asynchronen Sends:

Was sind bewährte Methoden für die Verwendung von SmtpClient, SendAsync und Dispose unter .NET 4.0

3
hatchet

Das Einfädeln ist hier nicht die falsche Option, aber wenn Sie eine Ausnahme nicht selbst behandeln, sprudelt dies und stürzt Ihren Prozess ab. Es ist egal, in welchem ​​Thread du das machst.

Also anstelle von mailer.SendTheMail () versuchen Sie Folgendes:

new Thread(() => { 
  try 
  {
    mailer.SendTheMail();
  }
  catch(Exception ex)
  {
    // Do something with the exception
  }
});

Besser noch, verwenden Sie die asynchronen Funktionen des SmtpClient, wenn Sie können. Sie müssen jedoch immer noch mit Ausnahmen umgehen.

Ich würde sogar vorschlagen, dass Sie einen Blick auf die neue Parallet Task-Bibliothek von .Net 4 werfen. Das bietet zusätzliche Funktionen, mit denen Sie Ausnahmefälle handhaben können, und funktioniert gut mit dem Thread-Pool von ASP.Net.

3
Richard

Warum also nicht einen separaten Poller/Dienst haben, der sich ausschließlich mit dem Versand von E-Mails befasst? So können Sie Ihre Registrierung nach dem Ausführen nur in der Zeit ausführen, die für das Schreiben in die Datenbank/Nachrichtenwarteschlange erforderlich ist, und das Senden der E-Mail bis zum nächsten Abrufintervall verzögern.

Ich überlege gerade dasselbe Problem und denke, ich möchte wirklich nicht einmal das Senden von E-Mails in der Post-Back-Anfrage des Servers initiieren. Der Prozess hinter dem Bereitstellen der Webseiten sollte daran interessiert sein, eine Antwort an den Benutzer ASAP zurückzugeben, je mehr Arbeit Sie versuchen, umso langsamer zu sein.

Sehen Sie sich den Befehl Abfrageaufteilungsabfrage ( http://martinfowler.com/bliki/CQRS.html ) an. Martin Fowler erklärt, dass im Befehlsteil einer Operation andere Modelle als im Abfrageteil verwendet werden können. In diesem Szenario wäre der Befehl "Benutzer registrieren", die Abfrage wäre die Aktivierungs-E-Mail, wobei die lose Analogie verwendet wird. Das entsprechende Zitat wäre wahrscheinlich:

Mit separaten Modellen meinen wir meist verschiedene Objektmodelle, die wahrscheinlich in verschiedenen logischen Prozessen ablaufen

Ebenfalls lesenswert ist der Wikipedia-Artikel zu CQRS ( http://en.wikipedia.org/wiki/Command%E2%80%93query_separation ). Ein wichtiger Punkt, der dies hervorhebt, ist:

es ist eindeutig als Programmierrichtlinie und nicht als Regel für eine gute Codierung gedacht

Das heißt, verwenden Sie es, wenn Ihr Code, die Programmausführung und das Verständnis des Programmierers davon profitieren würden. Dies ist ein gutes Beispielszenario.

Dieser Ansatz hat den zusätzlichen Vorteil, dass alle Bedenken bezüglich Mufti-Threading und die Kopfschmerzen, die alles bringen kann, aufgehoben werden.

2
A. Murray

Verwenden Sie diesen Weg-

private void email(object parameters)
    {
        Array arrayParameters = new object[2];
        arrayParameters = (Array)parameters;
        string Email = (string)arrayParameters.GetValue(0);
        string subjectEmail = (string)arrayParameters.GetValue(1);
        if (Email != "[email protected]")
        {
            OnlineSearch OnlineResult = new OnlineSearch();
            try
            {
                StringBuilder str = new StringBuilder();
                MailMessage mailMessage = new MailMessage();

                //here we set the address
                mailMessage.From = fromAddress;
                mailMessage.To.Add(Email);//here you can add multiple emailid
                mailMessage.Subject = "";
                //here we set add bcc address
                //mailMessage.Bcc.Add(new MailAddress("[email protected]"));
                str.Append("<html>");
                str.Append("<body>");
                str.Append("<table width=720 border=0 align=left cellpadding=0 cellspacing=5>");

                str.Append("</table>");
                str.Append("</body>");
                str.Append("</html>");
                //To determine email body is html or not
                mailMessage.IsBodyHtml = true;
                mailMessage.Body = str.ToString();
                //file attachment for this e-mail message.
                Attachment attach = new Attachment();
                mailMessage.Attachments.Add(attach);
                mailClient.Send(mailMessage);
            }

    }


  protected void btnEmail_Click(object sender, ImageClickEventArgs e)
    {
        try
        {
            string To = txtEmailTo.Text.Trim();
            string[] parameters = new string[2];
            parameters[0] = To;
            parameters[1] = PropCase(ViewState["StockStatusSub"].ToString());
            Thread SendingThreads = new Thread(email);
            SendingThreads.Start(parameters);
            lblEmail.Visible = true;
            lblEmail.Text = "Email Send Successfully ";
        }
1
Rahul

Ich habe dasselbe Thema für mein Projekt bearbeitet:

Zuerst versuchte Thread wie Sie:
- Ich verliere den Kontext
- Problem bei der Ausnahmebehandlung
- Häufig wird gesagt, Thread ist eine schlechte Idee in IIS ThreadPool 

Also wechsle ich und versuche es mit asynchronously:
- "asynchron" ist fake in einer asp.net-Webanwendung. Es wurden nur Warteschlangenanrufe gesetzt und der Kontext wurde geändert 

Also mache ich Windows-Dienst und rufe die Werte über die SQL-Tabelle ab: happy end

Für eine schnelle Lösung: Machen Sie einen asynchronen Anruf von ajax, teilen Sie dem Benutzer fake yes mit, aber setzen Sie den Sendeauftrag in Ihrem mvc-Controller fort

1
asdf_enel_hak

Wenn Sie Lecks erkennen möchten, müssen Sie einen Profiler wie diesen verwenden:

http://memprofiler.com/

Ich sehe nichts falsch mit Ihrer Lösung, kann aber fast garantieren, dass diese Frage als subjektiv abgeschlossen wird.

Eine andere Option ist die Verwendung von jQuery, um einen Ajax-Aufruf an den Server zu tätigen und den E-Mail-Fluss zu aktivieren. Auf diese Weise ist die Benutzeroberfläche nicht gesperrt.

Viel Glück!

Matt

0
Matt Cashatt