SmtpClient.SendAsync bug in ASP.NET 2.0 - asynchronous

I may be wrong, but if you are working with SmtpClient.SendAsync in ASP.NET
2.0 and it throws an exception, the thread processing the request waits
indefinitely for the operation to complete.
To reproduce this problem, simply use an invalid SMTP address for the host
that could not be resolved when sending an email.
Note that you should set Page.Async = true to use SendAsync.
If Page.Async is set to false and Send throws an exception the thread
does not block, and the page is processed correctly.
TIA.

Note that you should set Page.Async = true to use SendAsync.
Please explain the rationale behind this. Misunderstanding what Page.Async does may be the cause of your problems.
Sorry, I was unable to get an example working that reproduced the problem.
See http://msdn.microsoft.com/en-us/magazine/cc163725.aspx (WICKED CODE: Asynchronous Pages in ASP.NET 2.0)
EDIT: Looking at your code example, I can see that you're not using RegisterAsyncTask() and the PageAsyncTask class. I think you must do this when executing asynchronous tasks on a Page where #Async is set to true. The example from MSDN Magazine looks like this:
protected void Page_Load(object sender, EventArgs e)
{
PageAsyncTask task = new PageAsyncTask(
new BeginEventHandler(BeginAsyncOperation),
new EndEventHandler(EndAsyncOperation),
new EndEventHandler(TimeoutAsyncOperation),
null
);
RegisterAsyncTask(task);
}
Inside BeginAsyncOperation, then, should you send a mail asynchronously.

RegisterAsyncTask could not be used.
Look at the BeginEventHandler delegate:
public delegate IAsyncResult BeginEventHandler(
Object sender,
EventArgs e,
AsyncCallback cb,
Object extraData
)
It should return an IAsyncResult.
Now look at the SmtpClient.SendAsync function :
public void SendAsync(
MailMessage message,
Object userToken
)
There is no return value.
Anyway this is working fine, as long as SmtpClient.SendAsync does not throw an exception.

Here is mine. Give it a try.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Using an incorrect SMTP server
SmtpClient client = new SmtpClient(#"smtp.nowhere.private");
// Specify the e-mail sender.
// Create a mailing address that includes a UTF8 character
// in the display name.
MailAddress from = new MailAddress("someone#somewhere.com",
"SOMEONE" + (char)0xD8 + " SOMEWHERE",
System.Text.Encoding.UTF8);
// Set destinations for the e-mail message.
MailAddress to = new MailAddress("someone#somewhere.com");
// Specify the message content.
MailMessage message = new MailMessage(from, to);
message.Body = "This is a test e-mail message sent by an application. ";
// Include some non-ASCII characters in body and subject.
string someArrows = new string(new char[] { '\u2190', '\u2191', '\u2192', '\u2193' });
message.Body += Environment.NewLine + someArrows;
message.BodyEncoding = System.Text.Encoding.UTF8;
message.Subject = "test message 1" + someArrows;
message.SubjectEncoding = System.Text.Encoding.UTF8;
// Set the method that is called back when the send operation ends.
client.SendCompleted += new
SendCompletedEventHandler(SendCompletedCallback);
// The userState can be any object that allows your callback
// method to identify this send operation.
// For this example, the userToken is a string constant.
string userState = "test message1";
try
{
client.SendAsync(message, userState);
}
catch (System.Net.Mail.SmtpException ex)
{
Response.Write(string.Format("Send Error [{0}].", ex.InnerException.Message));
}
finally
{
}
}
private void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
{
// Get the unique identifier for this asynchronous operation.
String token = (string)e.UserState;
if (e.Cancelled)
{
Response.Write(string.Format("[{0}] Send canceled.", token));
}
if (e.Error != null)
{
Response.Write(string.Format("[{0}] {1}", token, e.Error.ToString()));
}
else
{
Response.Write("Message sent.");
}
}
}

Related

Unable to send mail if one mail not present with ASP

