HTML 在生产环境中找不到模板 (NET Core 3.1)
HTML templates could not be found in Production (NET Core 3.1)
我有一个 Web 应用程序,它应该在注册时向用户发送验证电子邮件。我正在使用驻留在根目录下的文件夹 (EmailTemplate) 中的电子邮件模板。
问题是当我在 Google Cloud Platform 上部署版本时,我在注册时遇到此错误
模板在本地主机调试测试中运行良好,但在云测试中运行不正常。
.csproj 文件
<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>
正在使用模板的电子邮件服务
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;
}
}
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();
}
根据 asp.net core 3.1
可以通过相对于 Web 根目录的路径访问静态文件。因此在生产环境中无法访问 wwwroot
之外的任何文件。在这种情况下,您必须将 EmailTemplate
文件夹移动到 wwwroot
中,如下图所示:
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";
希望以上步骤能相应地指导您。
我有一个 Web 应用程序,它应该在注册时向用户发送验证电子邮件。我正在使用驻留在根目录下的文件夹 (EmailTemplate) 中的电子邮件模板。
问题是当我在 Google Cloud Platform 上部署版本时,我在注册时遇到此错误
模板在本地主机调试测试中运行良好,但在云测试中运行不正常。
.csproj 文件
<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>
正在使用模板的电子邮件服务
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;
}
}
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();
}
根据 asp.net core 3.1
可以通过相对于 Web 根目录的路径访问静态文件。因此在生产环境中无法访问 wwwroot
之外的任何文件。在这种情况下,您必须将 EmailTemplate
文件夹移动到 wwwroot
中,如下图所示:
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";
希望以上步骤能相应地指导您。