async/await seems to hang inside web method - c#

In C# I have a web service with an operation result defined as below:
OperationResult<createAccountResponse> CreateAccount(string token, createAccountServiceModel model);
Inside that method I call another method with a signature indicating it is async, like so:
var sendEmailInvite = this.SendExhibitorInviteEmailAsync(new ExhibitorInviteEmailPartialRequest()
{
CompanyId = company.CompanyID,
EventId = event.EventID
});
And inside SendExhibitorInviteEmailAsync I await a method which is also marked as async. Here is that method (snipped for brevity)
public async Task<ExhibitorInviteEmailResponse> SendExhibitorInviteEmailAsync(ExhibitorInviteEmailResolvedRequest request)
{
ExhibitorInviteEmailResponse response = null;
try
{
response = new ExhibitorInviteEmailResponse();
var apiKey = "snip";
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage();
msg.SetFrom(new EmailAddress("noreply#domain.com", "Display name"));
msg.AddTo(new EmailAddress(request.EmailAddress, request.AccountOwnerFirstName));
msg.SetTemplateId("snipped");
\
msg.SetTemplateData(dynamicTemplateData);
await client.SendEmailAsync(msg);
}
catch (Exception ex)
{
response = new ExhibitorInviteEmailResponse
{
Success = false,
Error = true,
ErrorMessage = ex.Message
};
}
return response;
}
If the email is meant to be sent (flag field in the json) then I start working on the email.If no email is meant to be sent, the whole method takes about a second which was what it was before.
The issue I am having is when I run this method from Postman or from C# generated by Postman, it seems the async code for sending the email causes the duration of the request to be 30+ seconds - so it seems like something is not waiting for the email to send? When I run this via a browser it works in 1-2 seconds with no delay.
What is the recommended flow when using Postman and async? Do all internal method's parents have to await as well?

Related

SendGrid works, but still throws exception not catchable in catch() block

