In C# MVC 5, is there any built in way to create email templates from .cshtml files?
I'd like to compile the .cshtml based on the model we pass in, thus creating a raw .html string.
It appears the compilation is not callable in C# MVC. We need the raw HTML output because we're trying to send emails.
If possible, avoid 3rd party libraries in the answers - trying to stick within vanilla C#.
What I've tried
Here-strings could be a possible solution, but they could get messy and hard to read. They also wouldn't show the same IDE linting as having a .cshtml file.
Absolutely you can do this yourself.
Options 1: use _partialViews with a dynamic model and renderpartial to or the option to render to HTMLstring #Html.Partial(_PartialView,(ModelClass)View.Data)
Option 2: build your template based on the user/tenant/selection on the server side.
This example is getting ready to send out a confirmation on registration, and inside the comments you can see where you can switch/load different HTML temaplates
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
// var user = await _userManager.FindByEmailAsync(model.Email);
if (result.Succeeded)
{
// Send an email with this link
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
//Email from Email Template
string Message = "Please confirm your account by clicking here";
// string body;
var webRoot = _env.WebRootPath; //get wwwroot Folder
//Get TemplateFile located at wwwroot/Templates/EmailTemplate/Register_EmailTemplate.html
var pathToFile = _env.WebRootPath
+ Path.DirectorySeparatorChar.ToString()
+ "Templates"
+ Path.DirectorySeparatorChar.ToString()
+ "EmailTemplate"
+ Path.DirectorySeparatorChar.ToString()
+ "Confirm_Account_Registration.html";
var subject = "Confirm Account Registration";
var builder = new BodyBuilder();
using (StreamReader SourceReader = System.IO.File.OpenText(pathToFile))
{
builder.HtmlBody = SourceReader.ReadToEnd();
}
//{0} : Subject
//{1} : DateTime
//{2} : Email
//{3} : Username
//{4} : Password
//{5} : Message
//{6} : callbackURL
string messageBody = string.Format(builder.HtmlBody,
subject,
String.Format("{0:dddd, d MMMM yyyy}", DateTime.Now),
model.Email,
model.Email,
model.Password,
Message,
callbackUrl
);
await _emailSender.SendEmailAsync(model.Email, subject, messageBody);
ViewData["Message"] = $"Please confirm your account by clicking this link: <a href='{callbackUrl}' class='btn btn-primary'>Confirmation Link</a>";
ViewData["MessageValue"] = "1";
_logger.LogInformation(3, "User created a new account with password.");
return RedirectToLocal(returnUrl);
}
ViewData["Message"] = $"Error creating user. Please try again later";
ViewData["MessageValue"] = "0";
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
This is a method I use to strongly type a model to a .cshtml and get Razor to render html as a variable I can use with email bodies.
public string ProcessView<TModel>(string viewName, object model, ControllerContext controllerContext, ViewDataDictionary viewData, TempDataDictionary tempData)
{
Check.NullOrEmpty(viewName, "viewName", "View name is required.");
Check.Null(viewData, "viewData", "It might be nothing, but view data was null and the original .net code has a special case for it.");
var dictionary = new ViewDataDictionary(viewData)
{
Model = model,
};
using (var writer = new System.IO.StringWriter())
{
var hostView = new RazorView(controllerContext, "nothing", String.Empty, false, Enumerable.Empty<string>());
var tempContext = new ViewContext(controllerContext, hostView, viewData, tempData, writer);
var view = FindPartialView(tempContext, viewName);
var viewContext = new ViewContext(controllerContext, view, dictionary, tempData, writer);
view.Render(viewContext, writer);
return writer.ToString();
}
}
private static IView FindPartialView(ViewContext viewContext, string partialViewName)
{
var viewEngineCollection = ViewEngines.Engines;
ViewEngineResult result = viewEngineCollection.FindPartialView(viewContext, partialViewName);
if (result.View != null)
{
return result.View;
}
StringBuilder builder = new StringBuilder();
foreach (string str in result.SearchedLocations)
{
builder.AppendLine();
builder.Append(str);
}
throw new InvalidOperationException("Could not find view named:" + partialViewName + " The following locations where checked: " + builder);
}
Related
I am working on a project which is in ASP.NET Core.
In this project users have to confirm their mail before using their panel. I have written this part of code but there is a problem.
When I debug project and I get the confirmation link, copy it and
paste it to browser, mail confirmation goes successful
but
when I send confirmation Url by email to user's mail and user clicks on it to redirect to my website confirmation fails.
I don't understand the issue because it's weird. I have hard coded some part to test it but nothing changed.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model)
{
try
{
if (ModelState.IsValid)
{
if (model.ReferralCode != null)
{
var tmpUser = userManager.Users.FirstOrDefault(f => f.IntroductionCode == model.ReferralCode);
if(tmpUser == null)
{
return Json(new { result = "error", target = "register", message = $"No user found with this({model.ReferralCode}) referral code" });
}
}
var user = new ApplicationUser
{
Id = Guid.NewGuid().ToString(),
FullName = model.FullName,
Email = model.Email,
UserName = model.Email,
Balance = 0,
ReferralCode = model.ReferralCode,
IntroductionCode = new Random().RandomString(16),
IsVerified = false
};
var signUpResut = await userManager.CreateAsync(user, model.Password);
if (signUpResut == IdentityResult.Success)
{
var token = await userManager.GenerateEmailConfirmationTokenAsync(user);
var emailActivationUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, verifyToken = token });
new MailHelper(_logger).SendConfirmationEmail(user.FullName,user.Id, user.Email, token);
_logger.Log(LogLevel.Information, $"User {model.Email} Registered Successfully.");
return Json(new { result = "success", target = "register", message = "You have successfully registered. Check your mail to activate account." });
}
return Json(new { result = "error", target = "register", message = "Something went wrong in server side." });
}
return Json(new { result = "error", target = "register", message = "Something went wrong in server side." });
}
catch (Exception exc)
{
_logger.Log(LogLevel.Critical, $"Failed Registeration : {exc.Message}");
return Json(new { result = "error", target = "register", message = "Something went wrong in server side." });
}
}
here is mail sender code
public bool SendConfirmationEmail(string name, string id, string email, string confirmationToken)
{
try
{
var mailMessage = new MimeMessage();
mailMessage.From.Add(new MailboxAddress("***", "***"));
mailMessage.To.Add(new MailboxAddress(name, email));
mailMessage.Subject = "subject";
var configurationUrl = $"https://localhost:44323/Account/ConfirmEmail?userId={id}&verifyToken={confirmationToken}";
mailMessage.Body = MailBodyMaker($"Click here", "Click here");
using (var smtpClient = new SmtpClient())
{
smtpClient.Connect("smtp.gmail.com", 465, true);
smtpClient.Authenticate("***", "****");
smtpClient.Send(mailMessage);
smtpClient.Disconnect(true);
}
return true;
}
catch(Exception exc)
{
_logger.Log(LogLevel.Critical, $"Email sending finished with exception ${exc.Message}");
return false;
}
}
The confirmation link looks like below in debug mode
https://localhost:44323/Account/ConfirmEmail?userId=9bb1a751-813b-48d2-a44c-74fd32a2db9a&verifyToken=CfDJ8A%2FFQtr0XBRFinX98FbsJc5LpPXqjstNllYq%2Br7kr6BHFfA7lBINCCoviE0nqJ6EQc1sJ7RW87jNsaR3fEkEbKoOhemFE62GCrTfn9gEizWV99lZhMrLxJPzGm1u6j3x%2FARoBqVuCVpp34ki0OZM%2BEJi31hNbwyowZ4YwoOnKjMqAOdu2bVG46WfXZBRG9AiOaFNTy326ijQmaTVDNSBl8lQR4gBWkmmRAdkcdFfOasLHD24wyUjmqgkOM2yTJ19Dw%3D%3D
and it looks like below in email body
https://localhost:44323/Account/ConfirmEmail?userId=9bb1a751-813b-48d2-a44c-74fd32a2db9a&verifyToken=CfDJ8A/FQtr0XBRFinX98FbsJc5LpPXqjstNllYq+r7kr6BHFfA7lBINCCoviE0nqJ6EQc1sJ7RW87jNsaR3fEkEbKoOhemFE62GCrTfn9gEizWV99lZhMrLxJPzGm1u6j3x/ARoBqVuCVpp34ki0OZM+EJi31hNbwyowZ4YwoOnKjMqAOdu2bVG46WfXZBRG9AiOaFNTy326ijQmaTVDNSBl8lQR4gBWkmmRAdkcdFfOasLHD24wyUjmqgkOM2yTJ19Dw==
Certain characters must be escaped in url, and your verification token contains such characters, however you put it as is into your url here:
var configurationUrl = $"https://localhost:44323/Account/ConfirmEmail?userId={id}&verifyToken={confirmationToken}";
To escape them - use Uri.EscapeDataString:
var configurationUrl = $"https://localhost:44323/Account/ConfirmEmail?userId={Uri.EscapeDataString(id)}&verifyToken={Uri.EscapeDataString(confirmationToken)}";
First of all, i have check other solutions here too but it doesn't seem to solve my problem.
The problem is, everything is working ok. If a user clicks on "Forget Password" and enters his username. He is sent an email with reset password link. Now if he clicks on the url, he is directed to the url but the resetpassword page isn't being loaded, instead the other condition, which is HttpNotFound() page is being loaded. Seriously need to know what is going wrong with my code.
This is my ForgotPassword action
[HttpPost]
public ActionResult ForgotPassword(string username)
{
string message = "";
using (MBNSystemEntities db = new MBNSystemEntities())
{
var userdetails = db.Users.Where(x => x.UserName == username).FirstOrDefault();
if (userdetails != null)
{
string validationKey = Guid.NewGuid().ToString().Substring(0, 8);
string validationPin = Guid.NewGuid().ToString().Substring(0, 4);
SendMail(userdetails.Email, validationKey, "ResetPassword");
UserValidationRequest uvr = new UserValidationRequest();
uvr.UserId = userdetails.UserId;
uvr.ValidationType = 1;
uvr.ValidationExpiryDate = DateTime.Now.AddDays(1);
uvr.ValidationKey = validationKey;
uvr.ValidationPin = validationPin;
uvr.ValidationStatus = 0;
db.UserValidationRequests.Add(uvr);
db.SaveChanges();
message = "Reset Password link has been sent to your email id.";
}
else
{
message = "Account Not Found";
}
}
ViewBag.Message = message;
return View();
}
And this is my ResetPassword action
public ActionResult ResetPassword(string validationKey)
{
if(string.IsNullOrWhiteSpace(validationKey))
{
return HttpNotFound();
}
using (MBNSystemEntities db = new MBNSystemEntities())
{
var user = db.UserValidationRequests.Where(x => x.ValidationKey == validationKey).FirstOrDefault();
if (user != null)
{
ResetPasswordModel model = new ResetPasswordModel();
model.validationKey = validationKey;
return View(model);
}
else
{
return HttpNotFound();
}
}
}
I checked by putting breakpoint. The validationKey is passed null in Forgetpassword(string validationkey) instead of the actual validationkey generated in ForgotPassword action.
This is the email Sample:
your email sent is wrong in terms of passing values
localhost:44338/Accounts/ResetPassword?vk={value}
it is looking for a parameter named vk but it should be validationKey
if you can change your SendEmail function to add validationKey instead of vk it will address your problem
or
in your ResetPassword(string validationKey) you can change the name to
ResetPassword(string vk)
I am trying to write code for email confirmation during email registration for my website. I have a bit customized User model which is similar to regular User code.
The code for sending the email confirmation is in within the Register action:
userService.InsertUser(user);
var appuser = new ApplicationUser(user); //{ UserName = model.Email, Email = model.Email };
appuser.UserName = model.Email;
//var result = await UserManager.CreateAsync(appuser, model.Password);
//if (result.Succeeded)
//{
// await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
// // Send an email with this link
string code = await UserManager.GenerateEmailConfirmationTokenAsync(appuser.Id);
//string htmlcode = HttpUtility.UrlEncode(code);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = appuser.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(appuser.Id, "Confirm your account", "Please confirm your account by clicking here");
return RedirectToAction("RegisterComplete");
From the code, I got the callbackurl which is like http://localhost:63599/Account/ConfirmEmail?userId=22&code=C3mfUtYqGzVCsrCnE5VDzC0wfsbgP6lMSVgqFk25kym3uGh0%2B%2Bltqyh0VRim1ulbwSRHBzAgUCJ1WceipbRcErddmNJqzkksZN50QO%2FqTRprpgKcW19wX33QtftwCh7zUz%2B01aghCdg8jZ8Ff%2FNVfRvbjyIW0wavN0Ueq3xl6jBmrv%2BrnbtuKLJO%2BuFE2zHF
When I type the code into the browser, it will run the ConfirmEmail action from AccountController:
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
var result = await UserManager.ConfirmEmailAsync(userId, code);
//if (result.Succeeded)
//{
// UserService userService = new UserService();
// long uid = long.Parse(userId);
// User user = userService.GetBy(u => u.UserID == uid).FirstOrDefault();
// user.IsVerified = true;
// userService.Update(user);
//}
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
But the result.Succeeded is false. And result.Error[0] : Name cannot be null or empty.
I cannot find anymore information about this error in this context and therefore I am quite stuck.
If I change the confirmation code, I will get result.Error[0]: Invalid token, this made me think that at least my token has been correct. But I don't what this error is complaining about.
Any help/pointers will be appreciated.
I have a fairly standard implementation of Identity in my Core 2 application.
I have enabled the requirement for a confirmation email. This all works well - it generates and sends the email, with a valid link, which when clicks confirms the user's account. No problems there.
The situation is though, that if an email goes missing/is deleted/never arrives for whatever reason.
What I am doing, is using the standard login method, and if IsNotAllowed then I push them towards a page which tells them they need to activate their account, check their email etc.
On that page, I have a Form with a button which Posts to a controller to regenerate and resend the email.
I literally copy and pasted the code from the standard Register method into my custom method.
The problem is when clicking the link the Activation fails. I get an InvalidToken error.
Any ideas why and how to fix?
Login Method
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(model.Email,
model.Password, model.RememberMe, lockoutOnFailure : false);
if (result.IsNotAllowed)
{
var user = await _userManager.FindByEmailAsync(model.Email);
EmailConfirmationViewModel viewModel = new EmailConfirmationViewModel();
viewModel.Email = model.Email;
return RedirectToAction("AwaitingEmailConfirmation", viewModel);
}
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return RedirectToAction("Index", "Races");
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToAction(nameof(Lockout));
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Register Method
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName,
MobileNumber = model.MobileNumber,
Marketing = model.Marketing,
Newsletter = model.Newsletter,
Source = "Website"
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
//await _createContact.CreateContactAsync(model.Email, model.FirstName,
//model.LastName, model.Marketing, model.Newsletter);
var fields = new Dictionary<string, string>();
fields.Add("firstname", model.FirstName);
fields.Add("lastname", model.LastName);
fields.Add("newsletter", model.Newsletter.ToString());
fields.Add("marketing", model.Marketing.ToString());
fields.Add("source", "Website");
string publicaccountid = "55ebcc8b-b23f-4843-9dcb-1df08811de65";
var createcontact = ElasticEmailClient.Api.Contact.AddAsync(publicAccountID: publicaccountid,
email : model.Email, field : fields, sendActivation : false);
//await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created a new account with password.");
EmailConfirmationViewModel viewModel = new EmailConfirmationViewModel();
viewModel.Email = user.Email;
return RedirectToAction("AwaitingEmailConfirmation", viewModel);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Resend Email Method
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> ResendConfirmationEmail(EmailConfirmationViewModel model)
{
var user = await _userManager.FindByEmailAsync(model.Email);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
return RedirectToAction("AwaitingEmailConfirmation", model);
}
I have the following function that re-generate a confirmation email (to complete registration) in case of an expired link.
[AllowAnonymous]
[HttpGet]
[Route("ResendConfirmationEmail")]
public async Task<IHttpActionResult> ResendConfirmationEmail(string userId = "")
{
if (string.IsNullOrWhiteSpace(userId))
{
ModelState.AddModelError("", "User Id is required");
return BadRequest(ModelState);
}
if (userId.GetHashCode() != null)
{
var code = await UserManager.GenerateEmailConfirmationTokenAsync(userId);
var callbackUrl = new Uri(Url.Link("ConfirmEmailRoute", new { userId, code = code }));
string subject = "Please confirm your account";
string body = "Please confirm your account by clicking this : link";
SendEmail(?????, callbackUrl, subject, body);
}
return Ok();
}
How can I get the email of the user from the webUsers table based on his userid?
You can fetch user from database using UserManager's FindByIdAsync and then get the email
var user = await UserManager.FindByIdAsync(userId);
var email = user.Email;