Aracılığıyla paylaş


Aracılı hizmet sağlama

Aracılı hizmet aşağıdaki öğelerden oluşur:

Yukarıdaki listedeki öğelerin her biri aşağıdaki bölümlerde ayrıntılı olarak açıklanmıştır.

Bu makaledeki tüm kodlarda, C#'nin null atanabilir başvuru türleri özelliğinin etkinleştirilmesi kesinlikle önerilir.

Hizmet arabirimi

Hizmet arabirimi standart bir .NET arabirimi olabilir (genellikle C# dilinde yazılır), ancak istemci ve hizmet farklı işlemlerde çalıştırıldığında arabirimin ServiceRpcDescriptorRPC üzerinden kullanılabilmesini sağlamak için hizmetinizin kullanacağı türetilmiş türe göre ayarlanan yönergelere uygun olmalıdır. Bu kısıtlamalar genellikle özelliklere ve dizin oluşturuculara izin verilmemesi ve çoğu veya tüm yöntemlerin döndürülmesi Task veya zaman uyumsuz başka bir dönüş türü içerir.

ServiceJsonRpcDescriptor aracılı hizmetler için önerilen türetilmiş türdür. İstemci ve hizmet RPC'nin StreamJsonRpc iletişim kurmasını gerektirdiğinde bu sınıf kitaplığı kullanır. StreamJsonRpc, burada açıklandığı gibi hizmet arabiriminde belirli kısıtlamalar uygular.

Arabirimi , System.IAsyncDisposable, veya hatta Microsoft.VisualStudio.Threading.IAsyncDisposable türetilebilirIDisposable, ancak bu sistem tarafından gerekli değildir. Oluşturulan istemci proxy'leri her iki şekilde de uygulanır IDisposable .

Basit bir hesap makinesi hizmet arabirimi şu şekilde bildirilebilir:

public interface ICalculator
{
    ValueTask<double> AddAsync(double a, double b, CancellationToken cancellationToken);
    ValueTask<double> SubtractAsync(double a, double b, CancellationToken cancellationToken);
}

Bu arabirimdeki yöntemlerin uygulanması zaman uyumsuz bir yöntem garanti etmese de, bu arabirim bu hizmeti uzaktan çağırabilecek istemci proxy'sini oluşturmak için kullanıldığından her zaman zaman uyumsuz yöntem imzaları kullanırız ve bu da zaman uyumsuz yöntem imzası garanti eder .

Arabirim, istemcilerine hizmette gerçekleşen olayları bildirmek için kullanılabilecek olayları bildirebilir.

Olayların veya gözlemci tasarım deseninin ötesinde, istemciye "geri çağırması" gereken aracılı bir hizmet, bir istemcinin hizmet isterken özellik aracılığıyla ServiceActivationOptions.ClientRpcTarget uygulaması ve sağlaması gereken sözleşme olarak hizmet veren ikinci bir arabirim tanımlayabilir. Böyle bir arabirim, aracılı hizmet arabirimiyle aynı tasarım desenlerine ve kısıtlamalarına uymalıdır, ancak sürüm oluşturmayla ilgili ek kısıtlamalarla birlikte.

Yüksek performanslı, geleceğe dönük rpc arabirimi tasarlamaya yönelik ipuçları için Aracılı Hizmet Tasarlamaya yönelik En İyi Yöntemler'i gözden geçirin.

İstemcilerinin uygulama ayrıntılarını daha fazla kullanıma sunmadan arabirime başvurabilmesi için bu arabirimi hizmeti uygulayan derlemeden ayrı bir derlemede bildirmek yararlı olabilir. Hizmet uygulamasını göndermek için kendi uzantınızı rezerve ederken diğer uzantıların başvurması için arabirim derlemesini NuGet paketi olarak göndermek de yararlı olabilir.

Hizmetinizin .NET Framework, .NET Core, .NET 5 veya sonraki bir sürümü çalıştıran herhangi bir .NET işleminden kolayca çağrılabilmesini sağlamak için netstandard2.0 hizmet arabiriminizi bildiren derlemeyi hedeflemeyi göz önünde bulundurun.

Test Etme

Otomatikleştirilmiş testler, arabirimin RPC hazır olup olmadığını doğrulamak için hizmet arabiriminizle birlikte yazılmalıdır.

Testler, arabirimden geçirilen tüm verilerin serileştirilebilir olduğunu doğrulamalıdır.

Microsoft.VisualStudio.Sdk.TestFramework.Xunit paketindeki sınıfını arabirim test sınıfınızı türetmek için yararlı bulabilirsinizBrokeredServiceContractTestBase<TInterface,TServiceMock>. Bu sınıf, arabiriminiz için bazı temel kural testlerini, olay testi gibi yaygın onaylara yardımcı olacak yöntemleri ve daha fazlasını içerir.

Yöntemler

Her bağımsız değişkenin ve dönüş değerinin tamamen seri hale getirildiğini onaylar. Yukarıda bahsedilen test temel sınıfını kullanıyorsanız kodunuz şu şekilde görünebilir:

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);
    }
}

