This is similar to issue #235
Originally, under 7.0.1 I found that when running as a synchronous operation the system would stop processing when it would hit the POST method on SendGridClient. I found that it would process if I ran the task in its own thread, but when running in its own thread I would get the following exception:
System.InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending.
I updated today to version 7.0.3 using the synchronous operation, it continued processing the page as expected, however; was presented with the same error message as though it was processed from a second thread.
Error Message:
System.InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending.
Below is some code (I am posting more than what is probably necessary, but it will give you more to look at):
ApiBaseController.cs:
protected BLL.EmailClient.IEmailClient EmailClient
{
get
{
return this.emailClient;
}
}
public ApiBaseController()
{
emailClient = BLL.EmailClient.Email.GetEmailClient(new BLL.EmailClient.EmailOptions()
{
APIKey = ConfigurationManager.AppSettings["SgBacchusAPIKey"]
});
}
ProvisionController.cs:
public IHttpActionResult Post(DTO.Registration.ProvisionOptions options)
{
this.provision = options;
provisionHub = GlobalHost.ConnectionManager.GetHubContext<ProvisionHub>();
BeginProvisioningProcess(new ApplicationDbContext());
return Ok();
}
async private void BeginProvisioningProcess(ApplicationDbContext context)
{
try
{
SendHubMessage("Starting...");
var company = CreateCompany(context);
var provisioningHandler = new ProvisioningHandler[]
{
CreatePropertySettings,
CreateUserAccountAsync
};
// Chain the logic handler together.
var chained = (ProvisioningHandler)Delegate.Combine(provisioningHandler);
// Loop through each item and see if it fails or not.
var successful = true;
foreach (ProvisioningHandler chainableLogicCall in chained.GetInvocationList())
if (!(await chainableLogicCall(company, context)))
{
successful = false;
break;
}
if (successful)
{
context.SaveChanges();
//await SendEmailAsync(emailFromAddress, provision.UserName, Resources.LanguagePack.AccountCreatedSuccessSubject, Resources.LanguagePack.AccountCreatedSuccessMsg);
EmailClient.SendMessage(emailFromAddress, provision.UserName, Resources.LanguagePack.AccountCreatedSuccessSubject, Resources.LanguagePack.AccountCreatedSuccessMsg);
}
else
{
var msg = new StringBuilder();
msg.AppendLine("Name: " + provision.CompanyName);
msg.AppendLine("Contact Phone Number: " + provision.PhoneNumber);
msg.AppendLine("Contact email: " + provision.UserName);
msg.AppendLine("Output Message: " + outputMessage);
//await SendEmailAsync(emailFromAddress, supportEmailAddress, "Account Setup Failure", msg.ToString());
}
}
catch (Exception ex)
{
var msg = new StringBuilder();
msg.AppendLine("Name: " + provision.CompanyName);
msg.AppendLine("Contact Phone Number: " + provision.PhoneNumber);
msg.AppendLine("Contact email: " + provision.UserName);
msg.AppendLine("Error Message: " + ex.Message);
//await SendEmailAsync(emailFromAddress, supportEmailAddress, "Account Setup Failure", msg.ToString());
}
finally
{
SendHubMessage("Complete");
}
}
SendGridClient.cs
public override void SendMessage(string emailFrom, string emailRecipient, string subjectLine, string HTMLMessage)
{
dynamic sg = new SendGridAPIClient(options.APIKey, "https://api.sendgrid.com");
SendGrid.Helpers.Mail.Email from = new SendGrid.Helpers.Mail.Email(emailFrom);
SendGrid.Helpers.Mail.Email to = new SendGrid.Helpers.Mail.Email(emailRecipient);
string subject = subjectLine;
Content content = new Content("text/html", HTMLMessage);
Mail mail = new Mail(from, subject, to, content);
try
{
string ret = mail.Get();
dynamic response = sg.client.mail.send.post(requestBody: ret);
Console.WriteLine(response.StatusCode);
Console.WriteLine(response.Body.ReadAsStringAsync().Result);
Console.WriteLine(response.Headers.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Now the email is sent successfully, however; the overall response for the call to POST results in a failure. I think this is likely due to a POST to SendGridClient while doing a POST to ASP.NET WebAPI.
At first glance, I'm not sure what the issue is, but reading this http://stackoverflow.com/a/28806198 makes me think that a careful examination of all asynchronous calls are in order.
I'm leaving this open and putting it on our backlog for further review. Thanks!
I read through the forum message from http://stackoverflow.com/a/28806198, I swapped all my signatures to make sure everything was async/await and that appeared to work.
Updated Code:
async public Task<IHttpActionResult> Post(DTO.Registration.ProvisionOptions options)
{
this.provision = options;
provisionHub = GlobalHost.ConnectionManager.GetHubContext<ProvisionHub>();
await BeginProvisioningProcess(new ApplicationDbContext());
return Ok();
}
async private Task BeginProvisioningProcessAsync(ApplicationDbContext context)
{
try
{
SendHubMessage("Starting...");
var company = CreateCompany(context);
var provisioningHandler = new ProvisioningHandler[]
{
CreatePropertySettingsAsync,
CreateUserAccountAsync
};
// Chain the logic handler together.
var chained = (ProvisioningHandler)Delegate.Combine(provisioningHandler);
// Loop through each item and see if it fails or not.
var successful = true;
foreach (ProvisioningHandler chainableLogicCall in chained.GetInvocationList())
if (!(await chainableLogicCall(company, context)))
{
successful = false;
break;
}
if (successful)
{
context.SaveChanges();
await EmailClient.SendMessageAsync(emailFromAddress, provision.UserName, Resources.LanguagePack.AccountCreatedSuccessSubject, Resources.LanguagePack.AccountCreatedSuccessMsg);
}
else
{
var msg = new StringBuilder();
msg.AppendLine("Name: " + provision.CompanyName);
msg.AppendLine("<br/>Contact Phone Number: " + provision.PhoneNumber);
msg.AppendLine("<br/>Contact email: " + provision.UserName);
msg.AppendLine("<br/>Output Message: " + outputMessage);
await EmailClient.SendMessageAsync(emailFromAddress, supportEmailAddress, "Account Setup Failure", msg.ToString());
}
}
catch (Exception ex)
{
var msg = new StringBuilder();
msg.AppendLine("Name: " + provision.CompanyName);
msg.AppendLine("<br/>Contact Phone Number: " + provision.PhoneNumber);
msg.AppendLine("<br/>Contact email: " + provision.UserName);
msg.AppendLine("<br/>Error Message: " + ex.Message);
await EmailClient.SendMessageAsync(emailFromAddress, supportEmailAddress, "Account Setup Failure", msg.ToString());
}
finally
{
SendHubMessage("Complete");
}
}
async public override Task SendMessageAsync(string emailFrom, string emailRecipient, string subjectLine, string HTMLMessage)
{
dynamic sg = new SendGridAPIClient(options.APIKey, "https://api.sendgrid.com");
SendGrid.Helpers.Mail.Email from = new SendGrid.Helpers.Mail.Email(emailFrom);
SendGrid.Helpers.Mail.Email to = new SendGrid.Helpers.Mail.Email(emailRecipient);
string subject = subjectLine;
Content content = new Content("text/html", HTMLMessage);
Mail mail = new Mail(from, subject, to, content);
try
{
string ret = mail.Get();
dynamic response = await sg.client.mail.send.post(requestBody: ret);
Console.WriteLine(response.StatusCode);
Console.WriteLine(response.Body.ReadAsStringAsync().Result);
Console.WriteLine(response.Headers.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Also, I had to apply await to sg.client.mail.send.post
Thanks for sharing your solution with the community!
Please email us at [email protected] with your T-shirt size and mailing address :)
Received your email, thanks! We appreciate the kind words.
I was using the exact same code but I get an runtime error "A first chance exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in System.Core.dll
Additional information: 'SendGrid.CSharp.HTTP.Client.Response' does not contain a definition for 'GetAwaiter'"
I am wondering how are you able to get around it? I am using .Net 4.5.2 and SendGrid 7.0.3 and SendGrid.CSharp.HTTP.Client 2.0.4.
async public static void SendGridSendEmail(string to, string from, string fromName, string body, string subject, string emailCampaignId, string emailListId, string emailId,
string cc, string bcc)
{
try
{
#region "Offical SendGrid API"
String apiKey = ConfigurationManager.AppSettings["SendGridApiKey"].ToString();
dynamic sg = new SendGrid.SendGridAPIClient(apiKey, "https://api.sendgrid.com");
Email fromAdd = new Email(from, fromName);
String subjectEmail = subject;
Email toAdd = new Email(to);
Content content = new Content("text/html", body);
Mail mail = new Mail(fromAdd, subjectEmail, toAdd, content);
// Email Tracking Code
TrackingSettings trackingSettings = new TrackingSettings();
ClickTracking clickTracking = new ClickTracking();
clickTracking.Enable = true;
clickTracking.EnableText = false;
//Apply Tracking Settings to the outgoing mail
mail.TrackingSettings = trackingSettings;
String ret = mail.Get();
string requestBody = ret;
//dynamic response = sg.client.mail.send.beta.post(requestBody: requestBody).ConfigureAwait(false);
dynamic response = await sg.client.mail.send.post(requestBody: requestBody);
string strStatusCode = ((object)response.StatusCode).ToString();
string strResults = ((object)response.Body.ReadAsStringAsync().Result).ToString();
string strHeader = response.Headers.ToString();
string strRet = ((object)ret).ToString();
System.Diagnostics.Debug.WriteLine(strStatusCode);
System.Diagnostics.Debug.WriteLine(strResults);
#endregion
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
using (System.Diagnostics.EventLog eventLog = new System.Diagnostics.EventLog("Application"))
{
eventLog.Source = "Application";
if (ex.InnerException != null)
eventLog.WriteEntry("Debug SendEmail Error SendGrid. Message: " + ex.Message + ". Inner Exception: " + ex.InnerException.Message);
else
eventLog.WriteEntry("Debug SendEmail Error SendGrid. Message: " + ex.Message + ".");
}
}
}
@PandaBoy00,
I believe there is an issue with the function return signature, as your return value is void.
Check this out: http://theburningmonk.com/2012/10/c-beware-of-async-void-in-your-code/
I am targeting .NET Framework 4.6.1
SendGrid 7.0.3
SendGrid.CSharp.HTTP.Client 2.0.4.
@PandaBoy00 ,
I had to change all my signatures to return at least a type of Task or Task<T> I had void once before, and had issues, not the same issue, but still issues...
@b1tzer0,
Thanks for the follow up!
I tried await SendGridService.SendEmail(from_email, emailModel.UserEmail, subject, body);
However, I am getting following error:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'SendGrid.CSharp.HTTP.Client.Response' does not contain a definition for 'GetAwaiter'
at CallSite.Target(Closure , CallSite , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at SmartTask.WebApi.Services.SendGridService.<SendEmail>d__1.MoveNext()
Is await supported anymore?
@shyamal890 Could you please provide the relevant code so we can help you troubleshoot?
@shyamal890 I think the GetAwaiter method isn't implemented in the HTTP.Client. If you use await, you will get that error.
@PandaBoy00 Ya, I thought so but the code in the issue do use await method. I wonder if I am doing something wrong.
@thinkingserious Here is my code implementation:
public static async Task SendEmail(string from_email_id, string to_email_id, string subject, string body)
{
dynamic sg = new SendGridAPIClient(sendgrid_api_key);
Email from = new Email(from_email_id);
Email to = new Email(to_email_id);
//Content content = new Content("text/plain", body);
Content content = new Content("text/html", body);
Mail mail = new Mail(from, subject, to, content);
//Email email = new Email("[email protected]");
//mail.Personalization[0].AddTo(email);
try
{
dynamic response = await sg.client.mail.send.post(requestBody: mail.Get());
var statusCode = response.StatusCode;
var result = response.Body.ReadAsStringAsync().Result;
var headers = response.Headers.ToString();
}
catch (Exception ex)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
}
@shyamal890 it depends on what you want to do with the await. I have tried several times with the await and never got it to work becox it is missing the implementation in the dependency (CSharp.HTTP.Client). The only wait I got it to work was without the await keyword. Maybe you can try and move the await to the caller method?
@PandaBoy00 @shyamal890,
Looking deeper into the client, perhaps the issue is here: https://github.com/sendgrid/csharp-http-client/blob/master/CSharpHTTPClient/Client.cs#L245, because of the .Result call there.
Thoughts?
We received your CLA @PandaBoy00! Thanks!
What does CLA mean?
So finally can I use await or not possible as of now. Any alternative method?
This solution appears to work: https://github.com/sendgrid/sendgrid-csharp/issues/259#issuecomment-229396688
But we will investigate this deeper for a proper fix: https://github.com/sendgrid/sendgrid-csharp/issues/259#issuecomment-231204883
@shyamal890 please follow this ticket: https://github.com/sendgrid/csharp-http-client/issues/9
Though, I'll post an update here when we are able to fix.
@shyamal890, @PandaBoy00, @b1tzer0,
I believe I've made a proper fix, if you have a moment, please take a look:
I'm planning to merge this one in today and then update the dependency here. It looks like this will be a breaking change.
@shyamal890, @PandaBoy00, @b1tzer0,
Fixed with:
https://github.com/sendgrid/sendgrid-csharp/releases/tag/v8.0.0
https://www.nuget.org/packages/Sendgrid/8.0.0