Comunicazione remota con i servizi in C# con Reliable ServicesService Remoting in C# with Reliable Services

Per i servizi che non sono legati a un protocollo di comunicazione o uno stack particolare, ad esempio WebAPI, Windows Communication Foundation (WCF) o altri, il framework Reliable Services fornisce un meccanismo remoto per impostare in modo semplice e rapido chiamate di procedura remota per i servizi.For services that aren't tied to a particular communication protocol or stack, such as WebAPI, Windows Communication Foundation (WCF), or others, the Reliable Services framework provides a remoting mechanism to quickly and easily set up remote procedure calls for services. In questo articolo viene illustrato come impostare le chiamate di procedura remota per i servizi scritti con C#.This article discusses how to set up remote procedure calls for services written with C#.

Impostare la comunicazione remota in un servizioSet up Remoting on a Service

La procedura di impostazione della funzionalità remota per un servizio è costituita da due semplici passaggi.Setting up remoting for a service is done in two simple steps:

  1. Creare un'interfaccia per l’implementazione del servizio.Create an interface for your service to implement. Questa interfaccia definisce i metodi che sono disponibili per una chiamata di procedura remota nel servizioThis interface defines the methods that are available for a remote procedure call on your service. e devono essere metodi asincroni di restituzione di attività.The methods must be task-returning asynchronous methods. L'interfaccia deve implementare Microsoft.ServiceFabric.Services.Remoting.IService per segnalare che il servizio dispone di un'interfaccia remota.The interface must implement Microsoft.ServiceFabric.Services.Remoting.IService to signal that the service has a remoting interface.
  2. Usare un listener di comunicazione remota nel servizio.Use a remoting listener in your service. RemotingListener è un'implementazione ICommunicationListener che offre funzionalità di comunicazione remota.RemotingListener is an ICommunicationListener implementation that provides remoting capabilities. Lo spazio dei nomi Microsoft.ServiceFabric.Services.Remoting.Runtime contiene un metodo di estensione CreateServiceRemotingListener, per i servizi con e senza stato, che può essere usato per creare un listener di comunicazione remota con il protocollo di trasporto predefinito per la comunicazione remota.The Microsoft.ServiceFabric.Services.Remoting.Runtime namespace contains an extension method,CreateServiceRemotingListener for both stateless and stateful services that can be used to create a remoting listener using the default remoting transport protocol.

Nota

Lo spazio dei nomi Remoting è disponibile come pacchetto NuGet separato denominato Microsoft.ServiceFabric.Services.Remoting

Ad esempio, il servizio senza stato seguente espone un metodo singolo per ottenere "Hello World" su una chiamata RPC.For example, the following stateless service exposes a single method to get "Hello World" over a remote procedure call.

using Microsoft.ServiceFabric.Services.Communication.Runtime;
using Microsoft.ServiceFabric.Services.Remoting;
using Microsoft.ServiceFabric.Services.Remoting.Runtime;
using Microsoft.ServiceFabric.Services.Runtime;

public interface IMyService : IService
{
    Task<string> HelloWorldAsync();
}

class MyService : StatelessService, IMyService
{
    public MyService(StatelessServiceContext context)
        : base (context)
    {
    }

    public Task<string> HelloWorldAsync()
    {
        return Task.FromResult("Hello!");
    }

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return new[] { new ServiceInstanceListener(context => this.CreateServiceRemotingListener(context)) };
    }
}

Nota

Gli argomenti e i tipi restituiti nell'interfaccia del servizio possono essere semplici, complessi o personalizzati ma, in tutti i casi, devono essere serializzabili mediante il serializzatore .NET DataContractSerializer.

Chiamare i metodi del servizio remotoCall Remote Service Methods

La chiamata dei metodi su un servizio mediante lo stack remoto viene eseguita usando un proxy locale al servizio tramite la classe Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxy .Calling methods on a service by using the remoting stack is done by using a local proxy to the service through the Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxy class. Il metodo ServiceProxy crea un proxy locale usando la stessa interfaccia implementata dal servizio.The ServiceProxy method creates a local proxy by using the same interface that the service implements. Con tale proxy, è possibile chiamare i metodi nell'interfaccia in modalità remota.With that proxy, you can call methods on the interface remotely.


IMyService helloWorldClient = ServiceProxy.Create<IMyService>(new Uri("fabric:/MyApplication/MyHelloWorldService"));

