- ABP Framework version: v7.4.4
- UI Type: Angular
- Database System: MongoDB
- Tiered (for MVC) or Auth Server Separated (for Angular): no
- Exception message and full stack trace:
- Steps to reproduce the issue:
Hello Support Team,
I have defined a shared layout for email template using CSS classes in the HTML document. But it does not render the styles.
1. Here is my shared layout template:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to TICO International Corporation</title>
<style type="text/css">
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #fafafa;
}
.email-container {
max-width: 600px;
width: 100%;
margin: 0 auto;
background-color: #ffffff;
border: 1px solid #cccccc;
}
.header {
padding: 0;
text-align: center;
color: #ffffff;
background-image: url('headerbackgroud.jpg');
background-size: cover;
background-position: center;
}
.header-content {
padding: 20px;
background: rgba(0, 86, 168, 0.8);
}
.header img {
max-width: 100%;
width: 120px;
}
.header p {
font-size: 1.4em;
font-weight: bold;
text-transform: uppercase;
margin: 10px 0;
}
.content {
padding: 36px 30px;
background-color: #ffffff;
}
.content p {
color: #000000;
}
.button {
display: inline-block;
background-color: #D32F2F;
color: #ffffff;
padding: 10px 16px;
border-radius: 10px;
text-decoration: none;
}
.footer {
background-color: #f2f2f2;
padding: 20px 0;
text-align: center;
}
.footer-info {
display: flex;
flex-direction: column;
align-items: center;
font-size: 10px;
}
.footer-info p,
.footer-info a {
margin: 2px;
}
.footer-info a {
color: #0867ec;
}
@media screen and (max-width: 600px) {
.header {
background-image: none;
}
.header-content {
padding: 20px 10px;
}
.email-container {
width: 100%;
border: none;
}
.content,
.header,
.footer {
padding: 20px 10px;
}
.footer-info {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="email-container">
<div class="header">
<div class="header-content">
<img src="https://testing.ticogroup.com/assets/images/template_email/tico.png" width="120" alt="TICO Logo" />
<p>Tico International Corporation</p>
</div>
</div>
<div class="content">
{{content}}
</div>
<div class="footer">
<div class="footer-info">
<p><strong>Head Office</strong>: 14th Floor, Discovery Complex Building, 302 Cau Giay Str., Cau Giay Ward, Hanoi City.</p>
<p><strong>Hotline:</strong> +84.24 3518 9999</p>
<p><strong>Email:</strong> info@ticogroup.com</p>
<p><strong>Website:</strong> <a href="https://testing.ticogroup.com/" target="_blank">https://testing.ticogroup.com</a></p>
</div>
</div>
</div>
</body>
</html>
2. And here is my content template:
{{ include '/Localization/Template/Layout/email.tpl' }}
<p>Hello <strong>{{model.name}}</strong>,</p>
<p>To complete the setup of your account on our software system, please click on below button to verify your email:
</p>
<p></p>
<div style="text-align: center;">
<a href="{{model.link}}" class="button">Verify Account</a>
</div>
<p>Use this password <strong>{{model.password}}</strong> to log in the system. This process takes only a minute and is an essential step to ensure you have full access to the necessary resources and tools to start your job efficiently.</p>
<p>If you encounter any issues during the verification process, feel free to contact our IT department at tech@ticogroup.com.
</p>
<p>We wish you a productive workday and a great experience at TICO!</p>
<p>Best regards,</p>
<p><strong>{{model.fullname}}</strong></p>
<p>Position: <strong>{{model.position}}</strong></p>
<p>Tico International Corporation</p>
3. Here is my templates definition:
public class EmailTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition(
name: EmailTemplateNames.EmailLayout,
displayName: new LocalizableString("EmailLayoutTemplate", "LogiPlatResource"),
defaultCultureName: "en",
isLayout: true)
.WithVirtualFilePath("/Localization/Template/Layout/email.tpl", isInlineLocalized: true)
.WithScribanEngine());
context.Add(
new TemplateDefinition(
name: EmailTemplateNames.StaffEmailConfirmation,
layout: EmailTemplateNames.EmailLayout,
displayName: new LocalizableString("StaffEmailConfirmationTemplate", "LogiPlatResource"),
defaultCultureName: "en")
.WithVirtualFilePath("/Localization/Template/Register", isInlineLocalized: false)
.WithScribanEngine());
}
}
4. Here are screenshots: Layout template: Content template:
Please, help me solve this issue: the content template cannot get the shared layout template and can not render styles
Thank you in advance
5 Answer(s)
-
0
It works for me:
public class EmailTemplateDefinitionProvider : TemplateDefinitionProvider { public override void Define(ITemplateDefinitionContext context) { context.Add( new TemplateDefinition( name: EmailTemplateNames.EmailLayout, displayName: new LocalizableString("EmailLayoutTemplate", "LogiPlatResource"), defaultCultureName: "en", isLayout: true) .WithVirtualFilePath("/Localization/Template/Layout/email.tpl", isInlineLocalized: true) .WithScribanEngine()); context.Add( new TemplateDefinition( name: EmailTemplateNames.StaffEmailConfirmation, layout: EmailTemplateNames.EmailLayout, displayName: new LocalizableString("StaffEmailConfirmationTemplate", "LogiPlatResource"), defaultCultureName: "en") .WithVirtualFilePath("/Localization/Template/Register", isInlineLocalized: false) .WithScribanEngine()); } }
[Route("api/qa/template")] public class QaTemplateController : QaController { private readonly ITemplateRenderer _templateRenderer; public QaTemplateController(ITemplateRenderer templateRenderer) { _templateRenderer = templateRenderer; } [HttpGet] public async Task<IActionResult> GetStaffEmailConfirmationTemplateAsync() { return Content( await _templateRenderer.RenderAsync( EmailTemplateNames.StaffEmailConfirmation, new { } ), "text/html"); } }
-
0
Could you share an example project to reproduce the problem with me via email? I will check it.
my email is shiwei.liang@volosoft.com
-
0
Hi mate, Thank you for supporting. The issue is of sanitization and I have solved it.
The binding model works fine with the layout template without content template. But I got an error when binding the model to the layout template with content template. Here is my method:
public virtual async Task<string> GetTemplateAsync(GetEmailTemplateInput input) { var currentLanguage = CultureInfo.CurrentCulture.Name; var corp = ObjectMapper.Map<Corporation, CorporationDto>(await _corporationRepository.GetAsync(input.CorporationId!.Value)); if (corp == null) { return await _templateRenderer.RenderAsync(input.TemplateName, new { }, currentLanguage); } if (currentLanguage != LocalizationConsts.DefaultLanguage) { corp = corp.Translate(currentLanguage); } var model = new EmailLayoutTemplate { Title = corp.Title != null ? corp.Title : string.Empty, Address = corp.Address != null ? corp.Address : string.Empty, Email = corp.Email != null ? corp.Email : string.Empty, Phone = corp.Phone != null ? corp.Phone : string.Empty, Website = corp.Website != null ? corp.Website : string.Empty }; return await _templateRenderer.RenderAsync(input.TemplateName, model, currentLanguage); }
I've debuged (the para model is not null and has properties) and found that the error occured at the last line: _templateRenderer.RenderAsync(input.TemplateName, model, currentLanguage); I'm not sure whether the _templateRenderer.RenderAsync() supports binding to the layout template? Please, help me sovle this issue.
Here is the error log:
2024-03-13 23:28:55.361 +07:00 [ERR] ---------- RemoteServiceErrorInfo ---------- { "code": null, "message": "An internal error occurred during your request!", "details": null, "data": {}, "validationErrors": null } 2024-03-13 23:28:55.363 +07:00 [ERR] <input>(9,41) : error : Cannot get the member model.title for a null object. <input>(9,41) : error : Cannot get the member model.title for a null object. 2024-03-13 23:28:55.369 +07:00 [INF] Executing ObjectResult, writing value of type 'Volo.Abp.Http.RemoteServiceErrorResponse'. 2024-03-13 23:28:55.370 +07:00 [INF] Executed action LogiPlat.Controllers.MailMessages.MailMessageController.GetTemplateAsync (LogiPlat.HttpApi) in 3278.8221ms 2024-03-13 23:28:55.370 +07:00 [INF] Executed endpoint 'LogiPlat.Controllers.MailMessages.MailMessageController.GetTemplateAsync (LogiPlat.HttpApi)' 2024-03-13 23:28:55.498 +07:00 [INF] Request finished HTTP/2 GET https://localhost:44343/template?templateName=LogiPlat.Email.Confirmation&corporationId=37d67126-bb7b-90f0-f5ab-3a1103be317d - - - 500 - application/json;+charset=utf-8 3417.4624ms
-
0
Hi,
Can you try this?
var globalContext = new Dictionary<string, object> { { "model", model } }; return await _templateRenderer.RenderAsync(input.TemplateName, model, currentLanguage, globalContext);
-
0
Thank you, Liangshiwei. It works.