Home > database >  HTML templates could not be found in Production (NET Core 3.1)
HTML templates could not be found in Production (NET Core 3.1)

Time:02-22

I have a web application that is supposed to send verification emails to users upon signing up. I am using email templates that reside in a folder(EmailTemplate) under the root directory like so.

Solution explorer

The problem is when I deploy the release on Google Cloud Platform I get this error when signing up

Exception in production

The templates are working fine in localhost debug testing but not in cloud testing.

.csproj file

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UserSecretsId>removed intentionally</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <None Include="app.yaml" CopyToOutputDirectory="Always" />
  </ItemGroup>
 
  <ItemGroup>
    <Compile Remove="Repos\**" />
    <Content Remove="Repos\**" />
    <EmbeddedResource Remove="Repos\**" />
    <None Remove="Repos\**" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.21" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.21" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.21" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.21">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Http.Polly" Version="3.1.15" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.5" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.5" />
  </ItemGroup>

</Project>

Email service that is using the templates

 public class EmailService : IEmailService
    {
       
        private const string templatePath = @"EmailTemplate/{0}.html";
        private readonly SMTPConfigModel _smtpConfig;

        public EmailService(IOptions<SMTPConfigModel> smtpConfig)
        {
            _smtpConfig = smtpConfig.Value;
        }

        public async Task SendTestEmail(EmailDataModel testEmailModel)
        {
            testEmailModel.Subject = FillPlaceholders("Hello {{UserName}} this test subject", testEmailModel.PlaceHolders);
            testEmailModel.Body = FillPlaceholders(EmailBodyTemplateGetter("TestEmail"), testEmailModel.PlaceHolders);
            await SendEmail(testEmailModel);
            
        }

        public async Task SendVerificationEmail(EmailDataModel emailVerifyModel)
        {
            emailVerifyModel.Subject = FillPlaceholders("Please verify your email", emailVerifyModel.PlaceHolders);
            emailVerifyModel.Body = FillPlaceholders(EmailBodyTemplateGetter("EmailVerification"), emailVerifyModel.PlaceHolders);
            await SendEmail(emailVerifyModel);
        }

        private async Task SendEmail(EmailDataModel email)
        {
            MailMessage mail = new MailMessage
            {
                Subject = email.Subject,
                Body = email.Body,
                From = new MailAddress(_smtpConfig.SenderAddress, _smtpConfig.SenderDisplayName),
                IsBodyHtml = _smtpConfig.IsBodyHTML
            };

            foreach (var item in email.ToEmails)
                mail.To.Add(item);

            NetworkCredential networkCredential = new NetworkCredential(_smtpConfig.UserName, _smtpConfig.Password);

            SmtpClient smtpClient = new SmtpClient
            {
                Host = _smtpConfig.Host,
                Port = _smtpConfig.Port,
                EnableSsl = _smtpConfig.EnableSSL,
                UseDefaultCredentials = _smtpConfig.UseDefaultCredentials,
                Credentials = networkCredential
            };

            mail.BodyEncoding = Encoding.Default;
            await smtpClient.SendMailAsync(mail);
        }

        private string EmailBodyTemplateGetter(string templateName)
        {
 =========> string filepath = string.Format(templatePath, templateName); <==========
            var body = File.ReadAllText(filepath);
            return body;
        }

        private string FillPlaceholders(string text, List<KeyValuePair<string, string>> keyValuePairs)
        {
            if (!string.IsNullOrEmpty(text) && keyValuePairs != null)
                foreach (var placeholder in keyValuePairs)
                    if (text.Contains(placeholder.Key))
                        text = text.Replace(placeholder.Key, placeholder.Value);
            return text;
        }

    }

Calling method in class Register.cs

 public async Task<IActionResult> OnPostAsync(string returnUrl = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
            if (ModelState.IsValid)
            {
                var user = new PtUser { UserName = Input.Email, Email = Input.Email, FirstName = Input.FirstName, LastName = Input.LastName };
                var result = await _userManager.CreateAsync(user, Input.Password);
                if (result.Succeeded)
                {
                    _logger.LogInformation("User created a new account with password.");

                    var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                    code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                    var callbackUrl = Url.Page(
                        "/Account/ConfirmEmail",
                        pageHandler: null,
                        values: new { area = "Identity", userId = user.Id, code = code, returnUrl = returnUrl },
                        protocol: Request.Scheme);

                    EmailDataModel emailDataModel = new EmailDataModel() 
                    {
                        ToEmails = new List<string>() { Input.Email },
                        PlaceHolders = new List<KeyValuePair<string, string>>()
                        {
                            new KeyValuePair<string, string>("{{UserName}}", user.FirstName),
                            new KeyValuePair<string, string>("{{Link}}", callbackUrl)
                        }
                    };

             =======> await _emailService.SendVerificationEmail(emailDataModel); <============


                    if (_userManager.Options.SignIn.RequireConfirmedAccount)
                    {
                        return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
                    }
                    else
                    {
                        await _signInManager.SignInAsync(user, isPersistent: false);
                        return LocalRedirect(returnUrl);
                    }
                }
                foreach (var error in result.Errors)
                {
                    ModelState.AddModelError(string.Empty, error.Description);
                }
            }

            // If we got this far, something failed, redisplay form
            return Page();
        }

CodePudding user response:

As per asp.net core 3.1 static files are accessible via a path relative to the web root. So any files outside of the wwwroot cannot be accessed in production. In that case you have to move your EmailTemplate folder inside the wwwroot as you can see the picture below:

enter image description here

And then in Startup.Configure you have to add reference of this middleware app.UseStaticFiles(); while you would browse the directory you have to modify code like this way

Note: In that case your path would be like wwwroot/EmailTemplate/EmailTemplate.html and your code should be like : private const string templatePath = @"wwwroot/EmailTemplate/{0}.html";

Hope above steps guided you accordingly.

  • Related