.NET MVC Contact Form Fails to Send Email on Server - asp.net-mvc

I am using .NET MVC to send a contact form to my work email. The code runs with no errors, but the email never arrives. It works on my local server when using a gmail account, but fails on my server when trying to use my work email. To temporarily fix this issue, I have successfully added .aspx version of the contact form and it works fine. Any ideas why MVC in particular doesn't work on the server? Thanks in advance for your help.
using System;
using System.Web.Mvc;
using Eclipse.Models;
using System.Net.Mail;
namespace Eclipse.Controllers
{
public class HomeController : Controller
{
private void SendEmail(string subjectText, string bodyText)
{
MailMessage message = new MailMessage();
message.To.Add("info#emaildomain.com");
message.From = new MailAddress("noreply#emaildomain.com",
"My Company Inc.");
message.Subject = subjectText;
message.Body = bodyText;
message.IsBodyHtml = false;
SmtpClient client = new SmtpClient("mail.emaildomain.com");
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.Port = 25;
client.EnableSsl = false;
client.Credentials = new System.Net.NetworkCredential("noreply#emaildomain.com",
"password");
client.Send(message);
client.Dispose();
}
[Route("~/Contact")]
public ActionResult Contact()
{
if (TempData["FormMessage"] != null)
{
ViewBag.Message = TempData["FormMessage"].ToString();
TempData.Remove("FormMessage");
}
return View();
}
[Route("~/ResultQuestion")]
[HttpPost]
public ActionResult ResultQuestion(ResultContact form)
{
if (ModelState.IsValid)
{
try
{
System.Text.StringBuilder builder = new System.Text.StringBuilder();
builder.Append("Contact Information\n");
builder.AppendFormat("Contact Name: {0}\n", form.Name);
builder.AppendFormat("Contact Email: {0}\n", form.Email);
builder.AppendFormat("Contact Phone: {0}\n", form.Phone);
builder.AppendFormat("Gender: {0}\n", form.Gender);
builder.AppendFormat("Age: {0}\n", form.Age);
builder.AppendFormat("Event: {0}\n", form.Event);
builder.AppendFormat("Bib: {0}\n", form.Bib);
builder.Append("\nQuestions\n");
builder.Append(form.Question);
SendEmail("Result Question from web site", builder.ToString());
ModelState.Clear();
}
catch (Exception error)
{
ModelState.Clear();
TempData["FormMessage"] = string.Format("We're sorry but an error occurred while submitting your request! {0}",
error.Message);
}
}
TempData["FormMessage"] = "Thank you for contacting us. We will be in contact with you soon.";
return RedirectToAction("Contact");
}
[Route("~/EventInquiry")]
[HttpPost]
public ActionResult EventInquiry(EventContact form)
{
if (ModelState.IsValid)
{
try
{
System.Text.StringBuilder builder = new System.Text.StringBuilder();
builder.Append("Contact Information\n");
builder.AppendFormat("Contact Name: {0}\n", form.Name);
builder.AppendFormat("Contact Email: {0}\n", form.Email);
builder.AppendFormat("Contact Phone: {0}\n", form.Phone);
builder.Append("\nEvent Information\n");
builder.AppendFormat("Event Name: {0}\n", form.Event);
builder.AppendFormat("Event Date: {0}\n", form.Date);
builder.AppendFormat("Location: {0}, {1}\n", form.City, form.State);
builder.AppendFormat("Sport: {0}\n", form.Sport);
builder.AppendFormat("Expected Participants: {0}\n", form.Participants);
builder.Append("\nComments\n");
builder.Append(form.Comments);
SendEmail("Event Inquiry from web site", builder.ToString());
ModelState.Clear();
}
catch (Exception error)
{
ModelState.Clear();
TempData["FormMessage"] = string.Format("We're sorry but an error occurred while submitting your request! {0}",
error.Message);
}
}
TempData["FormMessage"] = "Thank you for contacting us. We will be in contact with you soon.";
return RedirectToAction("Contact");
}
}
}

Likely your email server is blocking the access. To make it work with gmail you had to change a setting in gmail to allow applications to send on behalf of the account. I think you'll need to do something similar with your email server but without more information it's hard to say.

Related

Email view not found for 'model' in Postal.MVC

