Skip to main content

APEX Styling and Customization Implementation

Overview

APEX provides tenant-level CSS customization capabilities through a dedicated tenant CSS endpoint and caching system. The implementation allows complete visual customization while maintaining core functionality, validated against the actual TenantManagement service.

Technical Implementation (Validated from Codebase)

Tenant CSS Endpoint

File: services/TenantManagement/TenantManagement.WebApi/Program.cs Endpoint: /static/styles/tenant.css

endpoints.MapGet("/static/styles/tenant.css", async (HttpContext context, 
[FromQuery] Guid? tenantId,
[FromServices] ITenantService tenantService) =>
{
if (!tenantId.HasValue)
return Results.BadRequest("Tenant ID is required");

byte[] css = await tenantService.GetTenantCssAsync(tenantId.Value);
string hash = Convert.ToHexString(SHA1.HashData(css));
var etag = new EntityTagHeaderValue($"\"{hash}\"");

return TypedResults.File(css, MediaTypeNames.Text.Css, entityTag: etag);
});

Features:

  • Dynamic CSS serving per tenant
  • ETag-based caching for performance
  • SHA1 hash for cache invalidation
  • Proper MIME type handling

TenantService CSS Management

Validated Methods:

public interface ITenantService
{
Task<byte[]> GetTenantCssAsync(Guid tenantId);
// Other tenant management methods...
}

Caching Implementation:

public async Task<byte[]> GetTenantCssAsync(Guid tenantId)
{
string cacheKey = $"TenantService:GetTenantCssAsync:{tenantId}";

// Check cache first
var cachedCss = await cachingService.GetAsync<byte[]>(cacheKey);
if (cachedCss != null)
return cachedCss;

// Load from data source and cache
var css = await LoadTenantCssFromDataSource(tenantId);
await cachingService.SetAsync(cacheKey, css);

return css;
}

Data Model Integration

Validated Properties:

public class Tenant
{
public Guid Id { get; set; }
public string Name { get; set; }
public string StyleSheet { get; set; } // CSS URL or content
// Other tenant properties...
}

public class TenantResponseDto
{
public string StyleSheet { get; set; }
// Other response properties...
}

Portal Integration

CSS Loading Architecture

The Apex Portal integrates with tenant styling through:

  1. Dynamic CSS Loading: Portal loads tenant-specific CSS at runtime
  2. Cache Headers: ETag support for efficient browser caching
  3. Fallback Mechanism: Default platform styles when tenant CSS unavailable
  4. Theme Variables: CSS custom properties for consistent theming

Integration Pattern

<!-- In Portal index.html or layout -->
<link rel="stylesheet" href="/static/styles/platform.css" />
<link rel="stylesheet" href="/static/styles/tenant.css?tenantId={tenant-id}" id="tenant-styles" />

JavaScript Integration

// Dynamic tenant CSS loading
function loadTenantStyles(tenantId) {
const existingLink = document.getElementById('tenant-styles');
if (existingLink) {
existingLink.remove();
}

const link = document.createElement('link');
link.id = 'tenant-styles';
link.rel = 'stylesheet';
link.href = `/static/styles/tenant.css?tenantId=${tenantId}`;

document.head.appendChild(link);
}

CSS Customization Framework

Base CSS Structure

APEX uses Bootstrap 5 as its foundation with custom CSS variables for theming:

:root {
/* Primary Colors */
--bs-primary: #de1f26;
--bs-secondary: #475766;
--bs-success: #658d1b;
--bs-info: #26c3ff;
--bs-warning: #f2cd00;
--bs-danger: #de1f26;

/* Branding */
--logo: url('https://example.com/logo.png');

/* Typography */
--bs-body-font-family: "Segoe UI", sans-serif;
--bs-body-font-size: 1rem;

/* Spacing and Layout */
--bs-border-radius: 0.375rem;
--bs-border-width: 1px;
}

Theme Variables (Validated from Portal)

Based on ProgressiveWebAppConfiguration.md:

:root {
/* PWA Theme Integration */
--pwa-theme-color: #de1f26;
--pwa-background-color: #ffffff;

/* Portal-Specific Variables */
--portal-sidebar-bg: var(--bs-light);
--portal-header-bg: var(--bs-primary);
--portal-content-bg: #ffffff;
}

UI Component Styling

Integrated with UI.Razor component styling system:

/* Timeline Component Customization */
.timeline-container {
--timeline-line-color: var(--bs-secondary);
--timeline-current-color: var(--bs-danger);
}

/* Event State Styling */
.event-pending { background-color: var(--bs-warning); }
.event-completed { background-color: var(--bs-success); }
.event-review-required { background-color: var(--bs-info); }

