I have limited experience with .NET async Tasks, but I've used BackgroundWorker and IAsyncResult in the past. The code below is in an MVC4 project. When there are no errors, everything works fine. The problem is when something fails. There seems to be something unexpected happening with the async call which I'm at a loss to explain.
Currently, the SMTP server settings are set to a non-existant server so the code bombs out at the async call to await smtp.SendMailAsync(message);
. Rather than the call to the mail server generating an error, the error seems to come from somewhere in the user creation process "The Name is already taken." This is definitely not the case, as any randomly entered username causes the message (eg: "asdhadsgasgdjasdgj@sadasd.com").
What I'd expect:
- The user is created
- Roles are added
- The call to send the email fails, generating a 'Connection refused to mail server' error.
What actually happens:
- The user is created
- Roles are added
- The call to send the email fails, but the error message comes from somewhere in .NET's user creation logic in IUserStore.
I get the expected result locally, but on the server I get the bizarre behaviour.
The create user method seeming to cause the error:
public virtual Task CreateAsync(User user)
{
if (user == null)
throw new ArgumentNullException("user");
return Task.Factory.StartNew(() =>
{
using (SqlConnection cn = new SqlConnection(DbHelper.ConnectionString))
{
cn.Insert<User>(user);
}
});
}
The action in registration controller:
[POST("register")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel vm)
{
if (ModelState.IsValid)
{
var user = AutoMapper.Mapper.Map<User>(vm);
user.PasswordDate = DateTime.Now;
var result = await _userManager.CreateAsync(user); // Error message seems to come from here
if (result.Succeeded)
{
// Add roles
//...
foreach (var area in vm.AllowedAreas.Where(a => a.Value))
await _userManager.AddClaimAsync(user.Id, new Claim("Area", area.Key));
// Generate an email and send it out
var thanksForRegisteringVm = new ThanksForRegisteringViewModel();
thanksForRegisteringVm.FullName = vm.FullName;
thanksForRegisteringVm.EmailAddress = vm.EmailAddress;
var emailBody = RenderViewAsString.Render("~/Views/Emails/ThanksForRegistering.cshtml", thanksForRegisteringVm);
var message = new MailMessage()
{
Subject = "Thanks for Registering",
Body = emailBody,
IsBodyHtml = true
};
message.To.Add(vm.EmailAddress);
var smtp = new SmtpClient();
await smtp.SendMailAsync(message); // this will definitely be the true source of the error - a non existant mail server in web.config
return RedirectToAction("ThanksForRegistering");
}
else
{
foreach (var error in result.Errors)
ModelState.AddModelError("", error);
}
}
// If we got this far, something failed, redisplay form
setUpRegisterViewModel(vm);
return View(vm);
}
Update: We've narrowed this down to a difference somewhere in the IIS config between local, dev and live. Local and dev work, live doesn't. Things we've eliminated:
- App pool (created a new one)
- Elmah (removed from live)
- applicationhost.config
The Action Method is getting called twice, regardless of controller, for both GET and POST requests, but only when an exception is thrown. It's as though the first exception causes the action method to execute again.