I am using Postal.MVC5 in my ASP MVC5 project. Initially i had my smtp configuration in web.config so this piece of code was working just fine and was able to send email successfully.
public async Task<ActionResult>SendEmail(EmployeeViewModel emailModel)
{
string _errorMessage;
emailModel.To = "john#outlook.com";
emailModel.Subject = "Test Subject";
emailModel.Message = "Test Message";
await emailModel.SendAsync();
return Json(new {ErrorMessage = _errorMessage});
}
Now, i need to setup Smtpclient during runtime and followed steps shown in this stackoverflow post: How to Set SMTP Client in POSTAL MVC not in WEB.Config
public async Task<ActionResult>SendEmail(EmployeeEmailViewModel emailModel)
{
string _errorMessage;
emailModel.To = "john#outlook.com";
emailModel.Subject = "Test Subject";
emailModel.Message = "Test Message";
//new code
SmtpClient client = new SmtpClient("smtp.test.com");
client.UseDefaultCredentials = false;
client.Credentials = new NetworkCredential("secretId", "notSecretPassword");
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.Port = 111;
client.EnableSsl = true;
Postal.EmailService emailService = new EmailService(new ViewEngineCollection(), () => client);
await emailService.SendAsync(emailModel);
return Json(new {ErrorMessage = _errorMessage});
}
Now this code throws an error something like this:
System.Exception: Email view not found for EmployeeEmailViewModel. Locations searched:
at Postal.EmailViewRenderer.CreateView(String viewName, ControllerContext controllerContext)
at Postal.EmailViewRenderer.Render(Email email, String viewName)
I do have 'EmployeeEmailViewModel' view in ~/Views/Emails/EmployeeEmailViewModel.cshtml .
Any idea why i am getting this error message or how i can resolve it?
Thanks
Sanjeev

Have different version of SendEmailAsync

I am using the default ASP.NET MVC, Identity template... I want to send a confirmation email to my clients.
The default implementation which comes with a new project template, has a Register Method in AccountController.cs
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email.Trim(), Email = model.Email.Trim(), FirstName = model.FirstName.Trim(), LastName = model.LastName.Trim() };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// Send an email with this link
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
string message = "Please confirm your account by clicking here";
await UserManager.SendEmailAsync(user.Id, "Confirm your account", HttpUtility.UrlEncode(message));
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
There is a call to UserManager.SendEmailAsync, now this method is defined in Microsoft.AspNet.Identity and I don't want to change it.
The actual send email function is in IdentityConfig.cs
public class SendGridEmailService : IIdentityMessageService
{
public async Task SendAsync(IdentityMessage message)
{
var apiKey = ConfigurationManager.AppSettings["SendGridApiKey"];
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("info#mycompany.com", "DX Team"),
Subject = message.Subject,
PlainTextContent = message.Body,
HtmlContent = message.Body
};
msg.TemplateId = /* I want to pass templateId here */
msg.Personalizations[0].Substitutions.Add("confirmurl", /* I want to pass Username here */);
msg.Personalizations[0].Substitutions.Add("confirmurl", /* I want to pass confirm url here */);
msg.AddTo(new EmailAddress("info#mycompant.com", "Test User"));
var response = await client.SendEmailAsync(msg);
}
}
Now as you see, I am using Sendgrid to send email... so I don't want a message.body to email... I have made some templates and I I want to pass teplate Id with some substituation tags, like username to be replaced in the template.
So I don't want this generic SendAsync method... I want something like
SendGridAsync(SendGridMessage message)
Is it possible to add this method, so I can choose when to call SendAsync and when to call SendGridAsync?
You don't need to use the built in mail service, especially when you want to do something that's a little more complicated.
Define your own messaging service:
public interface IMyMessageService
{
Task SendConfirmationMessage(string confirmUrl, string to)
// define methods for other message types that you want to send
}
public class MyMessageServie : IMyMessageService
{
public async Task SendConfirmationMessage(string confirmUrl, string to)
{
var apiKey = ConfigurationManager.AppSettings["SendGridApiKey"];
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("info#mycompany.com", "DX Team"),
Subject = message.Subject,
PlainTextContent = message.Body,
HtmlContent = message.Body
};
msg.TemplateId = /* I want to pass templateId here */
msg.Personalizations[0].Substitutions.Add("confirmurl", confirmUrl);
msg.AddTo(new EmailAddress(to, "Test User"));
var response = await client.SendEmailAsync(msg);
}
}
Register IMyMessageService in your DI framework, and inject it into the controller where the emails are being sent from (e.g. the AccountController).
Now, your register action would look like this (assumes I've injected IMyMessageService and have an instance in _myMessageService):
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email.Trim(), Email = model.Email.Trim(), FirstName = model.FirstName.Trim(), LastName = model.LastName.Trim() };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// Send an email with this link
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// USE YOUR MESSAGE SERVICE
await _myMessageService.SendConfirmationMessage(callbackUrl, user.Email);
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}

Identity 2 - Confirm Email and then allow user to set password