Note: To the moderator that incorrectly closed this question, it's completely different from the generic nullref question. This is specific to SendGrid.
I believe I'm following pretty close to documented SendGrid usage:
public async Task<string> SendEmailSendGrid(string emailTo, string subject, string body) {
var apiKey = SafeTrim(ConfigurationManager.AppSettings["SendGridAPIKey"]);
var client = new SendGridClient(apiKey);
var from = new EmailAddress(SafeTrim(ConfigurationManager.AppSettings["SendGridEmail"]));
var to = new EmailAddress(emailTo);
var msg = MailHelper.CreateSingleEmail(from, to, subject, string.Empty, body);
try {
var response = await client.SendEmailAsync(msg);
//return response;
return "SUCCESS";
} catch (Exception ex) {
return "ERROR in SendEmailSendGrid(): " + ex.Message;
}
}
And the caller:
var result = utils.SendEmailSendGrid(decodedEmail, "email test", "This is a test email using SendGrid.");
And the error I get every time EVEN THOUGH IT WORKS and the email actually sends and arrives in my inbox:
Object reference not set to an instance of an object.
That error occurs on this line:
var response = await client.SendEmailAsync(msg);
I verified that all my variables are populated as expected - none are null or empty. I am passing an empty string to the plain text param (because I always want HTML contents), but I also tried passing that some content and it made no difference; same error.
A strange thing: this blows up so hard that my catch block is never entered. Instead, as soon as the exception is thrown, this full-screen window comes up in my VS2022:
So it is working and sending the email, but why the heavy crash? What am I doing wrong?
The method is awaitable:
public async Task<string> SendEmailSendGrid(...
Yet, the caller is not awaiting the result:
var result = utils.SendEmailSendGrid(decodedEmail, ...
Either await the result:
var result = await utils.SendEmailSendGrid(decodedEmail, ...
Or, if the invoking method is not an async method:
var result = utils.SendEmailSendGrid(decodedEmail, ...).GetAwaiter().GetResult();
Visual Studio is not breaking inside of your try/catch b/c the exception is in the .NET framework by not awaiting the result. You should be able to resolve that by enabling "just my code" in the visual studio debugger settings (IIRC).

How to test a controller POST method which returns no data in response content in .NET Core 3.1?

i am new to integration tests. I have a controller method which adds a user to the database, as shown below:
[HttpPost]
public async Task<IActionResult> CreateUserAsync([FromBody] CreateUserRequest request)
{
try
{
var command = new CreateUserCommand
{
Login = request.Login,
Password = request.Password,
FirstName = request.FirstName,
LastName = request.LastName,
MailAddress = request.MailAddress,
TokenOwnerInformation = User
};
await CommandBus.SendAsync(command);
return Ok();
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
As you have noticed my method returns no information about the user which has been added to the database - it informs about the results of handling a certain request using the status codes. I have written an integration test to check is it working properly:
[Fact]
public async Task ShouldCreateUser()
{
// Arrange
var createUserRequest = new CreateUserRequest
{
Login = "testowyLogin",
Password = "testoweHaslo",
FirstName = "Aleksander",
LastName = "Kowalski",
MailAddress = "akowalski#onet.poczta.pl"
};
var serializedCreateUserRequest = SerializeObject(createUserRequest);
// Act
var response = await HttpClient.PostAsync(ApiRoutes.CreateUserAsyncRoute,
serializedCreateUserRequest);
// Assert
response
.StatusCode
.Should()
.Be(HttpStatusCode.OK);
}
I am not sure is it enough to assert just a status code of response returned from the server. I am confused because, i don't know, shall i attach to assert section code, which would get all the users and check does it contain created user for example. I don't even have any id of such a user because my application finds a new id for the user while adding him/her to the database. I also have no idea how to test methods like that:
[HttpGet("{userId:int}")]
public async Task<IActionResult> GetUserAsync([FromRoute] int userId)
{
try
{
var query = new GetUserQuery
{
UserId = userId,
TokenOwnerInformation = User
};
var user = await QueryBus
.SendAsync<GetUserQuery, UserDto>(query);
var result = user is null
? (IActionResult) NotFound(new
{
Message = (string) _stringLocalizer[UserConstants.UserNotFoundMessageKey]
})
: Ok(user);
return result;
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
I believe i should somehow create a user firstly in Arrange section, get it's id and then use it in Act section with the GetUserAsync method called with the request sent by HttpClient. Again the same problem - no information about user is returned, after creation (by the way - it is not returned, because of my CQRS design in whole application - commands return no information). Could you please explain me how to write such a tests properly? Have i missed anything? Thanks for any help.
This is how I do it:
var response = (CreatedResult) await _controller.Post(createUserRequest);
response.StatusCode.Should().Be(StatusCodes.Status201Created);
The second line above is not necessary, just there for illustration.
Also, your response it's better when you return a 201 (Created) instead of the 200(OK) on Post verbs, like:
return Created($"api/users/{user.id}", user);
To test NotFound's:
var result = (NotFoundObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status404NotFound);
The NotFoundObjectResult assumes you are returning something. If you are just responding with a 404 and no explanation, replace NotFoundObjectResult with a NotFoundResult.
And finally InternalServerErrors:
var result = (ObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status500InternalServerError);
You can use integrationFixture for that using this NuGet package. This is an AutoFixture alternative for integration tests.
The documented examples use Get calls but you can do other calls too. Logically, you should test for the status code (OkObjectResult means 200) value and the response (which could be an empty string, that is no problem at all).
Here is the documented example for a normal Get call.
[Fact]
public async Task GetTest()
{
// arrange
using (var fixture = new Fixture<Startup>())
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var controller = fixture.Create<SearchEngineController>();
// act
var response = await controller.GetNumberOfCharacters("Hoi");
// assert
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
Assert.Equal(8, ((OkObjectResult)response.Result).Value);
}
}
}
private void SetupStableServer(FluentMockServer fluentMockServer, string response)
{
fluentMockServer.Given(Request.Create().UsingGet())
.RespondWith(Response.Create().WithBody(response, encoding: Encoding.UTF8)
.WithStatusCode(HttpStatusCode.OK));
}
In the example above, the controller is resolved using the DI described in your Startup class.
You can also do an actual REST call using using Refit. The application is self hosted inside your test.
using (var fixture = new RefitFixture<Startup, ISearchEngine>(RestService.For<ISearchEngine>))
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var refitClient = fixture.GetRefitClient();
var response = await refitClient.GetNumberOfCharacters("Hoi");
await response.EnsureSuccessStatusCodeAsync();
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
}
}

Web API Controller returning Task not always waits for task completion (puppeteer-sharp)

I have Web API controller which returns Task which is orginally created in external library service. I return Task in all the chain from serice to controller, but the problem is that when i make the HTTP call to that controller, first time when i have started the API (it`s always takes a bit longer first time) it returns the expected result perfectly, bu when I make the request second time and so on.. it returns some partial result.
When I debug it it always returns the expected correct result. Obvously there is something that is now awaited..
here is the code:
public async Task<HttpResponseMessage> DownloadBinary(string content)
{
byte[] recordToDown = await ExternalLibraryConverter.GetAsync(content);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(recordToDown)
};
result.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "Test file"
};
// added so Angular can see the Content-Disposition header
result.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/pdf");
return result;
}
and the service:
public static async Task<byte[]> GetAsync(string content)
{
await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision)
.ConfigureAwait(false);
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
}).ConfigureAwait(false);
using (var page = await browser.NewPageAsync().ConfigureAwait(false))
{
await page.SetCacheEnabledAsync(false).ConfigureAwait(false);
await page.SetContentAsync(content).ConfigureAwait(false);
await page.AddStyleTagAsync("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700").ConfigureAwait(false);
// few more styles add
var result = await page.GetContentAsync().ConfigureAwait(false);
PdfOptions pdfOptions = new PdfOptions()
{
PrintBackground = true,
MarginOptions = new PuppeteerSharp.Media.MarginOptions {
Right = "15mm", Left = "15mm", Top = "20mm", Bottom = "20mm" },
};
byte[] streamResult = await page.PdfDataAsync(pdfOptions)
.ConfigureAwait(false);
browser.Dispose();
return streamResult;
}
}
There are a lot of await in the service with extenral library as you can see. I tried using ConfigureAwait(false) everywhere where await is used, but this didnt help neither.
I think you should not do a .ConfigureAwait on the controller level, look at this article for more information: https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html.
ASP.NET team dropped the use of SynchronizationContext, so using it in your controller is pointless.
As the article states, you should still use it on your service level, as you don't know whether or not a UI could plug itself to the service and use it, but on your WEB API, you can drop it.

Unable to update previous messages after reconnection

I created a bot with a command, that allows the user to configure some sort of 'feed' to their channel.
This feed is supposed to send a message, save guild, channel and message id. And in a stand-alone update cycle, try to update the message with new information.
This all works fairly well, as long as it is within the same session.
Say the bot losses it's connection due to a discord outage, and re-connects x amount of time later, the bot no longer seems to be able to find, and thus update the message anymore.
In particular, it seems to be unable to retrieve the message by id
var message = await channel.GetMessageAsync(playtimeFeed.MessageId) as SocketUserMessage;
It's worth to note that I make use of _settings which is persisted in json format, and is loaded again upon bot reboot.
I also confirmed that the message still exists in the server at the channel, with the same message id. And that the bot has permissions to view the message history of the channel.
Thus my question, how come the GetMessageAsync is unable to retrieve a previously posted message after reconnecting?
Initialy invoked command
public async Task BindPlaytimeFeedAsync(ICommandContext context)
{
var builder = await _scumService.GetTop25PlaytimeByDate(new DateTime(), DateTime.Now);
var message = await context.Channel.SendMessageAsync(null, false, builder.Build());
_settings.PlaytimeFeed = new MessageInfo()
{
GuildId = context.Guild.Id,
ChannelId = context.Channel.Id,
MessageId = message.Id,
};
var ptFeedMessage = await context.Channel.SendMessageAsync("Playtime feed is now bound to this channel (this message self-destructs in 5 seconds)");
await Task.Delay(5000);
await ptFeedMessage.DeleteAsync();
}
The refresh interval of the feed is defined alongside the bot itself using a timer as seen below.
...
_client = new DiscordSocketClient(
new DiscordSocketConfig
{
LogLevel = LogSeverity.Verbose,
AlwaysDownloadUsers = true, // Start the cache off with updated information.
MessageCacheSize = 1000
}
);
_service = ConfigureServices();
_feedInterval = new Timer(async (e) =>
{
Console.WriteLine("doing feed stuff");
await HandleFeedsAsync();
}, null, 15000, 300000);
CmdHandler = new CommandHandler(_service, state);
...
private async Task HandleFeedsAsync()
{
var botSettings = _service.GetService<ISettings>() as BotSettings;
await HandleKdFeedAsync(botSettings.KdFeed);
await HandlePlaytimeFeedAsync(botSettings.PlaytimeFeed);
await HandleWeeklyPlaytimeFeed(botSettings.WeeklyPlaytimeFeed);
await HandleAdminFeed(botSettings);
}
And ultimately the message is overwritten using the below snippet.
private async Task HandlePlaytimeFeedAsync(MessageInfo playtimeFeed)
{
if (playtimeFeed == null)
return;
var scumService = _service.GetService<ScumService>();
var guild = _client.GetGuild(playtimeFeed.GuildId);
var channel = guild.GetTextChannel(playtimeFeed.ChannelId);
var message = await channel.GetMessageAsync(playtimeFeed.MessageId) as SocketUserMessage;
if (message == null)
return;
var builder = await scumService.GetTop25PlaytimeByDate(new DateTime(), DateTime.Now);
await message.ModifyAsync(prop =>
{
prop.Embed = builder.Build();
});
}
var message = await channel.GetMessageAsync(playtimeFeed.MessageId) as SocketUserMessage;
The GetMessageAsync method attempts to retrieve a message from cache as a SocketUserMessage, if however the message is not found in cache, a rest request is performed which would return a RestUserMessge. By performing a soft cast on the result of GetMessageAsync, you can get null if/when a RestUserMessage is returned.
When the possibility exists that the message you are dealing with can be either a Socket entity or Rest entity, simply use the interface to interact with it -- IUserMessage.

How to complete request before redirecting

Using C#/Asp.Net
I have an application that goes out to a web service. On return there's a couple of things that happen:
void Cleanup(Response response)
{
// My web service takes up to 30 seconds
// then this method is called
// I send this email
var email = SaleEmail.Create(
response.ID
DateTime.Now,
"A sale was made!");
email.Send();
// Then redirect
Response.Redirect(response.RedirectUrl, false);
Context.ApplicationInstance.CompleteRequest();
}
The idea is, on completion of the web service an email is sent, then the page is redirected.
Previously, I used a normal redirect - the result was that 90% of the emails were never sent.
I've changed the redirect pattern, however it's still not perfect - I'm guessing 25% of emails are still not coming through.
Anyone advise any improvements to the pattern I have?
Email code:
public static void Send(MailMessage message)
{
Guard.Argument.NotNull(() => message);
var c = new SmtpClient();
try
{
c.Send(message);
}
catch (Exception ex)
{
}
finally
{
c.Dispose();
message.Dispose();
}
}
Maybe
Try to implement async task method with sendAsync and await
this await will help you to wait how much needed to send email before jump to redirect
//async Task
public async Task Cleanup(Response response)
{
using (var smtpClient = new SmtpClient())
{
await smtpClient.SendAsync();...//await
}
}
you should rewrite your initialization somehow, make it look like this:
smtpClient.SendAsync();
smtpClient.SendCompleted += new SendCompletedEventHandler(smtpClient_SendCompleted);
on smtpClient_SendCompleted function write your redirection code

Categories

Resources