Aynı ada sahip birden çok yöntem bildirirseniz aşırı yükleme çözümlemesini test etmeyi göz önünde bulundurun. Test yönteminin yöntemi çağırabilmesi ve doğru bağımsız değişkenlerle doğru yöntemin çağrıldığından emin olmak için, üzerindeki her yöntem için sahte hizmetinize bu yöntemin bağımsız değişkenlerini depolayan bir internal alan ekleyebilirsiniz.

Ekinlikler

Arabiriminizde bildirilen tüm olaylar RPC hazırlığı için de test edilmelidir. Aracılı bir hizmetten tetiklenen olaylar RPC serileştirme sırasında başarısız olursa bir test hatasına neden olmaz çünkü olaylar "yangın ve unutma"dır.

Yukarıda belirtilen test temel sınıfını kullanıyorsanız, bu davranış zaten bazı yardımcı yöntemlere yerleşiktir ve bu şekilde görünebilir (değiştirilmemiş parçalar kısa süre için atlanmış olarak):

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));
    }
}

Hizmeti uygulama

Hizmet sınıfı, önceki adımda bildirilen RPC arabirimini uygulamalıdır. Bir hizmet veya RPC için kullanılan arabirim dışında başka arabirimler uygulayabilir IDisposable . İstemcide oluşturulan ara sunucu yalnızca hizmet arabirimini IDisposable, ve muhtemelen sistemi desteklemek için birkaç diğer seçme arabirimini uygular, bu nedenle hizmet tarafından uygulanan diğer arabirimlere yönelik bir atama istemcide başarısız olur.

Burada uyguladığımız yukarıda kullanılan hesap makinesi örneğini göz önünde bulundurun:

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);
    }
}

Yöntem gövdelerinin kendilerinin zaman uyumsuz olması gerekmediğinden, hizmet arabirimine uyum sağlamak için dönüş değerini açıkça bir yapılı ValueTask<TResult> dönüş türüne sarmalarız.

Gözlemlenebilir tasarım desenini uygulama

Hizmet arabiriminizde bir gözlemci aboneliği sunuyorsanız şu şekilde görünebilir:

Task<IDisposable> SubscribeAsync(IObserver<YourDataType> observer);

Yöntem IObserver<T> çağrısı tamamlandıktan sonra istemci döndürülen IDisposable değeri atana kadar istemcinin güncelleştirmeleri almaya devam edebilmesi için bağımsız değişkeninin genellikle bu yöntem çağrısının yaşam süresinden uzun olması gerekir. Bunu kolaylaştırmak için hizmet sınıfınız, durumunuz için yapılan tüm güncelleştirmelerin tüm aboneleri güncelleştirmek üzere numaralandıracağı bir abonelik koleksiyonu IObserver<T> içerebilir. Koleksiyonunuzun numaralandırmasının, birbirlerine ve özellikle bu koleksiyondaki mutasyonlara göre iş parçacığı açısından güvenli olduğundan emin olun ve bu aboneliklerin ek abonelikleri veya bertarafları yoluyla gerçekleşebilir.

aracılığıyla OnNext gönderilen tüm güncelleştirmelerin hizmetinize durum değişikliklerinin hangi sırayla sunulduğuna dikkat edin.

İstemci ve RPC sistemlerinde kaynak sızıntılarını önlemek için OnCompleted OnError tüm abonelikler bir çağrısıyla sonlandırılmalıdır. Bu, kalan tüm aboneliklerin açıkça tamamlanması gereken hizmet elden çıkarmayı içerir.

Gözlemci tasarım düzeni, gözlemlenebilir bir veri sağlayıcısı uygulama ve özellikle RPC'yi göz önünde bulundurarak hakkında daha fazla bilgi edinin.

Tek kullanımlık hizmetler

Hizmet sınıfınızın atılabilir olması gerekmez, ancak istemci proxy'sini hizmetinize attığında veya istemci ile hizmet arasındaki bağlantı kaybolduğunda atılacak hizmetler kaybolur. Tek kullanımlık arabirimler şu sırayla test edilir: System.IAsyncDisposable, Microsoft.VisualStudio.Threading.IAsyncDisposable, IDisposable. Hizmetten kurtulmak için bu listeden yalnızca hizmet sınıfınızın uyguladığı ilk arabirim kullanılır.

