Open Closed

Does Text Template support CSS class? #6844


User avatar
0
dzungle created
  • 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)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    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");
        }
    }
    

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    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

  • User Avatar
    0
    dzungle created

    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
    
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Can you try this?

    var globalContext = new Dictionary<string, object>
    {
        { "model", model }
    };
    return await _templateRenderer.RenderAsync(input.TemplateName, model, currentLanguage, globalContext);
    
  • User Avatar
    0
    dzungle created

    Thank you, Liangshiwei. It works.

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v9.3.0-preview. Updated on June 13, 2025, 11:37