Nutzen eines vermittelten Dienstes
Dieses Dokument beschreibt den gesamten Code, die Muster und die Vorsichtsmaßnahmen, die für den Erwerb, die allgemeine Nutzung und die Entsorgung eines vermittelten Dienstes relevant sind. Um zu erfahren, wie Sie einen bestimmten vermittelten Dienst verwenden, nachdem Sie ihn erworben haben, schlagen Sie bitte in der entsprechenden Dokumentation für diesen vermittelten Dienst nach.
Bei allen Codes in diesem Dokument wird die Aktivierung der Funktion nullbare Referenztypen von C# dringend empfohlen.
Abrufen eines IServiceBrokers
Um einen vermittelten Dienst abzurufen, müssen Sie zunächst über eine Instanz von IServiceBroker verfügen. Wenn Ihr Code im Kontext von MEF (Managed Extensibility Framework) oder einem VSPackage ausgeführt wird, benötigen Sie in der Regel den globalen Dienst-Broker.
Vermittelte Dienste selbst sollten den IServiceBroker verwenden, der ihnen zugewiesen wird, wenn ihre Service Factory aufgerufen wird.
Der globale Dienst-Broker
Visual Studio bietet zwei Möglichkeiten, den globalen Dienst-Broker zu erhalten.
Verwenden Sie GlobalProvider.GetServiceAsync, um den SVsBrokeredServiceContainer anzufordern:
IBrokeredServiceContainer container = await AsyncServiceProvider.GlobalProvider.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
IServiceBroker serviceBroker = container.GetFullAccessServiceBroker();
Ab Visual Studio 2022 kann Code, der in einer MEF-aktivierten Erweiterung ausgeführt wird, den globalen Dienst-Broker importieren:
[Import(typeof(SVsFullAccessServiceBroker))]
IServiceBroker ServiceBroker { get; set; }
Beachten Sie das Argument typeof
für das Attribut Import, das erforderlich ist.
Jede Anfrage für den globalen IServiceBroker erzeugt eine neue Instanz eines Objekts, das als Sicht auf den globalen vermittelten Dienst Container dient. Diese eindeutige Instanz des Dienst-Brokers bietet Ihrem Client die Möglichkeit, AvailabilityChanged Ereignisse zu empfangen, die für die Verwendung durch diesen Client einzigartig sind. Wir empfehlen, dass jeder Client bzw. jede Klasse in Ihrer Erweiterung einen eigenen vermittelten Dienst erwirbt, indem Sie einen der oben genannten Ansätze verwenden, anstatt eine Instanz zu erhalten und diese in der gesamten Erweiterung zu verwenden. Dieses Code-Muster fördert auch sichere Codierungsmuster, bei denen ein vermittelter Dienst nicht den globalen Dienst-Broker verwenden sollte.
Wichtig
Implementierungen von IServiceBroker implementieren in der Regel nicht IDisposable, aber diese Objekte können nicht gesammelt werden, solange AvailabilityChanged-Handler existieren. Achten Sie darauf, das Hinzufügen/Entfernen von Ereignis-Handlern auszubalancieren, insbesondere wenn der Code den Dienst-Broker während der Lebensdauer des Prozesses verwerfen könnte.
Kontextspezifische Dienst-Broker
Die Verwendung eines geeigneten Dienst-Brokers ist eine wichtige Voraussetzung für das Sicherheitsmodell von vermittelten Diensten, insbesondere im Kontext von Live Share-Sitzungen.
Vermittelte Dienste werden mit einem eigenen IServiceBroker aktiviert und sollten diese Instanz für alle ihre eigenen Vermittelte Dienste verwenden, einschließlich der Dienste, die mit Proffer angeboten werden. Ein solcher Code liefert einen BrokeredServiceFactory, der einen Dienst-Broker empfängt, der von dem instanziierten vermittelten Dienst verwendet werden soll.
Abrufen eines Proxy für einen vermittelten Dienst
Der Abruf eines vermittelten Dienstes erfolgt normalerweise mit der Methode GetProxyAsync.
Die GetProxyAsync Methode benötigt ein ServiceRpcDescriptor und die Schnittstelle des Dienstes als generisches Argument. Die Dokumentation des vermittelten Dienstes, den Sie anfragen, sollte angeben, wo Sie den Deskriptor erhalten und welche Schnittstelle Sie verwenden müssen. Bei vermittelten Diensten, die in Visual Studio enthalten sind, sollte die zu verwendende Schnittstelle in der IntelliSense-Dokumentation zum Deskriptor erscheinen. Erfahren Sie, wie Sie Deskriptoren für vermittelte Visual Studio-Dienste finden können in Verfügbare vermittelte Dienste entdecken.
IServiceBroker broker; // Acquired as described earlier in this topic
IMyService? myService = await broker.GetProxyAsync<IMyService>(serviceDescriptor, cancellationToken);
using (myService as IDisposable)
{
Assumes.Present(myService); // Throw if service was not available
await myService.SayHelloAsync();
}
Wie bei allen Anfragen für vermittelte Dienste aktiviert der vorangehende Code eine neue Instanz eines vermittelten Dienstes.
Nach der Nutzung des Dienstes vernichtet der vorangehende Code den Proxy, wenn die Ausführung den using
-Block verlässt.
Wichtig
Jeder abgerufene Proxy muss vernichtet werden, auch wenn die Schnittstelle des Dienstes nicht von IDisposable abgeleitet ist.
Die Vernichtung ist wichtig, weil der Proxy oft über E/A-Ressourcen verfügt, die verhindern, dass er in die Sammlung aufgenommen wird.
Die Vernichtung beendet die E/A und bietet so die Möglichkeit, den Proxy zu entsorgen.
Verwenden Sie einen bedingten Cast nach IDisposable für Disposal und stellen Sie sich darauf ein, dass der Cast fehlschlägt, um eine Ausnahme für null
Proxys oder Proxys, die IDisposable nicht wirklich implementieren, zu vermeiden.
Stellen Sie sicher, dass Sie das neueste Microsoft.ServiceHub.Analyzers NuGet-Paket installieren und die ISBxxxx Analyzer-Regeln aktiviert lassen, um solche Lecks zu vermeiden.
Die Vernichtung des Proxys führt zur Vernichtung des vermittelten Dienstes, der für diesen Client bestimmt war.
Wenn Ihr Code einen vermittelten Dienst benötigt und seine Arbeit nicht beenden kann, wenn der Dienst nicht verfügbar ist, können Sie dem/der Benutzer*in einen Bildschirm mit einem Fehler anzeigen, anstatt eine Ausnahme auszulösen, wenn der Code das Benutzererlebnis bestimmt.
Client-RPC-Ziele
Einige vermittelte Dienste akzeptieren oder erfordern ein Client-RPC-Ziel (Remote Procedure Call) für "Callbacks". Eine solche Option oder Anforderung sollte in der Dokumentation des jeweiligen vermittelten Dienstes enthalten sein. Für vermittelte Visual Studio-Dienste sollten diese Informationen in der IntelliSense-Dokumentation zum Deskriptor enthalten sein.
In einem solchen Fall kann ein Client einen ServiceActivationOptions.ClientRpcTarget wie diesen bereitstellen:
IMyService? myService = await broker.GetProxyAsync<IMyService>(
serviceDescriptor,
new ServiceActivationOptions
{
ClientRpcTarget = new MyCallbackObject(),
},
cancellationToken);
Aufrufen des Client-Proxy
Das Ergebnis einer Anfrage an einen vermittelten Dienst ist eine Instanz der von einem Proxy implementierten Dienstschnittstelle. Dieser Proxy leitet Aufrufe und Ereignisse in beide Richtungen weiter, wobei sich das Verhalten in einigen wichtigen Punkten von dem unterscheidet, was man bei einem direkten Aufruf des Dienstes erwarten würde.
Observer-Muster
Wenn der Servicevertrag Parameter vom Typ IObserver<T> entgegennimmt, erfahren Sie mehr darüber, wie ein solcher Typ konstruiert wird, unter Wie man einen Beobachter implementiert.
Ein ActionBlock<TInput> kann angepasst werden, um IObserver<T> mit der AsObserver Erweiterungsmethode zu implementieren. Die Klasse System.Reactive.Observer aus dem Reactive-Framework ist eine weitere Alternative zur eigenen Implementierung der Schnittstelle.
Ausgelöste Ausnahmen durch den Proxy
- Erwarten Sie, dass RemoteInvocationException für jede Ausnahme ausgelöst wird, die vom vermittelten Dienst ausgelöst wird. Die ursprüngliche Ausnahme finden Sie in der InnerException.
Dies ist ein natürliches Verhalten für einen remote gehosteten Dienst, denn es entspricht dem Verhalten von JsonRpc.
Wenn es sich um einen lokalen Dienst handelt, verpackt der lokale Proxy alle Ausnahmen auf die gleiche Weise, sodass der Code des Clients nur einen Ausnahmepfad hat, der für lokale und remote Dienste funktioniert.
- Prüfen Sie die Eigenschaft ErrorCode, wenn die Dokumentation des Dienstes vorschlägt, dass bestimmte Codes auf der Grundlage bestimmter Bedingungen festgelegt werden, auf die Sie verzweigen können.
- Eine breitere Palette von Fehlern wird durch das Abfangen von RemoteRpcException mitgeteilt, das der Basistyp für RemoteInvocationException ist.
- Erwarten Sie, dass ConnectionLostException von jedem Aufruf ausgelöst wird, wenn die Verbindung zu einem Remote-Dienst unterbrochen wird oder der Prozess, der den Dienst hostet, abstürzt. Dies ist vor allem dann von Bedeutung, wenn der Dienst remote abgerufen werden kann.
Zwischenspeichern des Proxys
Die Aktivierung eines vermittelten Dienstes und des zugehörigen Proxys ist mit einem gewissen Aufwand verbunden, insbesondere wenn der Dienst von einem Remote-Prozess stammt.
Wenn die häufige Nutzung eines vermittelten Dienstes das Zwischenspeichern des Proxy über viele Aufrufe einer Klasse hinweg rechtfertigt, kann der Proxy in einem Feld dieser Klasse gespeichert werden.
Die Klasse, die den Proxy enthält, sollte "disposable" sein und den Proxy in ihrer Dispose
Methode vernichten.
Betrachten Sie das folgende Beispiel:
class MyExtension : IDisposable
{
readonly IServiceBroker serviceBroker;
IMyService? serviceProxy;
internal MyExtension(IServiceBroker serviceBroker)
{
this.serviceBroker = serviceBroker;
}
async Task SayHiAsync(CancellationToken cancellationToken)
{
if (this.serviceProxy is null)
{
this.serviceProxy = await this.serviceBroker.GetProxyAsync<IMyService>(serviceDescriptor, cancellationToken);
Assumes.Present(this.serviceProxy);
}
await this.serviceProxy.SayHelloAsync();
}
public void Dispose()
{
(this.serviceProxy as IDisposable)?.Dispose();
}
}
Der vorangehende Code ist im Großen und Ganzen korrekt, aber er berücksichtigt keine Race Conditions zwischen Dispose
und SayHiAsync
.
Der Code berücksichtigt auch keine AvailabilityChanged-Ereignisse, die dazu führen sollten, dass der zuvor erhaltene Proxy vernichtet wird und der Proxy bei der nächsten Anforderung erneut erhalten wird.
Die Klasse ServiceBrokerClient wurde entwickelt, um diese Race- und Invalidierungsbedingungen zu behandeln, damit Ihr eigener Code einfach bleibt. Betrachten Sie dieses aktualisierte Beispiel, das den Proxy mit Hilfe dieser Hilfsklasse zwischenspeichert:
class MyExtension : IDisposable
{
readonly ServiceBrokerClient serviceBrokerClient;
internal MyExtension(IServiceBroker serviceBroker)
{
this.serviceBrokerClient = new ServiceBrokerClient(serviceBroker);
}
async Task SayHiAsync(CancellationToken cancellationToken)
{
using var rental = await this.serviceBrokerClient.GetProxyAsync<IMyService>(descriptor, cancellationToken);
Assumes.Present(rental.Proxy); // Throw if service is not available
IMyService myService = rental.Proxy;
await myService.SayHelloAsync();
}
public void Dispose()
{
// Disposing the ServiceBrokerClient will dispose of all proxies
// when their rentals are released.
this.serviceBrokerClient.Dispose();
}
}
Der vorhergehende Code ist immer noch dafür verantwortlich, den ServiceBrokerClient und jede Verwendung eines Proxy zu vernichten. Race-Bedingungen zwischen der Vernichtung und der Verwendung des Proxys werden vom ServiceBrokerClient-Objekt gehandhabt, das jeden zwischengespeicherten Proxy zum Zeitpunkt seiner eigenen Vernichtung vernichtet oder wenn die letzte Nutzung des Proxys freigegeben wurde, je nachdem, was zuletzt eintritt.
Wichtige Vorbehalte bezüglich der ServiceBrokerClient
ServiceBrokerClient indiziert die zwischenspeichernden Proxys nur auf der Grundlage von ServiceMoniker. Wenn Sie ServiceActivationOptions übergeben und ein zwischenspeichernder Proxy bereits verfügbar ist, wird der zwischenspeichernde Proxy ohne Verwendung von ServiceActivationOptions zurückgegeben, was zu einem unerwarteten Verhalten des Dienstes führt. Ziehen Sie in solchen Fällen die direkte Verwendung von IServiceBroker in Betracht.
Speichern Sie das von ServiceBrokerClient.GetProxyAsync abgerufene ServiceBrokerClient.Rental<T> nicht in einem Feld. Der Proxy wird bereits über den Bereich einer Methode hinaus durch die ServiceBrokerClient zwischengespeichert. Wenn Sie mehr Kontrolle über die Lebensdauer des Proxys benötigen, insbesondere bei einer erneuten Anforderung aufgrund eines AvailabilityChanged-Ereignisses, verwenden Sie stattdessen direkt IServiceBroker und speichern Sie den Proxy des Dienstes in einem Feld.
Erstellen und speichern Sie ServiceBrokerClient in einem Feld und nicht in einer lokalen Variablen. Wenn Sie ihn erstellen und als lokale Variable in einer Methode verwenden, bringt das keinen Mehrwert gegenüber der direkten Verwendung von IServiceBroker, aber Sie müssen jetzt über zwei Objekte (den Client und die Nutzung) verfügen statt über eines (den Dienst).
Die Wahl zwischen IServiceBroker und ServiceBrokerClient
Beide sind benutzungsfreundlich, und die Standardeinstellung sollte wahrscheinlich IServiceBroker sein.
Kategorie | IServiceBroker | ServiceBrokerClient |
---|---|---|
Benutzungsfreundlich | Ja | Ja |
Erfordert Vernichtung | No | Ja |
Verwaltet die Lebensdauer des Proxys | Nein Der/die Besitzer*in muss den Proxy vernichten, wenn er/sie ihn nicht mehr benötigt. | Ja, sie werden am Leben gehalten und wiederverwendet, solange sie gültig sind. |
Anwendbar für statuslose Dienste | Ja | Ja |
Anwendbar für statusbasierte Dienste | Ja | No |
Anwendbar, wenn dem Proxy Ereignis-Handler hinzugefügt werden | Ja | No |
Ereignis zur Benachrichtigung, wenn der alte Proxy ungültig wird | AvailabilityChanged | Invalidated |
ServiceBrokerClient bietet Ihnen eine bequeme Möglichkeit zur schnellen und häufigen Wiederverwendung eines Proxy, bei der es Ihnen egal ist, ob der zugrundeliegende Dienst zwischen den Vorgängen auf oberster Ebene unter Ihnen geändert wird. Wenn Ihnen diese Dinge jedoch wichtig sind und Sie die Lebensdauer Ihrer Proxys selbst verwalten möchten oder wenn Sie Ereignis-Handler benötigen (was bedeutet, dass Sie die Lebensdauer des Proxys verwalten müssen), sollten Sie IServiceBroker verwenden.
Resilienz bei Dienstunterbrechungen
Es gibt einige Arten von Dienstunterbrechungen, die bei vermittelten Diensten möglich sind:
- Ein Dienst ist nicht verfügbar.
- Eine abgebrochene Verbindung zu einem zuvor erworbenen vermittelten Dienst.
- Eine Änderung der Verfügbarkeit des Dienstes, falls eine zukünftige Anfrage für diesen Dienst gestellt wird.
Fehler bei der Aktivierung eines vermittelten Dienstes
Wenn eine Anfrage für einen vermittelten Dienst von einem verfügbaren Dienst erfüllt werden kann, aber die Service Factory eine unbehandelte Ausnahme auslöst, wird ein ServiceActivationFailedException an den Client zurückgegeben, damit dieser den Fehler verstehen und an die Benutzer*innen liefern kann.
Wenn eine Anfrage für einen vermittelten Dienst nicht mit einem verfügbaren Dienst abgeglichen werden kann, wird null
an den Client zurückgegeben.
In diesem Fall wird AvailabilityChanged ausgegeben, wenn der Dienst später verfügbar wird.
Die Anfrage wird möglicherweise nicht abgelehnt, weil der Dienst nicht verfügbar ist, sondern weil die angebotene Version niedriger ist als die angeforderte Version. Ihr Plan könnte vorsehen, die Anfrage mit einer niedrigeren Version zu wiederholen, von der Ihr Client weiß, dass sie existiert und mit der er interagieren kann.
Wenn sich die Latenz durch die fehlgeschlagenen Versionsprüfungen bemerkbar macht, kann der Client das VisualStudioServices.VS2019_4Services.RemoteBrokeredServiceManifest anfordern, um sich einen vollständigen Überblick über die verfügbaren Dienste und Versionen aus einer Remote-Quelle zu verschaffen.
Umgang mit abgebrochenen Verbindungen
Ein erfolgreich erworbener vermittelter Dienst Proxy kann aufgrund einer abgebrochenen Verbindung oder eines Absturzes des Prozesses, der ihn hostet, fehlschlagen. Nach einer solchen Unterbrechung wird jeder Aufruf dieses Proxy mit ConnectionLostException ausgelöst.
Ein Client eines vermittelten Dienstes kann solche Verbindungsabbrüche proaktiv erkennen und darauf reagieren, indem er das Ereignis Disconnected behandelt. Um dieses Ereignis zu erreichen, muss ein Proxy auf IJsonRpcClientProxy gecastet werden, um das Objekt JsonRpc abzurufen. Dieser Cast sollte nur unter bestimmten Bedingungen erfolgen, damit der Dienst nicht unterbrochen wird, wenn er lokal ist.
if (this.myService is IJsonRpcClientProxy clientProxy)
{
clientProxy.JsonRpc.Disconnected += JsonRpc_Disconnected;
}
void JsonRpc_Disconnected(object? sender, JsonRpcDisconnectedEventArgs args)
{
if (args.Reason == DisconnectedReason.RemotePartyTerminated)
{
// consider reacquisition of the service.
}
}
Umgang mit Änderungen der Dienstverfügbarkeit
Clients von vermittelten Diensten können Benachrichtigungen darüber erhalten, wann sie einen vermittelten Dienst, den sie zuvor abgefragt haben, erneut anfordern sollten, indem sie das Ereignis AvailabilityChanged behandeln. Handler für dieses Ereignis sollten vor der Anfrage eines vermittelten Dienstes hinzugefügt werden, um sicherzustellen, dass ein Ereignis, das kurz nach der Anfrage eines Dienstes ausgelöst wird, nicht aufgrund einer Race Condition verloren geht.
Wenn ein vermittelter Dienst nur für die Dauer der Ausführung einer asynchronen Methode angefragt wird, ist es nicht empfehlenswert, dieses Ereignis zu behandeln. Das Ereignis ist vor allem für Clients relevant, die ihren Proxy über längere Zeiträume speichern, sodass sie Änderungen des Dienstes kompensieren müssen und in der Lage sind, ihren Proxy zu aktualisieren.
Dieses Ereignis kann auf jedem Thread ausgelöst werden, möglicherweise gleichzeitig mit Code, der einen Dienst nutzt, den das Ereignis beschreibt.
Mehrere Statusänderungen können zum Auslösen dieses Ereignisses führen, darunter:
- Eine Lösung oder ein Ordner wird geöffnet oder geschlossen.
- Eine Live Share Sitzung wird gestartet.
- Ein dynamisch registrierter vermittelter Dienst, der gerade entdeckt wurde.
Bei einem betroffenen vermittelten Dienst wird dieses Ereignis nur bei Clients ausgelöst, die diesen Dienst zuvor angefordert haben, unabhängig davon, ob diese Anfrage erfüllt wurde oder nicht.
Das Ereignis wird höchstens einmal pro Dienst nach jeder Anfrage für diesen Dienst ausgelöst. Wenn der Client zum Beispiel den Dienst A anfragt und der Dienst B eine Änderung der Verfügbarkeit erfährt, wird für diesen Client kein Ereignis ausgelöst. Später, wenn der Dienst A eine Änderung der Verfügbarkeit erfährt, erhält der Client das Ereignis. Wenn der Client den Dienst A nicht erneut anfordert, werden nachfolgende Verfügbarkeitsänderungen für A keine weiteren Benachrichtigungen für diesen Client auslösen. Sobald der Client A erneut anfragt, ist er berechtigt, die nächste Benachrichtigung für diesen Dienst zu erhalten.
Das Ereignis wird ausgelöst, wenn ein Dienst verfügbar wird, nicht mehr verfügbar ist oder sich die Implementierung ändert, so dass alle früheren Clients den Dienst erneut anfordern müssen.
ServiceBrokerClient behandelt Ereignisse zur Änderung der Verfügbarkeit von zwischengespeicherten Proxys automatisch, indem die alten Proxys vernichtet werden, wenn eine Nutzung zurückgegeben wurde, und eine neue Instanz des Dienstes angefordert wird, wenn der Besitzer eine solche anfordert. Diese Klasse kann Ihren Code erheblich vereinfachen, wenn der Dienst statuslos ist und es nicht erforderlich ist, dass Ihr Code Ereignis-Handler an den Proxy anhängt.
Abrufen einer Pipe für einen vermittelten Dienst
Obwohl der Zugriff auf einen vermittelten Dienst über einen Proxy die allgemeinste und bequemste Technik ist, kann es in fortgeschrittenen Szenarien besser oder notwendig sein, eine Pipe zu diesem Dienst anzufordern, damit der Client das RPC direkt steuern oder jeden anderen Datentyp direkt kommunizieren kann.
Eine Pipe zu dem vermittelten Dienst kann über die Methode GetPipeAsync abgerufen werden. Diese Methode nimmt eine ServiceMoniker anstelle einer ServiceRpcDescriptor, da das von einem Deskriptor bereitgestellte RPC-Verhalten nicht erforderlich ist. Wenn Sie einen Deskriptor haben, können Sie den Moniker über die Eigenschaft ServiceRpcDescriptor.Moniker abrufen.
Pipes sind zwar an E/A gebunden, kommen aber nicht für die Garbage Collection in Frage. Vermeiden Sie Speicherlecks, indem Sie diese Pipes immer dann abschließen, wenn sie nicht mehr verwendet werden.
Im folgenden Ausschnitt wird ein vermittelter Dienst aktiviert und der Client verfügt über eine direkte Pipe zu diesem Dienst. Der Client sendet dann den Inhalt einer Datei an den Dienst und trennt die Verbindung.
async Task SendMovieAsync(string movieFilePath, CancellationToken cancellationToken)
{
IServiceBroker serviceBroker;
IDuplexPipe? pipe = await serviceBroker.GetPipeAsync(serviceMoniker, cancellationToken);
if (pipe is null)
{
throw new InvalidOperationException($"The brokered service '{serviceMoniker}' is not available.");
}
try
{
// Open the file optimized for async I/O
using FileStream fs = new FileStream(movieFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true);
await fs.CopyToAsync(pipe.Output.AsStream(), cancellationToken);
}
catch (Exception ex)
{
// Complete the pipe, passing through the exception so the remote side understands what went wrong.
await pipe.Input.CompleteAsync(ex);
await pipe.Output.CompleteAsync(ex);
throw;
}
finally
{
// Always complete the pipe after successfully using the service.
await pipe.Input.CompleteAsync();
await pipe.Output.CompleteAsync();
}
}
Testen von Clients mit vermittelten Diensten
Vermittelte Dienste sind eine sinnvolle Abhängigkeit, die Sie beim Testen Ihrer Erweiterung nachbilden können. Wenn Sie einen vermittelten Dienst spiegeln, empfehlen wir Ihnen die Verwendung eines Frameworks, das die Schnittstelle in Ihrem Namen implementiert und den Code, den Sie benötigen, in die spezifischen Mitglieder einfügt, die Ihr Client aufrufen wird. Dies bietet die Möglichkeit, dass Ihre Tests weiterhin kompiliert und ohne Unterbrechungen ausgeführt werden, wenn der Schnittstelle des vermittelten Dienstes Mitglieder hinzugefügt werden.
Wenn Sie das Microsoft.VisualStudio.Sdk.TestFramework zum Testen Ihrer Erweiterung verwenden, kann Ihr Test Standard-Code enthalten, um einen Mock-Dienst anzubieten, den Ihr Client-Code abfragen und ausführen kann. Nehmen wir zum Beispiel an, Sie wollten den vermittelten Dienst VisualStudioServices.VS2022.FileSystem in Ihren Tests nachbilden. Sie könnten das Mock mit diesem Code bereitstellen:
IBrokeredServiceContainer sbc = await AsyncServiceProvider.GlobalProvider.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
Mock<IFileSystem> mockFileSystem = new Mock<IFileSystem>();
sbc.Proffer(VisualStudioServices.VS2022.FileSystem, (ServiceMoniker moniker, ServiceActivationOptions options, IServiceBroker serviceBroker, CancellationToken cancellationToken) => new ValueTask<object?>(mockFileSystem.Object));
Der Container des nachgebildeten vermittelten Dienstes erfordert nicht wie Visual Studio selbst, dass ein angebotener Dienst zuerst registriert wird.
Ihr zu testender Code kann den vermittelten Dienst ganz normal abrufen, mit der Ausnahme, dass er während des Tests Ihre Attrappe erhält und nicht den echten Dienst, den er erhalten würde, wenn er unter Visual Studio ausgeführt würde:
IBrokeredServiceContainer sbc = await AsyncServiceProvider.GlobalProvider.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
IServiceBroker serviceBroker = sbc.GetFullAccessServiceBroker();
IFileSystem? proxy = await serviceBroker.GetProxyAsync<IFileSystem>(VisualStudioServices.VS2022.FileSystem);
using (proxy as IDisposable)
{
Assumes.Present(proxy);
await proxy.DeleteAsync(new Uri("file://some/file"), recursive: false, null, this.TimeoutToken);
}