Elden çıkarma göz önünde bulundurulduğunda iş parçacığı güvenliğini göz önünde bulundurun. Hizmetinizdeki diğer kod çalışırken yönteminiz Dispose herhangi bir iş parçacığında çağrılabilir (örneğin, bir bağlantı bırakılıyorsa).

Özel durumlar oluşturma

Özel durumlar oluştururken, istemci RemoteInvocationExceptiontarafından içinde alınan hata kodunu denetlemek için belirli bir ErrorCode ile oluşturmayı LocalRpcException göz önünde bulundurun. İstemcilere hata kodu sağlamak, hatanın niteliğine göre özel durum iletilerini veya türlerini ayrıştırmaktan daha iyi dallanmalarını sağlayabilir.

JSON-RPC belirtimi uyarınca, hata kodları pozitif sayılar da dahil olmak üzere -32000'den büyük olmalıdır.

Diğer aracılı hizmetleri kullanma

Aracılı bir hizmetin kendisi başka bir aracılı hizmete erişim gerektirdiğinde, hizmet fabrikasına sağlanan hizmetin kullanılmasını IServiceBroker öneririz, ancak aracılı hizmet kaydı bayrağını ayarlarken AllowTransitiveGuestClients özellikle önemlidir.

Hesap makinesi hizmetimizin davranışını uygulamak için diğer aracılı hizmetlere ihtiyacı varsa bu kılavuza uymak için oluşturucuyu bir IServiceBrokerkabul edecek şekilde değiştiririz:

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;
    }

    // ...
}

Aracılı bir hizmetin güvenliğini sağlama ve aracılı hizmetleri kullanma hakkında daha fazla bilgi edinin.

Durum bilgisi olan hizmetler

İstemci başına durum

Hizmeti isteyen her istemci için bu sınıfın yeni bir örneği oluşturulur. Yukarıdaki sınıftaki Calculator bir alan, her istemci için benzersiz olabilecek bir değer depolar. Bir işlem her gerçekleştirildiğinde artan bir sayaç ekleyebileceğimizi varsayalım:

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);
    }
}

Aracılı hizmetiniz iş parçacığı güvenli uygulamaları izlemek için yazılmalıdır. Önerilen ServiceJsonRpcDescriptorkullanılırken, istemcilerle uzak bağlantılar bu belgede açıklandığı gibi hizmetinizin yöntemlerinin eşzamanlı yürütülmesini içerebilir. İstemci bir işlemi paylaştığında ve AppDomain hizmetiyle paylaştığında, istemci hizmetinizi birden çok iş parçacığından eşzamanlı olarak çağırabilir. Yukarıdaki örneğin iş parçacığı güvenli uygulaması alanı artırmak operationCounter için kullanabilirInterlocked.Increment(Int32).

Paylaşılan durum

Hizmetinizin tüm istemcilerinde paylaşması gereken bir durum varsa, bu durum VS Paketiniz tarafından örneği oluşturulmuş ve hizmetinizin oluşturucusunda bağımsız değişken olarak geçirilen ayrı bir sınıfta tanımlanmalıdır.

Yukarıda tanımlanan öğesinin operationCounter hizmetin tüm istemcileri genelindeki tüm işlemleri saymasını istediğimizi varsayalım. Alanı şu yeni durum sınıfına kaldırmamız gerekir:

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);
    }
}

Artık hizmetimizin birden çok örneğinde paylaşılan durumu yönetmek için zarif ve test edilebilir bir yönteme Calculator sahibiz. Daha sonra hizmeti açmak için kodu yazarken bu State sınıfın bir kez nasıl oluşturulduğunu ve hizmetin her örneğiyle Calculator nasıl paylaşılacağını göreceğiz.

Paylaşılan durumla ilgilenirken iş parçacığının güvenli olması özellikle önemlidir çünkü çağrılarını zamanlayan birden çok istemci etrafında hiçbir varsayımda bulunulmadığından, bunlar hiçbir zaman eşzamanlı olarak yapılmaz.

Paylaşılan durum sınıfınızın diğer aracılı hizmetlere erişmesi gerekiyorsa, aracılı hizmetinizin tek bir örneğine atanan bağlamsal hizmet aracılarından biri yerine genel hizmet aracısını kullanmalıdır. Genel hizmet aracısını aracılı bir hizmet içinde kullanmak, bayrak ayarlandığında güvenlik etkilerini ProvideBrokeredServiceAttribute.AllowTransitiveGuestClients de taşır.

Güvenlik sorunları