string message = await helloWorldClient.HelloWorldAsync();

Il framework remoto propaga le eccezioni generate dal servizio al client.The remoting framework propagates exceptions thrown by the service to the client. Di conseguenza, quando si usa ServiceProxy, il client è responsabile per la gestione delle eccezioni generate dal servizio.As a result, when using ServiceProxy, the client is responsible for handling the exceptions thrown by the service.

Durata del proxy servizioService Proxy Lifetime

La creazione di ServiceProxy è un'operazione semplice e, pertanto, è possibile creare quanti proxy si desidera.ServiceProxy creation is a lightweight operation, so you can create as many as you need. Le istanze del proxy servizio possono essere usate più volte, fintanto che sono necessarie.Service Proxy instances can be reused as long as they are needed. Se una chiamata di procedura remota genera un'eccezione, è possibile comunque riusare la stessa istanza del proxy.If a remote procedure call throws an Exception, you can still reuse the same proxy instance. Ogni proxy servizio contiene un client di comunicazione usato per inviare messaggi sulla rete.Each ServiceProxy contains a communication client used to send messages over the wire. Durante le chiamate remote, vengono effettuati controlli interni per verificare che il client di comunicazione sia valido.While invoking remote calls, internal checks are performed to determine if the communication client is valid. In base ai risultati di tali controlli, se necessario, il client di comunicazione viene ricreato.Based on the results of those checks, the communication client is recreated if needed. Pertanto, se si verifica un'eccezione, non è necessario ricreare ServiceProxy.Therefore, if an exception occurs, you do not need to recreate ServiceProxy.

Durata di ServiceProxyFactoryServiceProxyFactory Lifetime

ServiceProxyFactory è una factory che crea istanze di proxy per interfacce di connessione remota diverse.ServiceProxyFactory is a factory that creates proxy instances for different remoting interfaces. Se si usa l'API ServiceProxy.Create per la creazione di un proxy, il framework crea un singleton ServiceProxy.If you use the api ServiceProxy.Create for creating proxy, then the framework creates a singleton ServiceProxy. È utile per crearne una manualmente quando è necessario eseguire l'override delle proprietà IServiceRemotingClientFactory.It is useful to create one manually when you need to override IServiceRemotingClientFactory properties. La creazione di una factory è un'operazione costosa.Factory creation is an expensive operation. ServiceProxyFactory mantiene una cache interna del client di comunicazione.ServiceProxyFactory maintains an internal cache of communication client. La procedura consigliata consiste nel memorizzare nella cache ServiceProxyFactory il più a lungo possibile.Best practice is to cache ServiceProxyFactory for as long as possible.

Gestione delle eccezioni remoteRemoting Exception Handling

Tutte le eccezioni generate dall'API del servizio vengono inviate nuovamente al client come AggregateException.All remote exceptions thrown by the service API are sent back to the client as AggregateException. RemoteExceptions deve essere serializzabile per DataContract; in caso contrario, l'API del proxy genera una ServiceException contenente l'errore di serializzazione.RemoteExceptions should be DataContract serializable; if they are not, the proxy API throws ServiceException with the serialization error in it.

ServiceProxy gestisce tutte le eccezioni di failover per la partizione del servizio per la quale è stato creato.ServiceProxy handles all failover exceptions for the service partition it is created for. Risolve nuovamente gli endpoint in presenza di eccezioni di failover (eccezioni non temporanee) e tenta di nuovo la chiamata con l'endpoint corretto.It re-resolves the endpoints if there are failover exceptions (non-transient exceptions) and retries the call with the correct endpoint. Il numero di tentativi per le eccezioni di failover è indefinito.The number of retries for failover exceptions are indefinite. In caso di eccezioni temporanee, il proxy ripete la chiamata.If transient exceptions occur, the proxy retries the call.

I parametri di ripetizione dei tentativi predefiniti sono forniti da OperationRetrySettings.Default retry parameters are provied by OperationRetrySettings.

È possibile configurare questi valori passando l'oggetto OperationRetrySettings al costruttore ServiceProxyFactory.You can configure these values by passing OperationRetrySettings object to ServiceProxyFactory constructor.

Come usare lo stack V2 per la comunicazione remotaHow to use the Remoting V2 stack

