fix(sidebar): resolve MatchingService live + settings "סגור" label

Sidebar lookups threw ObjectDisposedException on the EspoCrmClient's
HttpClient after any settings save. TryInitializeCrm rebuilds the CRM
stack (new HttpClient + MatchingService) and disposes the old HttpClient,
but the long-lived sidebar ReadingPaneViewModel had captured the old
MatchingService and kept calling into the disposed client.

ReadingPaneViewModel now resolves the MatchingService live on each lookup
via a Func<IMatchingService?> provider (() => host.MatchingService), so
it always uses the current stack and degrades gracefully (shows "not
configured") when none exists. SidebarController no longer captures a
snapshot.

Also: rename the Settings dialog's bottom button from "ביטול" to "סגור".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
PointStar
2026-06-09 16:10:47 +03:00
parent 6a51a58224
commit d7a53da59f
5 changed files with 30 additions and 13 deletions
@@ -152,7 +152,7 @@
MinWidth="100" />
<Button Command="{Binding CancelCommand}"
IsCancel="True"
Content="ביטול"
Content="סגור"
Padding="20,5"
MinWidth="100" />
</StackPanel>
@@ -27,7 +27,12 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
public sealed partial class ReadingPaneViewModel : ObservableObject
{
private readonly IMatchingService _matchingService;
// Resolved live on each lookup rather than captured once: the host
// rebuilds the CRM stack (new HttpClient + MatchingService) whenever
// settings are saved and disposes the old HttpClient. A captured
// snapshot would then call into a disposed HttpClient and throw
// ObjectDisposedException on every sidebar lookup.
private readonly Func<IMatchingService?> _matchingServiceProvider;
private readonly ILogger _logger;
private CancellationTokenSource? _activeLookup;
// Stashes the ambiguous MatchResult while the user is drilling into
@@ -94,9 +99,9 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
/// </summary>
public event EventHandler<string>? OpenInBrowserRequested;
public ReadingPaneViewModel(IMatchingService matchingService, ILogger logger)
public ReadingPaneViewModel(Func<IMatchingService?> matchingServiceProvider, ILogger logger)
{
_matchingService = matchingService ?? throw new ArgumentNullException(nameof(matchingService));
_matchingServiceProvider = matchingServiceProvider ?? throw new ArgumentNullException(nameof(matchingServiceProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -162,12 +167,20 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
_ambiguousReturnTarget = null;
State = ReadingPaneState.Loading;
var svc = _matchingServiceProvider();
if (svc == null)
{
ErrorMessage = "התוסף עדיין לא מוגדר. עדכן בהגדרות.";
State = ReadingPaneState.Error;
return;
}
var cts = new CancellationTokenSource();
_activeLookup = cts;
try
{
var result = await _matchingService.LookupAsync(senderEmail, cts.Token).ConfigureAwait(true);
var result = await svc.LookupAsync(senderEmail, cts.Token).ConfigureAwait(true);
if (cts.IsCancellationRequested) return;
if (result == null)
@@ -296,6 +309,14 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
{
if (candidate == null || string.IsNullOrWhiteSpace(candidate.Id)) return;
var svc = _matchingServiceProvider();
if (svc == null)
{
ErrorMessage = "התוסף עדיין לא מוגדר. עדכן בהגדרות.";
State = ReadingPaneState.Error;
return;
}
CancelInFlight();
var cts = new CancellationTokenSource();
_activeLookup = cts;
@@ -310,7 +331,7 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
try
{
var result = await _matchingService.LookupContactAsync(candidate.Id, rememberedEmail, cts.Token).ConfigureAwait(true);
var result = await svc.LookupContactAsync(candidate.Id, rememberedEmail, cts.Token).ConfigureAwait(true);
if (cts.IsCancellationRequested) return;
if (result == null || result.Contact == null)
+2 -2
View File
@@ -33,6 +33,6 @@ using System.Security;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.2.16.0")]
[assembly: AssemblyFileVersion("1.2.16.0")]
[assembly: AssemblyVersion("1.2.17.0")]
[assembly: AssemblyFileVersion("1.2.17.0")]
-1
View File
@@ -367,7 +367,6 @@ namespace OutlookAddin.Services
_sidebarController = new SidebarController(
taskPanes,
_outlookApp,
MatchingService!,
this,
System.Windows.Threading.Dispatcher.CurrentDispatcher,
Logger);
@@ -27,7 +27,6 @@ namespace OutlookAddin.Services
private readonly CustomTaskPaneCollection _taskPanes;
private readonly Outlook.Application _app;
private readonly IMatchingService _matchingService;
private readonly AddInHost _host;
private readonly Dispatcher _dispatcher;
private readonly ILogger _logger;
@@ -38,14 +37,12 @@ namespace OutlookAddin.Services
public SidebarController(
CustomTaskPaneCollection taskPanes,
Outlook.Application app,
IMatchingService matchingService,
AddInHost host,
Dispatcher dispatcher,
ILogger logger)
{
_taskPanes = taskPanes ?? throw new ArgumentNullException(nameof(taskPanes));
_app = app ?? throw new ArgumentNullException(nameof(app));
_matchingService = matchingService ?? throw new ArgumentNullException(nameof(matchingService));
_host = host ?? throw new ArgumentNullException(nameof(host));
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -83,7 +80,7 @@ namespace OutlookAddin.Services
if (_bindings.ContainsKey(key)) return;
var view = new ReadingPaneView();
var vm = new ReadingPaneViewModel(_matchingService, _logger)
var vm = new ReadingPaneViewModel(() => _host.MatchingService, _logger)
{
EspoCrmBaseUrl = _host.Settings.EspoCrmUrl
};