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.

Made with ❤️ on ABP v9.1.0-preview. Updated on December 12, 2024, 07:15