Così come per il pacchetto per la comunicazione remota NuGet 2.8, è possibile usare lo stack V2 per la comunicazione remota.As of the NuGet Remoting package version 2.8, you have the option to use the Remoting V2 stack. Lo stack V2 per la comunicazione remota è più efficiente e offre funzionalità come API serializzabili personalizzate e più collegabili.The Remoting V2 stack is more performant and provides features like custom serialization and more pluggable API's. Il codice del modello continua a usare lo stack V1 per la comunicazione remota.Template code continues to use the Remoting V1 Stack. La comunicazione remota V2 non è compatibile con V1, ovvero con lo stack per la comunicazione remota precedente, seguire le istruzioni qui di seguito su come passare da V1 a V2 senza compromettere la disponibilità del servizio.Remoting V2 is not compatible with V1 (the previous Remoting stack), so follow the instructions below on how to upgrade from V1 to V2 without impacting service availability.

Gli approcci seguenti sono disponibili per l'abilitazione dello stack V2.The following approaches are available to enable the V2 stack.

Uso di un attributo assembly per usare lo stack V2Using an assembly attribute to use the V2 stack

Questa procedura modifica il codice del modello per usare lo Stack V2 usando un attributo assembly.These steps change template code to use the V2 Stack using an assembly attribute.

  1. Modificare la risorsa Endpoint da "ServiceEndpoint" a "ServiceEndpointV2" nel manifesto del servizio.Change the Endpoint Resource from "ServiceEndpoint" to "ServiceEndpointV2" in the service manifest.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Usare il metodo di estensione Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners per creare i listener di comunicazione remota (uguale per V1 e V2).Use the Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners extension method to create remoting listeners (equal for both V1 and V2).

     protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return this.CreateServiceRemotingInstanceListeners();
     }
    
  3. Contrassegnare l'assembly che contiene le interfacce di comunicazione remota con un attributo FabricTransportServiceRemotingProvider.Mark the assembly containing the remoting interfaces with a FabricTransportServiceRemotingProvider attribute.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListener = RemotingListener.V2Listener, RemotingClient = RemotingClient.V2Client)]
    

Non sono necessarie modifiche di codice nel progetto client.No code changes are required in the client project. Compilare l'assembly client con l'assembly dell'interfaccia, per assicurarsi che venga usato l'attributo assembly indicato in precedenza.Build the client assembly with the interface assembly to make sure that the assembly attribute shown above is used.

Uso delle classi V2 esplicite per usare lo stack V2Using explicit V2 classes to use the V2 stack

Come alternativa all'uso di un attributo assembly, lo stack V2 può anche essere abilitato usando classi V2 esplicite.As an alternative to using an assembly attribute, the V2 stack can also be enabled by using explicit V2 classes.

Questa procedura modifica il codice del modello per usare lo Stack V2 usando classi V2 esplicite.These steps change template code to use the V2 Stack using explicit V2 classes.

  1. Modificare la risorsa Endpoint da "ServiceEndpoint" a "ServiceEndpointV2" nel manifesto del servizio.Change the Endpoint Resource from "ServiceEndpoint" to "ServiceEndpointV2" in the service manifest.

    <Resources>
     <Endpoints>
       <Endpoint Name="ServiceEndpointV2" />
     </Endpoints>
    </Resources>
    
  2. Usare FabricTransportServiceRemotingListener dello spazio dei nomi Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime.Use the FabricTransportServiceRemotingListener from the Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime namespace.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
     {
         return new[]
         {
             new ServiceInstanceListener((c) =>
             {
                 return new FabricTransportServiceRemotingListener(c, this);
    
             })
         };
     }
    
  3. Usare FabricTransportServiceRemotingClientFactory dello spazio dei nomi Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client per creare i client.Use the FabricTransportServiceRemotingClientFactory from the Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client namespace to create clients.

    var proxyFactory = new ServiceProxyFactory((c) =>
           {
               return new FabricTransportServiceRemotingClientFactory();
           });
    

Come eseguire l'aggiornamento dal servizio di comunicazione remota V1 a V2.How to upgrade from Remoting V1 to Remoting V2.