Paylaşılan Bir Canlı Paylaşım oturumuna katılan diğer makinelerdeki diğer kullanıcılar tarafından olası erişime açık hale getiren bayrakla ProvideBrokeredServiceAttribute.AllowTransitiveGuestClients kaydedilmişse, aracılı hizmetiniz için güvenlik dikkate alınır.

Aracılı Hizmetin Güvenliğini Sağlama'ya göz atın ve bayrağı ayarlamadan AllowTransitiveGuestClients önce gerekli güvenlik azaltmalarını alın.

Hizmet adı

Aracılı bir hizmetin serileştirilebilir bir adı ve istemcinin hizmeti isteyebileceği isteğe bağlı bir sürümü olmalıdır. A ServiceMoniker , bu iki bilgi parçası için kullanışlı bir sarmalayıcıdır.

Hizmet takma adı, CLR (Ortak Dil Çalışma Zamanı) türünün derleme tam adıyla benzerdir. Genel olarak benzersiz olmalıdır ve bu nedenle hizmet adının kendisine ön ek olarak şirketinizin adını ve belki de uzantı adınızı içermelidir.

Bu adı başka bir yerde kullanmak üzere bir static readonly alanda tanımlamak yararlı olabilir:

public static readonly ServiceMoniker Moniker = new ServiceMoniker("YourCompany.Extension.Calculator", new Version("1.0"));

Hizmetinizin çoğu kullanımı, takma adınızı doğrudan kullanmasa da, proxy yerine kanallar üzerinden iletişim kuran bir istemci, takma adı gerektirir.

Bir sürüm bir takma ad üzerinde isteğe bağlı olsa da, hizmet yazarlarına davranış değişikliklerinde istemcilerle uyumluluğu korumak için daha fazla seçenek sunduğu için sürüm sağlanması önerilir.

Hizmet tanımlayıcısı

Hizmet tanımlayıcısı, hizmet adını bir RPC bağlantısını çalıştırmak ve yerel veya uzak ara sunucu oluşturmak için gereken davranışlarla birleştirir. Tanımlayıcı, RPC arabiriminizi etkili bir şekilde bir kablo protokolüne dönüştürmekle sorumludur. Bu hizmet tanımlayıcısı, türetilmiş bir ServiceRpcDescriptortürün örneğidir. Tanımlayıcı, bu hizmete erişmek için ara sunucu kullanacak tüm istemcilerin kullanımına sunulmalıdır. Hizmetin proffering de bu tanımlayıcı gerektirir.

Visual Studio türetilmiş türlerden birini tanımlar ve tüm hizmetler için kullanılmasını önerir: ServiceJsonRpcDescriptor. Bu tanımlayıcı, RPC bağlantıları için kullanır StreamJsonRpc ve içinde hizmet RemoteInvocationExceptiontarafından oluşturulan özel durumları sarmalama gibi bazı uzak davranışlara öykünen yerel hizmetler için yüksek performanslı bir yerel proxy oluşturur.

sınıfını ServiceJsonRpcDescriptor JSON-RPC protokolünün JsonRpc JSON veya MessagePack kodlaması için yapılandırmayı destekler. Daha kompakt olduğundan ve 10 kat daha yüksek performanslı olabileceği için MessagePack kodlamasını öneririz.

Hesap makinesi hizmetimiz için aşağıdaki gibi bir tanımlayıcı tanımlayabiliriz:

/// <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);

Yukarıda görebileceğiniz gibi, bir biçimleyici ve sınırlayıcı seçeneği mevcuttur. Tüm birleşimler geçerli olmadığından, şu bileşimlerden birini öneririz:

ServiceJsonRpcDescriptor.Formatters ServiceJsonRpcDescriptor.MessageDelimiters En iyi kullanım alanı:
MessagePack BigEndianInt32LengthHeader Yüksek performans
UTF8 (JSON) HttpLikeHeaders Diğer JSON-RPC sistemleriyle birlikte çalışma

Nesneyi son parametre olarak belirterekMultiplexingStream.Options, istemci ve hizmet arasında paylaşılan RPC bağlantısı, büyük ikili verilerin JSON-RPC üzerinden verimli bir şekilde aktarılmasını sağlamak için JSON-RPC bağlantısıyla paylaşılan MultiplexingStream'deki tek bir kanaldır.

Strateji, ExceptionProcessing.ISerializable hizmetinizden atılan özel durumların serileştirilmesine ve istemcide oluşturulurken olarak Exception.InnerException korunmasına RemoteInvocationException neden olur. Bu ayar olmadan, istemcide daha az ayrıntılı özel durum bilgileri kullanılabilir.

