Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebAPI V3 with ASP.NET WebAPI #259

Closed
b1tzer0 opened this issue Jun 29, 2016 · 22 comments
Closed

WebAPI V3 with ASP.NET WebAPI #259

b1tzer0 opened this issue Jun 29, 2016 · 22 comments
Labels
status: help wanted requesting help from the community type: question question directed at the library

Comments

@b1tzer0
Copy link

b1tzer0 commented Jun 29, 2016

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.

@thinkingserious thinkingserious added type: question question directed at the library status: help wanted requesting help from the community labels Jun 29, 2016
@thinkingserious
Copy link
Contributor

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!

@b1tzer0
Copy link
Author

b1tzer0 commented Jun 29, 2016

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

@thinkingserious
Copy link
Contributor

Thanks for sharing your solution with the community!

Please email us at [email protected] with your T-shirt size and mailing address :)

@thinkingserious
Copy link
Contributor

Received your email, thanks! We appreciate the kind words.

@PandaBoy00
Copy link

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 + ".");
                }
            }

        }

@thinkingserious
Copy link
Contributor

@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/

@b1tzer0
Copy link
Author

b1tzer0 commented Jun 29, 2016

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...

@thinkingserious
Copy link
Contributor

@b1tzer0,

Thanks for the follow up!

@shyamal890
Copy link

shyamal890 commented Jul 7, 2016

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?

@thinkingserious
Copy link
Contributor

@shyamal890 Could you please provide the relevant code so we can help you troubleshoot?

@PandaBoy00
Copy link

@shyamal890 I think the GetAwaiter method isn't implemented in the HTTP.Client. If you use await, you will get that error.

@shyamal890
Copy link

shyamal890 commented Jul 7, 2016

@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);
            }
        }

@PandaBoy00
Copy link

@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?

@thinkingserious
Copy link
Contributor

@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?

@thinkingserious
Copy link
Contributor

We received your CLA @PandaBoy00! Thanks!

@shyamal890
Copy link

What does CLA mean?

@thinkingserious
Copy link
Contributor

@shyamal890 https://github.com/sendgrid/sendgrid-csharp/blob/master/CONTRIBUTING.md#cla

@shyamal890
Copy link

So finally can I use await or not possible as of now. Any alternative method?

@thinkingserious
Copy link
Contributor

This solution appears to work: #259 (comment)

But we will investigate this deeper for a proper fix: #259 (comment)

@thinkingserious
Copy link
Contributor

@shyamal890 please follow this ticket: sendgrid/csharp-http-client#9

Though, I'll post an update here when we are able to fix.

@thinkingserious
Copy link
Contributor

@shyamal890, @PandaBoy00, @b1tzer0,

I believe I've made a proper fix, if you have a moment, please take a look:

  1. This is the client code that was modified
  2. Here is the new working example.

I'm planning to merge this one in today and then update the dependency here. It looks like this will be a breaking change.

@thinkingserious
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: help wanted requesting help from the community type: question question directed at the library
Projects
None yet
Development

No branches or pull requests

4 participants