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:
- Dynamic CSS Loading: Portal loads tenant-specific CSS at runtime
- Cache Headers: ETag support for efficient browser caching
- Fallback Mechanism: Default platform styles when tenant CSS unavailable
- 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:
- Server-Side Caching: TenantService caches CSS content in memory/distributed cache
- HTTP Caching: ETag headers for efficient browser caching
- CDN Integration: CSS files can be served through CDN for global distribution
- 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();
Related Documentation
Official APEX Documentation
- Styling Guide: Customizing Your Tenant's Appearance
- Website Builder: Styling Integration
- PWA Configuration: Theme and Manifest
Platform Documentation
- Portal Configuration: PWA Configuration
- UI Components: Horizontal Timeline Styling
- Capabilities Overview: APEX Capabilities
Getting Started
- Configure Tenant CSS: Set up StyleSheet property in tenant configuration
- Implement CSS Variables: Use CSS custom properties for consistent theming
- Test Endpoint: Validate
/static/styles/tenant.cssendpoint functionality - Optimize Caching: Configure appropriate cache headers and invalidation
- Monitor Performance: Set up analytics for CSS delivery and rendering performance
- 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.