I've written code for sending a newsletter, all is working fine, but there is a problem if one of the email addresses in the list, doesn't exit or the domain does not exist.
In this case the script stops immediately and the sending of the mail list is not finished.
Here is the part of the code I want to modify.
public static void SendMessage(String sender, String recipient, String message, String object)
{
try
{
MailMessage mail = new MailMessage(sender, recipient);
mail.Subject = object;
mail.IsBodyHtml = true;
mail.Body = message;
SmtpClient smtp = new SmtpClient();
smtp.Host = "my.smtp.com";
smtp.Send(mail);
}
catch (Exception e)
{ throw new Exception("AdminEmail - SendMessage >> recipient: " + recipient + " - generic error: " + e.Message); }
}
Hope somone can help me, thank you very much!
Welcome to SO.
From what I can infer from your description your are not handling the exception that is thrown by SendMessage.
Handle the exception in the caller method. Or do a dirty fix as shown below...
This is not a real fix. But will help you understand the issue...You have to determine in your calling method what to do if SendMessage throws an exception.
public static void SendMessage(String sender, String recipient, String message, String object)
{
try
{
MailMessage mail = new MailMessage(sender, recipient);
mail.Subject = object;
mail.IsBodyHtml = true;
mail.Body = message;
SmtpClient smtp = new SmtpClient();
smtp.Host = "my.smtp.com";
smtp.Send(mail);
}
catch (Exception e)
{
//Just log error and continue to process
}
}

How to resend email using SendAsync() in asp.net