Implementation Examples

Tenant CSS Customization

/* Tenant-specific overrides */
:root {
/* Brand Colors */
--bs-primary: #3498db;
--bs-primary-rgb: 52, 152, 219;

/* Custom Logo */
--logo: url('https://tenant.example.com/logo.png');

/* Typography */
--bs-body-font-family: "Roboto", sans-serif;
}

/* Component Overrides */
.btn-primary {
border-radius: 8px;
font-weight: 600;
}

/* Portal Layout Customization */
.main-layout {
--portal-sidebar-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

/* Dashboard Customization */
.dashboard-card {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 12px;
}

Dark Mode Support

[data-bs-theme="dark"] {
--bs-primary: #6ea8fe;
--bs-body-color: #dee2e6;
--bs-body-bg: #212529;
--bs-border-color: #495057;

/* Portal Dark Mode */
--portal-sidebar-bg: #343a40;
--portal-header-bg: #495057;
--portal-content-bg: #2b3035;
}

Performance Optimization

Caching Strategy

Validated Implementation:

  1. Server-Side Caching: TenantService caches CSS content in memory/distributed cache
  2. HTTP Caching: ETag headers for efficient browser caching
  3. CDN Integration: CSS files can be served through CDN for global distribution
  4. Compression: Gzip compression for CSS delivery

Cache Invalidation

public async Task UpdateTenantStyleSheetAsync(Guid tenantId, string newStyleSheet)
{
// Update tenant record
await tenantRepository.UpdateStyleSheetAsync(tenantId, newStyleSheet);

// Invalidate cache
string cacheKey = $"TenantService:GetTenantCssAsync:{tenantId}";
await cachingService.RemoveAsync(cacheKey);

// Optionally trigger CSS recompilation
await RecompileTenantCssAsync(tenantId);
}

Testing and Validation

Unit Tests (Validated)

From tests/TenantManagement/unittests/TenantManagement.Services.UnitTests/:

[Test]
public async Task GetTenantCssAsync_ReturnsCssFromCache()
{
// Arrange
var css = new byte[] { 1, 2, 3 };
await cachingService.SetAsync($"TenantService:GetTenantCssAsync:{tenantId}", css);

// Act
var result = await tenantService.GetTenantCssAsync(tenantId);

// Assert
result.ShouldBe(css);
}

Integration Testing

  • CSS endpoint response validation
  • ETag header generation and validation
  • Caching behavior verification
  • Multi-tenant isolation testing

Security Considerations

CSS Injection Prevention

  • Content Validation: Validate CSS content before storage
  • Sanitization: Remove potentially harmful CSS properties
  • CSP Headers: Content Security Policy for CSS sources
  • MIME Type Enforcement: Strict MIME type checking

Access Control

  • Tenant Isolation: Ensure tenants can only access their own CSS
  • Authentication: Validate tenant access permissions
  • Rate Limiting: Prevent CSS endpoint abuse

Monitoring and Analytics

Performance Metrics

  • CSS delivery response times
  • Cache hit rates for tenant CSS
  • CSS file sizes and optimization opportunities
  • Browser rendering performance impact

Usage Analytics

public class StylingTelemetry
{
public void TrackCssRequested(Guid tenantId, bool cacheHit);
public void TrackCssUpdated(Guid tenantId, int cssSize);
public void TrackRenderingPerformance(string componentName, TimeSpan renderTime);
}

API Reference

Tenant CSS Client

File: libs/TenantManagement/TenantManagement.Client/ApiClients.cs

public partial class TenantManagementClient
{
public virtual async Task Tenant_cssAsync(Guid? tenantId, CancellationToken cancellationToken)
{
var url = $"/static/styles/tenant.css?tenantId={tenantId}";
// HTTP client implementation...
}
}

Service Integration

// Dependency injection setup
services.AddScoped<ITenantService, TenantService>();
services.AddHttpClient<TenantManagementClient>();

// CSS endpoint registration
app.MapTenantCssEndpoint();

Official APEX Documentation

Platform Documentation

Getting Started

  1. Configure Tenant CSS: Set up StyleSheet property in tenant configuration
  2. Implement CSS Variables: Use CSS custom properties for consistent theming
  3. Test Endpoint: Validate /static/styles/tenant.css endpoint functionality
  4. Optimize Caching: Configure appropriate cache headers and invalidation
  5. Monitor Performance: Set up analytics for CSS delivery and rendering performance
  6. Validate Security: Ensure CSS content validation and sanitization

This implementation guide is based on the actual tenant CSS management system found in the APEX platform codebase, ensuring accuracy and trustworthiness of all technical details.