Per eseguire l'aggiornamento da V1 a V2, sono necessari gli aggiornamenti in due passaggi.In order to upgrade from V1 to V2, 2-step upgrades are required. La procedura seguente deve essere eseguita nell'ordine elencato.Following steps to be executed in the sequence listed.

  1. Aggiornare il servizio da V1 a V2 mediante l'attributo CompactListener.Upgrade the V1 Service to V2 Service by using CompactListener Attribute. Questa modifica assicura che il servizio sia in ascolto sul listener V1 e V2.This change makes sure that service is listening on V1 and V2 Listener.

    a) Aggiungere una risorsa endpoint con il nome "ServiceEndpointV2" nel manifesto del servizio.a) Add an Endpoint Resource with name as "ServiceEndpointV2" in the service manifest.

    <Resources>
      <Endpoints>
        <Endpoint Name="ServiceEndpointV2" />  
      </Endpoints>
    </Resources>
    

    b) Usare il metodo di estensione seguente per creare un listener di comunicazione remota.b) Use Following Extension Method to Create Remoting Listener.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return this.CreateServiceRemotingInstanceListeners();
    }
    

    c) Aggiungere l'attributo assembly nelle interfacce per la comunicazione remota per usare CompatListener e il client V2.c) Add Assembly Attribute on Remoting Interfaces to use CompatListener and V2 Client.

    [assembly: FabricTransportServiceRemotingProvider(RemotingListener = RemotingListener.CompatListener, RemotingClient = RemotingClient.V2Client)]
    
  2. Aggiornare il client V1 alla versione V2 con l'attributo client V2.Upgrade the V1 Client to V2 Client by using V2 Client Attribute. Questo passaggio si accerta che il client usi lo stack V2.This step makes sure Client is using V2 stack. Non è richiesta alcuna modifica nel progetto/servizio client.No Change in Client Project/Service is required. La compilazione di progetti client con assembly di interfaccia aggiornata è sufficiente.Building Client projects with updated interface assembly is sufficient.

  3. Questo passaggio è facoltativo.This step is optional. Usare l'attributo V2Listener e aggiornare il servizio V2.Use V2Listener Attribute and then Upgrade the V2 Service. Questo passaggio assicura che il servizio sia in ascolto solo sul listener V2.This step makes sure that service is listening only on V2 Listener.

[assembly: FabricTransportServiceRemotingProvider(RemotingListener = RemotingListener.V2Listener, RemotingClient = RemotingClient.V2Client)]

Come usare la serializzazione personalizzata con la comunicazione remota V2.How to use Custom Serialization with Remoting V2.

