이 문서에서는 .NET Remoting을 사용하여 WCF(Windows Communication Foundation)를 사용하는 애플리케이션을 마이그레이션하는 방법을 설명합니다. 이러한 제품 간의 유사한 개념을 비교한 다음 WCF에서 몇 가지 일반적인 원격 시나리오를 수행하는 방법을 설명합니다.
.NET 원격은 이전 버전과의 호환성을 위해서만 지원되는 레거시 제품입니다. 클라이언트와 서버 간에 별도의 신뢰 수준을 유지할 수 없으므로 혼합 신뢰 환경에서는 안전하지 않습니다. 예를 들어 인터넷 또는 신뢰할 수 없는 클라이언트에 .NET 원격 엔드포인트를 노출해서는 안 됩니다. 기존 원격 애플리케이션을 더 안전하고 최신 기술로 마이그레이션하는 것이 좋습니다. 애플리케이션의 디자인에서 HTTP만 사용하고 RESTful인 경우 Web API를 ASP.NET 것이 좋습니다. 자세한 내용은 ASP.NET Web API를 참조하세요. 애플리케이션이 SOAP를 기반으로 하거나 TCP와 같은 Http 이외의 프로토콜이 필요한 경우 WCF를 사용하는 것이 좋습니다.
.NET 원격 처리와 WCF 비교
이 섹션에서는 .NET Remoting의 기본 구성 요소를 해당 WCF와 비교합니다. 이러한 구성 요소는 나중에 WCF에서 몇 가지 일반적인 클라이언트 서버 시나리오를 만드는 데 사용됩니다. 다음 차트에서는 .NET Remoting과 WCF 간의 주요 유사점과 차이점을 요약합니다.
| .NET 리모팅 | WCF (Windows Communication Foundation) | |
|---|---|---|
| 서버 유형 | 서브 클래스 MarshalByRefObject |
[ServiceContract] 속성으로 표시 |
| 서비스 작업 | 서버 형식의 공용 메서드 |
[OperationContract] 속성으로 표시 |
| 직렬화 |
ISerializable 또는 [Serializable] |
DataContractSerializer 또는 XmlSerializer |
| 전달된 개체 | 값별 또는 참조별 | 값별 전용 |
| 오류/예외 | 직렬화 가능한 모든 예외 | FaultContract<TDetail> |
| 클라이언트 프록시 개체 | 강력한 형식의 투명 프록시는 MarshalByRefObjects에서 자동으로 생성됩니다. | 강력한 형식의 프록시는 ChannelFactory<TChannel을 사용하여 주문형으로 생성됩니다.> |
| 플랫폼 필요 | 클라이언트와 서버 모두 Microsoft OS 및 .NET을 사용해야 합니다. | 플랫폼 간 |
| 메시지 형식 | 비공개 | 업계 표준(예: SOAP 및 WS-*) |
서버 구현 비교
.NET 원격에서 서버 만들기
.NET 원격 서버 형식은 MarshalByRefObject에서 파생되어야 하며 다음과 같이 클라이언트에서 호출할 수 있는 메서드를 정의해야 합니다.
public class RemotingServer : MarshalByRefObject
{
public Customer GetCustomer(int customerId) { … }
}
이 서버 유형의 공용 메서드는 클라이언트에서 사용할 수 있는 공용 계약이 됩니다. 서버의 공용 인터페이스와 해당 구현 간에는 분리가 없습니다. 하나의 형식이 둘 다 처리합니다.
서버 유형이 정의되면 다음 예제와 같이 클라이언트에서 사용할 수 있습니다.
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();
원격 유형을 서버로 사용할 수 있도록 하는 방법에는 구성 파일 사용을 포함하여 여러 가지가 있습니다. 이것은 하나의 예에 불과합니다.
WCF에서 서버 만들기
WCF의 동등한 단계에는 공용 "서비스 계약"과 구체적인 구현이라는 두 가지 형식을 만드는 작업이 포함됩니다. 첫 번째는 [ServiceContract]로 표시된 인터페이스로 선언됩니다. 클라이언트에서 사용할 수 있는 메서드는 [OperationContract]로 표시됩니다.
[ServiceContract]
public interface IWCFServer
{
[OperationContract]
Customer GetCustomer(int customerId);
}
서버의 구현은 다음 예제와 같이 별도의 구체적인 클래스에 정의됩니다.
public class WCFServer : IWCFServer
{
public Customer GetCustomer(int customerId) { … }
}
이러한 형식이 정의되면 다음 예제와 같이 클라이언트에서 WCF 서버를 사용할 수 있습니다.
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();
}
비고
TCP는 두 예제에서 최대한 비슷하게 유지하는 데 사용됩니다. HTTP를 사용하는 예제는 이 항목의 뒷부분에 있는 시나리오 연습을 참조하세요.
WCF 서비스를 구성하고 호스트하는 방법에는 여러 가지가 있습니다. 이는 "자체 호스팅"으로 알려진 한 가지 예일 뿐입니다. 자세한 내용은 다음 항목을 참조하세요.
클라이언트 구현 비교
.NET 원격에서 클라이언트 만들기
.NET 원격 서버 개체를 사용할 수 있게 되면 다음 예제와 같이 클라이언트에서 사용할 수 있습니다.
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.");
Activator.GetObject()에서 반환된 RemotingServer 인스턴스를 "투명 프록시"라고 합니다. 클라이언트에서 RemotingServer 형식에 대한 공용 API를 구현하지만 모든 메서드는 다른 프로세스 또는 컴퓨터에서 실행되는 서버 개체를 호출합니다.
WCF에서 클라이언트 만들기
WCF의 동등한 단계에는 채널 팩터리를 사용하여 프록시를 명시적으로 만드는 작업이 포함됩니다. Remoting과 마찬가지로 프록시 개체를 사용하여 다음 예제와 같이 서버에서 작업을 호출할 수 있습니다.
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.");
이 예제에서는 원격 예제와 가장 유사하기 때문에 채널 수준에서 프로그래밍을 보여 줍니다. 또한 클라이언트 프로그래밍을 간소화하는 코드를 생성하는 Visual Studio의 서비스 참조 추가 방법도 사용할 수 있습니다. 자세한 내용은 다음 항목을 참조하세요.
Serialization 사용 방법
.NET 원격 및 WCF는 직렬화를 사용하여 클라이언트와 서버 간에 개체를 보내지만 다음과 같은 중요한 방법이 다릅니다.
직렬화할 대상을 나타내기 위해 서로 다른 직렬 변환기 및 규칙을 사용합니다.
.NET Remoting은 한 계층의 메서드 또는 속성을 통해 보안 경계를 넘어 다른 계층에서 코드를 실행할 수 있도록 하는 "참조 기준" 직렬화를 지원합니다. 이 기능은 보안 취약성을 노출하며 Remoting 엔드포인트가 신뢰할 수 없는 클라이언트에 노출되지 않아야 하는 주된 이유 중 하나입니다.
직렬화는 Remoting에서 사용할 때 옵트아웃(직렬화하지 않을 항목을 명시적으로 제외) 방식이며, WCF에서 사용할 때는 옵트인(직렬화할 멤버를 명시적으로 표시) 방식입니다.
.NET 원격의 직렬화
.NET 원격은 클라이언트와 서버 간에 개체를 직렬화하고 역직렬화하는 두 가지 방법을 지원합니다.
값에 따라 개체의 값이 계층 경계를 넘어 직렬화되고 해당 개체의 새 인스턴스가 다른 계층에 만들어집니다. 새 인스턴스의 메서드 또는 속성에 대한 호출은 로컬에서만 실행되며 원래 개체 또는 계층에 영향을 주지 않습니다.
참조에 의한 – 특수한 "개체 참조"는 계층 간 경계를 넘어 직렬화됩니다. 한 계층이 해당 개체의 메서드 또는 속성과 상호 작용하면 원래 계층의 원래 개체와 다시 통신합니다. 참조 개체는 서버에서 클라이언트로 또는 클라이언트에서 서버로 어느 방향으로든 흐를 수 있습니다.
Remoting의 값별 형식은 다음 예제와 같이 [Serializable] 특성으로 표시되거나 ISerializable을 구현합니다.
[Serializable]
public class RemotingCustomer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int CustomerId { get; set; }
}
참조별 형식은 다음 예제와 같이 MarshalByRefObject 클래스에서 파생됩니다.
public class RemotingCustomerReference : MarshalByRefObject
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int CustomerId { get; set; }
}
Remoting의 참조별 개체의 의미를 이해하는 것이 매우 중요합니다. 계층(클라이언트 또는 서버)이 참조별 개체를 다른 계층으로 보내는 경우 모든 메서드 호출은 개체를 소유한 계층에서 다시 실행됩니다. 예를 들어 서버에서 반환된 참조 개체에서 메서드를 호출하는 클라이언트는 서버에서 코드를 실행합니다. 마찬가지로 클라이언트에서 제공하는 참조 개체에서 메서드를 호출하는 서버는 클라이언트에서 코드를 다시 실행합니다. 이러한 이유로 완전히 신뢰할 수 있는 환경 내에서만 .NET 원격을 사용하는 것이 좋습니다. 신뢰할 수 없는 클라이언트에 공용 .NET 원격 엔드포인트를 노출하면 원격 서버가 공격에 취약합니다.
WCF의 Serialization
WCF는 값별 직렬화만 지원합니다. 클라이언트와 서버 간에 교환할 형식을 정의하는 가장 일반적인 방법은 다음 예제와 같습니다.
[DataContract]
public class WCFCustomer
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public int CustomerId { get; set; }
}
[DataContract] 특성은 이 형식을 클라이언트와 서버 간에 직렬화 및 역직렬화할 수 있는 형식으로 식별합니다. [DataMember] 특성은 직렬화할 개별 속성 또는 필드를 식별합니다.
WCF는 계층 간에 개체를 보내면 값만 직렬화하고 다른 계층에 개체의 새 인스턴스를 만듭니다. 개체의 값과의 상호 작용은 로컬에서만 발생합니다. .NET 원격 참조 개체가 수행하는 방식으로 다른 계층과 통신하지 않습니다. 자세한 내용은 직렬화 및 역직렬화를 참조하세요.
예외 처리 기능
.NET 리모팅의 예외
원격 서버에서 throw된 예외는 직렬화되고 클라이언트로 전송되며 다른 예외와 마찬가지로 클라이언트에서 로컬로 throw됩니다. 예외 형식을 하위 클래스로 지정하고 [Serializable]로 표시하여 사용자 지정 예외를 만들 수 있습니다. 대부분의 프레임워크 예외는 이미 이러한 방식으로 표시되어 서버에서 throw되고, 직렬화되고, 클라이언트에서 다시 throw될 수 있습니다. 이 디자인은 개발 중에 편리하지만 서버 쪽 정보를 실수로 클라이언트에 공개할 수 있습니다. 이는 완전히 신뢰할 수 있는 환경에서만 원격을 사용해야 하는 여러 가지 이유 중 하나입니다.
WCF의 예외 및 오류
WCF는 실수로 정보가 공개될 수 있으므로 서버에서 클라이언트로 임의의 예외 형식을 반환하는 것을 허용하지 않습니다. 서비스 작업에서 예기치 않은 예외가 발생하면 일반적인 FaultException이 클라이언트에서 발생합니다. 이 예외는 문제가 발생한 이유나 위치에 대한 정보를 전달하지 않으며 일부 애플리케이션의 경우 이것으로 충분합니다. 클라이언트에 더 풍부한 오류 정보를 전달해야 하는 애플리케이션은 오류 계약을 정의하여 이 작업을 수행합니다.
이렇게 하려면 먼저 오류 정보를 전달하는 [DataContract] 형식을 만듭니다.
[DataContract]
public class CustomerServiceFault
{
[DataMember]
public string ErrorMessage { get; set; }
[DataMember]
public int CustomerId {get;set;}
}
각 서비스 작업에 사용할 오류 계약을 지정합니다.
[ServiceContract]
public interface IWCFServer
{
[OperationContract]
[FaultContract(typeof(CustomerServiceFault))]
Customer GetCustomer(int customerId);
}
서버는 FaultException을 throw하여 오류 조건을 보고합니다.
throw new FaultException<CustomerServiceFault>(
new CustomerServiceFault() {
CustomerId = customerId,
ErrorMessage = "Illegal customer Id"
});
클라이언트가 서버에 요청을 할 때마다, 오류를 일반적인 예외로 처리할 수 있습니다.
try
{
Customer customer = server.GetCustomer(-1);
}
catch (FaultException<CustomerServiceFault> fault)
{
Console.WriteLine($"Fault received: {fault.Detail.ErrorMessage}");
}
오류 계약에 대한 자세한 내용은 다음을 참조하세요 FaultException.
보안 고려사항
.NET 원격 통신의 보안
일부 .NET 원격 채널은 채널 계층(IPC 및 TCP)에서 인증 및 암호화와 같은 보안 기능을 지원합니다. HTTP 채널은 인증 및 암호화 모두에 IIS(인터넷 정보 서비스)를 사용합니다. 이러한 지원에도 불구하고 .NET Remoting을 안전하지 않은 통신 프로토콜로 간주하고 완전히 신뢰할 수 있는 환경 내에서만 사용해야 합니다. 공용 원격 엔드포인트를 인터넷 또는 신뢰할 수 없는 클라이언트에 노출하지 마세요.
WCF의 보안
WCF는 .NET Remoting에서 발견된 취약성의 종류를 해결하기 위해 보안을 염두에 두고 설계되었습니다. WCF는 전송 및 메시지 수준에서 보안을 제공하며 인증, 권한 부여, 암호화 등에 대한 다양한 옵션을 제공합니다. 자세한 내용은 다음 항목을 참조하세요.
WCF로 마이그레이션
Remoting에서 WCF로 마이그레이션하는 이유
.NET 원격은 레거시 제품입니다. .NET Remoting에 설명된 대로 레거시 제품으로 간주되며 새 개발에는 권장되지 않습니다. WCF 또는 ASP.NET Web API는 신규 및 기존 애플리케이션에 권장됩니다.
WCF는 플랫폼 간 표준을 사용합니다. WCF는 플랫폼 간 상호 운용성을 염두에 두고 설계되었으며 많은 업계 표준(SOAP, WS-Security, WS-Trust 등)을 지원합니다. WCF 서비스는 Windows 이외의 운영 체제에서 실행되는 클라이언트와 상호 운용할 수 있습니다. 원격은 주로 서버 및 클라이언트 애플리케이션이 Windows 운영 체제에서 .NET Framework를 사용하여 실행되는 환경을 위해 설계되었습니다.
WCF에는 기본 제공 보안이 있습니다. WCF는 보안을 염두에 두고 설계되었으며 인증, 전송 수준 보안, 메시지 수준 보안 등에 대한 다양한 옵션을 제공합니다. Remoting은 애플리케이션이 쉽게 상호 운용할 수 있도록 설계되었지만 신뢰할 수 없는 환경에서 안전하게 설계되지 않았습니다. WCF는 신뢰할 수 있는 환경과 신뢰할 수 없는 환경에서 모두 작동하도록 설계되었습니다.
마이그레이션 권장 사항
다음은 .NET 원격에서 WCF로 마이그레이션하는 데 권장되는 단계입니다.
서비스 계약을 만듭니다. 서비스 인터페이스 형식을 정의하고 [ServiceContract] 특성으로 표시합니다. 클라이언트가 [OperationContract]를 사용하여 호출할 수 있는 모든 메서드를 표시합니다.
데이터 계약을 만듭니다. 서버와 클라이언트 간에 교환될 데이터 형식을 정의하고 [DataContract] 특성으로 표시합니다. 클라이언트에서 [DataMember]와 함께 사용할 수 있는 모든 필드와 속성을 표시합니다.
오류 계약을 만듭니다(선택 사항). 오류가 발생할 때 서버와 클라이언트 간에 교환될 형식을 만듭니다. 이러한 형식을 [DataContract] 및 [DataMember]로 표시하여 직렬화할 수 있도록 합니다. 모든 서비스 작업에 대해, [OperationContract]로 표기한 경우, [FaultContract]로도 표기하여 어떤 오류를 반환할 수 있는지 나타내기 위해 사용합니다.
서비스를 구성하고 호스트합니다. 서비스 계약이 만들어지면 다음 단계는 엔드포인트에서 서비스를 노출하도록 바인딩을 구성하는 것입니다. 자세한 내용은 엔드포인트: 주소, 바인딩 및 계약을 참조하세요.
원격 애플리케이션이 WCF로 마이그레이션된 후에도 .NET 원격에 대한 종속성을 제거하는 것이 중요합니다. 이렇게 하면 원격 취약성이 애플리케이션에서 제거됩니다. 여기에는 다음과 같은 단계가 포함됩니다.
MarshalByRefObject 사용을 중단합니다. MarshalByRefObject 형식은 원격에만 존재하며 WCF에서 사용되지 않습니다. 하위 클래스 MarshalByRefObject인 모든 애플리케이션 유형을 제거하거나 변경해야 합니다.
[Serializable] 및 ISerializable 사용을 중단합니다. [Serializable] 특성 및 ISerializable 인터페이스는 원래 신뢰할 수 있는 환경 내에서 형식을 직렬화하도록 설계되었으며 원격에서 사용됩니다. WCF serialization은 [DataContract] 및 [DataMember]로 표시된 형식을 사용합니다. 애플리케이션에서 사용하는 데이터 형식은 [DataContract]를 사용하도록 수정해야 하며 ISerializable 또는 [Serializable]을 사용하지 않도록 수정해야 합니다.
마이그레이션 시나리오
이제 WCF에서 다음과 같은 일반적인 원격 시나리오를 수행하는 방법을 살펴보겠습니다.
서버는 클라이언트에 값별 개체를 반환합니다.
서버는 참조 호출을 통해 클라이언트에 개체를 반환합니다.
클라이언트가 개체를 값별로 서버에 보냅니다.
비고
클라이언트에서 서버로 개체를 참조로 보내는 것은 WCF에서 허용되지 않습니다.
이러한 시나리오를 읽을 때 .NET 원격에 대한 기준 인터페이스는 다음 예제와 비슷하다고 가정합니다. WCF를 사용하여 동등한 기능을 구현하는 방법만 설명하려고 하기 때문에 .NET 원격 구현은 여기서 중요하지 않습니다.
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) {…}
}
시나리오 1: 값으로 개체를 반환하는 서비스
이 시나리오에서는 값으로 클라이언트에 개체를 반환하는 서버를 보여 줍니다. WCF는 항상 값으로 서버의 개체를 반환하므로 다음 단계에서는 일반 WCF 서비스를 빌드하는 방법을 간단히 설명합니다.
먼저 WCF 서비스에 대한 공용 인터페이스를 정의하고 [ServiceContract] 특성으로 표시합니다. [OperationContract]를 사용하여 클라이언트가 호출할 서버 쪽 메서드를 식별합니다.
[ServiceContract] public interface ICustomerService { [OperationContract] Customer GetCustomer(int customerId); [OperationContract] bool UpdateCustomer(Customer customer); }다음 단계는 이 서비스에 대한 데이터 계약을 만드는 것입니다. [DataContract] 특성으로 표시된 클래스(인터페이스 아님)를 만들어 이 작업을 수행합니다. 클라이언트와 서버 모두에 표시할 개별 속성 또는 필드가 [DataMember]로 표시됩니다. 파생 형식을 허용하려면 [KnownType] 특성을 사용하여 식별해야 합니다. WCF에서 이 서비스에 대해 직렬화 또는 역직렬화할 수 있는 유일한 형식은 서비스 인터페이스와 이러한 "알려진 형식"입니다. 이 목록에 없는 다른 형식을 교환하려고 하면 거부됩니다.
[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; } }다음으로, 서비스 인터페이스에 대한 구현을 제공합니다.
public class CustomerService : ICustomerService { public Customer GetCustomer(int customerId) { // read from database } public bool UpdateCustomer(Customer customer) { // write to database } }WCF 서비스를 실행하려면 특정 WCF 바인딩을 사용하여 특정 URL에서 해당 서비스 인터페이스를 노출하는 엔드포인트를 선언해야 합니다. 이 작업은 일반적으로 서버 프로젝트의 web.config 파일에 다음 섹션을 추가하여 수행됩니다.
<configuration> <system.serviceModel> <services> <service name="Server.CustomerService"> <endpoint address="http://localhost:8083/CustomerService" binding="basicHttpBinding" contract="Shared.ICustomerService" /> </service> </services> </system.serviceModel> </configuration>그런 다음, 다음 코드로 WCF 서비스를 시작할 수 있습니다.
ServiceHost customerServiceHost = new ServiceHost(typeof(CustomerService)); customerServiceHost.Open();이 ServiceHost가 시작되면 web.config 파일을 사용하여 적절한 계약, 바인딩 및 엔드포인트를 설정합니다. 구성 파일에 대한 자세한 내용은 구성 파일을 사용하여 서비스 구성을 참조하세요. 서버를 시작하는 이 스타일을 자체 호스팅이라고 합니다. WCF 서비스 호스팅에 대한 다른 선택에 대한 자세한 내용은 호스팅 서비스를 참조하세요.
클라이언트 프로젝트의 app.config 서비스의 엔드포인트에 대한 일치하는 바인딩 정보를 선언해야 합니다. Visual Studio에서 이 작업을 수행하는 가장 쉬운 방법은 서비스 참조를 추가하여 app.config 파일을 자동으로 업데이트하는 것입니다. 또는 이러한 동일한 변경 내용을 수동으로 추가할 수 있습니다.
<configuration> <system.serviceModel> <client> <endpoint name="customerservice" address="http://localhost:8083/CustomerService" binding="basicHttpBinding" contract="Shared.ICustomerService"/> </client> </system.serviceModel> </configuration>서비스 참조 추가 사용에 대한 자세한 내용은 방법: 서비스 참조 추가, 업데이트 또는 제거를 참조하세요.
이제 클라이언트에서 WCF 서비스를 호출할 수 있습니다. 이 작업은 해당 서비스에 대한 채널 팩터리를 만들고, 채널을 요청하고, 해당 채널에서 원하는 메서드를 직접 호출하여 수행합니다. 채널이 서비스의 인터페이스를 구현하고 기본 요청/회신 논리를 처리하므로 이 작업을 수행할 수 있습니다. 해당 메서드 호출의 반환 값은 서버 응답의 역직렬화된 복사본입니다.
ChannelFactory<ICustomerService> factory = new ChannelFactory<ICustomerService>("customerservice"); ICustomerService service = factory.CreateChannel(); Customer customer = service.GetCustomer(42); Console.WriteLine($" Customer {customer.FirstName} {customer.LastName} received.");
서버에서 클라이언트로 WCF에서 반환되는 개체는 항상 값에 따라 다릅니다. 개체는 서버에서 보낸 데이터의 역직렬화된 복사본입니다. 클라이언트는 콜백을 통해 서버 코드를 호출할 위험 없이 이러한 로컬 복사본에서 메서드를 호출할 수 있습니다.
시나리오 2: 서버에서 참조로 개체를 반환합니다.
이 시나리오에서는 참조로 클라이언트에 개체를 제공하는 서버를 보여 줍니다. .NET 원격 처리에서는 MarshalByRefObject에서 파생된 모든 형식이 참조를 통해 직렬화되어 자동으로 처리됩니다. 이 시나리오의 예로 여러 클라이언트가 독립적인 세션 서버 쪽 개체를 가질 수 있습니다. 앞에서 설명한 것처럼 WCF 서비스에서 반환되는 개체는 항상 값에 따라 반환되므로 참조별 개체와 직접적인 동등한 개체는 없지만 개체를 사용하여 EndpointAddress10 참조 시맨틱과 비슷한 결과를 얻을 수 있습니다. 서버에서 세션별 참조 개체를 가져오기 위해 클라이언트에서 사용할 수 있는 직렬화 가능한 값별 개체입니다. 이렇게 하면 독립적인 세션 서버 쪽 개체를 가진 여러 클라이언트가 있는 시나리오를 사용할 수 있습니다.
먼저 세션 개체 자체에 해당하는 WCF 서비스 계약을 정의해야 합니다.
[ServiceContract(SessionMode = SessionMode.Allowed)] public interface ISessionBoundObject { [OperationContract] string GetCurrentValue(); [OperationContract] void SetCurrentValue(string value); }팁 (조언)
세션 개체는 [ServiceContract]로 표시되어 일반 WCF 서비스 인터페이스가 됩니다. SessionMode 속성을 설정하면 세션 서비스임을 나타냅니다. WCF에서 세션은 두 엔드포인트 간에 전송된 여러 메시지의 상관 관계를 지정하는 방법입니다. 즉, 클라이언트가 이 서비스에 대한 연결을 가져오면 클라이언트와 서버 간에 세션이 설정됩니다. 클라이언트는 이 단일 세션 내의 모든 상호 작용에 대해 서버 쪽 개체의 단일 고유 인스턴스를 사용합니다.
다음으로, 이 서비스 인터페이스의 구현을 제공해야 합니다. [ServiceBehavior]로 표시하고 InstanceContextMode를 설정하면 각 세션에 대해 이 형식의 고유한 인스턴스를 사용하려고 WCF에 알릴 수 있습니다.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] public class MySessionBoundObject : ISessionBoundObject { private string _value; public string GetCurrentValue() { return _value; } public void SetCurrentValue(string val) { _value = val; } }이제 이 세션 개체의 인스턴스를 가져오는 방법이 필요합니다. EndpointAddress10 개체를 반환하는 다른 WCF 서비스 인터페이스를 만들어 이 작업을 수행합니다. 클라이언트가 세션 개체를 만드는 데 사용할 수 있는 엔드포인트의 직렬화 가능한 형식입니다.
[ServiceContract] public interface ISessionBoundFactory { [OperationContract] EndpointAddress10 GetInstanceAddress(); }그리고 이 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); } }이 구현은 세션 개체를 만들기 위해 싱글톤 채널 팩터리를 유지 관리합니다. GetInstanceAddress()가 호출되면 채널을 만들고 이 채널과 연결된 원격 주소를 효과적으로 가리키는 EndpointAddress10 개체를 만듭니다. EndpointAddress10은 단순히 값별로 클라이언트에 반환할 수 있는 데이터 형식입니다.
아래 예제와 같이 다음 두 가지 작업을 수행하여 서버의 구성 파일을 수정해야 합니다.
세션 개체의 <엔드포인트를 설명하는 클라이언트> 섹션을 선언합니다. 서버가 이 상황에서 클라이언트 역할을 하므로 이 작업이 필요합니다.
팩터리 및 세션 상태를 유지하는 개체에 대한 엔드포인트를 선언합니다. 클라이언트가 서비스 엔드포인트와 통신하여 EndpointAddress10을 획득하고 세션 채널을 만들 수 있도록 허용해야 합니다.
<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>그런 다음, 다음 서비스를 시작할 수 있습니다.
ServiceHost factoryHost = new ServiceHost(typeof(SessionBoundFactory)); factoryHost.Open(); ServiceHost sessionHost = new ServiceHost(typeof(MySessionBoundObject)); sessionHost.Open();프로젝트의 app.config 파일에서 이와 동일한 엔드포인트를 선언하여 클라이언트를 구성합니다.
<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>이 세션 개체를 만들고 사용하려면 클라이언트에서 다음 단계를 수행해야 합니다.
ISessionBoundFactory 서비스에 대한 채널을 만듭니다.
해당 채널을 사용하여 해당 서비스를 호출하여 EndpointAddress10을 가져옵니다.
EndpointAddress10을 사용하여 세션 개체를 가져오는 채널을 만듭니다.
세션 개체와 상호 작용하여 여러 호출에서 동일한 인스턴스로 유지되는 것을 보여 줍니다.
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는 항상 값으로 개체를 반환하지만 EndpointAddress10을 사용하여 참조 시맨틱에 해당하는 의미 체계를 지원할 수 있습니다. 이렇게 하면 클라이언트가 세션 WCF 서비스 인스턴스를 요청할 수 있으며, 그 후에는 다른 WCF 서비스처럼 상호 작용할 수 있습니다.
시나리오 3: 클라이언트가 서버에 By-Value 인스턴스를 보냅니다.
이 시나리오에서는 클라이언트가 비기본 개체 인스턴스를 값에 의한 전송으로 서버에 보내는 방법을 보여줍니다. WCF는 값으로만 개체를 보내므로 이 시나리오에서는 일반적인 WCF 사용을 보여 줍니다.
시나리오 1에서 동일한 WCF 서비스를 사용합니다.
클라이언트를 사용하여 새 값별 개체(Customer)를 만들고, ICustomerService 서비스와 통신할 채널을 만들고, 개체를 전송합니다.
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}.");고객 객체는 직렬화되어 서버로 전송됩니다. 서버에서 해당 객체는 새 복사본으로 역직렬화됩니다.
비고
또한 이 코드는 파생 형식(PremiumCustomer)을 보내는 방법을 보여 줍니다. 서비스 인터페이스는 Customer 개체를 예상하지만 Customer 클래스의 [KnownType] 특성에 PremiumCustomer도 허용되었습니다. WCF는 이 서비스 인터페이스를 통해 다른 형식을 직렬화하거나 역직렬화하려는 시도가 실패합니다.
일반적인 WCF 데이터 교환은 값으로 이루어집니다. 이렇게 하면 이러한 데이터 개체 중 하나에서 메서드를 호출하면 로컬에서만 실행되며 다른 계층에서는 코드를 호출하지 않습니다. 서버 에서 반환된 참조별 개체와 같은 작업을 수행할 수 있지만 클라이언트에서 참조 별 개체를 서버에 전달할 수는 없습니다. 클라이언트와 서버 간에 대화가 필요한 시나리오는 이중 서비스를 사용하여 WCF에서 수행할 수 있습니다. 자세한 내용은 Duplex Services를 참조하세요.
요약
.NET 원격은 완전히 신뢰할 수 있는 환경 내에서만 사용하기 위한 통신 프레임워크입니다. 레거시 제품이며 이전 버전과의 호환성을 위해서만 지원됩니다. 새 애플리케이션을 빌드하는 데 사용하면 안 됩니다. 반대로 WCF는 보안을 염두에 두고 설계되었으며 신규 및 기존 애플리케이션에 권장됩니다. 대신 WCF 또는 ASP.NET Web API를 사용하도록 기존 원격 애플리케이션을 마이그레이션하는 것이 좋습니다.