İpucu: Uygulama ayrıntısı olarak ServiceRpcDescriptor kullandığınız türetilmiş herhangi bir tür yerine tanımlayıcınızı kullanıma sunma. Bu, daha sonra API'de hataya neden olan değişiklikler olmadan uygulama ayrıntılarını değiştirme esnekliği sağlar.

Kullanıcıların hizmetinizi kullanmalarını kolaylaştırmak için tanımlayıcınızdaki xml belge açıklamasına hizmet arabiriminize bir başvuru ekleyin. Ayrıca, varsa hizmetinizin istemci RPC hedefi olarak kabul edilen arabirimine de başvurun.

Bazı daha gelişmiş hizmetler de istemciden bazı arabirimlere uygun bir RPC hedef nesnesi kabul edebilir veya gerektirebilir. Böyle bir durumda, istemcinin bir örneğini sağlaması gereken arabirimi belirtmek için parametresi olan bir oluşturucu Type clientInterface kullanınServiceJsonRpcDescriptor.

Tanımlayıcıyı sürüm oluşturma

Zaman içinde hizmetinizin sürümünü artırmak isteyebilirsiniz. Böyle bir durumda, her sürüm için benzersiz bir sürüm kullanarak, desteklemek istediğiniz her sürüm ServiceMoniker için bir tanımlayıcı tanımlamanız gerekir. Birden çok sürümü aynı anda desteklemek geriye dönük uyumluluk için iyi olabilir ve genellikle tek bir RPC arabirimiyle yapılabilir.

Visual Studio, özgün ServiceRpcDescriptor öğeyi VisualStudioServices aracılı hizmeti ekleyen ilk sürümü temsil eden iç içe geçmiş sınıfın altında bir virtual özellik olarak tanımlayarak sınıfıyla birlikte bu deseni izler. Kablo protokolünü değiştirmemiz veya hizmetin işlevselliğini eklememiz/değiştirmemiz gerektiğinde, Visual Studio yeni ServiceRpcDescriptorbir döndüren sonraki bir iç içe geçmiş sınıfta bir override özellik bildirir.

Visual Studio uzantısı tarafından tanımlanan ve kullanıma sunulan bir hizmet için, özgün uzantının yanında başka bir tanımlayıcı özelliği bildirmek yeterli olabilir. Örneğin, 1.0 hizmetinizin UTF8 (JSON) biçimlendiricisini kullandığını ve MessagePack'e geçmenin önemli bir performans avantajı sunacağını fark ettiğinizi varsayalım. Biçimlendiriciyi değiştirmek, protokolü bozan bir kablo değişikliği olduğundan, aracılı hizmet sürüm numarasının ve ikinci bir tanımlayıcının arttırılması gerekir. İki tanımlayıcı birlikte şöyle görünebilir:

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 });

İki tanımlayıcı bildirsek de (ve daha sonra iki hizmeti hazırlamamız ve kaydetmemiz gerekecek) ancak bunu tek bir hizmet arabirimi ve uygulamasıyla gerçekleştirebiliriz ve birden çok hizmet sürümünü desteklemeye yönelik ek yükü oldukça düşük tutabiliriz.

Hizmeti sağlama

Aracılı hizmetiniz bir istek geldiğinde oluşturulmalıdır ve bu, hizmeti talep etme adlı bir adımla düzenlenmiştir.

Hizmet fabrikası

komutunu kullanmak GlobalProviderSVsBrokeredServiceContaineriçin kullanınGetServiceAsync. Ardından hizmetinizi görüntülemek için bu kapsayıcıyı arayın IBrokeredServiceContainer.Proffer .

Aşağıdaki örnekte, daha önce bildirilen ve örneğine ServiceRpcDescriptorayarlanmış olan alanı kullanarak CalculatorService bir hizmet sunun. Bir temsilci olan BrokeredServiceFactory servis fabrikamızı geçiriyoruz.

IBrokeredServiceContainer container = await AsyncServiceProvider.GlobalProvider.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
container.Proffer(
    CalculatorService,
    (moniker, options, serviceBroker, cancellationToken) => new ValueTask<object?>(new CalculatorService()));

Aracılı hizmet genellikle istemci başına bir kez örneği oluşturulur. Bu, genellikle bir kez örneklenen ve tüm istemciler arasında paylaşılan diğer VS (Visual Studio) hizmetlerinden bir çıkıştır. Her hizmet ve/veya bağlantısı, istemcinin çalıştığı yetkilendirme düzeyi, tercih CultureInfo ettiği şey vb. hakkında istemci başına durumu koruyabildiği için istemci başına hizmetin bir örneğinin oluşturulması daha iyi güvenlik sağlar. Daha sonra göreceğimiz gibi, bu isteğe özgü bağımsız değişkenleri kabul eden daha ilginç hizmetlere de olanak tanır.