L'esempio seguente usa la serializzazione Json con la comunicazione remota V2.Following example uses Json Serialization with Remoting V2.

  1. Implementare l'interfaccia IServiceRemotingMessageSerializationProvider per offrire l'implementazione per la serializzazione personalizzata.Implement IServiceRemotingMessageSerializationProvider interface to provide implementation for custom serialization. Di seguito è riportato il frammento di codice che illustra come dovrebbe apparire l'implementazione.Here is the code-snippet on how implementation looks like.

     public class ServiceRemotingJsonSerializationProvider : IServiceRemotingMessageSerializationProvider
     {
         public IServiceRemotingRequestMessageBodySerializer CreateRequestMessageSerializer(Type serviceInterfaceType,
             IEnumerable<Type> requestBodyTypes)
         {
             return new ServiceRemotingRequestJsonMessageBodySerializer(serviceInterfaceType, requestBodyTypes);
         }
    
         public IServiceRemotingResponseMessageBodySerializer CreateResponseMessageSerializer(Type serviceInterfaceType,
             IEnumerable<Type> responseBodyTypes)
         {
             return new ServiceRemotingResponseJsonMessageBodySerializer(serviceInterfaceType, responseBodyTypes);
         }
    
         public IServiceRemotingMessageBodyFactory CreateMessageBodyFactory()
         {
             return new JsonMessageFactory();
         }
     }
    
     class JsonMessageFactory : IServiceRemotingMessageBodyFactory
     {
         public IServiceRemotingRequestMessageBody CreateRequest(string interfaceName, string methodName,
             int numberOfParameters)
         {
             return new JsonRemotingRequestBody();
         }
    
         public IServiceRemotingResponseMessageBody CreateResponse(string interfaceName, string methodName)
         {
             return new JsonRemotingResponseBody();
         }
     }
    
     class ServiceRemotingRequestJsonMessageBodySerializer : IServiceRemotingRequestMessageBodySerializer
     {
         public ServiceRemotingRequestJsonMessageBodySerializer(Type serviceInterfaceType,
             IEnumerable<Type> parameterInfo)
         {
         }
    
         public OutgoingMessageBody Serialize(IServiceRemotingRequestMessageBody serviceRemotingRequestMessageBody)
         {
             if (serviceRemotingRequestMessageBody == null)
             {
                 return null;
             }
    
             var writeStream = new MemoryStream();
             var jsonWriter = new JsonTextWriter(new StreamWriter(writeStream));
    
             var serializer = JsonSerializer.Create(new JsonSerializerSettings()
             {
                 TypeNameHandling = TypeNameHandling.All
             });
             serializer.Serialize(jsonWriter, serviceRemotingRequestMessageBody);
    
             jsonWriter.Flush();
             var segment = new ArraySegment<byte>(writeStream.ToArray());
             var segments = new List<ArraySegment<byte>> { segment };
             return new OutgoingMessageBody(segments);
         }
    
         public IServiceRemotingRequestMessageBody Deserialize(IncomingMessageBody messageBody)
         {
             using (var sr = new StreamReader(messageBody.GetReceivedBuffer()))
    
             using (JsonReader reader = new JsonTextReader(sr))
             {
                 var serializer = JsonSerializer.Create(new JsonSerializerSettings()
                 {
                     TypeNameHandling = TypeNameHandling.All
                 });
    
                 return serializer.Deserialize<JsonRemotingRequestBody>(reader);
             }
         }
     }
    
     class ServiceRemotingResponseJsonMessageBodySerializer : IServiceRemotingResponseMessageBodySerializer
     {
         public ServiceRemotingResponseJsonMessageBodySerializer(Type serviceInterfaceType,
             IEnumerable<Type> parameterInfo)
         {
         }
    
         public OutgoingMessageBody Serialize(IServiceRemotingResponseMessageBody responseMessageBody)
         {
             var json = JsonConvert.SerializeObject(responseMessageBody, new JsonSerializerSettings()
             {
                 TypeNameHandling = TypeNameHandling.All
             });
             var bytes = Encoding.UTF8.GetBytes(json);
             var segment = new ArraySegment<byte>(bytes);
             var list = new List<ArraySegment<byte>> { segment };
             return new OutgoingMessageBody(list);
         }
    
         public IServiceRemotingResponseMessageBody Deserialize(IncomingMessageBody messageBody)
         {
             using (var sr = new StreamReader(messageBody.GetReceivedBuffer()))
    
             using (var reader = new JsonTextReader(sr))
             {
                 var serializer = JsonSerializer.Create(new JsonSerializerSettings()
                 {
                     TypeNameHandling = TypeNameHandling.All
                 });
    
                 return serializer.Deserialize<JsonRemotingResponseBody>(reader);
             }
         }
     }
    
     internal class JsonRemotingResponseBody : IServiceRemotingResponseMessageBody
     {
         public object Value;
    
         public void Set(object response)
         {
             this.Value = response;
         }
    
         public object Get(Type paramType)
         {
             return this.Value;
         }
     }
    
     class JsonRemotingRequestBody : IServiceRemotingRequestMessageBody
     {
         public readonly Dictionary<string, object> parameters = new Dictionary<string, object>();        
    
         public void SetParameter(int position, string parameName, object parameter)
         {
             this.parameters[parameName] = parameter;
         }
    
         public object GetParameter(int position, string parameName, Type paramType)
         {
             return this.parameters[parameName];
         }
     }
    
  2. Eseguire l'override del provider di serializzazione predefinito con JsonSerializationProvider per il listener di comunicazione remota.Override Default Serialization Provider with JsonSerializationProvider for Remoting Listener.

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
     return new[]
     {
         new ServiceInstanceListener((c) =>
         {
             return new FabricTransportServiceRemotingListener(c, this,
                 new ServiceRemotingJsonSerializationProvider());
         })
     };
    }
    
  3. Eseguire l'override del provider di serializzazione predefinito con JsonSerializationProvider per il factory client di comunicazione remota.Override Default Serialization Provider with JsonSerializationProvider for Remoting Client Factory.

  var proxyFactory = new ServiceProxyFactory((c) =>
            {
                return new FabricTransportServiceRemotingClientFactory(
                    serializationProvider: new ServiceRemotingJsonSerializationProvider());
            });

Passaggi successiviNext steps