Dela via


Så här migrerar du DCOM med hanterad kod till WCF

Windows Communication Foundation (WCF) är det rekommenderade och säkra valet jämfört med DCOM (Distributed Component Object Model) för hanterade kodanrop mellan servrar och klienter i en distribuerad miljö. Den här artikeln visar hur du migrerar kod från DCOM till WCF för följande scenarier.

  • Fjärrtjänsten returnerar ett objekts bivärde till klienten

  • Klienten skickar ett objekt efter värde till fjärrtjänsten

  • Fjärrtjänsten returnerar ett objekt med referens till klienten

Av säkerhetsskäl är det inte tillåtet att skicka ett objekt med referens från klienten till tjänsten i WCF. Ett scenario som kräver en konversation fram och tillbaka mellan klient och server kan uppnås i WCF med hjälp av en duplex-tjänst. Mer information om duplex-tjänster finns i Duplex Services.

Mer information om hur du skapar WCF-tjänster och -klienter för dessa tjänster finns i Grundläggande WCF-programmering, design och implementering av tjänster och byggnadsklienter.

DCOM-exempelkod

För dessa scenarier har DCOM-gränssnitten som illustreras med WCF följande struktur:

[ComVisible(true)]  
[Guid("AA9C4CDB-55EA-4413-90D2-843F1A49E6E6")]  
public interface IRemoteService  
{  
   Customer GetObjectByValue();  
   IRemoteObject GetObjectByReference();  
   void SendObjectByValue(Customer customer);  
}  
  
[ComVisible(true)]  
[Guid("A12C98DE-B6A1-463D-8C24-81E4BBC4351B")]  
public interface IRemoteObject  
{  
}  
  
public class Customer  
{  
}  

Tjänsten returnerar ett objekt efter värde

I det här scenariot gör du ett anrop till en tjänst och metoden returnerar ett objekt som skickas per värde från servern till klienten. Det här scenariot representerar följande COM-anrop:

public interface IRemoteService  
{  
    Customer GetObjectByValue();  
}  

I det här scenariot tar klienten emot en deserialiserad kopia av ett objekt från fjärrtjänsten. Klienten kan interagera med den här lokala kopian utan att anropa tillbaka till tjänsten. Med andra ord är klienten garanterad att tjänsten inte kommer att vara involverad på något sätt när metoder på den lokala kopian anropas. WCF returnerar alltid objekt från tjänsten efter värde, så följande steg beskriver hur du skapar en vanlig WCF-tjänst.

Steg 1: Definiera WCF-tjänstgränssnittet

Definiera ett offentligt gränssnitt för WCF-tjänsten och markera det med attributet [ServiceContractAttribute]. Markera de metoder som du vill exponera för klienter med attributet [OperationContractAttribute]. I följande exempel visas hur du använder dessa attribut för att identifiera de gränssnitts- och gränssnittsmetoder på serversidan som en klient kan anropa. Metoden som används för det här scenariot visas i fetstil.

using System.Runtime.Serialization;  
using System.ServiceModel;  
using System.ServiceModel.Web;
. . .  
[ServiceContract]  
public interface ICustomerManager  
{  
    [OperationContract]  
    void StoreCustomer(Customer customer);  
  
    [OperationContract]     Customer GetCustomer(string firstName, string lastName);
  
}  

Steg 2: Definiera datakontraktet

Därefter bör du skapa ett datakontrakt för tjänsten, som beskriver hur data ska utbytas mellan tjänsten och dess klienter. Klasser som beskrivs i datakontraktet bör markeras med attributet [DataContractAttribute]. De enskilda egenskaper eller fält som du vill ska vara synliga för både klienten och servern ska markeras med attributet [DataMemberAttribute]. Om du vill att typer som härleds från en klass i datakontraktet ska tillåtas måste du identifiera dem med attributet [KnownTypeAttribute]. WCF serialiserar eller deserialiserar endast typer i tjänstgränssnittet och typer som identifieras som kända typer. Om du försöker använda en typ som inte är en känd typ uppstår ett undantag.

Mer information om datakontrakt finns i Datakontrakt.

[DataContract]  
[KnownType(typeof(PremiumCustomer))]  
public class Customer  
{  
    [DataMember]  
    public string Firstname;  
    [DataMember]  
    public string Lastname;  
    [DataMember]  
    public Address DefaultDeliveryAddress;  
    [DataMember]  
    public Address DefaultBillingAddress;  
}  
 [DataContract]  
