I'm attempting to create an Azure WebJob that will execute Functions.Methods on a set timer. I have setup a simple WebJob Project in VS2017 and added Microsoft.Azure.Webjobs.Extensions to allow me to reference Timers.
As a learning process, i'm attempting to create a WebJob that will call a Function Method to send an email to my address when the TimerTrigger Fires (send an email every 60 seconds for example)
My Main void looks like this:
public static void Main()
{
var config = new JobHostConfiguration();
config.Tracing.ConsoleLevel = System.Diagnostics.TraceLevel.Verbose;
config.UseTimers();
var host = new JobHost();
host.RunAndBlock();
}
The Function method i would like to call looks like this:
[NoAutomaticTrigger] //See below note on [NoAutomaticTrigger]
public static void SendMail([TimerTrigger("00:00:10", RunOnStartup = true)] TextWriter log)
{
log.WriteLine("[" + DateTime.Now.ToString() + "] Preparing to send Mail");
MailMessage Message = new MailMessage();
SmtpClient MailClient = new SmtpClient();
String FromAddress = "****";
String FromName = "****";
String Password = "****;
String ToAddress = "****";
MailClient.UseDefaultCredentials = false;
MailClient.Credentials = new System.Net.NetworkCredential(FromAddress, Password);
MailClient.Port = 587;
MailClient.Host = "****";
MailClient.DeliveryMethod = SmtpDeliveryMethod.Network;
MailClient.EnableSsl = true;
Message.To.Add(ToAddress);
Message.From = new MailAddress(FromAddress, FromName);
Message.Subject = "WebJob | Test Mail";
Message.Body = "TimerTrigger Function Sendmail triggered at: " + DateTime.Now.ToString();
MailClient.Send(Message);
log.WriteLine("[" + DateTime.Now.ToString() + "] Mail Successfully Sent");
}
Now if i Debug the above locally i see this
The SendMail Method Never fires.
Now if I upload this as a WebJob to azure, then navigate to WebJob Dashboard/Functions/Function Invocation Log and "Run Function" I receive an e-mail shortly after from My SendMail Method with the following output:
[5/20/2017 11:47:31 AM] Preparing to send Mail
[5/20/2017 11:47:32 AM] Mail Successfully Sent
RE: [NoAutomaticTrigger]
One suggestion I've had was to remove the [NoAutomaticTrigger] attribute from the SendMail Method as this tells the jobhost to ignore the timer but if I do this I get:
No job functions found. Try making your job classes and methods public. If you're using binding extensions (e.g. ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. config.UseServiceBus(), config.UseTimers(), etc.).
So the question is, how can I make my sendmail method fire on the specified [TimerTrigger] when running in Azure as a WebJob
Thanks in advance
So the question is, how can I make my sendmail method fire on the specified [TimerTrigger] when running in Azure as a WebJob
Based on your code, I found that you missed to pass the config parameter to JobHost.
var host = new JobHost(config);
In addition, the NoAutomaticTrigger attribute did need to be removed and the TimerTrigger attribute should be applied to a parameter whose type is TimerInfo. Code below is for your reference.
public static void SendMail([TimerTrigger("00:00:10")] TimerInfo timer, TextWriter log)
After that, the TimerTrigger will work.
Related
I have a Contact page where users are able to complete a form which is sent to a Controller.
This Controller generates an email based on form data and returns a success view to the user.
The problem is that the success view is not displayed until email sending is completed, but this takes too much time (around 30 seconds), which is not acceptable.
First, I tried synchronous sending:
public ActionResult Contact(string TestMessage)
{
// Prepare SMTP client
SmtpClient Client = new SmtpClient()
{
DeliveryMethod = SmtpDeliveryMethod.Network,
EnableSsl = true,
Host = "smtp.test.com",
Port = 587,
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential("smtpuser", "smtppass")
};
// Prepare message
MailMessage MailMessage = new MailMessage()
{
From = new MailAddress("sender#mydomain.com"),
Subject = "Test",
BodyEncoding = System.Text.Encoding.UTF8,
SubjectEncoding = System.Text.Encoding.UTF8
};
MailMessage.To.Add(new MailAddress("recipient#mydomain.com"));
MailMessage.Body = TestMessage;
// Send mail
Client.Send(MailMessage);
// Return success view
return View("ContactSuccess");
}
After that I tried asynchonous sending. I have spent hours reading StackOverflow questions to find the miracle solution. But the result is always the same, page is still waiting for email sending to be completed.
Asynchronous attempt 1
// Send mail
Client.SendCompleted += (s, e) =>
{
Client.Dispose();
MailMessage.Dispose();
};
System.Threading.ThreadPool.QueueUserWorkItem(o =>
Client.SendAsync(MailMessage, Tuple.Create(Client, MailMessage)));
Asynchronous attempt 2
System.Threading.Tasks.Task.Run(() => SendViaAsync(MailMessage));
Asynchronous attempt 3
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
cancellationToken => Client.Send(MailMessage)
);
Asynchronous attempt 4
// Send mail
System.Threading.ThreadPool.QueueUserWorkItem(SendViaAsync, MailMessage);
With added methods:
private void SendViaAsync(object MailMessageObject)
{
MailMessage MailMessage = (MailMessage)MailMessageObject;
SmtpClient Client = Utilities.CreateEmailClient();
Client.SendCompleted += new SendCompletedEventHandler(SmtpClient_SendCompleted);
Client.Send(MailMessage);
}
void SmtpClient_SendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
MailMessage mail = e.UserState as MailMessage;
if (!e.Cancelled && e.Error != null)
{
// error handling
}
}
Asynchronous attempt 5
public async System.Threading.Tasks.Task<ActionResult> Contact(string TestMessage)
// ...
// Send mail
await SendEmail(MailMessage);
With added method:
public async System.Threading.Tasks.Task SendEmail(MailMessage MailMessage)
{
SmtpClient Client = new SmtpClient()
{
DeliveryMethod = SmtpDeliveryMethod.Network,
EnableSsl = true,
Host = "smtp.test.com",
Port = 587,
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential("smtpuser", "smtppass")
};
await Client.SendMailAsync(MailMessage);
}
Please note that when locally testing, email is sent immediately (in both synchronous or asynchronous modes). In production only, it takes around 30 seconds. So maybe there is something like an antivirus scan in my hosting provider, but anyway I can't figure out why the success view is not sent until mail sending is completed.
I am desperate. Any help would be much appreciated.
Any attempts with await will definitely fail to accomplish what you want by definition. But in general I don't think it's wise to have an asp.net page starting up processes which may keep running beyond the lifetime of the request. And perhaps that's exactly what's happening internally: ASP.Net not finalising the request until all its child processes have finished.
Personally, I would de-couple the process altogether. First because each part will be simpler to maintain, but also because it's more secure.
Have your application dump the email, or the data to build the email, into a queue of sorts. This could be a database, a file, a folder or whatever, but some temporary storage.
Then develop a background service or daemon, which reads this queue and processes the emails. It will end up being more flexible, more scalable and safer.
I think I have understood the problem. I can't believe it.
I didn't mention it in my bug reproduction, but I am using Google reCAPTCHA in the contact form.
It seems that reCAPTCHA is very slow in production environment!
After deactivating it, it appears that email is immediately sent.
Now, I just have to replace reCAPTCHA with a faster checking mecanism.
Sorry for disturbance, I hope this will help some of us later. At least some asynchronous programming examples are listed here.
There is a builtin functionality in asp net you can use it as easy as written below
public ActionResult SendEmail(User user)
{
HostingEnvironment.QueueBackgroundWorkItem(ct =>
{
// Prepare SMTP client
SmtpClient Client = new SmtpClient()
{
DeliveryMethod = SmtpDeliveryMethod.Network,
EnableSsl = true,
Host = "smtp.test.com",
Port = 587,
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential("smtpuser", "smtppass")
};
// Prepare message
MailMessage MailMessage = new MailMessage()
{
From = new MailAddress("sender#mydomain.com"),
Subject = "Test",
BodyEncoding = System.Text.Encoding.UTF8,
SubjectEncoding = System.Text.Encoding.UTF8
};
MailMessage.To.Add(new MailAddress("recipient#mydomain.com"));
MailMessage.Body = TestMessage;
// Send mail
Client.Send(MailMessage);
});
// Return success view
return View("ContactSuccess");
}
if you want to go over advance scenarios , like handling many background jobs you can use Hangfire library
this blog post will also help you solve your problem
I'm trying to integrate SendGrid to a .Net 4.5 application using WebJobs.
I made the basic configurations required to send a basic email. I'm trying to run and test it in my local machine. I can't figure out how to push messages to the queue. I can't upgrade the .Net version of the application as of now. If it is possible to do this without using webjobs that is also fine.
Program.cs
static void Main()
{
var config = new JobHostConfiguration();
config.UseTimers();
config.Queues.MaxDequeueCount = 2;
config.Queues.MaxPollingInterval = TimeSpan.FromSeconds(4);
config.Queues.BatchSize = 2;
if (config.IsDevelopment)
{
config.UseDevelopmentSettings();
}
config.UseSendGrid();
var host = new JobHost(config);
host.RunAndBlock();
}
Functions.cs
public static void ProcessQueueMessage([QueueTrigger("queue")] string message, TextWriter log, [SendGrid(From = "no-reply#company.com", To = "employee#company.com")] out Mail mail)
{
log.WriteLine(message);
mail = new Mail();
var personalization = new Personalization();
personalization.AddBcc(new Email("employee#company.com"));
mail.AddPersonalization(personalization);
mail.Subject = "Test Email Subject";
mail.AddContent(new Content("text/html", $"The message '{message}' was successfully processed."));
}
Found the following functions:
SendGrid_Test_002.Functions.ProcessQueueMessage
ServicePointManager.DefaultConnectionLimit is set to the default value of 2. This can limit the connection throughput to services like Azure Storage. For more information, see https://aka.ms/webjobs-connections.
Job host started
I get this on the console.
Thanks in advance :)
I just had to feed the messages into the webjob queue using the following code.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
// Create the queue client.
CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
// Retrieve a reference to a container.
CloudQueue queue = queueClient.GetQueueReference("email-queue-name");
// Create the queue if it doesn't already exist
queue.CreateIfNotExists();
// Create message to be sent to the queue
CloudQueueMessage message = new CloudQueueMessage(JsonConvert.SerializeObject(new
{
From = emailContent.From,
To = emailContent.To,
Cc = emailContent.Cc,
Bcc = emailContent.Bcc,
Subject = emailContent.Subject,
Message = emailContent.Message
}).ToString());
queue.AddMessage(message);
this is how when I need to send email gives me error. But the mistake that since gives me is this:
An asynchronous operation cannot be started at this time. Asynchronous
operations may only be started within an asynchronous handler or
module or during certain events in the Page lifecycle. If this
exception occurred while executing a Page, ensure that the Page is
marked <%# Page Async="true" %>. This exception may also indicate an
attempt to call an "async void" method, which is generally unsupported
within ASP.NET request processing. Instead, the asynchronous method
should return a Task, and the caller should await it.
I have accumulated since the MVC and have used class to keep track of ie areas of the page. The reason I have used SendAsync is precisely that it goes a little faster to send email, etc..
This error only happens when I try to send email to users.
public static void NewPassword(string mail, string name, string password)
{
MailDefinition oMailDefinition = new MailDefinition();
oMailDefinition.BodyFileName = "~/MailList/emailskabelon/NewPassword.html";
oMailDefinition.From = FromMail;
Dictionary<string, string> oReplacements = new Dictionary<string, string>();
oReplacements.Add("<<navn>>", name);
oReplacements.Add("<<password>>", password);
System.Net.Mail.MailMessage oMailMessage = oMailDefinition.CreateMailMessage(mail, oReplacements, new LiteralControl());
oMailMessage.Subject = NewpasswordTitle + WebsiteName;
oMailMessage.IsBodyHtml = true;
SmtpClient smtp = new SmtpClient(AzureApi);
System.Net.NetworkCredential netcred = new System.Net.NetworkCredential(AzureName, AzurePassword);
smtp.UseDefaultCredentials = false;
smtp.EnableSsl = true;
smtp.Credentials = netcred;
smtp.Port = Convert.ToInt32("25");
smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
using (var smtpClient = new SmtpClient())
{
smtp.SendAsync(oMailMessage, null);
}
}
I've tried to do like this:
public static async NewPassword(string mail, string name, string password)
{
....
using (var smtpClient = new SmtpClient())
{
await smtp.SendAsync(oMailMessage, null);
}
i have see here: https://stackoverflow.com/a/35212320/7391454
Change your method to:
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);
}
}
And call it like:
var task = SendEmail(toEmailAddress, emailSubject, emailMessage);
var result = task.WaitAndUnwrapException();
Have a look here Asynchronously sending Emails in C#?
and here How to call asynchronous method from synchronous method in C#?
You can also try to define your async option inside a separate thread.
I believe that you already have inserted the async tag in your page.
And if everything is okay then try to put your code in below block.
this.Page.RegisterAsyncTask(new PageAsyncTask(async ctoken => {
var result = await SomeOperationAsync(ctoken);
// result operations.
}));
Not really replying your original question, but just wanted to stress that you are better off calling an email sending code without keeping the calling thread waiting. Although you are using async/await, in user's perspective, you are still waiting in the browser while the server is done sending emails. It may be few milliseconds, but still it's better to let this handled by a background worker.
So IMO, using HostingEnvironment.QueueBackgroundWorkItem(x=> SendEmail()); would be a better approach.
Having said that, you still have a slight risk of the asynchronous task being terminated if app domain recycles in the middle. But that's highly unlikely in your case I would say. Even if that happens, you can use a cancellation token and work your way around it.
MVC 5.1, .NET 4.5
In our simple web site we need to send an email. Our email service is called by a Post back event and it sends the email, then builds a result page and returns it.
This slows the page since the page has to wait for the synchronous email to be sent.
If we run the email in a different thread, can the site continue on to create the page and return it to the user as the email is being processed, or will a long running task cause the page to be not rendered.
Basically, can I send am email non blocking for performance reasons.
Note: We can't send the email request to a service or other system.
Assuming that you you use built in SmtpClient you can use SmtpClient.SendMailAsync method
smtpClient.SendMailAsync(new MailMessage{ ... });
Sends the specified message to an SMTP server for delivery as an asynchronous operation
Calling StartNew is functionally equivalent to creating a task by using one of its constructors, and then calling the Task.Start method to schedule the task for execution.
Task.Factory.StartNew(() => {
//do somethings
});
Example:
Task.Factory.StartNew(() => {
SendMail("Hello world", "email#yahoo.com", "TEST");
});
public static bool SendMail(string subject, string to, string body)
{
string fromMailAddress = ConfigurationManager.AppSettings["MailAddress"];
string fromMailPassword = ConfigurationManager.AppSettings["MailPassword"];
string fromMailName = ConfigurationManager.AppSettings["MailName"];
var networkConfig = new NetworkCredential(fromMailAddress, fromMailPassword);
var mailServer = new SmtpClient()
{
Host = ConfigurationManager.AppSettings["SmtpHost"],
UseDefaultCredentials = false,
Credentials = networkConfig
};
if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["SmtpPort"]))
mailServer.Port = Convert.ToInt32(ConfigurationManager.AppSettings["SmtpPort"]);
var message = new MailMessage()
{
Subject = subject,
SubjectEncoding = Encoding.UTF8,
IsBodyHtml = true,
BodyEncoding = Encoding.UTF8,
};
//message send config
message.To.Add(new MailAddress(to));
message.From = new MailAddress(fromMailAddress, fromMailName);
message.Body = body;
try
{
mailServer.SendAsync(message, null);
}
catch (Exception ex)
{
return false;
}
return true;
}
I have a small console application. It checks a few settings, makes some decisions, and sends an email. The problem is the email doesn't actually get sent until my application finishes. I want the email sent as soon as my method that sends the email completes.
Initially, I just created a MailMessage and called .Send(). That's when I noticed the mail was not being sent until the app finished.
Then I tried using the task parallel library.
var taskA = Task.Factory.StartNew(() => msg.Send());
Again, the messages don't get sent until my entire application finishes.
How do I sent an email when msg.send executes, not when the app completes?
SmptClient supports async sending of mail via SendAsync, however in practice in a web application this hangs the request thread.
To avoid blocking I recommend using the ThreadPool to fire off the email in a background thread. This won't block your application.
ThreadPool.QueueUserWorkItem(o => {
using (SmtpClient client = new SmtpClient(...))
{
using (MailMessage mailMessage = new MailMessage(...))
{
client.Send(mailMessage, Tuple.Create(client, mailMessage));
}
}
});
The most sure fire way to avoid delays would probably be to use a pickup directory, which will queue the message rather than send it immediately.
you should use a SMTP client. do it like this:
MailMessage mm = new MailMessage();
//fill in your message
NetworkCredential nc = new NetworkCredential(FromAddress, FromPassword);
SmtpClient sc = new SmtpClient(SmtpHost, SmtpPort);
sc.EnableSsl = true;
sc.Credentials = nc;
sc.Send(mm);
at this stage your mail will be sent.
But, sending an email is an async act, so it will take some time until you recive the mail.
Create a new MailMessage and send it with SmtpClient. It will send immediately. I will add an example.
EDIT: Populate the variables host, port with the smtp ser ver name and port number.
using (var mailer = new SmtpClient(host, port))
{
using (var message = new MailMessage(sender, recipient, subject, body) { IsBodyHtml = false })
{
mailer.UseDefaultCredentials = false;
mailer.Credentials = new NetworkCredential(user, pass);
mailer.EnableSsl = useSSL;
mailer.Timeout = Timeout;
mailer.Send(message);
}
}
If you still experience a delay, then the delay will be at the mail server.
Simply dispose the MailMessage and SmtpClient objects after the .Send() function.
SmtpClient smtpClient = new SmtpClient("server", 25);
smtpClient.UseDefaultCredentials = true;
MailMessage message = new MailMessage("ToAddress","FromAddress");
message.Subject = "Test email";
message.Body = "Test email";
smtpClient.Send(message);
message.Dispose();
smtpClient.Dispose();
Use SmtpClient with setting:
smtpClient.ServicePoint.MaxIdleTime = 2;
https://weblogs.asp.net/stanleygu/tip-14-solve-smtpclient-issues-of-delayed-email-and-high-cpu-usage