Önemli

Bu kılavuzdan sapan ve her istemciye yeni bir örnek yerine paylaşılan bir hizmet örneği döndüren bir hizmet fabrikasının, proxy'sini atacak ilk istemci, diğer istemciler bunu kullanmadan önce paylaşılan hizmet örneğinin elden çıkarılmasına yol açacağı için hiçbir zaman hizmetini uygulamamalıdır.IDisposable

Oluşturucunun paylaşılan durum nesnesine ve IServiceBrokeröğesine ihtiyaç duyması CalculatorService durumunda fabrikayı şu şekilde talep edebiliriz:

var state = new CalculatorService.State();
container.Proffer(
    CalculatorService,
    (moniker, options, serviceBroker, cancellationToken) => new ValueTask<object?>(new CalculatorService(state, serviceBroker)));

state Yerel değişken hizmet fabrikasının dışındadır ve bu nedenle yalnızca bir kez oluşturulur ve örnek oluşturulan tüm hizmetler arasında paylaşılır.

Hizmet, (örneğin, istemci RPC hedef nesnesinde yöntemleri çağırmak için) için de geçirilebilen erişim ServiceActivationOptions gerektiriyorsa daha gelişmiştir:

var state = new CalculatorService.State();
container.Proffer(CalculatorService, (moniker, options, serviceBroker, cancellationToken) =>
    new ValueTask<object?>(new CalculatorService(state, serviceBroker, options)));

Bu durumda hizmet oluşturucu, ile oluşturucu bağımsız değişkenlerinden biri olarak oluşturulduğu typeof(IClientCallbackInterface) varsayılarak ServiceJsonRpcDescriptor şöyle görünebilir:

internal class Calculator(State state, IServiceBroker serviceBroker, ServiceActivationOptions options)
{
    this.state = state;
    this.serviceBroker = serviceBroker;
    this.options = options;
    this.clientCallback = (IClientCallbackInterface)options.ClientRpcTarget;
}

Bu clientCallback alan, bağlantı atılana kadar hizmet istemciyi çağırmak istediğinde artık çağrılabilir.

Temsilci, BrokeredServiceFactory hizmet fabrikasının birden çok hizmet veya hizmetin farklı sürümlerini oluşturan paylaşılan bir yöntem olması durumunda bir parametre olarak alır ServiceMoniker . Bu takma ad istemciden gelir ve bekledikleri hizmetin sürümünü içerir. Hizmet, bu takma adı hizmet oluşturucuya ileterek, istemcinin beklediğiyle eşleşecek şekilde belirli hizmet sürümlerinin tuhaf davranışına öykünebilir.

Aracılı hizmet sınıfınızın içini AuthorizingBrokeredServiceFactory kullanmadığınız sürece temsilciyi IAuthorizationService IBrokeredServiceContainer.Proffer yöntemiyle kullanmaktan kaçının. Bellek IAuthorizationService sızıntısını önlemek için aracılı hizmet sınıfınızla birlikte bu atılmalıdır .

Hizmetinizin birden çok sürümünü destekleme

üzerindeki ServiceMonikersürümü artırdığınızda, istemci isteklerine yanıt vermeyi planladığınız aracılı hizmetinizin her sürümünün proffer'ını kullanmanız gerekir. Bu, hala desteklediğiniz her ServiceRpcDescriptor yöntemle çağrılarak IBrokeredServiceContainer.Proffer yapılır.

Hizmetinizi bir null sürümle sunmak, kayıtlı bir hizmetle kesin bir sürümün eşleşmediği tüm istemci isteklerinde eşleşecek bir 'tümünü yakala' görevi görür. Örneğin, 1.0 ve 1.1 hizmetinizi belirli sürümlerle sunabilir ve ayrıca hizmetinizi bir null sürümle kaydedebilirsiniz. Bu gibi durumlarda, hizmetinizi 1.0 veya 1.1 ile isteyen istemciler bu tam sürümler için seçtiğiniz hizmet fabrikasını çağırırken, 8.0 sürümünü isteyen bir istemci null sürüme sahip proffered hizmet fabrikanızın çağrılmasına yol açar. İstemci tarafından istenen sürüm hizmet fabrikasına sağlandığından, fabrika bu istemci için hizmeti yapılandırma veya desteklenmeyen bir sürümü imzalamak için geri dönüp dönmeyeceği null konusunda bir karar verebilir.

Sürümü olan bir null hizmet için istemci isteği yalnızca kayıtlı ve sürümle birlikte sunulan bir null hizmette eşleşir.

