Open Closed

MVC / Blazor - Head.Last Layout hooks style not injected #9747


User avatar
0
aziz.bouhejba created

Hello!

I am implementing tenant-specific branding in an ABP application with LeptonX Theme. The settings are saving correctly per tenant, but I cannot get the styles to apply using Layout Hooks.

Environment:

  • ABP Version: 9.2
  • UI Framework: Blazor WebApp (also tried with Auth Server MVC)
  • Theme: LeptonX Theme
  • Project Type: Microservice solution

What I'm trying to achieve: Apply custom colors per tenant using CSS variables injected into the page head.

What I've tried: Following these documentation links:

  • https://abp.io/community/articles/white-labeling-in-abp-framework-5trwmrfm#gsc.tab=0
  • https://abp.io/docs/latest/framework/ui/blazor/layout-hooks

The Problem:

Configure<AbpLayoutHookOptions>(options =>
{
    options.Add(
        LayoutHooks.Head.Last,  // This does NOT work - component never renders in my <Head> html
        typeof(AppearanceStylesComponent)
    );
    
    options.Add(
        LayoutHooks.Body.First,  // This WORKS - component renders correctly
        typeof(TestStyleComponent)
    );
});

16 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent

    To inject tenant-specific styles into the <head> using Layout Hooks in ABP with the LeptonX Theme, you must ensure that:

    1. The layout you are using actually implements the LayoutHooks.Head.Last hook.
    2. Your component is registered and available in the correct assembly.
    3. The theme's layout is not overriding or missing the hook point.

    Steps to Diagnose and Fix

    1. Verify the Layout Implements the Hook

    Check the LeptonX layout file (e.g., /Themes/LeptonX/Layouts/Application/Default.cshtml for MVC or the corresponding Blazor layout) and ensure it contains:

    @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.Last, StandardLayouts.Application)
    

    If this line is missing, the hook will not be triggered. You may need to override the layout in your project and add this line.

    2. Register the Component Correctly

    Your configuration is correct:

    Configure<AbpLayoutHookOptions>(options =>
    {
        options.Add(LayoutHooks.Head.Last, typeof(AppearanceStylesComponent));
    });
    

    Ensure AppearanceStylesComponent is a valid view/component and is discoverable by the DI system.

    3. For Blazor: Head Tag Limitations

    Blazor Server and WebAssembly have limitations with dynamic head content. The <head> section is not always re-rendered as expected. For Blazor, consider using the built-in HeadOutlet or inject styles via JavaScript interop if the hook does not work.

    4. For MVC: Overriding the Layout

    If you are using MVC for the Auth Server, you may need to override the default layout to ensure the hook is present.

    5. Theme NuGet Package Updates

    If you are using the LeptonX NuGet package, updates may overwrite your changes. Always check the layout after updates.

    Example: Adding the Hook to the Layout

    Override the layout file in your project (if not already present):

    @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.Last, StandardLayouts.Application)
    

    References


    If the hook is present in the layout and the component is registered, the style injection should work. If not, override the layout and add the hook explicitly. For Blazor, consider the limitations of head manipulation and use alternatives if necessary.


    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    What is the UI type of AppearanceStylesComponent and TestStyleComponent

    On which page does the AppearanceStylesComponent not work, and does the TestStyleComponent work?

    The page is MVC or Blazor?

    Thanks.

  • User Avatar
    0
    aziz.bouhejba created

    Both are Blazor ComponentBase components in a Blazor WebApp project and I showed them just to show they work on Body but not on head.

    The issue is injection of these components through layout hooks Head.Last They were working with LayoutHooks.Body.First

    We are trying to apply white label colors per tenant.

    So it looks like this

    using MyProject.AdministrationService.Constants;
    using MyProject.AdministrationService.Features;
    using Microsoft.AspNetCore.Components;
    using Volo.Abp.Features;
    using Volo.Abp.Settings;
    
    namespace MyProject.Blazor.Shared.Components.Layout;
    
    public partial class AppearanceStyles : ComponentBase
    {
        [Inject] private ISettingProvider SettingProvider { get; set; } = null!;
    
        public string? PrimaryColor { get; private set; }
        public string? SecondaryColor { get; private set; }
    
        protected override async Task OnInitializedAsync()
        {
            try
            {
                var primaryColor = await SettingProvider.GetOrNullAsync(SettingDefinitionConstants.BrandingPrimaryColor);
                var secondaryColor = await SettingProvider.GetOrNullAsync(SettingDefinitionConstants.BrandingSecondaryColor);
    
                PrimaryColor = primaryColor ?? SettingDefinitionConstants.BrandingPrimaryColorDefaultValue;
                SecondaryColor = secondaryColor ?? SettingDefinitionConstants.BrandingSecondaryColorDefaultValue;
            }
            catch
            {
                PrimaryColor = null;
                SecondaryColor = null;
            }
    
            await base.OnInitializedAsync();
        }
    }
    
    
    @using MyProject.AdministrationService.Constants
    @using MyProject.AdministrationService.Features
    @using Volo.Abp.Features
    @using Volo.Abp.Settings
    
    @if (!string.IsNullOrEmpty(PrimaryColor) || !string.IsNullOrEmpty(SecondaryColor))
    {
        <style>
            :root {
                --myproject-primary-color: @PrimaryColor;
                --myproject-secondary-color: @SecondaryColor;
            }
    
            .btn-primary {
                background-color: var(--myproject-primary-color);
                border-color: var(--myproject-primary-color);
            }
    
            .btn-primary:hover {
                background-color: var(--myproject-primary-color);
                border-color: var(--myproject-primary-color);
                opacity: 0.8;
            }
    
            .btn-primary:focus {
                background-color: var(--myproject-primary-color);
                border-color: var(--myproject-primary-color);
                box-shadow: 0 0 0 0.25rem rgba(from var(--myproject-primary-color) r g b / 0.25);
            }
    
            .bg-primary {
                background-color: var(--myproject-primary-color) !important;
            }
    
            .text-primary {
                color: var(--myproject-primary-color) !important;
            }
    
            .border-primary {
                border-color: var(--myproject-primary-color) !important;
            }
    
            .btn-outline-primary {
                border-color: var(--myproject-primary-color);
                color: var(--myproject-primary-color);
            }
    
            .btn-outline-primary:hover {
                background-color: var(--myproject-primary-color);
                border-color: var(--myproject-primary-color);
                color: #fff;
            }
    
            .btn-outline-primary:focus {
                background-color: var(--myproject-primary-color);
                border-color: var(--myproject-primary-color);
                color: #fff;
            }
    
            .nav-pills .nav-link.active, 
            .nav-pills .show > .nav-link {
                background-color: var(--myproject-primary-color);
            }
    
            .nav-link.active:hover {
                background-color: var(--myproject-primary-color);
                opacity: 0.8;
            }
    
            /* Secondary Color Styles */
            .btn-secondary {
                background-color: var(--myproject-secondary-color);
                border-color: var(--myproject-secondary-color);
            }
    
            .btn-secondary:hover {
                background-color: var(--myproject-secondary-color);
                border-color: var(--myproject-secondary-color);
                opacity: 0.8;
            }
    
            .btn-secondary:focus {
                background-color: var(--myproject-secondary-color);
                border-color: var(--myproject-secondary-color);
                box-shadow: 0 0 0 0.25rem rgba(from var(--myproject-secondary-color) r g b / 0.25);
            }
    
            .bg-secondary {
                background-color: var(--myproject-secondary-color) !important;
            }
    
            .text-secondary {
                color: var(--myproject-secondary-color) !important;
            }
    
            .border-secondary {
                border-color: var(--myproject-secondary-color) !important;
            }
    
            .btn-outline-secondary {
                border-color: var(--myproject-secondary-color);
                color: var(--myproject-secondary-color);
            }
    
            .btn-outline-secondary:hover {
                background-color: var(--myproject-secondary-color);
                border-color: var(--myproject-secondary-color);
                color: #fff;
            }
    
            .btn-outline-secondary:focus {
                background-color: var(--myproject-secondary-color);
                border-color: var(--myproject-secondary-color);
                color: #fff;
            }
        </style>
    }
    
            Configure<AbpLayoutHookOptions>(options =>
            {
                options.Add(
                    LayoutHooks.Head.Last,
                    typeof(AppearanceStyles)
                );
            });
    

    Also had the same issue in MVC by using

    public class BootstrapStyleViewComponent(ISettingProvider settingProvider) : AbpViewComponent
    {
        public async Task<IViewComponentResult> InvokeAsync()
        {
            var primaryColor = await settingProvider.GetOrNullAsync(WhileLabelAppSettings.PrimaryColor);
            return View("~/Components/BootstrapStyle/Default.cshtml", primaryColor);
        }
    }
    

    And also

    Configure<AbpLayoutHookOptions>(options =>
    {
        options.Add(
        LayoutHooks.Head.Last,
        typeof(BootstrapStyleViewComponent));
    });
    
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Thanks. Can you share a template project that can reproduce your hook problem?

    liming.ma@volosoft.com

    I will check the source code and provide a solution.

  • User Avatar
    0
    aziz.bouhejba created

    Hello, sent thank you.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you share whole template project source code?

    Thanks.

  • User Avatar
    0
    aziz.bouhejba created

    I emailed you the whole project source code at liming.ma@volosoft.com 3 days ago

    it's also very easy to reproduce.. 2 files to change

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I see, I will try to reproduce it.

    Thanks.

  • User Avatar
    0
    aziz.bouhejba created

    hey do you want me to send you the template again?

    or the repro steps are

    Add this component

    And register it like this in both Server and Wasm projects

    Thank you!

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you try to add to the App.Razor file and import the namespaces

    Thanks

    <LayoutHook Name="@LayoutHooks.Head.First" Layout="@StandardLayouts.Application" />
    
    <LayoutHook Name="@LayoutHooks.Head.Last" Layout="@StandardLayouts.Application" />
    

  • User Avatar
    0
    aziz.bouhejba created

    Thanks it works! we thought it would work out of the box with leptonx theme side menu

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Great 👍

  • User Avatar
    0
    aziz.bouhejba created

    Hello! sorry I'm reopening this only because I have the exact same issue with the mobile app Maui Blazor hybrid.

    We use Leptonx theme side menu

    using Volo.Abp.AspNetCore.Components.MauiBlazor.LeptonXTheme;

    and exact same

    Configure<AbpLayoutHookOptions>(options =>
            {
                options.Add(
                    LayoutHooks.Head.Last,
                    typeof(StyleHook)
                );
            });
    
    

    I tried adding this in App.razor

    <LayoutHook Name="@LayoutHooks.Head.Last" Layout="@StandardLayouts.Application" />
    

    Thank you!

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    The Maui Blazor hybrid has an Index.html in the wwwroot folder.

    It's static HTML page. You can add HTML/JS/CSS to it.

    Thanks.

  • User Avatar
    0
    aziz.bouhejba created

    Thank you for the quick answer, very appreciated. so it could still be possible to inject tenant style through other things than layout hooks. Like javascript injection. Have a good day

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Have a good day too. : )

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 v10.0.0-preview. Updated on August 28, 2025, 08:29