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.Configure
you have to add reference of this middlewareapp.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.