Example #1
0
        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)));
        }
Example #3
0
        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
            });
        }