I am writing an app (MVC5, Identity 2.0 and Entity Framework 6) that has a Users section that only Administrators can access. The only place to add users is in this section, unless you're registering a new Organisation (which is a parent of Users).
I have everything up and running but want to make the adding of users flow better. At present, the admin creates the user with a hard coded password and an e-mail is sent to them asking them to confirm their account. They click on this and the account is confirmed and then they have to login. Obviously, hard coding passwords is not suitable for a production app!
I would really like it for the user to be added, a random password generated, the confirm account e-mail to be sent and then once the user clicks on it, their account is confirmed and then they are re-directed to a page where they can reset their own password.
So, code ninjas, is this possible? If so, any advice would be appreciated!
Yes it is. You can remove the hard-coded password put in by the admin and replace your call to create user with var result = await UserManager.CreateAsync(user); Provide no password at this point. Make sure to dispatch a mail on creation to user to confirm email. Here's an example.
In the confirmEmail action view, you can create a password set form and post to back to confirmEmail. Sample below:
The HTTP Get ConfirmEmail
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
var confirmed = await UserManager.IsEmailConfirmedAsync(userId);
if(confirmed)
{
return await RedirectToDashboard(userId);
}
var result = await UserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
{
ViewBag.userId = userId;
ViewBag.code = code;
}
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
The HTTP POST to ConfirmEmail from your set password form:
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(SetPasswordViewModel model, string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
if (!ModelState.IsValid)
{
return View(model);
}
var result = await UserManager.AddPasswordAsync(userId, model.NewPassword);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(userId);
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
return await RedirectToDashboard(userId);
}
ViewBag.userId = userId;
ViewBag.code = code;
AddErrors(result);
return View(model);
}
Sample form to be put into the ConfirmEmailView
#using (Html.BeginForm("ConfirmEmail", "Account", new { userId = ViewBag.userId, code = ViewBag.code }, FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary("", new { #class = "color_orange" })
#Html.PasswordFor(m => m.NewPassword, new { #class = "form-control", placeholder = "New Password" })
#Html.PasswordFor(m => m.ConfirmPassword, new { #class = "form-control", placeholder = "Confirm Password" })
<input type="submit" value="Set password" class="btn" />
}
Remember to add a model to your confirmEmail view #model [ProjectName].Models.SetPasswordViewModel
And create the SetPasswordViewModel:
public class SetPasswordViewModel
{
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
I also need the same functionality. I have achieved this by a dirty code.
[AllowAnonymous]
Public ActionResult ConfirmEmail(string userId, string code)
{
if(userId == null || code == null)
{
return View("Error");
}
var user = UserManager.FindById(userId);
if(user == null)
{
}
else
{
RunAsync(ForceResetPassword(user));
return RedirectToAction("ResetPasswordAfterConfirmation","Account");
}
}
Public async Task<ActionResult> ForceResetPassword(ApplicationUser user)
{
string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = Url.Action("Reset Password", "Account", new {userId = user.Id, code = code}, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id,"Reset Password","Please reset your password by clicking here");
//Insert send email code here
}
private void RunAsync(Task task)
{
task.ContinueWith(t =>
{
}, TaskContinuationOptions.OnlyOnFaulted);
}
So they'll be receiving confirm email after admin created the account, then after they click the link, they'll be receiving another email again to reset password.
If you have a better way to achieve this, I'll be glad to know also. :)

MVC Email Confirmation (Sequence contains more than one element)

I'm currently trying to add email confirmation to my website and i'm experiencing a few problems.
I can succesfully register an account, upon registration the confirmationToken gets placed in my database and a email gets send with the query string link: http://www.example.com/RegistrationConfirmation?9ZPwZZrO-UmdpVpxXWjmRw when going to this link the controller action RegistrationConfirmation gets called and the method ConfirmAccount does a query to see if we can find a user with the confirmation token that was passed in the url.
When debugging I get the error "Sequence contains more than one element" on this line: Account user = context.Accounts.SingleOrDefault(u => u.ConfirmationToken == confirmationToken);
I'm not sure what's going wrong cause the token is unique and there are no duplicate tokens in the database.
Register HttpPost:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterModel model)
{
string confirmationToken = CreateToken();
Account account = new Account(model.Username, model.Password, model.FirstName, model.LastName, model.Email, false, confirmationToken);
if (DatabaseHandler.isUsernameDuplicate(account.Username))
{
// is duplicate // provide notification
}
else
{
Session["accountID"] = Repository.InsertAccount(new Account(model.Username, model.Password, model.FirstName, model.LastName, model.Email,false, confirmationToken));
// Email Logic
try
{
await client.SendMailAsync(message);
}
catch (Exception e)
{
ModelState.AddModelError("", "Problem sending email: " + e.Message);
}
return View("ConfirmEmail");
}
return View();
}
RegistrationConfirmation HttpGet:
[HttpGet]
[AllowAnonymous]
public ActionResult RegisterConfirmation(string Id)
{
if (ConfirmAccount(Id))
{
return RedirectToAction("ConfirmationSuccess");
}
return RedirectToAction("ConfirmationFailure");
}
ConfirmAccount method:
private bool ConfirmAccount(string confirmationToken)
{
RecipeDbContext context = new RecipeDbContext();
Account user = context.Accounts.SingleOrDefault(u => u.ConfirmationToken == confirmationToken);
if (user != null)
{
user.IsConfirmed = true;
DbSet<Account> dbSet = context.Set<Account>();
dbSet.Attach(user);
context.Entry(user).State = EntityState.Modified;
context.SaveChanges();
return true;
}
return false;
}
Remove SingleorDefault() and check the result.. you will get to know its not duplicate and if you want to avoid this error then use FirstorDefault().
Turned out nothing was wrong with the query itself, the RegisterConfirmation HttpGet did not seem to get the confirmToken value so I added "Id = Request.QueryString.ToString();" and everything is working perfect now :)

