static EmailTemplateValues _values; // So we don't recreate it every single time. public static void LoadTemplates(this IWebHostEnvironment env) { Func <string, string> templatePath = file => Path.Combine("_content", "AccountModule", "email", file); _confirmPassword = new EmailTemplate(new StreamReader(env.WebRootFileProvider.GetFileInfo(templatePath(Emails.ConfirmPassword)).CreateReadStream()).ReadToEnd()); _values = new EmailTemplateValues(); }
/// <summary> /// Determines if the provided template values contains all required values for this template. /// </summary> /// <exception cref="ArgumentNullException">If <paramref name="values"/> is null.</exception> /// <param name="values">The values to check over.</param> /// <returns>True if <paramref name="values"/> contains all the values that this template needs.</returns> public bool AreAllValuesDefined(EmailTemplateValues values) { if (values == null) { throw new ArgumentNullException(nameof(values)); } return(this.ValueKeys.All(k => values.ContainsKey(k))); }
public async Task <IActionResult> TestEmail( [FromServices] ITemplatedEmailSender email, [FromServices] UserManager <ApplicationUser> users ) { var user = await users.GetUserAsync(this.User); var address = await users.GetEmailAsync(user); // This is so I can also test templates, as well as letting the user test their settings. var template = new EmailTemplate("<h1>{{ race }} are demons, change my mind. There's a link {{ @a#b?c=race }}</h1>"); var values = new EmailTemplateValues() { { "race", "Lalafells" } }; var result = await email.SendTemplatedEmailAsync(address, "This is a test email", template, values); if (!result.Succeeded) { return(this.RedirectToAction("Settings", new { emailTestError = result.Error })); } return(this.RedirectToAction("Settings")); }
/// <summary> /// Resolves this template using the given values. /// </summary> /// <exception cref="InvalidOperationException">If <paramref name="values"/> does not contain all required values.</exception> /// <param name="values">The values to use.</param> /// <param name="accessor">Needed for <paramref name="generator"/>, can be null, but only recommended for testing.</param> /// <param name="generator">Used to generate links, can be null, but only recommended for testing.</param> /// <returns>A resolved version of this template, where all placeholders are replaced with their values.</returns> public string Resolve(EmailTemplateValues values, IHttpContextAccessor accessor, LinkGenerator generator) { if (!this.AreAllValuesDefined(values)) { throw new InvalidOperationException("Not all values have been defined for this template. TODO: List which values."); } var output = new StringBuilder(this._rawText.Length); var start = 0; Action <int> commit = endIndex => // endIndex is exclusive { output.Append(this._rawText.AsSpan(start, (endIndex - start))); }; // I'm not the greatest fan of single-function parsers, but it's *just* on the edge of what I'd // consider not featureful enough to bother with a properly structured one. for (int i = 0; i < this._rawText.Length; i++) { var ch = this._rawText[i]; // If we reach a placeholder, commit the current selection, then begin processing. if (ch == '{' && i != this._rawText.Length - 1 && this._rawText[i + 1] == '{') { commit(i); i += 2; // Skip both {{ if (i >= this._rawText.Length) { break; } // Skip spaces (not all whitespace, since that's invalid syntax in this case) while (i < this._rawText.Length && this._rawText[i] == ' ') { i++; } if (i >= this._rawText.Length) { break; } // Decide what type of placeholder it is. bool isLink = this._rawText[i] == '@'; if (isLink) // Link placeholder. ex: @Account#ConfirmEmail?token=valueKey }} { // (This doesn't validate input enough, but honestly it's more effort than it's worth for a basic single-function parser.) // Also code duplication. start = ++i; string controller; string action; string query; string valueKey; // Read until a # while (i < this._rawText.Length && this._rawText[++i] != '#') { } controller = this._rawText.Substring(start, i - start); i++; start = i; // Read until a ? while (i < this._rawText.Length && this._rawText[++i] != '?') { } action = this._rawText.Substring(start, i - start); i++; start = i; // Read until an = while (i < this._rawText.Length && this._rawText[++i] != '=') { } query = this._rawText.Substring(start, i - start); i++; start = i; // Read until whitespace or bracket while (i < this._rawText.Length && this._rawText[i] != ' ' && this._rawText[i] != '}') { i++; } valueKey = this._rawText.Substring(start, i - start); // Skip spaces, read past the }}, and then we're done. (Not enough validation done here, but meh) while (i < this._rawText.Length && this._rawText[i] == ' ') { i++; } i += 2; // Trusting the input too much. start = i; string url; if (generator != null) { url = generator.GetUriByAction( accessor.HttpContext, action, controller, null, accessor.HttpContext.Request.Scheme ); } else { url = $"/{controller}/{action}"; } output.AppendFormat("<a href='{0}?{1}={2}'>here</a>", url, query, values[valueKey]); } else // Normal placeholder. ex: username }} { // Read until space or bracket. start = i; while (i < this._rawText.Length && this._rawText[i] != ' ' && this._rawText[i] != '}') { i++; } var key = this._rawText.Substring(start, i - start); output.Append(Convert.ToString(values[key])); // Skip spaces, read past the }}, and then we're done. (Not enough validation done here, but meh) while (i < this._rawText.Length && this._rawText[i] == ' ') { i++; } i += 2; // Trusting the input too much. start = i; } } } if (start < this._rawText.Length) { commit(this._rawText.Length); } return(output.ToString()); }
public async Task <EmailResult> SendTemplatedEmailAsync(string toAddress, string subject, EmailTemplate template, EmailTemplateValues values) { var config = this._config; var contents = template.Resolve(values, this._accessor, this._generator); if (config.Layout != null) { contents = config.Layout.Resolve(new EmailTemplateValues { { "body", contents } }, this._accessor, this._generator); } try { var message = this.CreateMessage(config.Smtp, toAddress); message.Subject = subject; message.Body = new TextPart(TextFormat.Html) { Text = contents }; await this.CreateClientAsync(); await this._client.SendAsync(message); } catch (Exception ex) { return(new EmailResult { Succeeded = false, Error = ex.Message }); } return(new EmailResult { Succeeded = true }); }