本文介绍如何迁移使用 .NET 远程处理来使用 Windows Communication Foundation(WCF)的应用程序。 它比较了这些产品之间的类似概念,然后介绍了如何在 WCF 中完成多个常见的远程处理方案。
.NET 远程处理是仅支持向后兼容性的旧产品。 它在混合信任环境中不安全,因为它无法维护客户端和服务器之间的单独信任级别。 例如,不应向 Internet 或不受信任的客户端公开 .NET 远程处理终结点。 建议将现有远程处理应用程序迁移到更新且更安全的技术。 如果应用程序的设计仅使用 HTTP 且是 RESTful,我们建议 ASP.NET Web API。 有关详细信息,请参阅 ASP.NET Web API。 如果应用程序基于 SOAP 或需要非 Http 协议(如 TCP),我们建议使用 WCF。
比较 .NET 远程处理与 WCF
本部分将 .NET 远程处理的基本构建基块与其 WCF 等效项进行比较。 稍后我们将使用这些构建基块在 WCF 中创建一些常见的客户端-服务器方案。 下图总结了 .NET 远程处理和 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 中的等效步骤涉及使用通道工厂显式创建代理。 与远程处理一样,代理对象可以用来调用服务器上的操作,例如下面的例子:
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 中还提供了 “添加服务引用 ”方法,该方法生成代码以简化客户端编程。 有关详细信息,请参阅以下主题:
序列化用法
.NET 远程处理和 WCF 都使用序列化在客户端和服务器之间发送对象,但它们在以下重要方面有所不同:
它们使用不同的序列化程序和约定来指示要序列化的内容。
.NET 远程处理支持“按引用”序列化,允许一个层上的方法或属性访问在其他层(跨安全边界)上执行代码。 此功能公开安全漏洞,是远程处理终结点不应向不受信任的客户端公开的主要原因之一。
远程处理使用的序列化会选择退出(显式排除不进行序列化的成员)和 WCF 序列化会选择性加入(显式标记要序列化的成员)。
.NET 远程处理中的序列化
.NET 远程处理支持在客户端和服务器之间序列化和反序列化对象的两种方法:
按值 – 对象的值跨层边界序列化,并在另一层上创建该对象的新实例。 对新实例的方法或属性的任何调用仅在本地执行,并且不会影响原始对象或层。
按引用 - 跨层边界序列化的特殊“对象引用”。 当一个层与该对象的方法或属性进行交互时,此层会传输回至原始层上的原始对象。 按引用对象可以在任一方向 – 服务器到客户端或客户端到服务器中流动。
远程处理中按值类型会标记为 [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; }
}
了解远程处理中按引用对象的影响尤其重要。 如果任一层(客户端或服务器)将按引用对象发送至其他层,那么所有方法调用会在拥有此对象的层中进行执行。 例如,调用服务器返回的按引用对象中方法的客户端将在服务器中执行代码。 同样,调用客户端提供的按引用对象中方法的服务器将在客户端中执行代码。 因此,建议仅在完全受信任的环境中使用 .NET 远程处理。 向不受信任的客户端公开公共 .NET 远程处理终结点会使远程处理服务器容易受到攻击。
WCF 中的序列化
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 远程处理中的异常
远程处理服务器引发的异常将序列化、发送到客户端,并在客户端本地引发,就像任何其他异常一样。 通过将异常类型子类化并标记为 [Serializable],可以创建自定义异常。 大多数框架异常已经以这种方式标记,允许服务器抛出大多数异常,进行序列化,然后在客户端重新抛出。 尽管此设计在开发过程中很方便,但服务器端信息可能会无意中向客户端披露。 这是应仅在完全信任环境中使用远程处理的许多原因之一。
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 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 通道依赖于 Internet Information Services (IIS)进行身份验证和加密。 尽管有这个支持,您应该将 .NET 远程处理视为不安全的通信协议,并仅在完全受信任的环境中使用它。 切勿将公共远程处理终结点暴露给 Internet 或不受信任的客户端。
WCF 中的安全性
WCF 设计时考虑到了安全性,部分是为了解决 .NET 远程处理中发现的漏洞类型。 WCF 在传输和消息级别提供安全性,并提供许多身份验证、授权、加密等选项。 有关详细信息,请参阅以下主题:
迁移到 WCF
为什么从远程处理迁移到 WCF?
.NET 远程处理是一种旧产品。 如 .NET 远程处理中所述,它被视为旧产品,不建议用于新开发。 对于新的和现有的应用程序,建议使用 WCF 或 ASP.NET Web API。
WCF 使用跨平台标准。 WCF 设计时考虑到跨平台互作性,并支持许多行业标准(SOAP、WS-Security、WS-Trust 等)。 WCF 服务可以与在 Windows 以外的作系统上运行的客户端进行互作。 远程处理主要用于在 Windows作系统上使用 .NET Framework 运行服务器和客户端应用程序的环境。
WCF 具有内置安全性。 WCF 设计时考虑到安全性,并提供许多用于身份验证、传输级别安全性、消息级别安全性等选项。远程处理旨在使应用程序易于互作,但在非受信任的环境中并不设计安全。 WCF 设计用于在受信任和非受信任的环境中工作。
迁移建议
下面是从 .NET 远程处理迁移到 WCF 的建议步骤:
创建服务协定。 定义服务接口类型,并使用 [ServiceContract] 属性标记它们。标记允许客户端使用 [OperationContract] 调用的所有方法。
创建数据协定。 定义将在服务器和客户端之间交换的数据类型,并使用 [DataContract] 属性标记它们。 标记客户端将允许用于 [DataMember] 的所有字段和属性。
创建错误协定(可选)。 创建在遇到错误时将在服务器和客户端之间交换的类型。 使用 [DataContract] 和 [DataMember] 标记这些类型,使其可序列化。 对于标记为 [OperationContract] 的所有服务操作,还可将其标记为 [FaultContract],以指示它们可能会返回哪些错误。
配置和托管服务。 创建服务协定后,下一步是配置绑定以在终结点上公开服务。 有关详细信息,请参阅 终结点:地址、绑定和协定。
将远程处理应用程序迁移到 WCF 后,删除 .NET 远程处理上的依赖项仍然很重要。 这可确保从应用程序中删除任何远程处理漏洞。 这些步骤包括:
停止使用 MarshalByRefObject。 MarshalByRefObject 类型仅用于远程处理,WCF 不使用。 应删除或更改子类 MarshalByRefObject 的任何应用程序类型。
停止使用 [Serializable] 和 ISerializable。 [Serializable] 属性和 ISerializable 接口最初用于在受信任的环境中序列化类型,并为远程处理所用。 WCF 序列化依赖于标记为 [DataContract] 和 [DataMember] 的类型。 应用程序使用的数据类型应修改为使用 [DataContract],而不是使用 ISerializable 或 [Serializable]。
迁移方案
现在,让我们了解如何在 WCF 中完成以下常见远程处理方案:
服务器将对象按值返回到客户端
服务器按引用返回对象到客户端
客户端按值向服务器发送对象
注释
WCF 中不允许将对象逐一引用从客户端发送到服务器。
阅读这些方案后,假定 .NET 远程处理的基线接口将如下面示例所示。 此处的 .NET 远程处理实现并不重要,因为我们只想说明如何使用 WCF 实现等效功能。
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 中使用双工服务可实现需要在客户端和服务器间来回会话的方案。 有关详细信息,请参阅双工服务。
概要
.NET 远程处理是一个通信框架,旨在仅在完全受信任的环境中使用。 它是一种旧产品,仅支持向后兼容性。 它不应用于生成新应用程序。 相反,WCF 设计时考虑到了安全性,建议用于新的和现有的应用程序。 Microsoft建议迁移现有的远程处理应用程序以改用 WCF 或 ASP.NET Web API。