Asp.net Identity - token is not matching encoding issue?

I am trying to use asp.net identity for authentication, I am having some issues with encoding/decoding.
User clicks on forgot password link, so we call out:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[PassModelStateToTempData]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
logger.Info("reset_password attempting for {0}", model.Email);
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
{
this.Flash("Please check your email, we have sent you instructions on how to reset your password");
return RedirectToAction("ForgotPassword");
}
string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
logger.Debug("forgot_password code {0}", code);
var callbackUrl = Url.Action("ResetPassword", "Session", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
messagingService.ResetPassword(user.Email, callbackUrl);
this.Flash("Please check your email, we have sent you instructions on how to reset your password");
logger.Debug("remind_me successfully send out email to {0} {1}", model.Email, callbackUrl);
return RedirectToAction("ForgotPassword");
}
logger.Info("reset_password failed for {0}", model.Email);
// If we got this far, something failed, redisplay form
return RedirectToAction("ForgotPassword");
}
User gets email then clicks link so we run:
[HttpGet]
[AllowAnonymous]
public ActionResult ResetPassword(string code)
{
if (code == null)
{
this.Flash("Invalid login token, please enter your email address again");
return RedirectToAction("ForgotPassword");
}
var vm = new ResetPasswordViewModel
{
Code = code
};
return View(vm);
}
We pass on token into view - we ask for email and password, then user hits post and we run:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return RedirectToAction("ResetPassword");
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
logger.Info("reset_password user not found [{0}]", model.Email);
// Don't reveal that the user does not exist
return RedirectToAction("ResetPasswordConfirmation", "Session");
}
var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Session");
}
AddErrors(result);
return RedirectToAction("ResetPassword", new { code = model.Code });
}
For some reason tokens seem to not match, here are an example of the token I am getting - why the case difference?
Token:
2015-10-14 13:06:52.7545|DEBUG|Controllers.Application|forgot_password code BoUZZ9OS7rEkKMkEJzerWdds4dZLHFTHO/EkjQC2Zr8YJvCyjsXUKBRLZk8jmAqhjyxOzgqOLdJ8P/ji8y+om2ne7bcsLICzcdLSHzrP6BNEr1/+HKvHcYan+JzAX7Ifpgq7casmMj4f9esAdxejLA==
Notice the case difference:
2015-10-14 13:07:29.7164|INFO|Controllers.Application|reset_password attempting for my.email#gmail.com with token: bouzz9os7rekkmkejzerwdds4dzlhftho/ekjqc2zr8yjvcyjsxukbrlzk8jmaqhjyxozgqoldj8p/ji8y+om2ne7bcsliczcdlshzrp6bner1/+hkvhcyan+jzax7ifpgq7casmmj4f9esadxejla== -> Invalid token.
Your MVC routing is set up to generate lowercase URLs:
routes.LowercaseUrls = true;
This means that your codes are also being converted to lowercase. Possible solutions are:
Turn off LowercaseUrls if you can (or want)
Use MVC attribute routing, though this can be quite a switch.
The simplest option for you may be to simply create the URL yourself:
//Generate the URL without the code parameter
var callbackUrl = Url.Action(
"ResetPassword",
"Session",
new { userId = user.Id },
protocol: Request.Url.Scheme);
//Manually add the code, remembering to encode it
callbackUrl = callbackUrl + "&code=" HttpUtility.UrlEncode(code);

Resources