Poskytnutí zprostředkované služby
Zprostředkovaná služba se skládá z následujících prvků:
- Rozhraní , které deklaruje funkčnost služby a slouží jako kontrakt mezi službou a jejími klienty.
- Implementace tohoto rozhraní.
- Moniker služby, který službě přiřadí název a verzi.
- Popisovač , který v případě potřeby kombinuje moniker služby s chováním pro zpracování rpc (vzdálené volání procedur).
- Buď objednáte službu a zaregistrujte zprostředkovanou službu s balíčkem VS, nebo proveďte obojí s MEF (Managed Extensibility Framework).
Jednotlivé položky v předchozím seznamu jsou podrobně popsány v následujících částech.
S veškerým kódem v tomto článku se důrazně doporučuje aktivovat funkci odkazových typů s možnou hodnotou null jazyka C#.
Rozhraní služby
Rozhraní služby může být standardní rozhraní .NET (často napsané v jazyce C#), ale mělo by odpovídat pokynům nastaveným typem odvozeného ServiceRpcDescriptortypu, který bude vaše služba používat k zajištění toho, aby rozhraní bylo možné používat přes RPC, když klient a služba běží v různých procesech.
Tato omezení obvykle zahrnují, že vlastnosti a indexery nejsou povoleny, a většina nebo všechny metody vrací Task
nebo jiný asynchronní kompatibilní návratový typ.
Jedná se ServiceJsonRpcDescriptor o doporučený odvozený typ pro zprostředkované služby. Tato třída využívá knihovnu StreamJsonRpc , když klient a služba vyžadují rpc ke komunikaci. StreamJsonRpc používá určitá omezení pro rozhraní služby, jak je popsáno zde.
Rozhraní může být odvozeno z IDisposable, System.IAsyncDisposablenebo dokonce, Microsoft.VisualStudio.Threading.IAsyncDisposable ale to není vyžadováno systémem. Vygenerované proxy servery klienta se implementují IDisposable v obou směrech.
Jednoduché rozhraní služby kalkulačky může být deklarováno takto:
public interface ICalculator
{
ValueTask<double> AddAsync(double a, double b, CancellationToken cancellationToken);
ValueTask<double> SubtractAsync(double a, double b, CancellationToken cancellationToken);
}
Přestože implementace metod v tomto rozhraní nemusí zaručovat asynchronní metodu, vždy používáme asynchronní podpisy metod v tomto rozhraní, protože toto rozhraní se používá k vygenerování proxy klienta, který může vyvolat tuto službu vzdáleně, což jistě zaručuje podpis asynchronní metody.
Rozhraní může deklarovat události, které lze použít k upozorňování klientů na události, ke kterým dochází ve službě.
Kromě událostí nebo vzoru návrhu pozorovatele může zprostředkovaná služba, která musí "volat zpět" klientovi, definovat druhé rozhraní, které slouží jako kontrakt, který klient musí implementovat a poskytovat prostřednictvím ServiceActivationOptions.ClientRpcTarget vlastnosti při vyžádání služby. Takové rozhraní by mělo odpovídat všem stejným vzorům návrhu a omezením jako rozhraní zprostředkované služby, ale s přidanými omezeními správy verzí.
Projděte si osvědčené postupy pro navrhování zprostředkované služby , kde najdete tipy k návrhu výkonného a budoucího rozhraní RPC.
Může být užitečné deklarovat toto rozhraní v odlišném sestavení od sestavení, které implementuje službu, aby jeho klienti mohli odkazovat na rozhraní, aniž by služba musela zveřejnit více podrobností implementace. Může být také užitečné odeslat sestavení rozhraní jako balíček NuGet pro další rozšíření, která se mají odkazovat při rezervaci vlastního rozšíření pro odeslání implementace služby.
Zvažte cílení na sestavení, které deklaruje vaše rozhraní služby, aby netstandard2.0
bylo zajištěno, že vaše služba může být snadno vyvolána z jakéhokoli procesu .NET bez ohledu na to, jestli používá rozhraní .NET Framework, .NET Core, .NET 5 nebo novější.
Testování
Automatizované testy by měly být napsány společně s rozhraním služby, aby bylo možné ověřit připravenost rozhraní RPC.
Testy by měly ověřit, že všechna data předávaná přes rozhraní jsou serializovatelná.
Třídu můžete najít BrokeredServiceContractTestBase<TInterface,TServiceMock> z balíčku Microsoft.VisualStudio.Sdk.TestFramework.Xunit , který je užitečný k odvození testovací třídy rozhraní. Tato třída obsahuje některé základní konvence testování pro vaše rozhraní, metody, které pomáhají s běžnými kontrolními výrazy, jako je testování událostí a další.
Metody
Ověřte, že každý argument a návratová hodnota byly serializovány zcela. Pokud používáte výše uvedenou testovací základní třídu, může váš kód vypadat takto:
public interface IYourService
{
Task<bool> SomeOperationAsync(YourStruct arg1);
}
public static class Descriptors
{
public static readonly ServiceRpcDescriptor YourService = new ServiceJsonRpcDescriptor(
new ServiceMoniker("YourCompany.YourExtension.YourService", new Version(1, 0)),
clientInterface: null,
ServiceJsonRpcDescriptor.Formatters.MessagePack,
ServiceJsonRpcDescriptor.MessageDelimiters.BigEndianInt32LengthHeader,
new MultiplexingStream.Options { ProtocolMajorVersion = 3 })
.WithExceptionStrategy(StreamJsonRpc.ExceptionProcessing.ISerializable);
}
public class YourServiceMock : IYourService
{
internal YourStruct? SomeOperationArg1 { get; set; }
public Task<bool> SomeOperationAsync(YourStruct arg1, CancellationToken cancellationToken)
{
this.SomeOperationArg1 = arg1;
return true;
}
}
public class BrokeredServiceTests : BrokeredServiceContractTestBase<IYourService, YourServiceMock>
{
public BrokeredServiceTests(ITestOutputHelper logger)
: base(logger, Descriptors.YourService)
{
}
[Fact]
public async Task SomeOperation()
{
var arg1 = new YourStruct
{
Field1 = "Something",
};
Assert.True(await this.ClientProxy.SomeOperationAsync(arg1, this.TimeoutToken));
Assert.Equal(arg1.Field1, this.Service.SomeOperationArg1.Value.Field1);
}
}
Pokud deklarujete více metod se stejným názvem, zvažte testování rozlišení přetížení.
Do služby napodobení můžete přidat internal
pole pro každou metodu, ve které jsou uložené argumenty pro danou metodu, aby testovací metoda mohla metodu volat, a pak ověřit, zda byla vyvolána správná metoda se správnými argumenty.
Události
Všechny události deklarované ve vašem rozhraní by se také měly testovat pro připravenost RPC. Události vyvolané z zprostředkovanou službou nezpůsobí selhání testu, pokud během serializace RPC selže, protože události jsou "fire and forget".
Pokud používáte základní třídu testu uvedenou výše, je již integrovaná do některých pomocných metod a může vypadat takto (beze změny částí pro stručnost):
public interface IYourService
{
event EventHandler<int> NewTotal;
}
public class YourServiceMock : IYourService
{
public event EventHandler<int>? NewTotal;
internal void RaiseNewTotal(int arg) => this.NewTotal?.Invoke(this, arg);
}
public class BrokeredServiceTests : BrokeredServiceContractTestBase<IYourService, YourServiceMock>
{
[Fact]
public async Task NewTotal()
{
await this.AssertEventRaisedAsync<int>(
(p, h) => p.NewTotal += h,
(p, h) => p.NewTotal -= h,
s => s.RaiseNewTotal(50),
a => Assert.Equal(50, a));
}
}
Implementace služby
Třída služby by měla implementovat rozhraní RPC deklarované v předchozím kroku. Služba může implementovat IDisposable nebo jakékoli jiné rozhraní nad rámec rozhraní používaného pro RPC. Proxy server vygenerovaný na klientovi implementuje pouze rozhraní služby a IDisposablepřípadně několik dalších vybraných rozhraní pro podporu systému, takže přetypování na jiná rozhraní implementovaná službou selže na klientovi.
Představte si příklad kalkulačky použitý výše, který zde implementujeme:
internal class Calculator : ICalculator
{
public ValueTask<double> AddAsync(double a, double b, CancellationToken cancellationToken)
{
return new ValueTask<double>(a + b);
}
public ValueTask<double> SubtractAsync(double a, double b, CancellationToken cancellationToken)
{
return new ValueTask<double>(a - b);
}
}
Vzhledem k tomu, že samotné tělo metody nemusí být asynchronní, explicitně zabalíme návratovou hodnotu do vytvořeného ValueTask<TResult> návratového typu tak, aby odpovídalo rozhraní služby.
Implementace pozorovatelného vzoru návrhu
Pokud ve svém rozhraní služby nabídnete předplatné pozorovatele, může to vypadat takto:
Task<IDisposable> SubscribeAsync(IObserver<YourDataType> observer);
Argument IObserver<T> obvykle musí trvat dobu života volání této metody, aby klient mohl pokračovat v přijímání aktualizací po dokončení volání metody, dokud klient nezlikviduje vrácenou IDisposable hodnotu. Aby se to usnadnilo, může vaše třída služeb zahrnovat kolekci IObserver<T> předplatných, která by všechny aktualizace provedené ve vašem stavu následně vyčíslila, aby se aktualizovali všichni předplatitelé. Ujistěte se, že výčet kolekce je bezpečný pro přístup z více vláken a zejména s mutací v této kolekci, ke kterým může dojít prostřednictvím dalších předplatných nebo vyřazení těchto předplatných.
Dbejte na to, aby všechny aktualizace publikované prostřednictvím OnNext zachování pořadí, ve kterém byly změny stavu zavedeny ve vaší službě.
Všechna předplatná by se nakonec měla ukončit buď voláním, OnCompleted nebo OnError aby se zabránilo úniku prostředků v klientských systémech a systémech RPC. To zahrnuje vyřazení služeb, kde by se měla explicitně dokončit všechna zbývající předplatná.
Přečtěte si další informace o vzoru návrhu pozorovatele, o tom, jak implementovat pozorovatelného zprostředkovatele dat a zejména s ohledem na RPC.
Jednorázové služby
Vaše třída služeb nemusí být uvolnitelná, ale služby, které budou uvolněny, když klient zlikviduje svůj proxy server vaší služby nebo dojde ke ztrátě připojení mezi klientem a službou. Jedno použití se testuje v tomto pořadí: System.IAsyncDisposable, Microsoft.VisualStudio.Threading.IAsyncDisposable, IDisposable. K odstranění služby se použije pouze první rozhraní z tohoto seznamu, které vaše třída služby implementuje.
Při zvažování odstranění mějte na paměti bezpečnost vláken. Vaše Dispose metoda může být volána v jakémkoli vlákně, zatímco je spuštěn jiný kód ve vaší službě (například pokud dojde k vyřazení připojení).
Vyvolání výjimek
Při vyvolání výjimek zvažte vyvolání s konkrétním kódem ErrorCode, aby bylo možné řídit kód chyby přijatý klientem v nástroji RemoteInvocationException.LocalRpcException Poskytnutí klientů s kódem chyby jim může umožnit větvení na základě povahy chyby lépe než analýza zpráv nebo typů výjimek.
Kódy chyb podle specifikace JSON-RPC musí být větší než -32000, včetně kladných čísel.
Využívání dalších zprostředkovaných služeb
Pokud zprostředkovaná služba vyžaduje přístup k jiné zprostředkované službě, doporučujeme použít IServiceBroker službu, která je poskytována jeho službě factory, ale je obzvláště důležitá, když registrace zprostředkované služby nastaví AllowTransitiveGuestClients příznak.
Pokud by naše služba kalkulačky potřebovala k implementaci svého chování jiné zprostředkované služby, upravili bychom konstruktor tak, aby přijímal IServiceBroker:
internal class Calculator : ICalculator
{
private readonly State state;
private readonly IServiceBroker serviceBroker;
internal class Calculator(State state, IServiceBroker serviceBroker)
{
this.state = state;
this.serviceBroker = serviceBroker;
}
// ...
}
Přečtěte si další informace o tom, jak zabezpečit zprostředkovanou službu a využívat zprostředkované služby.
Stavové služby
Stav jednotlivých klientů
Pro každého klienta, který požaduje službu, se vytvoří nová instance této třídy.
Pole ve Calculator
výše uvedené třídě by uložilo hodnotu, která může být pro každého klienta jedinečná.
Předpokládejme, že přidáme čítač, který se zvýší při každém provedení operace:
internal class Calculator : ICalculator
{
int operationCounter;
public ValueTask<double> AddAsync(double a, double b, CancellationToken cancellationToken)
{
this.operationCounter++;
return new ValueTask<double>(a + b);
}
public ValueTask<double> SubtractAsync(double a, double b, CancellationToken cancellationToken)
{
this.operationCounter++;
return new ValueTask<double>(a - b);
}
}
Zprostředkovaná služba by měla být napsaná tak, aby dodržovala postupy bezpečné pro přístup z více vláken.
Při použití doporučeného ServiceJsonRpcDescriptorrozhraní můžou vzdálená připojení s klienty zahrnovat souběžné spouštění metod vaší služby, jak je popsáno v tomto dokumentu.
Když klient sdílí proces a AppDomain se službou, může klient volat vaši službu souběžně z více vláken.
Implementace výše uvedeného příkladu bezpečná pro přístup z více vláken může zvýšit Interlocked.Increment(Int32) operationCounter
pole.
Sdílený stav
Pokud je ve stavu, že vaše služba bude muset sdílet všechny své klienty, tento stav by měl být definován v jedinečné třídě, která je vytvořena vytvořením instance balíčku VS a předána jako argument konstruktoru vaší služby.
Předpokládejme, že chceme operationCounter
, aby výše definovaný spočítal všechny operace napříč všemi klienty služby.
Pole bychom museli v této nové třídě státu zvednout:
internal class Calculator : ICalculator
{
private readonly State state;
internal Calculator(State state)
{
this.state = state;
}
public ValueTask<double> AddAsync(double a, double b, CancellationToken cancellationToken)
{
this.state.IncrementCounter();
return new ValueTask<double>(a + b);
}
public ValueTask<double> SubtractAsync(double a, double b, CancellationToken cancellationToken)
{
this.state.IncrementCounter();
return new ValueTask<double>(a - b);
}
internal class State
{
private int operationCounter;
internal int OperationCounter => this.operationCounter;
internal void IncrementCounter() => Interlocked.Increment(ref this.operationCounter);
}
}
Teď máme elegantní, testovatelný způsob správy sdíleného stavu napříč několika instancemi naší Calculator
služby.
Později při zápisu kódu pro zpracování služby uvidíme, jak se tato State
třída vytvoří jednou a sdílí se s každou instancí Calculator
služby.
Při práci se sdíleným stavem je zvlášť důležité být bezpečné pro přístup z více klientů, protože není možné provádět žádné předpoklady týkající se plánování volání tak, aby se nikdy neprovedli souběžně.
Pokud vaše třída sdíleného stavu potřebuje přístup k jiným zprostředkovaným službám, měla by místo jednoho z kontextových služeb přiřazených k jednotlivé instanci zprostředkované služby použít globálního zprostředkovatele služeb. Použití globálního zprostředkovatele služeb v rámci zprostředkované služby nese s sebou bezpečnostní důsledky , když ProvideBrokeredServiceAttribute.AllowTransitiveGuestClients je příznak nastaven.
Záležitosti zabezpečení
Zabezpečení je pro vaši zprostředkovanou službu důležité, pokud je zaregistrovaná s ProvideBrokeredServiceAttribute.AllowTransitiveGuestClients příznakem, který zpřístupňuje možný přístup jiným uživatelům na jiných počítačích, které se účastní sdílené relace Live Share.
Před nastavením příznaku AllowTransitiveGuestClients si přečtěte, jak zabezpečit zprostředkovanou službu a provést nezbytná omezení zabezpečení.
Moniker služby
Zprostředkovaná služba musí mít serializovatelný název a volitelnou verzi, kterou může klient požádat o službu. A ServiceMoniker je pohodlný obal pro tyto dva části informací.
Moniker služby je analogický s úplným názvem sestavení typu CLR (Common Language Runtime). Musí být globálně jedinečný a proto by měl obsahovat název vaší společnosti a název vašeho rozšíření jako předpony samotného názvu služby.
Může být užitečné definovat tento moniker v static readonly
poli pro použití jinde:
public static readonly ServiceMoniker Moniker = new ServiceMoniker("YourCompany.Extension.Calculator", new Version("1.0"));
I když většina použití vaší služby nemusí používat váš moniker přímo, klient, který komunikuje přes kanály místo proxy, bude vyžadovat moniker.
I když je verze u monikeru volitelná, doporučuje se poskytnutí verze, protože autorům služeb poskytuje více možností pro zachování kompatibility s klienty při změnách chování.
Popisovač služby
Popisovač služby kombinuje moniker služby s chováním potřebným ke spuštění připojení RPC a vytvoření místního nebo vzdáleného proxy serveru. Popisovač je zodpovědný za efektivní převod rozhraní RPC na drátový protokol. Tento popisovač služby je instance odvozeného ServiceRpcDescriptortypu. Popisovač musí být zpřístupněn všem klientům, kteří budou pro přístup k této službě používat proxy server. Toto popisovač vyžaduje také odpisovač.
Visual Studio definuje jeden takový odvozený typ a doporučuje jeho použití pro všechny služby: ServiceJsonRpcDescriptor. Tento popisovač využívá StreamJsonRpc pro svá připojení RPC a vytvoří vysoce výkonný místní proxy server pro místní služby, který emuluje některé vzdálené chování, jako je zabalení výjimek vyvolané službou v RemoteInvocationException.
Podporuje ServiceJsonRpcDescriptor konfiguraci JsonRpc třídy pro kódování JSON nebo MessagePack protokolu JSON-RPC. Doporučujeme kódování MessagePacku, protože je kompaktnější a může být 10X výkonnější.
Popisovač pro naši službu kalkulačky můžeme definovat takto:
/// <summary>
/// The descriptor for the calculator brokered service.
/// Use the <see cref="ICalculator"/> interface for the client proxy for this service.
/// </summary>
public static readonly ServiceRpcDescriptor CalculatorService = new ServiceJsonRpcDescriptor(
Moniker,
Formatters.MessagePack,
MessageDelimiters.BigEndianInt32LengthHeader,
new MultiplexingStream.Options { ProtocolMajorVersion = 3 })
.WithExceptionStrategy(StreamJsonRpc.ExceptionProcessing.ISerializable);
Jak vidíte výše, je k dispozici volba formátovače a oddělovače. Protože nejsou všechny kombinace platné, doporučujeme některou z těchto kombinací:
ServiceJsonRpcDescriptor.Formatters | ServiceJsonRpcDescriptor.MessageDelimiters | Nejvhodnější pro |
---|---|---|
MessagePack | BigEndianInt32LengthHeader | Vysoký výkon |
UTF8 (JSON) | HttpLikeHeaders | Spolupráce s jinými systémy JSON-RPC |
Zadáním objektu jako konečného parametru MultiplexingStream.Options
je připojení RPC sdílené mezi klientem a službou pouze jeden kanál v MultiplexingStream, který se sdílí s připojením JSON-RPC, aby bylo možné efektivní přenos velkých binárních dat přes JSON-RPC.
Tato ExceptionProcessing.ISerializable strategie způsobí, že výjimky vyvolané vaší službou budou serializovány a zachovány jako Exception.InnerException RemoteInvocationException vyvolány v klientovi. Bez tohoto nastavení jsou v klientovi k dispozici méně podrobné informace o výjimce.
Tip: Vystavit popisovač jako ServiceRpcDescriptor místo jakéhokoli odvozeného typu, který použijete jako podrobnosti implementace. Díky tomu budete později flexibilněji měnit podrobnosti implementace bez zásadních změn rozhraní API.
Do komentáře dokumentu XML na popisovač zahrňte odkaz na rozhraní vaší služby, aby uživatelé mohli službu snadněji využívat. Pokud je to možné, odkazujte také na rozhraní, které služba přijímá jako cíl RPC klienta.
Některé pokročilejší služby můžou také přijímat nebo vyžadovat cílový objekt RPC z klienta, který odpovídá určitému rozhraní.
V takovém případě použijte ServiceJsonRpcDescriptor konstruktor s parametrem Type clientInterface
k určení rozhraní, které má klient poskytnout instanci.
Správa verzí popisovače
V průběhu času můžete chtít zvýšit verzi služby. V takovém případě byste měli definovat popisovač pro každou verzi, kterou chcete podporovat, pomocí jedinečné verze specifické ServiceMoniker pro každou verzi. Podpora více verzí současně může být vhodná pro zpětnou kompatibilitu a obvykle se dá provést pouze s jedním rozhraním RPC.
Visual Studio tento vzor se svou VisualStudioServices třídou definuje původní ServiceRpcDescriptor jako virtual
vlastnost pod vnořenou třídou, která představuje první verzi, která přidala zprostředkovanou službu.
Když potřebujeme změnit protokol wire nebo přidat/změnit funkce služby, Visual Studio deklaruje override
vlastnost v novější verzi vnořené třídy, která vrací novou ServiceRpcDescriptor.
Pro službu definovanou rozšířením sady Visual Studio a jejím návrhem může stačit deklarovat jinou vlastnost popisovače vedle původního popisovače. Předpokládejme například, že vaše služba 1.0 používala formátovací modul UTF8 (JSON) a uvědomujete si, že přepnutí na MessagePack by přináší významnou výhodu výkonu. Vzhledem k tomu, že změna formátovače je zásadní změna přenosového protokolu, vyžaduje zvýšení čísla zprostředkované verze služby a druhý popisovač. Dva popisovače dohromady můžou vypadat takto:
public static readonly ServiceJsonRpcDescriptor CalculatorService = new ServiceJsonRpcDescriptor(
new ServiceMoniker("YourCompany.Extension.Calculator", new Version("1.0")),
Formatters.UTF8,
MessageDelimiters.HttpLikeHeaders,
new MultiplexingStream.Options { ProtocolMajorVersion = 3 })
);
public static readonly ServiceJsonRpcDescriptor CalculatorService1_1 = new ServiceJsonRpcDescriptor(
new ServiceMoniker("YourCompany.Extension.Calculator", new Version("1.1")),
Formatters.MessagePack,
MessageDelimiters.BigEndianInt32LengthHeader,
new MultiplexingStream.Options { ProtocolMajorVersion = 3 });
I když deklarujeme dva popisovače (a později budeme muset navrhnout a zaregistrovat dvě služby), můžeme to udělat pouze s jedním rozhraním a implementací služby, což udržuje režii pro podporu více verzí služby poměrně nízké.
Nabídání služby
Zprostředkovaná služba se musí vytvořit, když přijde požadavek, který je uspořádaný prostřednictvím kroku označovaného jako převod služby.
Objekt pro vytváření služeb
Použít GlobalProvider.GetServiceAsync k vyžádání SVsBrokeredServiceContainer. Potom zavolejte IBrokeredServiceContainer.Proffer tento kontejner tak, aby odvolal vaši službu.
V následujícím příkladu nabízíme službu pomocí CalculatorService
pole deklarovaného dříve, které je nastaveno na instanci .ServiceRpcDescriptor
Předáváme ji naší službě BrokeredServiceFactory , což je delegát.
IBrokeredServiceContainer container = await AsyncServiceProvider.GlobalProvider.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
container.Proffer(
CalculatorService,
(moniker, options, serviceBroker, cancellationToken) => new ValueTask<object?>(new CalculatorService()));
Zprostředkovaná služba se obvykle vytvoří jednou na klienta. Jedná se o odchod od jiných služeb VS (Visual Studio), které se obvykle vytvářejí jednou a sdílí se napříč všemi klienty. Vytvoření jedné instance služby na klienta umožňuje lepší zabezpečení, protože každá služba nebo její připojení může uchovávat stav jednotlivých klientů o úrovni autorizace, na které klient pracuje, na tom, na čem je preferovaný CultureInfo atd. Jak uvidíme dále, umožňuje také zajímavějším službám, které přijímají argumenty specifické pro tento požadavek.
Důležité
Objekt pro vytváření služeb, který se od tohoto pokynu odchýlí a vrací instanci sdílené služby místo nové instance klienta, by nikdy neměl mít svou službu implementovat IDisposable, protože první klient, který má likvidovat jeho proxy server, povede k odstranění instance sdílené služby předtím, než ji ostatní klienti budou používat.
V pokročilejším případě, kdy CalculatorService
konstruktor vyžaduje objekt sdíleného stavu a objekt IServiceBroker, můžeme navrhnout továrnu takto:
var state = new CalculatorService.State();
container.Proffer(
CalculatorService,
(moniker, options, serviceBroker, cancellationToken) => new ValueTask<object?>(new CalculatorService(state, serviceBroker)));
state
Místní proměnná je mimo továrnu služby, a proto se vytvoří pouze jednou a sdílí se napříč všemi instancemi služeb.
Stále pokročilejší, pokud služba vyžadovala přístup k objektu ServiceActivationOptions (například k vyvolání metod v cílovém objektu RPC klienta), které by se daly předat také:
var state = new CalculatorService.State();
container.Proffer(CalculatorService, (moniker, options, serviceBroker, cancellationToken) =>
new ValueTask<object?>(new CalculatorService(state, serviceBroker, options)));
V tomto případě může konstruktor služby vypadat takto, za předpokladu, že ServiceJsonRpcDescriptor byly vytvořeny jako typeof(IClientCallbackInterface)
jeden z jeho argumentů konstruktoru:
internal class Calculator(State state, IServiceBroker serviceBroker, ServiceActivationOptions options)
{
this.state = state;
this.serviceBroker = serviceBroker;
this.options = options;
this.clientCallback = (IClientCallbackInterface)options.ClientRpcTarget;
}
Toto clientCallback
pole se teď dá vyvolat, kdykoli chce služba vyvolat klienta, dokud se připojení nezlikviduje.
Delegát BrokeredServiceFactory přebírá ServiceMoniker jako parametr v případě, že je objekt pro vytváření služeb sdílenou metodou, která vytvoří více služeb nebo odlišné verze služby založené na monikeru. Tento moniker pochází z klienta a zahrnuje verzi služby, kterou očekávají. Předáním tohoto monikeru konstruktoru služby může služba emulovat nechtěné chování konkrétních verzí služby tak, aby odpovídalo očekávání klienta.
Nepoužívejte delegáta AuthorizingBrokeredServiceFactory s metodou IBrokeredServiceContainer.Proffer , pokud nebudete používat IAuthorizationService uvnitř zprostředkované třídy služeb. Tato IAuthorizationService možnost musí být uvolněna s vaší zprostředkovanou třídou služby, aby nedošlo k nevrácení paměti.
Podpora více verzí služby
Když zvýšíte verzi na své ServiceMonikerstraně, musíte navrhnout každou verzi zprostředkované služby, pro kterou chcete reagovat na požadavky klientů. To se provádí voláním metody s každouServiceRpcDescriptor, IBrokeredServiceContainer.Proffer kterou stále podporujete.
Nabídky vaší služby s null
verzí budou sloužit jako "catch all", který se bude shodovat s každou žádostí klienta, pro kterou přesná verze odpovídá zaregistrované službě.
Můžete například navrhnout službu 1.0 a 1.1 s konkrétními verzemi a také zaregistrovat službu s verzí null
.
V takových případech klienti požadující vaši službu s verzí 1.0 nebo 1.1 vyvolá objekt pro vytváření služeb, který jste pro tyto přesné verze nabídli, zatímco klient, který požaduje verzi 8.0, vede k vyvolání objektu pro správu služeb s nulovou verzí.
Vzhledem k tomu, že se do služby továrny poskytuje požadovaná verze klienta, může se továrna rozhodnout, jak nakonfigurovat službu pro tohoto konkrétního klienta nebo jestli se má vrátit null
k označení nepodporované verze.
Žádost klienta o službu s null
verzí odpovídá pouze službě zaregistrované a vypisované s null
verzí.
Představte si případ, kdy jste publikovali řadu verzí služby, z nichž několik je zpětně kompatibilní, a proto může sdílet implementaci služby. Možnost catch-all můžeme využít, abychom nemuseli opakovaně vyčítat jednotlivé verze následujícím způsobem:
const string ServiceName = "YourCompany.Extension.Calculator";
ServiceRpcDescriptor CreateDescriptor(Version? version) =>
new ServiceJsonRpcDescriptor(
new ServiceMoniker(ServiceName, version),
ServiceJsonRpcDescriptor.Formatters.MessagePack,
ServiceJsonRpcDescriptor.MessageDelimiters.BigEndianInt32LengthHeader);
IBrokeredServiceContainer container = await AsyncServiceProvider.GlobalProvider.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
container.Proffer(
CreateDescriptor(new Version(2, 0)),
(moniker, options, serviceBroker, cancellationToken) => new ValueTask<object?>(new CalculatorServiceV2()));
container.Proffer(
CreateDescriptor(null), // proffer a catch-all
(moniker, options, serviceBroker, cancellationToken) => new ValueTask<object?>(moniker.Version switch {
{ Major: 1 } => new CalculatorService(), // match any v1.x request with our v1 service.
null => null, // We don't support clients that do not specify a version.
_ => null, // The client requested some other version we don't recognize.
}));
Registrace služby
Nabídky zprostředkované služby pro globální zprostředkovaný kontejner služeb se vyvolají, pokud služba nebyla poprvé zaregistrována. Registrace poskytuje kontejneru prostředky, aby předem věděl, které zprostředkované služby mohou být k dispozici a který balíček VS se má načíst, když jsou požadovány, aby bylo možné spustit kód pro načítání. Visual Studio tak může rychle spustit, aniž by bylo nutné načíst všechna rozšíření předem, ale při vyžádání klientem zprostředkované služby může načíst požadované rozšíření.
Registraci lze provést použitím ProvideBrokeredServiceAttribute třídy AsyncPackage-derived. Toto je jediné místo, kde ServiceAudience se dá nastavit.
[ProvideBrokeredService("YourCompany.Extension.Calculator", "1.0", Audience = ServiceAudience.Local)]
Výchozí Audience hodnota je ServiceAudience.Process, která zprostředkuje zprostředkovanou službu pouze jinému kódu v rámci stejného procesu. Nastavením ServiceAudience.Localse přihlásíte k zveřejnění zprostředkované služby jiným procesům patřícím do stejné relace sady Visual Studio.
Pokud vaše zprostředkovaná služba musí být vystavena hostům Live Share, Audience musí obsahovat ServiceAudience.LiveShareGuest a ProvideBrokeredServiceAttribute.AllowTransitiveGuestClients vlastnost nastavena na true
.
Nastavení těchto příznaků může představovat závažná ohrožení zabezpečení a nemělo by se provádět bez toho, abyste napřed odpovídali pokynům v části Postup zabezpečení zprostředkované služby.
Když zvýšíte verzi na svém ServiceMonikerpočítači, musíte zaregistrovat každou verzi zprostředkované služby, pro kterou chcete reagovat na požadavky klientů. Díky podpoře více než nejnovější verze zprostředkované služby pomáháte udržovat zpětnou kompatibilitu pro klienty starší verze zprostředkované služby, což může být užitečné zejména při zvažování scénáře Live Share, kdy každá verze sady Visual Studio, která sdílí relaci, může být jiná verze.
Registrace služby ve null
verzi bude sloužit jako "catch all", která se bude shodovat s každou žádostí klienta, pro kterou neexistuje přesná verze s registrovanou službou.
Službu 1.0 a 2.0 můžete například zaregistrovat v konkrétních verzích a také zaregistrovat službu s verzí null
.
Použití MEF k odkazování a registraci služby
Vyžaduje to Visual Studio 2022 Update 2 nebo novější.
Zprostředkovaná služba může být exportována přes MEF místo použití balíčku sady Visual Studio, jak je popsáno v předchozích dvou částech. Jedná se o kompromisy, které je potřeba vzít v úvahu:
Kompromis | Balíček proffer | Export MEF |
---|---|---|
Dostupnost | ✅ Zprostředkovaná služba je k dispozici okamžitě při spuštění VS. | ⚠️ Zprostředkovaná služba může být zpožděna, dokud se v procesu inicializuje MEF. Obvykle je to rychlé, ale může trvat několik sekund, když je mezipaměť MEF zastaralá. |
Připravenost pro různé platformy | ⚠️ Visual Studio pro Windows musí být vytvořený specifický kód. | ✅Zprostředkovaná služba v sestavení může být načtena v sadě Visual Studio pro Windows a také Visual Studio pro Mac. |
Export zprostředkované služby přes MEF místo použití balíčků VS:
- Ověřte, že nemáte žádný kód související s posledními dvěma oddíly. Konkrétně byste neměli mít žádný kód, který volá IBrokeredServiceContainer.Proffer a neměl by se na váš balíček vztahovat ProvideBrokeredServiceAttribute (pokud existuje).
IExportedBrokeredService
Implementujte rozhraní ve zprostředkované třídě služby.- Vyhněte se všem závislostem hlavního vlákna v konstruktoru nebo importu setter vlastností. Použijte metodu
IExportedBrokeredService.InitializeAsync
pro inicializaci zprostředkované služby, kde jsou povoleny hlavní závislosti vláken. ExportBrokeredServiceAttribute
Použijte u zprostředkované třídy služeb a zadejte informace o monikeru služby, cílové skupině a všech dalších požadovaných informacích souvisejících s registrací.- Pokud vaše třída vyžaduje odstranění, implementujte IDisposable místo IAsyncDisposable toho, že MEF vlastní životnost vaší služby a podporuje pouze synchronní odstranění.
- Ujistěte se, že váš
source.extension.vsixmanifest
soubor obsahuje projekt obsahující zprostředkovanou službu jako sestavení MEF.
Jako součást MEF může vaše zprostředkovaná služba importovat jakoukoli jinou část MEF ve výchozím rozsahu.
Při tom nezapomeňte místo System.ComponentModel.Composition.ImportAttribute System.Composition.ImportAttribute.
Důvodem je to, že ExportBrokeredServiceAttribute
se vyžaduje odvození a System.ComponentModel.Composition.ExportAttribute použití stejného oboru názvů MEF v celém typu.
Zprostředkovaná služba je jedinečná při importu několika speciálních exportů:
- IServiceBroker, které by se měly použít k získání dalších zprostředkovaných služeb.
- ServiceMoniker, což může být užitečné při exportu více verzí zprostředkované služby a potřebujete zjistit, jakou verzi klient požadoval.
- ServiceActivationOptions, což může být užitečné, když potřebujete, aby klienti zadali speciální parametry nebo cíl zpětného volání klienta.
- AuthorizationServiceClient, což může být užitečné, když potřebujete provést kontroly zabezpečení, jak je popsáno v tématu Postup zabezpečení zprostředkované služby. Tento objekt nemusí být odstraněn vaší třídou, protože bude uvolněn automaticky při zprostředkované službě.
Vaše zprostředkovaná služba nesmí používat MEF ImportAttribute k získání dalších zprostředkovaných služeb.
Místo toho může [Import]
IServiceBroker a dotazovat se na zprostředkované služby tradičním způsobem.
Další informace najdete v části Jak využívat zprostředkovanou službu.
Tady je ukázka:
using System;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ServiceHub.Framework;
using Microsoft.ServiceHub.Framework.Services;
using Microsoft.VisualStudio.Shell.ServiceBroker;
[ExportBrokeredService("Calc", "1.0")]
internal class MefBrokeredService : IExportedBrokeredService, ICalculator
{
internal static ServiceRpcDescriptor SharedDescriptor { get; } = new ServiceJsonRpcDescriptor(
new ServiceMoniker("Calc", new Version("1.0")),
clientInterface: null,
ServiceJsonRpcDescriptor.Formatters.MessagePack,
ServiceJsonRpcDescriptor.MessageDelimiters.BigEndianInt32LengthHeader,
new MultiplexingStream.Options { ProtocolMajorVersion = 3 });
// IExportedBrokeredService
public ServiceRpcDescriptor Descriptor => SharedDescriptor;
[Import]
IServiceBroker ServiceBroker { get; set; } = null!;
[Import]
ServiceMoniker ServiceMoniker { get; set; } = null!;
[Import]
ServiceActivationOptions Options { get; set; }
// IExportedBrokeredService
public Task InitializeAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public ValueTask<int> AddAsync(int a, int b, CancellationToken cancellationToken = default)
{
return new(a + b);
}
public ValueTask<int> SubtractAsync(int a, int b, CancellationToken cancellationToken = default)
{
return new(a - b);
}
}
Export více verzí zprostředkované služby
U ExportBrokeredServiceAttribute
zprostředkované služby se dá použít vícekrát, aby bylo možné nabídnout více verzí zprostředkované služby.
IExportedBrokeredService.Descriptor
Implementace vlastnosti by měla vrátit popisovač s monikerem, který odpovídá té, kterou klient požadoval.
Podívejte se na tento příklad, kdy služba kalkulačky exportovala formát 1.0 s formátováním UTF8, pak později přidá export 1.1, aby si mohli vychutnat výkon vítězství při použití formátování MessagePack.
[ExportBrokeredService("Calc", "1.0")]
[ExportBrokeredService("Calc", "1.1")]
internal class MefBrokeredService : IExportedBrokeredService, ICalculator
{
internal static ServiceRpcDescriptor SharedDescriptor1_0 { get; } = new ServiceJsonRpcDescriptor(
new ServiceMoniker("Calc", new Version("1.0")),
clientInterface: null,
ServiceJsonRpcDescriptor.Formatters.UTF8,
ServiceJsonRpcDescriptor.MessageDelimiters.HttpLikeHeaders,
new MultiplexingStream.Options { ProtocolMajorVersion = 3 });
internal static ServiceRpcDescriptor SharedDescriptor1_1 { get; } = new ServiceJsonRpcDescriptor(
new ServiceMoniker("Calc", new Version("1.1")),
clientInterface: null,
ServiceJsonRpcDescriptor.Formatters.MessagePack,
ServiceJsonRpcDescriptor.MessageDelimiters.BigEndianInt32LengthHeader,
new MultiplexingStream.Options { ProtocolMajorVersion = 3 });
// IExportedBrokeredService
public ServiceRpcDescriptor Descriptor =>
this.ServiceMoniker.Version == SharedDescriptor1_0.Moniker.Version ? SharedDescriptor1_0 :
this.ServiceMoniker.Version == SharedDescriptor1_1.Moniker.Version ? SharedDescriptor1_1 :
throw new NotSupportedException();
[Import]
ServiceMoniker ServiceMoniker { get; set; } = null!;
}
Počínaje verzí sady Visual Studio 2022 Update 12 (17.12) je možné exportovat službu s verzí tak, null
aby odpovídala libovolné žádosti klienta pro službu bez ohledu na verzi, včetně požadavku s null
verzí.
Taková služba se může vrátit null
z Descriptor
vlastnosti, aby odmítla požadavek klienta, pokud nenabízí implementaci verze, kterou klient požadoval.
Odmítnutí žádosti o službu
Zprostředkovaná služba může žádost o aktivaci klienta odmítnout vyvoláním metody InitializeAsync . Vyvolání způsobí ServiceActivationFailedException , že se klientovi vrátí zpět.