Hizmetinizin birçok sürümünü yayımladığınız, bazıları geriye dönük olarak uyumlu olan ve bu nedenle bir hizmet uygulamasını paylaşabileceğiniz bir durum düşünün. Her bir sürümü aşağıdaki gibi tekrar tekrar seçmek zorunda kalmamak için tümünü yakala seçeneğini kullanabiliriz:

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.
    }));

Hizmeti kaydetme

Aracılı bir hizmeti genel aracılı hizmet kapsayıcısına göndermek, hizmet ilk kez kaydedilmediği sürece ortaya çıkacaktır. Kayıt, kapsayıcının hangi aracılı hizmetlerin kullanılabilir olabileceğini ve talep eden kodu yürütmek için istendiğinde hangi VS Paketinin yüklendiğini önceden bilmesi için bir araç sağlar. Bu, Visual Studio'nun önceden tüm uzantıları yüklemeden hızlı bir şekilde başlatılmasına ve aracılı hizmetinin bir istemcisi tarafından istendiğinde gerekli uzantıyı yükleyebilmesine olanak tanır.

Kayıt işlemi, türetilmiş sınıfınıza AsyncPackageuygulanarak ProvideBrokeredServiceAttribute yapılabilir. Bu, ayarlanabilecek tek yerdir ServiceAudience .

[ProvideBrokeredService("YourCompany.Extension.Calculator", "1.0", Audience = ServiceAudience.Local)]

Varsayılan değer Audience , aracılı hizmetinizi yalnızca aynı işlemdeki diğer kodlara sunan değeridir ServiceAudience.Process. ayarını ServiceAudience.Localyaparak aracılı hizmetinizi aynı Visual Studio oturumuna ait diğer işlemlere maruz bırakabilirsiniz.

Aracılı hizmetinizin Live Share konuklarına açık olması gerekiyorsa , Audience özelliğinin dahil ServiceAudience.LiveShareGuest edilmesi ve özelliğinin ProvideBrokeredServiceAttribute.AllowTransitiveGuestClients olarak trueayarlanması gerekir. Bu bayrakların ayarlanması ciddi güvenlik açıklarına neden olabilir ve öncelikle Aracılı Hizmetin Güvenliğini Sağlama yönergelerine uymadan yapılmamalıdır.

üzerindeki ServiceMonikersürümü artırdığınızda, istemci isteklerine yanıt vermeyi planladığınız aracılı hizmetinizin her sürümünü kaydetmeniz gerekir. Aracılı hizmetinizin en son sürümünden daha fazlasını destekleyerek, eski aracılı hizmet sürümünüzün istemcileri için geriye dönük uyumluluğun korunmasına yardımcı olursunuz. Bu, oturumu paylaşan her Visual Studio sürümünün farklı bir sürüm olabileceği Live Share senaryosunu göz önünde bulundurduğunuzda özellikle yararlı olabilir.

Hizmetinizi bir null sürüme kaydetmek, kayıtlı bir hizmete sahip kesin bir sürümün mevcut olmadığı herhangi bir istemci isteğinde eşleşen bir 'tümünü yakala' görevi görür. Örneğin, 1.0 ve 2.0 hizmetinizi belirli sürümlerle kaydedebilir ve ayrıca hizmetinizi bir null sürümle kaydedebilirsiniz.

Hizmetinizi sağlamak ve kaydetmek için MEF kullanma

Bunun için Visual Studio 2022 Güncelleştirme 2 veya üzeri gerekir.

Aracılı bir hizmet, önceki iki bölümde açıklandığı gibi Visual Studio Paketi kullanmak yerine MEF aracılığıyla dışarı aktarılabilir. Bunun dikkate alınması gereken dezavantajları vardır:

Tradeoff Paket kasası MEF dışarı aktarma
Kullanılabilirlik ✅ Aracılı hizmet VS başlangıcında hemen kullanılabilir. ⚠✔ Aracılı hizmet, mef işlemde başlatılana kadar kullanılabilirlikte gecikebilir. Bu genellikle hızlıdır, ancak MEF önbelleği eski olduğunda birkaç saniye sürebilir.
Platformlar arası hazır olma ⚠️ Windows için Visual Studio'ya özgü kod yazılmalıdır. ✅Derlemenizdeki aracılı hizmet, Windows için Visual Studio'ya ve Mac için Visual Studio yüklenebilir.

