I have a fairly weird issue that I can't wrap my head around. So here is the code:
[HttpGet]
[AllowAnonymous]
public ActionResult SendEmail()
{
SendRegisterEmail("Test", "Testagain", "lastTest");
return View("Index");
}
public async Task<bool> SendRegisterEmail(string subject, string message, string to)
{
var email = new Email();
var mailAddress = new MailAddress(to);
email.Subject = subject;
email.Body = message;
var mailAddressCollection = new MailAddressCollection();
mailAddressCollection.Add(mailAddress);
email.To = (mailAddressCollection);
return await Send(email);
}
Now to prove my problem I broke down the code into single lines so I can see on which line it breaks. Here is what I found:
When I debug this and I step into the SendRegisterEmail method, the line that says var mailAddress = new MailAddress(to); gets run and then it exits out the function and runs the line return View("Index"); and the page loads up. The weird thing is I added a logging method on to Send at the very end and that logging never gets hit. I put a breakpoint on Send and it never got hit. It is as if creation of email crashed, decided to exit out to the caller function and continued with the code.
I don't have a faintest clue as to why.
SendRegisterEmail method is asynchronous, you're not awaiting for it.
Currently, you start a fire-and-forget operation and return View instruction is executed while you're creating a new MailAddress instance.
Most probably it throws FormatException which you can't catch because the exception is thrown in another thread.
update your action method to
public async Task <ActionResult> SendEmail()
{
await SendRegisterEmail("Test", "Testagain", "lastTest");
return View("Index");
}
Also awaiting before the return is not really needed, you can change it to
public Task<bool> SendRegisterEmail(string subject, string message, string to)
{
var email = new Email();
// ...
return Send(email);
}
but that's not related to the problem you've got.
Related
I have a class that uses Azure Service Bus and it basically sends an email by putting a message to a queue. When I write an integration tests I need to make sure I can receive the message that's been sent (no, I'm not testing Service Bus). So, here's the code for that:
[Fact]
public async Task PutEmailIntoTheQueue()
{
IlgQueueEmailInfo actual = new IlgQueueEmailInfo("from#address.com", "to#address.com", "subject", "test body", MessageBodyType.Text,
"email1#domain.com",
"email2#domain.com");
ServiceBusConnectionStringBuilder sbcb =
new ServiceBusConnectionStringBuilder(SERVICE_BUS_ENDPOINT_S, QUEUE_NAME_S, SAS_KEY_NAME, EMAIL_TESTS_SAS);
QueueClient receiveClient = new QueueClient(sbcb, ReceiveMode.ReceiveAndDelete);
bool hasBeenCalled = false;
//
// Check values here
//
async Task ReceiveMessageHandler(Message message, CancellationToken cancellationToken)
{
Output.WriteLine("Received Message\n");
Assert.True(message.Label != null && message.ContentType != null &&
message.Label.Equals(IlgEmailQueue.MESSAGE_LABEL_S, StringComparison.InvariantCultureIgnoreCase) &&
message.ContentType.Equals("application/json", StringComparison.InvariantCultureIgnoreCase));
byte[] body = message.Body;
string msgJson = Encoding.UTF8.GetString(body);
Output.WriteLine($"json: {msgJson}\n");
IlgQueueEmailInfo emailInfo = JsonConvert.DeserializeObject<IlgQueueEmailInfo>(msgJson);
Assert.NotNull(emailInfo);
Output.WriteLine("emailInfo is not NULL");
Assert.Equal(actual, emailInfo);
Output.WriteLine($"emailInfo equals to actual: {actual == emailInfo}\n");
Output.WriteLine("Setting hasBeenCalled to True");
hasBeenCalled = true;
await receiveClient.CompleteAsync(message.SystemProperties.LockToken);
}
receiveClient.RegisterMessageHandler(
ReceiveMessageHandler,
new MessageHandlerOptions(LogMessageHandlerException) {AutoComplete = true, MaxConcurrentCalls = 1});
await _emailQueue.QueueForSendAsync(actual.FromAddress, actual.ToAddresses[0], actual.Subj, actual.Body, MessageBodyType.Text, actual.CcAddress,
actual.BccAddress);
await Task.Delay(TimeSpan.FromSeconds(5));
Assert.True(hasBeenCalled);
}
But when I run it I get an exception saying something like "There's no currently active test". What is the best way of dealing with this kind of tests?
You are testing Azure Service Bus in a way that you're sending a message on er the wire and receive it. Looking at the code, you want to verify message is contracted in a certain way. Rather than going through the whole send / receive phase, you could short-circuit it by just testing the Message constructed by your code. Alternatively, create a plugin to intercept the message just before it's sent. In case you insist on sending/receiving, your code must not exist until assertion is either executed or there's a timeout. And that's due to the fact that message handler is a synchronous API that will continue execution of the test method before the message has a change to be processed by the callback. await Task.Delay(TimeSpan.FromSeconds(5)) is not guaranteed to be sufficient.
I am having an issue using the SendMailAsync When I change it out for a normal Send it works fine. When I use SendMailAsync I get a connection has timed out and no mail is sent. Am I setting it up the wrong way?
edit:
1. I changed the variable name!!!
2.The exact error happens here await smtpClient.SendMailAsync(mailMessage); It just hangs until the connection times out, and I get an error description of "Connection has timed out" The status code returned is General Error
I'm calling the sendEmail from form the onClick event of a button for now. await unitOfWork.Instance.SendEmail(...) It's a single line of code in the onclick event
public async Task SendEmail(List<string> notificationList, string emailSubject, string emailMessage)
{
var message = new MailMessage()
{
IsBodyHtml = true,
BodyEncoding = System.Text.Encoding.UTF8,
Subject = emailSubject,
From = new MailAddress(FromEmailAddress),
Body = emailMessage
};
try
{
foreach (var emailAddress in notificationList)
{
message.To.Add(emailAddress);
}
var people = string.Join(Environment.NewLine, message.To);
MessageBox.Show(#"from: " + unitOfWork.CurrentSystemUser.Person.Email + "To: " + people);
await SendMessage(message);
}
catch (Exception E)
{
...
}
finally
{
...
}
}
private async Task SendMessage(MailMessage mailMessage)
{
if (!CanSendSmtpEmail) return;
await SendSmtpMail(mailMessage);
}
public async Task SendSmtpMail(MailMessage mailMessage)
{
SmtpClient smtpClient = new SmtpClient(SmtpClientHost, int.Parse(SmtpClientPort));
smtpClient.SendCompleted += (s, e) => {
smtpClient.Dispose();
mailMessage.Dispose();
};
await smtpClient.SendMailAsync(mailMessage);
//smtpClient.Send(mailMessage) works fine
}
Increasing SmtpClient.Timeout won't work because it only applies to the synchronous Send method (see here).
Your code is working fine for me when I test it using GMail (like explained here) Can you give it a try with GMail or a different SMTP server? I think maybe your SMTP server is "acting funny". Also maybe you can show us how are you calling SendEmail method ?
Side notes:
Your question is really interesting (at least to me) but because of the way you wrote your code (property has the same name as method) people have down-voted it (you should fix that).
Also, can I see the full exception you get? (btw: catch (Exception E) - not good, use lowercase "e" for the exception argument name).
Make sure you understand exactly how asynchronous it really is.
I had a working code that was returning something from my CommonRestClient(which is simple wrapper for HttpClient):
public async Task<IActionResult> Index()
{
var uri = _baseUri.Concat(AvalancheServiceAdresses.NodeService);
using (var client = new CommonRestClient(uri))
{
var result = await client.GetAsync<List<NodeInfoContract>>(new Uri(NodeServiceConstant.NodesContractUrl, UriKind.Relative), null);
return View(result);
}
}
It worked fine and i was happy. Until I decide to move my code from view in another class, which should incapsulate entire REST logic and provide an OOP API.
So now my index looks like:
public async Task<IActionResult> Index()
{
var nodeInfoContracts = await _configManager.GetNodesList();
return View(nodeInfoContracts);
}
where GetNodesList is
public ConfiguredTaskAwaitable<List<NodeInfoContract>> GetNodesList()
{
var uri = _baseUri.Concat(AvalancheServiceAdresses.NodeService);
using (var client = new CommonRestClient(uri))
{
return client.GetAsync<List<NodeInfoContract>>(new Uri(NodeServiceConstant.NodesContractUrl, UriKind.Relative), null);
}
}
It's clear that provided codes are equal.
But now it always throws an exception when I try to get a result. In my GetAsync method it fails on following line with TaskCanceledException:
var response = await _client.GetAsync(url).ConfigureAwait(false);
But it's interestring: when I place a breakpoint on this line and step over its works fine. So here we have race condition or similar.
Why am I getting it? I tried to place CondigureAwait false/true, combining some code, but it always throws an error when breakpoints are off. I checked timeout which is several minutes, it can't cause this error.
In the second code snippet the client is disposed before the IO completed. Use the first form.
I am new to TAP and async/await in practice in C# so I may have some bad code smells here, so be gentle. :-)
I have a service method that looks as follows:
public OzCpAddOrUpdateEmailAddressToListOutput AddOrUpdateEmailAddressToList(
OzCpAddOrUpdateEmailAddressToListInput aParams)
{
var result = new OzCpAddOrUpdateEmailAddressToListOutput();
try
{
var mailChimManager = new MailChimpManager(aParams.MailChimpApiKey);
Task<Member> mailChimpResult =
mailChimManager.Members.AddOrUpdateAsync(
aParams.Listid,
new Member
{
EmailAddress = aParams.EmailAddress
});
//Poll async task until it completes.
//Give it at most 8 seconds to do what it needs to do
var outOfTime = DateTime.Now.AddSeconds(8);
while (!mailChimpResult.IsCompleted)
{
if (DateTime.Now > outOfTime)
{
throw new Exception("Timed out waiting for MailChimp API.");
}
}
//Should there have been a problem with the call then we raise an exception
if (mailChimpResult.IsFaulted)
{
throw new Exception(
mailChimpResult.Exception?.Message ??
"Unknown mail chimp library error.",
mailChimpResult.Exception);
}
else
{
//Call to api returned without failing but unless we have
//the email address subscribed we have an issue
if (mailChimpResult.Result.Status != Status.Subscribed)
{
throw new Exception(
$"There was a problem subscribing the email address
{aParams.EmailAddress} to the mailchimp list id
{aParams.Listid}");
}
}
}
catch (Exception ex)
{
result.ResultErrors.AddFatalError(PlatformErrors.UNKNOWN, ex.Message);
}
return result;
}
But when I call in from MVC Controller action mailChimpResult.IsCompleted always returns false and eventually I hit the timeout.
I realise this is because I am not chaining the async calls as per HttpClient IsComplete always return false and because of different threads this behaviour is "expected".
However I want my service method to hide the complexity of the async nature of what it is doing and merely do what appears to be a synchronous call in my action method namely:
var mailChimpResult =
_PlatformMailChimpService.AddOrUpdateEmailAddressToList(
new OzCpAddOrUpdateEmailAddressToListInput
{
EmailAddress = aFormCollection["aEmailAddress"],
Listid = ApplicationSettings.Newsletter.MailChimpListId.Value,
MailChimpApiKey = ApplicationSettings.Newsletter.MailChimpApiKey.Value
});
if (mailChimpResult.Result == true)
{
//So something
}
Ideally you should avoid the .Result and .IsFaulted properties of the Task and Task<T> objects, that was code smell number one. When you're using these objects you should use async and await through the entire stack. Consider your service written this way instead:
public async Task<OzCpAddOrUpdateEmailAddressToListOutput>
AddOrUpdateEmailAddressToList(
OzCpAddOrUpdateEmailAddressToListInput aParams)
{
var result = new OzCpAddOrUpdateEmailAddressToListOutput();
try
{
var mailChimManager = new MailChimpManager(aParams.MailChimpApiKey);
Member mailChimpResult =
await mailChimManager.Members.AddOrUpdateAsync(
aParams.Listid,
new Member
{
EmailAddress = aParams.EmailAddress
});
}
catch (Exception ex)
{
result.ResultErrors.AddFatalError(PlatformErrors.UNKNOWN, ex.Message);
}
return result;
}
Notice that I was able to remove all of that unnecessary polling and examining of properties. We mark the method as Task<OzCpAddOrUpdateEmailAddressToListOutput> returning and decorate it with the async keyword. This allows us to use the await keyword in the method body. We await the .AddOrUpdateAsync which yields the Member.
The consuming call into the service looks similar, following the same paradigm of async and await keywords with Task or Task<T> return types:
var mailChimpResult =
await _PlatformMailChimpService.AddOrUpdateEmailAddressToList(
new OzCpAddOrUpdateEmailAddressToListInput
{
EmailAddress = aFormCollection["aEmailAddress"],
Listid = ApplicationSettings.Newsletter.MailChimpListId.Value,
MailChimpApiKey = ApplicationSettings.Newsletter.MailChimpApiKey.Value
});
if (mailChimpResult.Result == true)
{
//So something
}
It is considered best practice to postfix the "Async" word to the method, to signify that it is asynchronous, i.e.; AddOrUpdateEmailAddressToListAsync.
I'm using separate test database for testing buisness logic purposes. My business logic however consists of mainly asynchronous methods which await on other methods multiple times. I'm having problems with testing such methods and I'm running out of ideas what's the cause and how to fix it...
Here's an example of a buisness logic method that I want to test:
public async Task<string> RegisterNewUser(string name, string surname, string address, string city, string phone, string email, string password, string repeatedPassword)
{
string errorMessage = null;
Person newUser = new Person();
// _context is my instance of my class inherited from DBContext
//Getting the next ID for new user
if (_context.People.Any())
newUser.Id = await _context.People.MaxAsync(record => record.Id) + 1;
else
newUser.Id = 0;
newUser.Name = name;
newUser.Surname = surname;
newUser.Address = address;
newUser.City = city;
newUser.Phone = phone;
newUser.Email = email;
newUser.Password = password;
bool validationSuccessful = true;
if (await _context.People.CountAsync(p => p.Email == newUser.Email) > 0)
{
errorMessage = "Given email address is already taken";
validationSuccessful = false;
}
if (validationSuccessful)
{
try
{
// Adding user to database
newUser.Password = GetHashedPassword(newUser.Password);
_context.People.Add(newUser);
// Adding activation info to database
RegistrationActivation act = new RegistrationActivation() { PersonId = newUser.Id, ActivationKey = "blabla"};
_context.RegistrationActivations.Add(act);
await _context.SaveChangesAsync();
}
catch (Exception e)
{
Exception exc = e;
while (exc.InnerException != null)
{
exc = exc.InnerException;
errorMessage = "Exception - " + exc.Message;
}
}
return errorMessage;
}
}
Here's my actual test method:
[TestMethod]
public void Login()
{
Person registered = PersonTestData.CreateGoodTestUser();
string error = UnitOfWork.CrudServices.MyPersonRepository.RegisterNewUser
(registered.Name, registered.Surname, registered.Address, registered.City, registered.Phone, registered.Email, registered.Password, registered.Password).Result;
Assert.IsTrue(error == null, error);
}
UnitOfWork in the code above is just an object instantiated for each test method at the begining and disposed after it completes. It connects with the test database and gives access to buisness logic methods in repositories.
In the current form the test is going to fail with exception in RegisterNewUser with message: Awaiting operation time limit exceeded or sth like this becouse it's translated into my native language...
Now if I comment out the code for adding a user and adding activation info (4 lines right before _context.SaveChangesAsync()) the test will be passed.
Furthermore if I remove all async / await functionality from the RegisterNewUser method - that is not using await and using methods without Async suffix without deleting lines mentioned above - the test is going to be passed as well...
I would be really grateful if somebody could shed some light into this problem.
Stephen Cleary has all the answers you need when it comes to async anything - http://blog.stephencleary.com/2012/02/async-unit-tests-part-2-right-way.html.
Besides Stephen's excellent advice for async unit testing you could also try using XUnit which supports async unit testing methods out of the box e.g.
[Fact]
public async void MyTest()
{
var temp = await SomeAsyncMethod();
}