I am using SendAsync to send an email. The reason I'm using async is simply to free up the UI rather than send multiple emails.
I have created the following callback event:
static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
{
var client = sender as SmtpClient;
var message = e.UserState as MailMessage;
if (e.Error.IsNotNull())
{
if (e.Error is SmtpFailedRecipientException)
{
var status = ((SmtpFailedRecipientException)(e.Error)).StatusCode;
if (status == SmtpStatusCode.MailboxBusy ||
status == SmtpStatusCode.MailboxUnavailable ||
status == SmtpStatusCode.TransactionFailed)
{
// a new message!
}
else
{
// TODO: Log other uncaught recipient failures
}
}
else
{
// TODO: Log all other failure reasons
}
}
client.Dispose();
message.Dispose();
}
As you can see I'm attempting to catch some recipients failures. If I find such an exception I want to try and resend the email.
I'm trying to work out how to resend the email safely. I'm thinking to create a new SmtpClient rather than reuse the existing one, but to be honest, I'm fairly new to .net and I'm not so sure of the implications.
Any advice would be appreciated.
Sending email asynchronously without delaying response back to the client(UI) requires a Backgroundworker in .Net. I implemented this on my site and will share the class source code with you.
using System;
using System.Collections.Generic;
using System.Web;
using System.ComponentModel; //Background worker namespace
using System.Net.Mail;
/// <summary>
/// Summary description for ClassName
/// </summary>
///
public class postmail
{
BackgroundWorker bw = new BackgroundWorker();
string email1, subject1, message1, failedemails;
public postmail(string email, string subject, string message)
{
bw.WorkerReportsProgress = false;
bw.WorkerSupportsCancellation = false;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
email1 = email;
subject1 = subject;
message1 = message;
}
public postmail()
{
// TODO: Complete member initialization
}
public void startsending() {
bw.RunWorkerAsync();
HttpContext.Current.Response.Buffer = true;
HttpContext.Current.Response.Flush(); // send all buffered output to client
HttpContext.Current.Response.End();
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
var finalemail = email1.Split(new[] { ',' }, StringSplitOptions.None);
//loop through the email addresses and send individually
for (int c = 0; c < finalemail.Length; c++) {
try
{
MailMessage mailMessage = new MailMessage();
// Sender Address
mailMessage.From = new MailAddress("emailaddress");
// Recepient Address
mailMessage.To.Add(finalemail[c].ToString());
// Subject
mailMessage.Subject = subject1.ToString();
// Body
mailMessage.Body = message1.ToString();
// format of mail message
mailMessage.IsBodyHtml = true;
// new instance of Smtpclient
SmtpClient mailSmtpClient = new SmtpClient("mail server");
//mailSmtpClient.EnableSsl = true;
mailSmtpClient.Credentials = new System.Net.NetworkCredential("emailaddress", "password");
// mail sent
Object userState = mailMessage;
mailSmtpClient.SendAsync(mailMessage, userState);
}
catch (Exception exc)
{
//fix for you
var ext = exc.ToString(); //catch exception for failed message
failedemails = failedemails + finalemail[c] + ","; //create a string of failed emails
}
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//called when the background process is done working
if(failedemails != null){
postmail(failedemails, subject1, message1); //resend the failed email
startsending();
}
}
}
Your concept might not be exact like mine but the key methods are:
Create an event handlers for the BackgroundWorker.
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = false;
bw.WorkerSupportsCancellation = false;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
try
{
//Send your mail
}
catch (Exception exc)
{
//Catch exception here and call the resend method
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//do something after completion
}
The fix i made for you was to build a string of all failed addresses, then resend them after the backgroundworker is done working. cheers!!

ThreadPool and Memory (BTStackServer) exception - .NET

In continuation with my problem here, I created a test app that uses ThreadPool threads to send emails. Here's the code I am using.
protected void btnSend_Click(object sender, EventArgs e)
{
EmailInfo em = new EmailInfo { Body = "Test", FromEmail = "test#test.com", Subject = "Test Email", To = "test#test.com" };
//txtNumEmails is a textbox where I can enter number of emails to send
for (int i = 0; i < Convert.ToInt32(this.txtNumEmails.Text); i++)
{
bool bResult = ThreadPool.QueueUserWorkItem(new WaitCallback(EmailNow), em);
}
}
public void EmailNow(object emailInfo) // Email Info object
{
EmailInfo em = emailInfo as EmailInfo;
SmtpClient client = new SmtpClient("localhost");
client.UseDefaultCredentials = true;
client.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
client.PickupDirectoryLocation = #"C:\temp\testemails\";
MailMessage m = new MailMessage();
m.To.Add(new MailAddress(em.To));
m.Body = em.Body;
m.IsBodyHtml = false;
m.From = new MailAddress(em.FromEmail);
m.Subject = em.Subject;
client.Send(m);
}
For smaller numbers (10k, 50k) they work great, but once I increase the number to 200k (which is my target), I got this exception:
It created about 186k emails before it threw this exception.
I am assuming this is not the exception caused due to lack of disk space (since i am storing all the emails locally in C:\temp\testemails but because it was low in RAM(?). As suggested by a this user, I used semaphores to limit them to 10k, but still ran in to the same issue. Here's the code for the one with semaphores.
protected void Button1_Click(object sender, EventArgs e)
{
EmailInfo em = new EmailInfo { Body = "Test", FromEmail = "test#test.com", Subject = "Test Email", To = "test#test.com" };
var semaphore = new SemaphoreSlim(10000, 10000);
for (int i = 0; i < Convert.ToInt32(this.txtNumEmails.Text); i++)
{
semaphore.Wait();
bool bResult = ThreadPool.QueueUserWorkItem( (state) => {
try
{
EmailNow(em);
}
catch (Exception)
{
throw;
}
finally
{
semaphore.Release();
}
}, null);
}
}
This one threw an exception as well but I see all 200k emails in the folder. I can definitely use try catch and gracefully exit if this exception occurs, but how would I prevent this from happening in the first place.
Try disposing SmtpClient object by either wrapping inside using block or calling dispose() explicitly.
Try reducing the 10000 to 1000 for the SemaphoreSlim constructor.

Silverlight OOB WebBrowser Exception

I've got an oob app with a webbrowser on it.
The webbrowser source is databound with a URI defined by me. The URI has a path to a webpage from my server that displays a PDF file from its hardrive.
Note that all this is done on a local network.
URI example: uri = new Uri(#"http://ServerName/ProjectName/PDFViewer.aspx?pdf=somePDF.pdf");
Page code-behind:
protected void Page_Load(object sender, EventArgs e)
{
string myURL = Request.Url.ToString();
string[] ParamArray = Regex.Split(myURL, "pdf=");
string Params = ParamArray[ParamArray.Length - 1];
if (Params.Length > 0)
{
Filename = Regex.Replace(Params, #"//", #"\\"); ;
if (File.Exists(Filename))
{
Response.ContentType = "Application/pdf";
Response.WriteFile(Filename); //Write the file directly to the HTTP content output stream.
Response.End();
}
else
this.Title = "PDF Not Found";
}
}
protected void Page_Load(object sender, EventArgs e) { string myURL = Request.Url.ToString(); string[] ParamArray = Regex.Split(myURL, "pdf="); //If the URL has parameters, then get them. If not, return a blank string string Params = ParamArray[ParamArray.Length - 1]; if (Params.Length > 0) { //to the called (src) web page Filename = Regex.Replace(Params, #"//", #"\\"); ; if (File.Exists(Filename)) { Response.ContentType = "Application/pdf"; Response.WriteFile(Filename); //Write the file directly to the HTTP content output stream. Response.End(); } else this.Title = "PDF Not Found"; } }
The first time I set the WebBrowser source everything it displays the PDF. But when I set the URI one second time the app throws an exception: Trying to revoke a drop target that has not been registered (Exception from HRESULT: 0x80040100).
I've done a few tests and here are the results:
1º new Uri(#"http://ServerName/ProjectName/PDFViewer.aspx?pdf=somePDF.pdf");
2º new Uri(#"http://ServerName/ProjectName/PDFViewer.aspx?pdf=someOtherPDF.pdf"); ->error
1º new Uri(#"http://ServerName/ProjectName/PDFViewer.aspx?pdf=somePDF.pdf");
2º new Uri(#"http://www.google.com"); ->error
1º new Uri(#"http://www.google.com");
2º new Uri(#"http://www.microsoft.com");
2º new Uri(#"http://ServerName/ProjectName/PDFViewer.aspx?pdf=somePDF.pdf");
3º new Uri(#"http://ServerName/ProjectName/PDFViewer.aspx?pdf=someOtherPDF.pdf"); ->error
I also forgot to say that when running the app from my browser (using a HTMLHost) the pages display just fine. Opening the pages using a browser will also work well.
It must be some problem with my aspx page. Any ideas?
Pedro
I've managed to resolve this by creating a new browser for each page. If you know of a more elegant solution please share.
I am not sure if I'm following the question/problem correctly but maybe loading the pages async and then assigning to webbrowser? Forgive me if I am off-base here.
public void ShowLink(string linkUrl)
{
if (App.Current.IsRunningOutOfBrowser)
{
var pageRequest = new WebClient();
pageRequest.DownloadStringCompleted += pageRequest_DownloadStringCompleted;
pageRequest.DownloadStringAsync(new Uri(linkUrl, UriKind.Absolute));
}
}
void pageRequest_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
webBrowserLink.NavigateToString(e.Result.ToString());
}

How to log POSTed forms submissions?

Back in the ASP classic days when i needed to write out the name/value pairs of forms submitted by POST i thru this loop into the page:
on error resume next
for each x in Request.Form
Response.AppendToLog x & "=" & Request(x)
next
It threw all the form fields and values into the log just as GETs are. Does IIS7 .net give me any better method? (this is for the dev/testing portion of the project i don't have any concern about the space or cycles used to accomplish this).
thx
You can create an http module to log all posts. It allows you to log outside of the pages, a single point of logging instead of having to add the logic to all pages where you want to log activity.
Here you have some of the code. You would have to avoid logging viewstate since is tons of useless information. So you have to add some logic to achieve this.
public class ActivityLogModule: IHttpModule
{
public void Init(HttpApplication application)
{
application.EndRequest += (new EventHandler(this.Application_EndRequest));
}
private void Application_EndRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
if (RecordActivity(context))
{
ActivityLogger.Instance.Log(application.Context.User.Identity.Name,
application.Context.Request.Url.AbsoluteUri,
application.Context.Request.Form.ToString());
}
}
public void Dispose(){}
protected bool RecordActivity(HttpContext context)
{
if (!context.Request.RequestType.Equals("POST"))
{
return false;
}
return true;
}
}
You could have something like this:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
LogPostValues();
}
private void LogPostValues()
{
string logPath = #"C:\PostedValuesLog.txt";
StringBuilder sb = new StringBuilder();
sb.AppendFormat("Logging: {0}", Request.Path);
sb.Append("Form Values");
foreach (string key in Request.Form)
{
string val = Request.Form[key];
sb.AppendFormat("{0} = {1}<br/>", key, val);
}
sb.Append(Environment.NewLine);
sb.Append("QueryString Values");
foreach (string key in Request.QueryString)
{
string val = Request.QueryString[key];
sb.AppendFormat("{0} = {1}<br/>", key, val);
}
sb.Append(Environment.NewLine);
sb.Append(Environment.NewLine);
sb.Append(Environment.NewLine);
File.AppendAllText(logPath, sb.ToString());
}
This is a crude method though and shouldn't really be used in production code. However, as this is just for development & testing, it should suffice to track what data is being posted to your page via the querystring and form.

Resources