From 816fb5e3b582bb26cad271e9a1a2602391378a39 Mon Sep 17 00:00:00 2001 From: Dev Date: Wed, 8 Apr 2026 14:50:54 +0200 Subject: [PATCH] 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 --- .../ViewModels/Tabs/DuplicatesViewModel.cs | 13 ++++++++++++- .../ViewModels/Tabs/PermissionsViewModel.cs | 19 ++++++++++++++++--- .../ViewModels/Tabs/SearchViewModel.cs | 13 ++++++++++++- .../ViewModels/Tabs/StorageViewModel.cs | 17 +++++++++++++++-- .../Tabs/UserAccessAuditViewModel.cs | 17 +++++++++++++++-- 5 files changed, 70 insertions(+), 9 deletions(-) diff --git a/SharepointToolbox/ViewModels/Tabs/DuplicatesViewModel.cs b/SharepointToolbox/ViewModels/Tabs/DuplicatesViewModel.cs index d5b9ace..b8eaf27 100644 --- a/SharepointToolbox/ViewModels/Tabs/DuplicatesViewModel.cs +++ b/SharepointToolbox/ViewModels/Tabs/DuplicatesViewModel.cs @@ -31,6 +31,7 @@ public partial class DuplicatesViewModel : FeatureViewModelBase private readonly IDuplicatesService _duplicatesService; private readonly ISessionManager _sessionManager; private readonly DuplicatesHtmlExportService _htmlExportService; + private readonly IBrandingService _brandingService; private readonly ILogger _logger; private TenantProfile? _currentProfile; private IReadOnlyList _lastGroups = Array.Empty(); @@ -64,12 +65,14 @@ public partial class DuplicatesViewModel : FeatureViewModelBase IDuplicatesService duplicatesService, ISessionManager sessionManager, DuplicatesHtmlExportService htmlExportService, + IBrandingService brandingService, ILogger logger) : base(logger) { _duplicatesService = duplicatesService; _sessionManager = sessionManager; _htmlExportService = htmlExportService; + _brandingService = brandingService; _logger = logger; ExportHtmlCommand = new AsyncRelayCommand(ExportHtmlAsync, CanExport); @@ -168,7 +171,15 @@ public partial class DuplicatesViewModel : FeatureViewModelBase if (dialog.ShowDialog() != true) return; 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 }); } catch (Exception ex) { StatusMessage = $"Export failed: {ex.Message}"; _logger.LogError(ex, "HTML export failed."); } diff --git a/SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs b/SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs index d136b76..5a070ac 100644 --- a/SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs +++ b/SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs @@ -26,6 +26,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase private readonly ISessionManager _sessionManager; private readonly CsvExportService? _csvExportService; private readonly HtmlExportService? _htmlExportService; + private readonly IBrandingService? _brandingService; private readonly ILogger _logger; // ── Observable properties ─────────────────────────────────────────────── @@ -128,6 +129,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase ISessionManager sessionManager, CsvExportService csvExportService, HtmlExportService htmlExportService, + IBrandingService brandingService, ILogger logger) : base(logger) { @@ -136,6 +138,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase _sessionManager = sessionManager; _csvExportService = csvExportService; _htmlExportService = htmlExportService; + _brandingService = brandingService; _logger = logger; ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); @@ -149,7 +152,8 @@ public partial class PermissionsViewModel : FeatureViewModelBase IPermissionsService permissionsService, ISiteListService siteListService, ISessionManager sessionManager, - ILogger logger) + ILogger logger, + IBrandingService? brandingService = null) : base(logger) { _permissionsService = permissionsService; @@ -157,6 +161,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase _sessionManager = sessionManager; _csvExportService = null; _htmlExportService = null; + _brandingService = brandingService; _logger = logger; ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); @@ -313,10 +318,18 @@ public partial class PermissionsViewModel : FeatureViewModelBase if (dialog.ShowDialog() != true) return; 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) - await _htmlExportService.WriteAsync(SimplifiedResults.ToList(), dialog.FileName, CancellationToken.None); + await _htmlExportService.WriteAsync(SimplifiedResults.ToList(), dialog.FileName, CancellationToken.None, branding); else - await _htmlExportService.WriteAsync(Results, dialog.FileName, CancellationToken.None); + await _htmlExportService.WriteAsync(Results, dialog.FileName, CancellationToken.None, branding); OpenFile(dialog.FileName); } catch (Exception ex) diff --git a/SharepointToolbox/ViewModels/Tabs/SearchViewModel.cs b/SharepointToolbox/ViewModels/Tabs/SearchViewModel.cs index 8da06cf..63461ce 100644 --- a/SharepointToolbox/ViewModels/Tabs/SearchViewModel.cs +++ b/SharepointToolbox/ViewModels/Tabs/SearchViewModel.cs @@ -17,6 +17,7 @@ public partial class SearchViewModel : FeatureViewModelBase private readonly ISessionManager _sessionManager; private readonly SearchCsvExportService _csvExportService; private readonly SearchHtmlExportService _htmlExportService; + private readonly IBrandingService _brandingService; private readonly ILogger _logger; private TenantProfile? _currentProfile; @@ -59,6 +60,7 @@ public partial class SearchViewModel : FeatureViewModelBase ISessionManager sessionManager, SearchCsvExportService csvExportService, SearchHtmlExportService htmlExportService, + IBrandingService brandingService, ILogger logger) : base(logger) { @@ -66,6 +68,7 @@ public partial class SearchViewModel : FeatureViewModelBase _sessionManager = sessionManager; _csvExportService = csvExportService; _htmlExportService = htmlExportService; + _brandingService = brandingService; _logger = logger; ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); @@ -168,7 +171,15 @@ public partial class SearchViewModel : FeatureViewModelBase if (dialog.ShowDialog() != true) return; 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); } catch (Exception ex) { StatusMessage = $"Export failed: {ex.Message}"; _logger.LogError(ex, "HTML export failed."); } diff --git a/SharepointToolbox/ViewModels/Tabs/StorageViewModel.cs b/SharepointToolbox/ViewModels/Tabs/StorageViewModel.cs index 6675408..7f15f40 100644 --- a/SharepointToolbox/ViewModels/Tabs/StorageViewModel.cs +++ b/SharepointToolbox/ViewModels/Tabs/StorageViewModel.cs @@ -21,6 +21,7 @@ public partial class StorageViewModel : FeatureViewModelBase private readonly ISessionManager _sessionManager; private readonly StorageCsvExportService _csvExportService; private readonly StorageHtmlExportService _htmlExportService; + private readonly IBrandingService? _brandingService; private readonly ILogger _logger; private TenantProfile? _currentProfile; @@ -134,6 +135,7 @@ public partial class StorageViewModel : FeatureViewModelBase ISessionManager sessionManager, StorageCsvExportService csvExportService, StorageHtmlExportService htmlExportService, + IBrandingService brandingService, ILogger logger) : base(logger) { @@ -141,6 +143,7 @@ public partial class StorageViewModel : FeatureViewModelBase _sessionManager = sessionManager; _csvExportService = csvExportService; _htmlExportService = htmlExportService; + _brandingService = brandingService; _logger = logger; ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); @@ -151,13 +154,15 @@ public partial class StorageViewModel : FeatureViewModelBase internal StorageViewModel( IStorageService storageService, ISessionManager sessionManager, - ILogger logger) + ILogger logger, + IBrandingService? brandingService = null) : base(logger) { _storageService = storageService; _sessionManager = sessionManager; _csvExportService = null!; _htmlExportService = null!; + _brandingService = brandingService; _logger = logger; ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); @@ -296,7 +301,15 @@ public partial class StorageViewModel : FeatureViewModelBase if (dialog.ShowDialog() != true) return; 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); } catch (Exception ex) diff --git a/SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs b/SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs index e37e8ac..4218885 100644 --- a/SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs +++ b/SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs @@ -25,6 +25,7 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase private readonly ISessionManager _sessionManager; private readonly UserAccessCsvExportService? _csvExportService; private readonly UserAccessHtmlExportService? _htmlExportService; + private readonly IBrandingService? _brandingService; private readonly ILogger _logger; // ── People picker debounce ────────────────────────────────────────────── @@ -118,6 +119,7 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase ISessionManager sessionManager, UserAccessCsvExportService csvExportService, UserAccessHtmlExportService htmlExportService, + IBrandingService brandingService, ILogger logger) : base(logger) { @@ -126,6 +128,7 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase _sessionManager = sessionManager; _csvExportService = csvExportService; _htmlExportService = htmlExportService; + _brandingService = brandingService; _logger = logger; ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); @@ -145,7 +148,8 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase IUserAccessAuditService auditService, IGraphUserSearchService graphUserSearchService, ISessionManager sessionManager, - ILogger logger) + ILogger logger, + IBrandingService? brandingService = null) : base(logger) { _auditService = auditService; @@ -153,6 +157,7 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase _sessionManager = sessionManager; _csvExportService = null; _htmlExportService = null; + _brandingService = brandingService; _logger = logger; ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport); @@ -329,7 +334,15 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase if (dialog.ShowDialog() != true) return; 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); } catch (Exception ex)