ARACılı hizmetinizi VS paketlerini kullanmak yerine MEF aracılığıyla dışarı aktarmak için:

  1. Son iki bölümle ilgili bir kodunuz olmadığını onaylayın. Özellikle öğesini çağıran IBrokeredServiceContainer.Proffer ve paketinize (varsa) uygulamaması ProvideBrokeredServiceAttribute gereken bir kodunuz olmamalıdır.
  2. Aracılı IExportedBrokeredService hizmet sınıfınızda arabirimini uygulayın.
  3. Oluşturucunuzdaki veya özellik ayarlayıcılarını içeri aktaran ana iş parçacığı bağımlılıklarından kaçının. IExportedBrokeredService.InitializeAsync Ana iş parçacığı bağımlılıklarına izin verilen aracılı hizmetinizi başlatmak için yöntemini kullanın.
  4. ExportBrokeredServiceAttribute hizmetinizin adı, hedef kitlesi ve kayıtla ilgili diğer gerekli bilgileri belirterek aracını aracılı hizmet sınıfınıza uygulayın.
  5. Sınıfınız elden çıkarma gerektiriyorsa, MEF hizmetinizin kullanım ömrüne sahip olduğundan ve yalnızca zaman uyumlu elden çıkarmayı desteklediğinden bunu IAsyncDisposable uygulayınIDisposable.
  6. Dosyanızın source.extension.vsixmanifest aracılı hizmetinizi mef derlemesi olarak içeren projeyi listelediğinden emin olun.

MEF bölümü olarak, aracılı hizmetiniz varsayılan kapsamdaki diğer MEF bölümlerini içeri aktarabilir . Bunu yaparken yerine kullandığınızdan System.ComponentModel.Composition.ImportAttribute System.Composition.ImportAttributeemin olun. Bunun nedeni ExportBrokeredServiceAttribute , tür boyunca aynı MEF ad alanından türetilen System.ComponentModel.Composition.ExportAttribute ve kullanan öğesinin gerekli olmasıdır.

Aracılı hizmet, birkaç özel dışarı aktarmayı içeri aktarabilmek için benzersizdir:

  • IServiceBroker, diğer aracılı hizmetleri almak için kullanılmalıdır.
  • ServiceMonikeraracılı hizmetinizin birden çok sürümünü dışarı aktardığınızda ve istemcinin hangi sürümü istediğinizi algılamanız gerektiğinde yararlı olabilir.
  • ServiceActivationOptions, istemcilerinizin özel parametreler veya bir istemci geri çağırma hedefi sağlaması gerektiğinde yararlı olabilir.
  • AuthorizationServiceClient, aracılı bir hizmetin güvenliğini sağlama bölümünde açıklandığı gibi güvenlik denetimleri gerçekleştirmeniz gerektiğinde yararlı olabilir. Aracılı hizmetiniz atıldığında otomatik olarak atılacağından, bu nesnenin sınıfınız tarafından atılması gerekmez.

Aracılı hizmetiniz diğer aracılı hizmetleri almak için MEF'leri ImportAttribute kullanmamalıdır. Bunun yerine aracılı hizmetleri geleneksel şekilde sorgulayabilir ve sorgulayabilir [Import] IServiceBroker . Daha fazla bilgi için bkz . Aracılı hizmet kullanma.

Örneği aşağıda verilmiştir:

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);
    }
}

Aracılı hizmetinizin birden çok sürümünü dışarı aktarma

aracılı ExportBrokeredServiceAttribute hizmetinizin birden çok sürümünü sunmak için aracılı hizmetinize birden çok kez uygulanabilir.

özelliğini uygulamanız, istemcinin IExportedBrokeredService.Descriptor istediği adla eşleşen bir adla bir tanımlayıcı döndürmelidir.

Hesap makinesi hizmetinin UTF8 biçimlendirmesiyle 1.0'ı dışarı aktardığı ve daha sonra MessagePack biçimlendirmesini kullanarak elde edilen performans kazançlarının keyfini çıkarmak için 1.1 dışarı aktarma eklediği bu örneği düşünün.

[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!;
}

Visual Studio 2022 Güncelleştirme 12 (17.12) ile başlayarak, sürüme sahip bir null istek de dahil olmak üzere sürümden bağımsız olarak, sürüme bağlı bir hizmet için herhangi bir istemci isteğiyle eşleşecek şekilde sürümleştirilmiş bir null hizmet dışarı aktarılabilir. Böyle bir hizmet, istemcinin istediği sürümün Descriptor bir uygulamasını sunmadığında istemci isteğini reddetmek için özelliğinden geri dönebilirnull.

Hizmet isteğini reddetme

Aracılı bir hizmet, yönteminden InitializeAsync oluşturarak istemcinin etkinleştirme isteğini reddedebilir. Oluşturma, istemciye geri bir ServiceActivationFailedException atılması neden olur.