public class PremiumCustomer : Customer  
{  
    [DataMember]  
    public int AccountID;  
}  
  
 [DataContract]  
public class Address  
{  
    [DataMember]  
    public string Street;  
    [DataMember]  
    public string Zipcode;  
    [DataMember]  
    public string City;  
    [DataMember]  
    public string State;  
    [DataMember]  
    public string Country;  
}  

Steg 3: Implementera WCF-tjänsten

Därefter bör du implementera WCF-tjänstklassen som implementerar det gränssnitt som du definierade i föregående steg.

public class CustomerService: ICustomerManager
{  
    public void StoreCustomer(Customer customer)  
    {  
        // write to a database  
    }  
    public Customer GetCustomer(string firstName, string lastName)  
    {  
        // read from a database  
    }  
}  

Steg 4: Konfigurera tjänsten och klienten

Om du vill köra en WCF-tjänst måste du deklarera en slutpunkt som exponerar tjänstgränssnittet på en specifik URL med hjälp av en specifik WCF-bindning. En bindning anger transport-, kodnings- och protokollinformationen för klienterna och servern som ska kommunicera. Du lägger vanligtvis till bindningar i tjänstprojektets konfigurationsfil (web.config). Följande visar en bindningspost för exempeltjänsten:

<configuration>  
  <system.serviceModel>  
    <services>  
      <service name="Server.CustomerService">  
        <endpoint address="http://localhost:8083/CustomerManager"
                  binding="basicHttpBinding"  
                  contract="Shared.ICustomerManager" />  
      </service>  
    </services>  
  </system.serviceModel>  
</configuration>  

Därefter måste du konfigurera klienten så att den matchar den bindningsinformation som anges av tjänsten. Det gör du genom att lägga till följande i klientens programkonfigurationsfil (app.config).

<configuration>  
  <system.serviceModel>  
    <client>  
      <endpoint name="customermanager"
                address="http://localhost:8083/CustomerManager"
                binding="basicHttpBinding"
                contract="Shared.ICustomerManager"/>  
    </client>  
  </system.serviceModel>  
</configuration>  

Steg 5: Kör tjänsten

Slutligen kan du själv vara värd för den i ett konsolprogram genom att lägga till följande rader i tjänstappen och starta appen. Mer information om andra sätt att vara värd för ett WCF-tjänstprogram, Hosting Services.

ServiceHost customerServiceHost = new ServiceHost(typeof(CustomerService));  
customerServiceHost.Open();  

Steg 6: Anropa tjänsten från klienten

Om du vill anropa tjänsten från klienten måste du skapa en kanalfabrik för tjänsten och begära en kanal, vilket gör att du kan anropa GetCustomer metoden direkt från klienten. Kanalen implementerar tjänstens gränssnitt och hanterar den underliggande logiken för begäran/svar åt dig. Returvärdet från det metodanropet är den deserialiserade kopian av tjänstsvaret.

ChannelFactory<ICustomerManager> factory =
     new ChannelFactory<ICustomerManager>("customermanager");  
ICustomerManager service = factory.CreateChannel();  
Customer customer = service.GetCustomer("Mary", "Smith");  

Klienten skickar ett eftervärdesobjekt till servern

I det här scenariot skickar klienten ett objekt till servern, efter värde. Det innebär att servern får en deserialiserad kopia av objektet. Servern kan anropa metoder för den kopian och garanteras att det inte finns någon återanrop till klientkoden. Som tidigare nämnts är normalt WCF-utbyte av data per värde. Detta garanterar att anropande metoder på ett av dessa objekt endast körs lokalt – det anropar inte kod på klienten.

Det här scenariot representerar följande COM-metodanrop:

public interface IRemoteService  
{  
    void SendObjectByValue(Customer customer);  
}  

I det här scenariot används samma tjänstgränssnitt och datakontrakt som i det första exemplet. Dessutom konfigureras klienten och tjänsten på samma sätt. I det här exemplet skapas en kanal för att skicka objektet och köra på samma sätt. I det här exemplet skapar du dock en klient som anropar tjänsten och skickar ett objekt efter värde. Tjänstmetoden som klienten anropar i tjänstkontraktet visas i fetstil:

[ServiceContract]  
public interface ICustomerManager  
{  
    [OperationContract]     void StoreCustomer(Customer customer);  
  
    [OperationContract]  
    Customer GetCustomer(string firstName, string lastName);  
}  

Lägg till kod till klienten som skickar ett värdebaserat objekt

Följande kod visar hur klienten skapar ett nytt eftervärdeskundobjekt, skapar en kanal för att kommunicera med ICustomerManager tjänsten och skickar kundobjektet till den.

Kundobjektet serialiseras och skickas till tjänsten, där det deserialiseras av tjänsten till en ny kopia av objektet. Alla metoder som tjänsten anropar på det här objektet körs endast lokalt på servern. Observera att den här koden visar hur du skickar en härledd typ (PremiumCustomer). Tjänstkontraktet förväntar sig ett Customer objekt, men tjänstdatakontraktet använder attributet [KnownTypeAttribute] för att ange att det PremiumCustomer också är tillåtet. WCF misslyckas med att serialisera eller deserialisera någon annan typ via det här tjänstgränssnittet.

PremiumCustomer customer = new PremiumCustomer();  
customer.Firstname = "John";  
customer.Lastname = "Doe";  
customer.DefaultBillingAddress = new Address();  
customer.DefaultBillingAddress.Street = "One Microsoft Way";  
customer.DefaultDeliveryAddress = customer.DefaultBillingAddress;  
customer.AccountID = 42;  
  
ChannelFactory<ICustomerManager> factory =  
   new ChannelFactory<ICustomerManager>("customermanager");  
ICustomerManager customerManager = factory.CreateChannel();  
customerManager.StoreCustomer(customer);  

Tjänsten returnerar ett objekt efter referens

I det här scenariot anropar klientappen fjärrtjänsten och metoden returnerar ett objekt som skickas med referens från tjänsten till klienten.

Som tidigare nämnts returnerar WCF-tjänster alltid objekt efter värde. Du kan dock uppnå ett liknande resultat med hjälp EndpointAddress10 av klassen. EndpointAddress10 är ett serialiserbart by-value-objekt som kan användas av klienten för att hämta ett sessionful by-reference-objekt på servern.

Beteendet för bireferensobjektet i WCF som visas i det här scenariot skiljer sig från DCOM. I DCOM kan servern returnera ett bireferensobjekt till klienten direkt och klienten kan anropa objektets metoder som körs på servern. I WCF är dock objektet som returneras alltid efter värde. Klienten måste ta det by-value-objektet, som representeras av EndpointAddress10 och använda det för att skapa ett eget sessionful by-reference-objekt. Klientmetoden anropar det sessionskänsliga objektet som körs på servern. Med andra ord är det här bireferensobjektet i WCF en vanlig WCF-tjänst som är konfigurerad för att vara sessionskänslig.

I WCF är en session ett sätt att korrelera flera meddelanden som skickas mellan två slutpunkter. Det innebär att när en klient får en anslutning till den här tjänsten upprättas en session mellan klienten och servern. Klienten använder en enda unik instans av objektet på serversidan för alla dess interaktioner i den här sessionen. Sessionskänsliga WCF-kontrakt liknar anslutningsorienterade mönster för nätverksbegäran/svar.

Det här scenariot representeras av följande DCOM-metod.

public interface IRemoteService  
{  
    IRemoteObject GetObjectByReference();  
}  

Steg 1: Definiera sessionskänsligt WCF-tjänstgränssnitt och -implementering

Definiera först ett WCF-tjänstgränssnitt som innehåller det sessionskänsliga objektet.

I den här koden markeras det sessionskänsliga objektet med ServiceContract attributet, som identifierar det som ett vanligt WCF-tjänstgränssnitt. Dessutom är egenskapen SessionMode inställd på att indikera att den kommer att vara en sessionskänslig tjänst.

[ServiceContract(SessionMode = SessionMode.Allowed)]  
public interface ISessionBoundObject  
{  
    [OperationContract]  
    string GetCurrentValue();  
  
    [OperationContract]  
    void SetCurrentValue(string value);  
}  

Följande kod visar tjänstimplementeringen.

