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.
The problem is when I deploy the release on Google Cloud Platform I get this error when signing up
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:
And then in
Startup.Configureyou have to add reference of this middlewareapp.UseStaticFiles();while you would browse the directory you have to modify code like this wayNote: In that case your path would be like
wwwroot/EmailTemplate/EmailTemplate.htmland your code should be like :private const string templatePath = @"wwwroot/EmailTemplate/{0}.html";
Hope above steps guided you accordingly.



