feat(11-03): inject IBrandingService into all 5 export ViewModels and assemble branding in ExportHtmlAsync

- Add IBrandingService field and DI constructor parameter to all 5 ViewModels
- Add optional IBrandingService? parameter to test constructors (PermissionsViewModel, StorageViewModel, UserAccessAuditViewModel)
- Assemble ReportBranding from GetMspLogoAsync + _currentProfile.ClientLogo before each WriteAsync call
- Pass branding as last parameter to WriteAsync in all ExportHtmlAsync methods
- Guard clause: branding assembly skipped (branding = null) when _brandingService is null (test constructors)
- Build: 0 warnings, 0 errors; tests: 254 passed / 0 failed / 26 skipped
This commit is contained in:
Dev
2026-04-08 14:50:54 +02:00
parent e77455f03f
commit 816fb5e3b5
5 changed files with 70 additions and 9 deletions

View File

@@ -31,6 +31,7 @@ public partial class DuplicatesViewModel : FeatureViewModelBase
private readonly IDuplicatesService _duplicatesService; private readonly IDuplicatesService _duplicatesService;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly DuplicatesHtmlExportService _htmlExportService; private readonly DuplicatesHtmlExportService _htmlExportService;
private readonly IBrandingService _brandingService;
private readonly ILogger<FeatureViewModelBase> _logger; private readonly ILogger<FeatureViewModelBase> _logger;
private TenantProfile? _currentProfile; private TenantProfile? _currentProfile;
private IReadOnlyList<DuplicateGroup> _lastGroups = Array.Empty<DuplicateGroup>(); private IReadOnlyList<DuplicateGroup> _lastGroups = Array.Empty<DuplicateGroup>();
@@ -64,12 +65,14 @@ public partial class DuplicatesViewModel : FeatureViewModelBase
IDuplicatesService duplicatesService, IDuplicatesService duplicatesService,
ISessionManager sessionManager, ISessionManager sessionManager,
DuplicatesHtmlExportService htmlExportService, DuplicatesHtmlExportService htmlExportService,
IBrandingService brandingService,
ILogger<FeatureViewModelBase> logger) ILogger<FeatureViewModelBase> logger)
: base(logger) : base(logger)
{ {
_duplicatesService = duplicatesService; _duplicatesService = duplicatesService;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_htmlExportService = htmlExportService; _htmlExportService = htmlExportService;
_brandingService = brandingService;
_logger = logger; _logger = logger;
ExportHtmlCommand = new AsyncRelayCommand(ExportHtmlAsync, CanExport); ExportHtmlCommand = new AsyncRelayCommand(ExportHtmlAsync, CanExport);
@@ -168,7 +171,15 @@ public partial class DuplicatesViewModel : FeatureViewModelBase
if (dialog.ShowDialog() != true) return; if (dialog.ShowDialog() != true) return;
try try
{ {
await _htmlExportService.WriteAsync(_lastGroups, dialog.FileName, CancellationToken.None); ReportBranding? branding = null;
if (_brandingService is not null)
{
var mspLogo = await _brandingService.GetMspLogoAsync();
var clientLogo = _currentProfile?.ClientLogo;
branding = new ReportBranding(mspLogo, clientLogo);
}
await _htmlExportService.WriteAsync(_lastGroups, dialog.FileName, CancellationToken.None, branding);
Process.Start(new ProcessStartInfo(dialog.FileName) { UseShellExecute = true }); Process.Start(new ProcessStartInfo(dialog.FileName) { UseShellExecute = true });
} }
catch (Exception ex) { StatusMessage = $"Export failed: {ex.Message}"; _logger.LogError(ex, "HTML export failed."); } catch (Exception ex) { StatusMessage = $"Export failed: {ex.Message}"; _logger.LogError(ex, "HTML export failed."); }

View File

@@ -26,6 +26,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly CsvExportService? _csvExportService; private readonly CsvExportService? _csvExportService;
private readonly HtmlExportService? _htmlExportService; private readonly HtmlExportService? _htmlExportService;
private readonly IBrandingService? _brandingService;
private readonly ILogger<FeatureViewModelBase> _logger; private readonly ILogger<FeatureViewModelBase> _logger;
// ── Observable properties ─────────────────────────────────────────────── // ── Observable properties ───────────────────────────────────────────────
@@ -128,6 +129,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase
ISessionManager sessionManager, ISessionManager sessionManager,
CsvExportService csvExportService, CsvExportService csvExportService,
HtmlExportService htmlExportService, HtmlExportService htmlExportService,
IBrandingService brandingService,
ILogger<FeatureViewModelBase> logger) ILogger<FeatureViewModelBase> logger)
: base(logger) : base(logger)
{ {
@@ -136,6 +138,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase
_sessionManager = sessionManager; _sessionManager = sessionManager;
_csvExportService = csvExportService; _csvExportService = csvExportService;
_htmlExportService = htmlExportService; _htmlExportService = htmlExportService;
_brandingService = brandingService;
_logger = logger; _logger = logger;
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
@@ -149,7 +152,8 @@ public partial class PermissionsViewModel : FeatureViewModelBase
IPermissionsService permissionsService, IPermissionsService permissionsService,
ISiteListService siteListService, ISiteListService siteListService,
ISessionManager sessionManager, ISessionManager sessionManager,
ILogger<FeatureViewModelBase> logger) ILogger<FeatureViewModelBase> logger,
IBrandingService? brandingService = null)
: base(logger) : base(logger)
{ {
_permissionsService = permissionsService; _permissionsService = permissionsService;
@@ -157,6 +161,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase
_sessionManager = sessionManager; _sessionManager = sessionManager;
_csvExportService = null; _csvExportService = null;
_htmlExportService = null; _htmlExportService = null;
_brandingService = brandingService;
_logger = logger; _logger = logger;
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
@@ -313,10 +318,18 @@ public partial class PermissionsViewModel : FeatureViewModelBase
if (dialog.ShowDialog() != true) return; if (dialog.ShowDialog() != true) return;
try try
{ {
ReportBranding? branding = null;
if (_brandingService is not null)
{
var mspLogo = await _brandingService.GetMspLogoAsync();
var clientLogo = _currentProfile?.ClientLogo;
branding = new ReportBranding(mspLogo, clientLogo);
}
if (IsSimplifiedMode && SimplifiedResults.Count > 0) if (IsSimplifiedMode && SimplifiedResults.Count > 0)
await _htmlExportService.WriteAsync(SimplifiedResults.ToList(), dialog.FileName, CancellationToken.None); await _htmlExportService.WriteAsync(SimplifiedResults.ToList(), dialog.FileName, CancellationToken.None, branding);
else else
await _htmlExportService.WriteAsync(Results, dialog.FileName, CancellationToken.None); await _htmlExportService.WriteAsync(Results, dialog.FileName, CancellationToken.None, branding);
OpenFile(dialog.FileName); OpenFile(dialog.FileName);
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -17,6 +17,7 @@ public partial class SearchViewModel : FeatureViewModelBase
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly SearchCsvExportService _csvExportService; private readonly SearchCsvExportService _csvExportService;
private readonly SearchHtmlExportService _htmlExportService; private readonly SearchHtmlExportService _htmlExportService;
private readonly IBrandingService _brandingService;
private readonly ILogger<FeatureViewModelBase> _logger; private readonly ILogger<FeatureViewModelBase> _logger;
private TenantProfile? _currentProfile; private TenantProfile? _currentProfile;
@@ -59,6 +60,7 @@ public partial class SearchViewModel : FeatureViewModelBase
ISessionManager sessionManager, ISessionManager sessionManager,
SearchCsvExportService csvExportService, SearchCsvExportService csvExportService,
SearchHtmlExportService htmlExportService, SearchHtmlExportService htmlExportService,
IBrandingService brandingService,
ILogger<FeatureViewModelBase> logger) ILogger<FeatureViewModelBase> logger)
: base(logger) : base(logger)
{ {
@@ -66,6 +68,7 @@ public partial class SearchViewModel : FeatureViewModelBase
_sessionManager = sessionManager; _sessionManager = sessionManager;
_csvExportService = csvExportService; _csvExportService = csvExportService;
_htmlExportService = htmlExportService; _htmlExportService = htmlExportService;
_brandingService = brandingService;
_logger = logger; _logger = logger;
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
@@ -168,7 +171,15 @@ public partial class SearchViewModel : FeatureViewModelBase
if (dialog.ShowDialog() != true) return; if (dialog.ShowDialog() != true) return;
try try
{ {
await _htmlExportService.WriteAsync(Results, dialog.FileName, CancellationToken.None); ReportBranding? branding = null;
if (_brandingService is not null)
{
var mspLogo = await _brandingService.GetMspLogoAsync();
var clientLogo = _currentProfile?.ClientLogo;
branding = new ReportBranding(mspLogo, clientLogo);
}
await _htmlExportService.WriteAsync(Results, dialog.FileName, CancellationToken.None, branding);
OpenFile(dialog.FileName); OpenFile(dialog.FileName);
} }
catch (Exception ex) { StatusMessage = $"Export failed: {ex.Message}"; _logger.LogError(ex, "HTML export failed."); } catch (Exception ex) { StatusMessage = $"Export failed: {ex.Message}"; _logger.LogError(ex, "HTML export failed."); }

View File

@@ -21,6 +21,7 @@ public partial class StorageViewModel : FeatureViewModelBase
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly StorageCsvExportService _csvExportService; private readonly StorageCsvExportService _csvExportService;
private readonly StorageHtmlExportService _htmlExportService; private readonly StorageHtmlExportService _htmlExportService;
private readonly IBrandingService? _brandingService;
private readonly ILogger<FeatureViewModelBase> _logger; private readonly ILogger<FeatureViewModelBase> _logger;
private TenantProfile? _currentProfile; private TenantProfile? _currentProfile;
@@ -134,6 +135,7 @@ public partial class StorageViewModel : FeatureViewModelBase
ISessionManager sessionManager, ISessionManager sessionManager,
StorageCsvExportService csvExportService, StorageCsvExportService csvExportService,
StorageHtmlExportService htmlExportService, StorageHtmlExportService htmlExportService,
IBrandingService brandingService,
ILogger<FeatureViewModelBase> logger) ILogger<FeatureViewModelBase> logger)
: base(logger) : base(logger)
{ {
@@ -141,6 +143,7 @@ public partial class StorageViewModel : FeatureViewModelBase
_sessionManager = sessionManager; _sessionManager = sessionManager;
_csvExportService = csvExportService; _csvExportService = csvExportService;
_htmlExportService = htmlExportService; _htmlExportService = htmlExportService;
_brandingService = brandingService;
_logger = logger; _logger = logger;
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
@@ -151,13 +154,15 @@ public partial class StorageViewModel : FeatureViewModelBase
internal StorageViewModel( internal StorageViewModel(
IStorageService storageService, IStorageService storageService,
ISessionManager sessionManager, ISessionManager sessionManager,
ILogger<FeatureViewModelBase> logger) ILogger<FeatureViewModelBase> logger,
IBrandingService? brandingService = null)
: base(logger) : base(logger)
{ {
_storageService = storageService; _storageService = storageService;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_csvExportService = null!; _csvExportService = null!;
_htmlExportService = null!; _htmlExportService = null!;
_brandingService = brandingService;
_logger = logger; _logger = logger;
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
@@ -296,7 +301,15 @@ public partial class StorageViewModel : FeatureViewModelBase
if (dialog.ShowDialog() != true) return; if (dialog.ShowDialog() != true) return;
try try
{ {
await _htmlExportService.WriteAsync(Results, FileTypeMetrics, dialog.FileName, CancellationToken.None); ReportBranding? branding = null;
if (_brandingService is not null)
{
var mspLogo = await _brandingService.GetMspLogoAsync();
var clientLogo = _currentProfile?.ClientLogo;
branding = new ReportBranding(mspLogo, clientLogo);
}
await _htmlExportService.WriteAsync(Results, FileTypeMetrics, dialog.FileName, CancellationToken.None, branding);
OpenFile(dialog.FileName); OpenFile(dialog.FileName);
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -25,6 +25,7 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly UserAccessCsvExportService? _csvExportService; private readonly UserAccessCsvExportService? _csvExportService;
private readonly UserAccessHtmlExportService? _htmlExportService; private readonly UserAccessHtmlExportService? _htmlExportService;
private readonly IBrandingService? _brandingService;
private readonly ILogger<FeatureViewModelBase> _logger; private readonly ILogger<FeatureViewModelBase> _logger;
// ── People picker debounce ────────────────────────────────────────────── // ── People picker debounce ──────────────────────────────────────────────
@@ -118,6 +119,7 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
ISessionManager sessionManager, ISessionManager sessionManager,
UserAccessCsvExportService csvExportService, UserAccessCsvExportService csvExportService,
UserAccessHtmlExportService htmlExportService, UserAccessHtmlExportService htmlExportService,
IBrandingService brandingService,
ILogger<FeatureViewModelBase> logger) ILogger<FeatureViewModelBase> logger)
: base(logger) : base(logger)
{ {
@@ -126,6 +128,7 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
_sessionManager = sessionManager; _sessionManager = sessionManager;
_csvExportService = csvExportService; _csvExportService = csvExportService;
_htmlExportService = htmlExportService; _htmlExportService = htmlExportService;
_brandingService = brandingService;
_logger = logger; _logger = logger;
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
@@ -145,7 +148,8 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
IUserAccessAuditService auditService, IUserAccessAuditService auditService,
IGraphUserSearchService graphUserSearchService, IGraphUserSearchService graphUserSearchService,
ISessionManager sessionManager, ISessionManager sessionManager,
ILogger<FeatureViewModelBase> logger) ILogger<FeatureViewModelBase> logger,
IBrandingService? brandingService = null)
: base(logger) : base(logger)
{ {
_auditService = auditService; _auditService = auditService;
@@ -153,6 +157,7 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
_sessionManager = sessionManager; _sessionManager = sessionManager;
_csvExportService = null; _csvExportService = null;
_htmlExportService = null; _htmlExportService = null;
_brandingService = brandingService;
_logger = logger; _logger = logger;
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
@@ -329,7 +334,15 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
if (dialog.ShowDialog() != true) return; if (dialog.ShowDialog() != true) return;
try try
{ {
await _htmlExportService.WriteAsync(Results, dialog.FileName, CancellationToken.None); ReportBranding? branding = null;
if (_brandingService is not null)
{
var mspLogo = await _brandingService.GetMspLogoAsync();
var clientLogo = _currentProfile?.ClientLogo;
branding = new ReportBranding(mspLogo, clientLogo);
}
await _htmlExportService.WriteAsync(Results, dialog.FileName, CancellationToken.None, branding);
OpenFile(dialog.FileName); OpenFile(dialog.FileName);
} }
catch (Exception ex) catch (Exception ex)