Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo articolo descrive come eseguire la migrazione di un'applicazione che usa la comunicazione remota .NET per usare Windows Communication Foundation (WCF). Confronta concetti simili tra questi prodotti e quindi descrive come eseguire diversi scenari comuni di comunicazione remota in WCF.
.NET Remoting è un prodotto legacy supportato solo per la compatibilità con le versioni precedenti. Non è sicuro in ambienti con attendibilità mista perché non può mantenere i livelli di attendibilità separati tra client e server. Ad esempio, è consigliabile non esporre mai un endpoint di comunicazione remota .NET a Internet o a client non attendibili. È consigliabile eseguire la migrazione di applicazioni remote esistenti a tecnologie più recenti e più sicure. Se la progettazione dell'applicazione usa solo HTTP ed è RESTful, è consigliabile ASP.NET API Web. Per altre informazioni, vedere ASP.NET API Web. Se l'applicazione è basata su SOAP o richiede protocolli non Http, ad esempio TCP, è consigliabile WCF.
Confronto tra comunicazione remota .NET e WCF
In questa sezione vengono confrontati i blocchi predefiniti di base di .NET Remoting con gli equivalenti WCF. Questi blocchi predefiniti verranno usati in un secondo momento per creare alcuni scenari client-server comuni in WCF. Il grafico seguente riepiloga le principali analogie e le differenze tra .NET Remoting e WCF.
Comunicazione remota .NET | WCF (Windows Communication Foundation) | |
---|---|---|
Tipo di server | Sottoclasse MarshalByRefObject |
Contrassegna con l'attributo [ServiceContract] |
Operazioni di servizio | Metodi pubblici sul tipo di server | Contrassegna con l'attributo [OperationContract] |
serializzazione |
ISerializable o [Serializable] |
DataContractSerializer o XmlSerializer |
Oggetti passati | Per valore o per riferimento | Solo per valore |
Errori/eccezioni | Qualsiasi eccezione serializzabile | FaultContract<TDetail> |
Oggetti proxy client | Proxy trasparenti fortemente tipizzati vengono creati automaticamente da MarshalByRefObjects. | I proxy fortemente tipizzati vengono generati su richiesta utilizzando ChannelFactory<TChannel> |
Piattaforma richiesta | Sia il client che il server devono usare il sistema operativo Microsoft e .NET | Multipiattaforma |
Formato messaggio | Privato | Standard del settore (ad esempio SOAP e WS-*) |
Confronto tra implementazioni server
Creazione di un server nella comunicazione remota .NET
I tipi di server remoting .NET devono derivare da MarshalByRefObject e definire i metodi che il client può chiamare, come illustrato di seguito:
public class RemotingServer : MarshalByRefObject
{
public Customer GetCustomer(int customerId) { … }
}
I metodi pubblici di questo tipo di server diventano il contratto pubblico disponibile per i client. Non esiste alcuna separazione tra l'interfaccia pubblica del server e la relativa implementazione: un tipo gestisce entrambi.
Dopo aver definito il tipo di server, può essere reso disponibile ai client, come nell'esempio seguente:
TcpChannel channel = new TcpChannel(8080);
ChannelServices.RegisterChannel(channel, ensureSecurity : true);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RemotingServer),
"RemotingServer",
WellKnownObjectMode.Singleton);
Console.WriteLine("RemotingServer is running. Press ENTER to terminate...");
Console.ReadLine();
Esistono molti modi per rendere disponibile il tipo di comunicazione remota come server, incluso l'uso dei file di configurazione. Questo è solo un esempio.
Creazione di un server in WCF
Il passaggio equivalente in WCF prevede la creazione di due tipi, ovvero il "contratto di servizio" pubblico e l'implementazione concreta. Il primo viene dichiarato come interfaccia contrassegnata con [ServiceContract]. I metodi disponibili per i client sono contrassegnati con [OperationContract]:
[ServiceContract]
public interface IWCFServer
{
[OperationContract]
Customer GetCustomer(int customerId);
}
L'implementazione del server viene definita in una classe concreta separata, come nell'esempio seguente:
public class WCFServer : IWCFServer
{
public Customer GetCustomer(int customerId) { … }
}
Dopo aver definito questi tipi, il server WCF può essere reso disponibile ai client, come nell'esempio seguente:
NetTcpBinding binding = new NetTcpBinding();
Uri baseAddress = new Uri("net.tcp://localhost:8000/wcfserver");
using (ServiceHost serviceHost = new ServiceHost(typeof(WCFServer), baseAddress))
{
serviceHost.AddServiceEndpoint(typeof(IWCFServer), binding, baseAddress);
serviceHost.Open();
Console.WriteLine($"The WCF server is ready at {baseAddress}.");
Console.WriteLine("Press <ENTER> to terminate service...");
Console.WriteLine();
Console.ReadLine();
}
Annotazioni
TCP viene usato in entrambi gli esempi per mantenerli il più simili possibile. Per esempi relativi all'uso di HTTP, vedere le procedure dettagliate dello scenario più avanti in questo tema.
Esistono molti modi per configurare e ospitare servizi WCF. Questo è solo un esempio, noto come "auto-ospitato". Per altre informazioni, vedere gli argomenti seguenti:
Confronto tra implementazioni client
Creazione di un client nella comunicazione remota .NET
Dopo aver reso disponibile un oggetto server Remoting .NET, può essere consumato dai client, come nell'esempio seguente:
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel, ensureSecurity : true);
RemotingServer server = (RemotingServer)Activator.GetObject(
typeof(RemotingServer),
"tcp://localhost:8080/RemotingServer");
RemotingCustomer customer = server.GetCustomer(42);
Console.WriteLine($"Customer {customer.FirstName} {customer.LastName} received.");
L'istanza remotingServer restituita da Activator.GetObject() è nota come "proxy trasparente". Implementa l'API pubblica per il tipo RemotingServer nel client, ma tutti i metodi chiamano l'oggetto server in esecuzione in un processo o un computer diverso.
Creazione di un client in WCF
Il passaggio equivalente in WCF prevede l'uso di una channel factory per creare il proxy in modo esplicito. Analogamente alla comunicazione remota, l'oggetto proxy può essere usato per richiamare le operazioni sul server, come nell'esempio seguente:
NetTcpBinding binding = new NetTcpBinding();
String url = "net.tcp://localhost:8000/wcfserver";
EndpointAddress address = new EndpointAddress(url);
ChannelFactory<IWCFServer> channelFactory =
new ChannelFactory<IWCFServer>(binding, address);
IWCFServer server = channelFactory.CreateChannel();
Customer customer = server.GetCustomer(42);
Console.WriteLine($" Customer {customer.FirstName} {customer.LastName} received.");
Questo esempio mostra la programmazione a livello di canale perché è più simile all'esempio di comunicazione remota. È disponibile anche l'approccio Aggiungi riferimento al servizio in Visual Studio che genera codice per semplificare la programmazione client. Per altre informazioni, vedere gli argomenti seguenti:
Utilizzo della serializzazione
Sia la comunicazione remota .NET che WCF usano la serializzazione per inviare oggetti tra client e server, ma differiscono in questi modi importanti:
Usano serializzatori e convenzioni diversi per indicare cosa serializzare.
La comunicazione remota .NET supporta la serializzazione "per riferimento" che consente l'accesso a metodi o proprietà su un livello per eseguire codice sull'altro livello, che si trova oltre i limiti di sicurezza. Questa funzionalità espone vulnerabilità di sicurezza ed è uno dei motivi principali per cui gli endpoint remoti non devono mai essere esposti a client non attendibili.
La serializzazione usata da Remoting è opt-out (escludere esplicitamente ciò che non si vuole serializzare) e la serializzazione WCF è opt-in (marcare esplicitamente quali membri serializzare).
Serializzazione nella comunicazione remota .NET
La comunicazione remota .NET supporta due modi per serializzare e deserializzare gli oggetti tra il client e il server:
Per valore : i valori dell'oggetto vengono serializzati attraverso i limiti del livello e viene creata una nuova istanza di tale oggetto nell'altro livello. Tutte le chiamate a metodi o proprietà della nuova istanza vengono eseguite solo in locale e non influiscono sull'oggetto o il livello originale.
Per riferimento – uno speciale "riferimento all'oggetto" viene serializzato attraverso i confini del livello. Quando un livello interagisce con metodi o proprietà di tale oggetto, comunica di nuovo all'oggetto originale nel livello originale. Gli oggetti con riferimento possono essere trasmessi in entrambe le direzioni, ovvero dal server al client o dal client al server.
I tipi di dati per valore nella comunicazione remota sono contrassegnati dall'attributo [Serializable] o implementano l'interfaccia ISerializable, come nell'esempio seguente:
[Serializable]
public class RemotingCustomer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int CustomerId { get; set; }
}
I tipi per riferimento derivano dalla classe MarshalByRefObject, come nell'esempio seguente:
public class RemotingCustomerReference : MarshalByRefObject
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int CustomerId { get; set; }
}
È estremamente importante comprendere le implicazioni degli oggetti by-reference di Remoting. Se uno dei livelli (client o server) invia un oggetto by-reference all'altro livello, tutte le chiamate al metodo vengono eseguite sul livello proprietario dell'oggetto. Ad esempio, un client che chiama i metodi in un oggetto per riferimento restituito dal server eseguirà il codice nel server. Analogamente, un server che chiama i metodi in un oggetto per riferimento fornito dal client eseguirà di nuovo il codice nel client. Per questo motivo, l'uso della comunicazione remota .NET è consigliato solo all'interno di ambienti completamente attendibili. L'esposizione di un endpoint di comunicazione remota .NET pubblico ai client non attendibili renderà un server remoto vulnerabile agli attacchi.
Serializzazione in WCF
WCF supporta solo la serializzazione in base al valore. Il modo più comune per definire un tipo da scambiare tra client e server è simile all'esempio seguente:
[DataContract]
public class WCFCustomer
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public int CustomerId { get; set; }
}
L'attributo [DataContract] identifica questo tipo come uno che può essere serializzato e deserializzato tra client e server. L'attributo [DataMember] identifica le singole proprietà o i campi da serializzare.
Quando WCF invia un oggetto tra livelli, serializza solo i valori e crea una nuova istanza dell'oggetto nell'altro livello. Tutte le interazioni con i valori dell'oggetto avvengono solo in locale: non comunicano con l'altro strato come fanno gli oggetti per riferimento di .NET Remoting. Per altre informazioni, vedere Serializzazione e deserializzazione.
Funzionalità di gestione delle eccezioni
Eccezioni nella comunicazione remota .NET
Le eccezioni generate da un server di comunicazione remota vengono serializzate, inviate al client e generate localmente nel client come qualsiasi altra eccezione. Le eccezioni personalizzate possono essere create sottoclassando il tipo di eccezione e contrassegnandolo con [Serializable]. La maggior parte delle eccezioni del framework è già contrassegnata in questo modo, permettendo che vengano gestite dal server, serializzate e rilanciate sul client. Anche se questa progettazione è utile durante lo sviluppo, le informazioni sul lato server possono inavvertitamente essere divulgate al client. Questo è uno dei molti motivi per cui la comunicazione remota deve essere usata solo in ambienti completamente attendibili.
Eccezioni e errori in WCF
WCF non consente la restituzione di tipi di eccezione arbitrari dal server al client perché potrebbe causare la divulgazione accidentale di informazioni. Se un'operazione del servizio genera un'eccezione imprevista, viene generata una FaultException a scopo generico nel client. Questa eccezione non contiene informazioni sul motivo o sulla posizione in cui si è verificato il problema e per alcune applicazioni è sufficiente. Le applicazioni che devono comunicare informazioni di errore più complete al client eseguono questa operazione definendo un contratto di errore.
A tale scopo, creare prima un tipo [DataContract] per contenere le informazioni sull'errore.
[DataContract]
public class CustomerServiceFault
{
[DataMember]
public string ErrorMessage { get; set; }
[DataMember]
public int CustomerId {get;set;}
}
Specificare il contratto di errore da usare per ogni operazione del servizio.
[ServiceContract]
public interface IWCFServer
{
[OperationContract]
[FaultContract(typeof(CustomerServiceFault))]
Customer GetCustomer(int customerId);
}
Il server segnala le condizioni di errore generando un'eccezione FaultException.
throw new FaultException<CustomerServiceFault>(
new CustomerServiceFault() {
CustomerId = customerId,
ErrorMessage = "Illegal customer Id"
});
E ogni volta che il client effettua una richiesta al server, può rilevare gli errori come eccezioni normali.
try
{
Customer customer = server.GetCustomer(-1);
}
catch (FaultException<CustomerServiceFault> fault)
{
Console.WriteLine($"Fault received: {fault.Detail.ErrorMessage}");
}
Per ulteriori informazioni sui contratti di guasto, vedere FaultException.
Considerazioni sulla sicurezza
Sicurezza nella comunicazione remota .NET
Alcuni canali remoti .NET supportano funzionalità di sicurezza come l'autenticazione e la crittografia a livello di canale (IPC e TCP). Il canale HTTP si basa su Internet Information Services (IIS) sia per l'autenticazione che per la crittografia. Nonostante questo supporto, è consigliabile considerare .NET Remoting un protocollo di comunicazione non sicuro e usarlo solo all'interno di ambienti completamente attendibili. Non esporre mai un endpoint di comunicazione remota pubblico a Internet o ai client non attendibili.
Sicurezza in WCF
WCF è stato progettato tenendo conto della sicurezza, in parte per risolvere i tipi di vulnerabilità rilevati nella comunicazione remota .NET. WCF offre sicurezza sia a livello di trasporto che a livello di messaggio e offre molte opzioni per l'autenticazione, l'autorizzazione, la crittografia e così via. Per altre informazioni, vedere gli argomenti seguenti:
Migrazione a WCF
Perché eseguire la migrazione dalla comunicazione remota a WCF?
.NET Remoting è un prodotto legacy. Come descritto in .NET Remoting, viene considerato un prodotto legacy e non è consigliato per il nuovo sviluppo. WCF o ASP.NET API Web sono consigliate per le applicazioni nuove ed esistenti.
WCF utilizza standard multipiattaforma. WCF è stato progettato tenendo conto dell'interoperabilità multipiattaforma e supporta molti standard di settore (SOAP, WS-Security, WS-Trust e così via). Un servizio WCF può interagire con i client in esecuzione in sistemi operativi diversi da Windows. La comunicazione remota è stata progettata principalmente per gli ambienti in cui le applicazioni server e client vengono eseguite con .NET Framework in un sistema operativo Windows.
WCF dispone di sicurezza predefinita. WCF è stato progettato tenendo conto della sicurezza e offre molte opzioni per l'autenticazione, la sicurezza a livello di trasporto, la sicurezza a livello di messaggio e così via. La comunicazione remota è stata progettata per semplificare l'interoperabilità delle applicazioni, ma non è stata progettata per essere protetta in ambienti non attendibili. WCF è stato progettato per funzionare sia in ambienti attendibili che non attendibili.
Raccomandazioni sulla migrazione
Di seguito sono riportati i passaggi consigliati per eseguire la migrazione dalla comunicazione remota .NET a WCF:
Creare il contratto di servizio. Definire i tipi di interfaccia del servizio e contrassegnarli con l'attributo [ServiceContract]. Contrassegnare tutti i metodi che i client potranno chiamare con [OperationContract].
Creare il contratto di dati. Definire i tipi di dati che verranno scambiati tra server e client e contrassegnarli con l'attributo [DataContract]. Contrassegnare tutti i campi e le proprietà che il client potrà usare con [DataMember].
Creare il contratto di guasto (facoltativo). Creare i tipi che verranno scambiati tra server e client quando vengono rilevati errori. Contrassegnare questi tipi con [DataContract] e [DataMember] per renderli serializzabili. Per tutte le operazioni del servizio contrassegnate con [OperationContract], contrassegnarle anche con [FaultContract] per indicare quali errori possono restituire.
Configurare e ospitare il servizio. Dopo aver creato il contratto di servizio, il passaggio successivo consiste nel configurare un'associazione per esporre il servizio in un endpoint. Per ulteriori informazioni, vedere Endpoint: indirizzi, associazioni e contratti.
Dopo aver eseguito la migrazione di un'applicazione remota a WCF, è comunque importante rimuovere le dipendenze dalla comunicazione remota .NET. In questo modo si garantisce che eventuali vulnerabilità di comunicazione remota vengano rimosse dall'applicazione. I passaggi sono i seguenti:
Interrompere l'uso di MarshalByRefObject. Il tipo MarshalByRefObject esiste solo per la comunicazione remota e non viene usato da WCF. Tutti i tipi di applicazione che devono essere rimossi o modificati dalla sottoclasse MarshalByRefObject.
Interrompere l'uso di [Serializable] e ISerializable. L'attributo [Serializable] e l'interfaccia ISerializable sono stati originariamente progettati per serializzare tipi all'interno di ambienti attendibili e sono utilizzati dal Remoting. La serializzazione WCF si basa sui tipi contrassegnati con [DataContract] e [DataMember]. I tipi di dati usati da un'applicazione devono essere modificati per usare [DataContract] e non per usare ISerializable o [Serializable].
Scenari di migrazione
Si vedrà ora come eseguire gli scenari comuni di comunicazione remota seguenti in WCF:
Il server restituisce un oggetto per valore al client
Il server restituisce un oggetto per riferimento al client
Il client invia un oggetto per valore al server
Annotazioni
L'invio di un oggetto per riferimento dal client al server non è consentito in WCF.
Durante la lettura di questi scenari, si supponga che le interfacce di base per .NET Remoting siano simili all'esempio seguente. L'implementazione di comunicazione remota .NET non è importante perché si vuole illustrare solo come usare WCF per implementare funzionalità equivalenti.
public class RemotingServer : MarshalByRefObject
{
// Demonstrates server returning object by-value
public Customer GetCustomer(int customerId) {…}
// Demonstrates server returning object by-reference
public CustomerReference GetCustomerReference(int customerId) {…}
// Demonstrates client passing object to server by-value
public bool UpdateCustomer(Customer customer) {…}
}
Scenario 1: Il servizio restituisce un oggetto per valore
Questo scenario illustra un server che restituisce un oggetto al client per valore. WCF restituisce sempre oggetti dal server in base al valore, quindi i passaggi seguenti descrivono semplicemente come compilare un normale servizio WCF.
Per iniziare, definire un'interfaccia pubblica per il servizio WCF e contrassegnarla con l'attributo [ServiceContract]. Usiamo [OperationContract] per identificare i metodi lato server che il client chiamerà.
[ServiceContract] public interface ICustomerService { [OperationContract] Customer GetCustomer(int customerId); [OperationContract] bool UpdateCustomer(Customer customer); }
Il passaggio successivo consiste nel creare il contratto dati per questo servizio. A tale scopo, creare classi (non interfacce) contrassegnate con l'attributo [DataContract]. Le singole proprietà o campi da visualizzare sia per client che per il server sono contrassegnati con [DataMember]. Se si desidera che i tipi derivati siano consentiti, è necessario usare l'attributo [KnownType] per identificarli. Gli unici tipi che WCF permetterà di serializzare o deserializzare per questo servizio sono quelli nell'interfaccia del servizio e questi "tipi noti". Il tentativo di scambiare qualsiasi altro tipo non incluso nell'elenco verrà rifiutato.
[DataContract] [KnownType(typeof(PremiumCustomer))] public class Customer { [DataMember] public string FirstName { get; set; } [DataMember] public string LastName { get; set; } [DataMember] public int CustomerId { get; set; } } [DataContract] public class PremiumCustomer : Customer { [DataMember] public int AccountId { get; set; } }
Verrà quindi specificata l'implementazione per l'interfaccia del servizio.
public class CustomerService : ICustomerService { public Customer GetCustomer(int customerId) { // read from database } public bool UpdateCustomer(Customer customer) { // write to database } }
Per eseguire il servizio WCF, è necessario dichiarare un endpoint che espone tale interfaccia del servizio in un URL specifico usando un'associazione WCF specifica. Questa operazione viene in genere eseguita aggiungendo le sezioni seguenti al file di web.config del progetto server.
<configuration> <system.serviceModel> <services> <service name="Server.CustomerService"> <endpoint address="http://localhost:8083/CustomerService" binding="basicHttpBinding" contract="Shared.ICustomerService" /> </service> </services> </system.serviceModel> </configuration>
Il servizio WCF può quindi essere avviato con il codice seguente:
ServiceHost customerServiceHost = new ServiceHost(typeof(CustomerService)); customerServiceHost.Open();
Quando viene avviato, ServiceHost usa il file web.config per stabilire il contratto, l'associazione e l'endpoint appropriati. Per altre informazioni sui file di configurazione, vedere Configurazione dei servizi tramite file di configurazione. Questo stile di avvio del server è noto come self-hosting. Per altre informazioni sulle altre opzioni per l'hosting di servizi WCF, vedere Hosting Services.
Il app.config del progetto client deve dichiarare le informazioni di associazione compatibili per l'endpoint del servizio. Il modo più semplice per eseguire questa operazione in Visual Studio consiste nell'usare Aggiungi riferimento al servizio, che aggiornerà automaticamente il file app.config. In alternativa, queste stesse modifiche possono essere aggiunte manualmente.
<configuration> <system.serviceModel> <client> <endpoint name="customerservice" address="http://localhost:8083/CustomerService" binding="basicHttpBinding" contract="Shared.ICustomerService"/> </client> </system.serviceModel> </configuration>
Per altre informazioni sull'uso di Aggiungi riferimento al servizio, vedere Procedura: Aggiungere, aggiornare o rimuovere un riferimento al servizio.
È ora possibile chiamare il servizio WCF dal client. Creiamo una factory di canali per quel servizio, chiedendo un canale e chiamando direttamente il metodo desiderato su quel canale. A tale scopo, il canale implementa l'interfaccia del servizio e gestisce automaticamente la logica di richiesta/risposta sottostante. Il valore restituito da tale chiamata al metodo è la copia deserializzata della risposta del server.
ChannelFactory<ICustomerService> factory = new ChannelFactory<ICustomerService>("customerservice"); ICustomerService service = factory.CreateChannel(); Customer customer = service.GetCustomer(42); Console.WriteLine($" Customer {customer.FirstName} {customer.LastName} received.");
Gli oggetti restituiti da WCF dal server al client sono sempre per valore. Gli oggetti sono copie deserializzate dei dati inviati dal server. Il client può chiamare metodi su queste copie locali senza alcun pericolo di richiamare il codice del server tramite callback.
Scenario 2: il server restituisce un oggetto in base al riferimento
Questo scenario illustra il server che fornisce un oggetto al client in base al riferimento. In .NET Remoting, questo viene gestito automaticamente per qualsiasi tipo derivato da MarshalByRefObject, che viene serializzato per riferimento. Un esempio di questo scenario è consentire a più client di avere oggetti lato server con sessione indipendente. Come accennato in precedenza, gli oggetti restituiti da un servizio WCF sono sempre per valore, pertanto non esiste un equivalente diretto di un oggetto by-reference, ma è possibile ottenere un risultato simile alla semantica by-reference usando un EndpointAddress10 oggetto . Si tratta di un oggetto serializzabile per valore che può essere utilizzato dal client per ottenere un oggetto per riferimento che mantiene lo stato della sessione sul server. Ciò consente lo scenario di avere più client con oggetti lato server indipendenti dotati di sessione.
Prima di tutto, è necessario definire un contratto di servizio WCF che corrisponde all'oggetto con sessione stesso.
[ServiceContract(SessionMode = SessionMode.Allowed)] public interface ISessionBoundObject { [OperationContract] string GetCurrentValue(); [OperationContract] void SetCurrentValue(string value); }
Suggerimento
Si noti che l'oggetto con sessione è contrassegnato con [ServiceContract], rendendolo una normale interfaccia del servizio WCF. L'impostazione della proprietà SessionMode indica che sarà un servizio con sessione. In WCF una sessione è un modo per correlare più messaggi inviati tra due endpoint. Ciò significa che una volta che un client ottiene una connessione a questo servizio, viene stabilita una sessione tra il client e il server. Il client userà una singola istanza univoca dell'oggetto lato server per tutte le interazioni all'interno di questa singola sessione.
Successivamente, è necessario fornire l'implementazione di questa interfaccia del servizio. Denotandolo con [ServiceBehavior] e impostando InstanceContextMode, viene indicato a WCF di voler usare un'istanza univoca di questo tipo per ogni sessione.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] public class MySessionBoundObject : ISessionBoundObject { private string _value; public string GetCurrentValue() { return _value; } public void SetCurrentValue(string val) { _value = val; } }
A questo punto è necessario un modo per ottenere un'istanza di questo oggetto che mantiene la sessione. A tale scopo, creare un'altra interfaccia del servizio WCF che restituisce un oggetto EndpointAddress10. Si tratta di una forma serializzabile di un endpoint che il client può usare per creare l'oggetto con sessione.
[ServiceContract] public interface ISessionBoundFactory { [OperationContract] EndpointAddress10 GetInstanceAddress(); }
E implementiamo questo servizio WCF:
public class SessionBoundFactory : ISessionBoundFactory { public static ChannelFactory<ISessionBoundObject> _factory = new ChannelFactory<ISessionBoundObject>("sessionbound"); public SessionBoundFactory() { } public EndpointAddress10 GetInstanceAddress() { IClientChannel channel = (IClientChannel)_factory.CreateChannel(); return EndpointAddress10.FromEndpointAddress(channel.RemoteAddress); } }
Questa implementazione gestisce una channel factory singleton per creare oggetti con stato di sessione. Quando viene chiamato GetInstanceAddress(), crea un canale e crea un oggetto EndpointAddress10 che punta effettivamente all'indirizzo remoto associato a questo canale. EndpointAddress10 è semplicemente un tipo di dati che può essere restituito al client in base al valore.
È necessario modificare il file di configurazione del server eseguendo le due operazioni seguenti, come illustrato nell'esempio seguente:
Dichiarare una <sezione client> che descrive l'endpoint per l'oggetto con sessione. Ciò è necessario perché il server funge anche da client in questa situazione.
Dichiarare gli endpoint per la factory e l'oggetto con stato di sessione. Ciò è necessario per consentire al client di comunicare con gli endpoint di servizio per acquisire EndpointAddress10 e creare il canale a sessione.
<configuration> <system.serviceModel> <client> <endpoint name="sessionbound" address="net.tcp://localhost:8081/SessionBoundObject" binding="netTcpBinding" contract="Shared.ISessionBoundObject"/> </client> <services> <service name="Server.CustomerService"> <endpoint address="http://localhost:8083/CustomerService" binding="basicHttpBinding" contract="Shared.ICustomerService" /> </service> <service name="Server.MySessionBoundObject"> <endpoint address="net.tcp://localhost:8081/SessionBoundObject" binding="netTcpBinding" contract="Shared.ISessionBoundObject" /> </service> <service name="Server.SessionBoundFactory"> <endpoint address="net.tcp://localhost:8081/SessionBoundFactory" binding="netTcpBinding" contract="Shared.ISessionBoundFactory" /> </service> </services> </system.serviceModel> </configuration>
È quindi possibile avviare questi servizi:
ServiceHost factoryHost = new ServiceHost(typeof(SessionBoundFactory)); factoryHost.Open(); ServiceHost sessionHost = new ServiceHost(typeof(MySessionBoundObject)); sessionHost.Open();
Il client viene configurato dichiarando gli stessi endpoint nel file di app.config del progetto.
<configuration> <system.serviceModel> <client> <endpoint name="customerservice" address="http://localhost:8083/CustomerService" binding="basicHttpBinding" contract="Shared.ICustomerService"/> <endpoint name="sessionbound" address="net.tcp://localhost:8081/SessionBoundObject" binding="netTcpBinding" contract="Shared.ISessionBoundObject"/> <endpoint name="factory" address="net.tcp://localhost:8081/SessionBoundFactory" binding="netTcpBinding" contract="Shared.ISessionBoundFactory"/> </client> </system.serviceModel> </configuration>
Per creare e usare questo oggetto con sessione, il client deve eseguire la procedura seguente:
Creare un canale per il servizio ISessionBoundFactory.
Usare quel canale per richiamare quel servizio per ottenere un EndpointAddress10.
Usare EndpointAddress10 per creare un canale per ottenere un oggetto con sessione.
Interagire con l'oggetto dotato di sessione per dimostrare che rimane la stessa istanza durante più chiamate.
ChannelFactory<ISessionBoundFactory> channelFactory = new ChannelFactory<ISessionBoundFactory>("factory"); ISessionBoundFactory sessionFactory = channelFactory.CreateChannel(); EndpointAddress10 address1 = sessionFactory.GetInstanceAddress(); EndpointAddress10 address2 = sessionFactory.GetInstanceAddress(); ChannelFactory<ISessionBoundObject> sessionObjectFactory1 = new ChannelFactory<ISessionBoundObject>(new NetTcpBinding(), address1.ToEndpointAddress()); ChannelFactory<ISessionBoundObject> sessionObjectFactory2 = new ChannelFactory<ISessionBoundObject>(new NetTcpBinding(), address2.ToEndpointAddress()); ISessionBoundObject sessionInstance1 = sessionObjectFactory1.CreateChannel(); ISessionBoundObject sessionInstance2 = sessionObjectFactory2.CreateChannel(); sessionInstance1.SetCurrentValue("Hello"); sessionInstance2.SetCurrentValue("World"); if (sessionInstance1.GetCurrentValue() == "Hello" && sessionInstance2.GetCurrentValue() == "World") { Console.WriteLine("sessionful server object works as expected"); }
WCF restituisce sempre oggetti per valore, ma è possibile supportare l'equivalente della semantica per riferimento tramite l'uso di EndpointAddress10. In questo modo il client può richiedere un'istanza del servizio WCF con sessione, dopo la quale può interagire con esso come qualsiasi altro servizio WCF.
Scenario 3: Il client invia al server un'istanza di By-Value
Questo scenario illustra il client che invia un'istanza di oggetto non primitivo al server in base al valore. Poiché WCF invia solo oggetti per valore, questo scenario illustra l'utilizzo normale di WCF.
Usare lo stesso servizio WCF dello scenario 1.
Usare il client per creare un nuovo oggetto valore (Customer), creare un canale per comunicare con il servizio ICustomerService e inviare l'oggetto a quest'ultimo.
ChannelFactory<ICustomerService> factory = new ChannelFactory<ICustomerService>("customerservice"); ICustomerService service = factory.CreateChannel(); PremiumCustomer customer = new PremiumCustomer { FirstName = "Bob", LastName = "Jones", CustomerId = 43, AccountId = 99}; bool success = service.UpdateCustomer(customer); Console.WriteLine($" Server returned {success}.");
L'oggetto cliente verrà serializzato e inviato al server, in cui viene deserializzato in una nuova copia di tale oggetto.
Annotazioni
Questo codice illustra anche l'invio di un tipo derivato (PremiumCustomer). L'interfaccia del servizio si aspetta un oggetto Customer, ma l'attributo [KnownType] nella classe Customer indicava che era consentito anche PremiumCustomer. WCF non consentirà alcun tentativo di serializzare o deserializzare qualsiasi altro tipo tramite questa interfaccia del servizio.
Gli scambi WCF normali di dati sono per valore. Ciò garantisce che la chiamata di metodi su uno di questi oggetti dati venga eseguita solo in locale, ma non richiamerà il codice sull'altro livello. Sebbene sia possibile ottenere oggetti per riferimento restituiti dal server, non è possibile che un client passi un oggetto per riferimento al server. Uno scenario che richiede una conversazione tra client e server può essere ottenuto in WCF usando un servizio duplex. Per altre informazioni, vedere Servizi duplex.
Riassunto
.NET Remoting è un framework di comunicazione destinato all'uso solo all'interno di ambienti completamente attendibili. Si tratta di un prodotto legacy e supportato solo per la compatibilità con le versioni precedenti. Non deve essere usato per compilare nuove applicazioni. Al contrario, WCF è stato progettato tenendo conto della sicurezza ed è consigliato per le applicazioni nuove ed esistenti. Microsoft consiglia di eseguire la migrazione delle applicazioni remote esistenti per utilizzare WCF o ASP.NET Web API.