Tjänsten är markerad med attributet [ServiceBehavior] och egenskapen InstanceContextMode är inställd på InstanceContextMode.PerSessions för att indikera att en unik instans av den här typen ska skapas för varje session.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]  
    public class MySessionBoundObject : ISessionBoundObject  
    {  
        private string _value;  
  
        public string GetCurrentValue()  
        {  
            return _value;  
        }  
  
        public void SetCurrentValue(string val)  
        {  
            _value = val;  
        }  
  
    }  

Steg 2: Definiera WCF-fabrikstjänsten för det sessionskänsliga objektet

Tjänsten som skapar det sessionskänsliga objektet måste definieras och implementeras. Följande kod visar hur du gör detta. Den här koden skapar en annan WCF-tjänst som returnerar ett EndpointAddress10 objekt. Det här är en serialiserbar form av en slutpunkt som kan användas för att skapa det sessionsfyllda objektet.

[ServiceContract]  
    public interface ISessionBoundFactory  
    {  
        [OperationContract]  
        EndpointAddress10 GetInstanceAddress();  
    }  

Följande är implementeringen av den här tjänsten. Den här implementeringen underhåller en singleton-kanalfabrik för att skapa sessionskänsliga objekt. När GetInstanceAddress anropas skapar den en kanal och skapar ett EndpointAddress10 objekt som pekar på den fjärradress som är associerad med den här kanalen. EndpointAddress10 är en datatyp som kan returneras till klientens eftervärde.

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

Steg 3: Konfigurera och starta WCF-tjänsterna

För att vara värd för dessa tjänster måste du göra följande tillägg till serverns konfigurationsfil (web.config).

  1. Lägg till ett <client> avsnitt som beskriver slutpunkten för det sessionskänsliga objektet. I det här scenariot fungerar servern också som en klient och måste konfigureras för att aktivera detta.

  2. I avsnittet <services> deklarerar du tjänstslutpunkter för det fabriks- och sessionskänsliga objektet. På så sätt kan klienten kommunicera med tjänstslutpunkterna, hämta EndpointAddress10 och skapa den sessionskänsliga kanalen.

Följande är en exempelkonfigurationsfil med de här inställningarna:

<configuration>  
  <system.serviceModel>  
    <client>  
      <endpoint name="sessionbound"  
                address="net.tcp://localhost:8081/SessionBoundObject"  
                binding="netTcpBinding"  
                contract="Shared.ISessionBoundObject"/>  
    </client>  
  
    <services>  
      <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>  

Lägg till följande rader i ett konsolprogram för att själv vara värd för tjänsten och starta appen.

ServiceHost factoryHost = new ServiceHost(typeof(SessionBoundFactory));  
factoryHost.Open();  
  
ServiceHost sessionBoundServiceHost = new ServiceHost(  
typeof(MySessionBoundObject));  
sessionBoundServiceHost.Open();  

Steg 4: Konfigurera klienten och anropa tjänsten

Konfigurera klienten så att den kommunicerar med WCF-tjänsterna genom att göra följande poster i projektets programkonfigurationsfil (app.config).

<configuration>  
  <system.serviceModel>  
    <client>  
      <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>  

Om du vill anropa tjänsten lägger du till koden i klienten för att göra följande:

  1. Skapa en kanal till tjänsten ISessionBoundFactory .

  2. Använd kanalen för att anropa ISessionBoundFactory tjänsten och hämta ett EndpointAddress10 objekt.

  3. EndpointAddress10 Använd för att skapa en kanal för att hämta ett sessionskänsligt objekt.

  4. SetCurrentValue Anropa metoderna och GetCurrentValue för att visa att den fortfarande är samma objektinstans som används i flera anrop.

ChannelFactory<ISessionBoundFactory> factory =  
        new ChannelFactory<ISessionBoundFactory>("factory");  
  
ISessionBoundFactory sessionBoundFactory = factory.CreateChannel();  
  
EndpointAddress10 address = sessionBoundFactory.GetInstanceAddress();  
  
ChannelFactory<ISessionBoundObject> sessionBoundObjectFactory =  
    new ChannelFactory<ISessionBoundObject>(  
        new NetTcpBinding(),  
        address.ToEndpointAddress());  
  
ISessionBoundObject sessionBoundObject =  
        sessionBoundObjectFactory.CreateChannel();  
  
sessionBoundObject.SetCurrentValue("Hello");  
if (sessionBoundObject.GetCurrentValue() == "Hello")  
{  
    Console.WriteLine("Session-full instance management works as expected");  
}  

Se även