65.9K
CodeProject 正在变化。 阅读更多。
Home

WCF 与 Web Services 和 .NET Remoting 的比较

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.43/5 (48投票s)

2009年12月1日

CPOL

10分钟阅读

viewsIcon

137345

downloadIcon

6

本文旨在探讨 WCF 与 Web Services 和 .NET Remoting 之间的区别。

引言

Windows Communication Foundation (WCF) 是一种结合了 XML Web Services 和 .NET Remoting 功能以及一些改进的技术。本文将对 WCF 与 Web Services 和 .NET Remoting 进行比较。

互操作性

.NET Remoting 在同构环境中工作。消费应用程序也必须是 .NET 应用程序。

Web Services 独立于平台和语言,不关心消费应用程序。但它受 HTTP 协议的限制。就性能而言,它们被认为是缓慢的。

WCF 可以在 Windows 环境下托管,但它可以被不同语言和不同平台的客户端利用。

协议利用

.NET Remoting 应用程序可以使用 HTTP、TCP 和 SMTP 协议。

XML Web Services 使用 SOAP,即通过 HTTP 的 XML。

WCF 除了这些协议外,还可以使用命名管道和 MSMQ。

会话管理

Web Services 默认不保留会话值。每次调用都会创建一个新的 Web Service 实例。`EnableSession` 属性用于每个 Web 方法以实现会话。默认情况下,它是 false,所以我们需要显式定义它。此属性允许 Web Service 直接从 `HttpContext.Current.Session` 属性访问会话值。例如

[WebMethod(EnableSession = true)] 
public string KeepSessionValue(bool SendValue) 
{ 
    string strSessionValue; 
    if (SendValue) 
    {
        rSessionValue = Session["newValue"].ToString(); 
    } 
    else 
    {
        if (null==Session["oldValue"]) 
            Session["oldValue"] = System.DateTime.Now.ToLongTimeString(); 
        strSessionValue = Session["oldValue"].ToString(); 
    }
    return strSessionValue; 
}

Web Services 的问题在于 Web Service 代理不保留值,因此如果只设置 `EnableSession`,它将不起作用。客户端需要使用 `System.Net.CookieContainer` 类来保留 cookie。在以下示例中,如果会话是新的,则会创建一个新的 `CookieContainer` 实例;否则,它将保留原始 cookie 值。

protected void button2_Click(object sender, EventArgs e) 
{ 
    Service1 obj = new Service1(); 
    CookieContainer objContainer = new CookieContainer(); 
    if (null == Session["cookieContainer"]) 
    { 
        obj.CookieContainer = objContainer; 
        Session["cookieContainer"] = objContainer; 
    } 
    else 
    { 
        objContainer = (CookieContainer)Session["cookieContainer"]; 
        obj.CookieContainer = objContainer; 
    } 
    Label1.Text = obj.KeepSessionValue(false); 
}

WCF 中,会话是从客户端应用程序显式定义和关闭的,这与 ASP.NET 应用程序中会话由服务器启动不同。WCF 中有两种会话:可靠会话负责事务,另一种会话类型保留对象的会话值。

我们选择支持会话的端点绑定。例如,`BasicHttpBinding` 不支持会话。首先,设置 `ServiceContract` 属性的 `SessionMode` 属性。它可以是

  • SessionMode.Required:会话是必需实现的。
  • SessionMode.Allowed:会话允许实现。
  • SessionMode.NotAllowed:不允许实现会话。它将不保留会话值。

默认情况下,它设置为 `Allowed`,因此我们不需要将其设置为 `Allowed`。

服务行为可以设置为 `InstanceContextMode.PerSession`、`InstanceContextMode.PerCall`、`InstanceContextMode.Single`。`InstanceContextMode` 用于定义每个调用的实例数量。

  • PerSession 为每个会话设置一个新实例。
  • PerCall 即使对于单个用户,也为每次调用设置一个新实例。
  • Single 为所有客户端的所有调用设置一个实例。它充当单例;如果实例不存在,则会创建它。

在下面的示例中,我将使用一个计数器变量在每次调用时增加其值。如果存在,它将保留以前的值,并增加计数器。

接口将把 `SessionMode` 定义为 `Allowed`。

[ServiceContract (SessionMode=SessionMode.Allowed)] 
public interface IService1 
{
    [OperationContract()] 
    int IncreaseCounter(); 
}

服务将如下实现此功能

public class Service1 : IService1 
{ 
    int Acounter; 

    public int IncreaseCounter() 
    {
        return Acounter++; 
    }
}

在客户端,它将在每次调用时增加计数器

protected void Button1_Click(object sender, EventArgs e) 
{ 
    ServiceReference2.Service1Client obj1 = 
               new ServiceReference2.Service1Client(); 
    Label1.Text = obj1.IncreaseCounter(); 
    Label1.Text += obj1.IncreaseCounter(); 
    Label1.Text += obj1.IncreaseCounter(); 
}

我们可以通过将 `SessionMode` 设置为 `SessionMode.NotAllowed` 来验证此状态管理。

当用户代理关闭时,该用户的会话终止;如果客户端想要显式关闭代理,则可以使用 `Dispose()` 方法。

.NET Remoting 中,状态管理有两种方式

  1. 客户端激活对象
  2. 服务器激活对象周知对象

周知对象进一步分为单次调用 (SingleCall) 和单例 (Singleton)

  • 在 SingleCall 中,客户端的每个请求都会创建一个新的远程对象,并且没有状态管理。
  • 在 Singleton 中,顾名思义,对象只实例化一次,并为所有客户端请求共享状态。

客户端激活对象 (CAO) 是有状态对象,并为单个客户端保留值。为客户端创建的实例变量在检索时保留其值。

我们将创建一个由客户端使用的远程处理类、一个客户端应用程序和一个用于客户端充当客户端和远程应用程序之间桥梁的监听器(服务器应用程序)。

对于我们的示例,我们将使用控制台应用程序来创建远程处理类并调用它。

  1. 创建一个类文件并将其命名为 *ARemotingClass.cs*。以下是其代码
  2. using System; 
    namespace ARemotingNameSpace 
    { 
        public class ARemotingClass : MarshalByRefObject 
        { 
            public ARemotingClass() 
            { 
                Console.WriteLine("Hi i m a remoting class"); 
            } 
            public int Add(int i, int j) 
            { 
                return i+j; 
            } 
            public int Multiply(int i, int j) 
            { 
                return i * j; 
            } 
        } 
    }
  3. 运行 *C:\WINDOWS\Microsoft.NET\Framework\<<您的已安装框架>>\csc /t:library /debug /r:System.Runtime.Remoting.dll ARemotingClass.cs*。
  4. 它将在当前目录中为 C# 文件生成一个 DLL 文件。

  5. 为服务器创建一个控制台应用程序以实现远程处理。向其添加上述 DLL 引用。添加 `System.Runtime.Remoting.Channels.Http` 的引用。此应用程序将充当主机,侦听我们在代码中定义的任何端口号。我们可以在 .NET Remoting 应用程序中定义 TCP 或 HTTP 通道。然后,我们将注册该通道。要在服务器上注册客户端激活对象,使用 `RemotingConfiguration.RegisterActivatedServiceType`。
  6. 以下是类文件的代码

    using System;
    using System.Runtime.Remoting;
    using System.Runtime.Remoting.Channels;
    using System.Runtime.Remoting.Channels.Http;
    using ARemotingNameSpace;
    namespace ARemotingNameSpace
    {
        public class ServerClass
        {
            public static void Main(string[] args)
            {
                ChannelServices.RegisterChannel(new HttpChannel(90));
                RemotingConfiguration.ApplicationName = "Calculate";
                RemotingConfiguration.RegisterActivatedServiceType(typeof(ARemotingClass));
                System.Console.WriteLine("Press enter key to close");
                System.Console.ReadLine();
            }
        }
    }
  7. 创建一个 Web 应用程序作为客户端应用程序来使用此远程处理服务。此应用程序将实例化并调用客户端激活对象。几乎以类似的方式,我们将注册一个客户端通道。它应该是 TCP 或 HTTP,具体取决于服务器通道。
  8. 然后,我们将使用 `RemotingConfiguration.RegisterActivatedClientType` 注册可远程处理的类。

    using System;
    using System.Runtime.Remoting;
    using System.Runtime.Remoting.Channels;
    using System.Runtime.Remoting.Channels.Http;
    using ARemotingNameSpace;
    public partial class _Default : System.Web.UI.Page
    {
        ARemotingClass objRemotingClass = new ARemotingClass();
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                HttpChannel ObjChannel = new HttpChannel();
                ChannelServices.RegisterChannel(ObjChannel);
                RemotingConfiguration.RegisterActivatedClientType(typeof(ARemotingClass),
                    "https://:90/Calculate");
            }
        }
        protected void Button1_Click(object sender, EventArgs e)
        {
            Label1.Text = "Result of addition is " + objRemotingClass.Add(2, 2) +
                " and result of multiplication is " + objRemotingClass.Multiply(5, 2);
        }
    }

    SingleCall 对象会丢失状态并被垃圾回收。对于 Singleton 和客户端激活对象,我们可以显式实现基于租约的生命周期。.NET 有一个内置的基于租约的管理器;它在对象不再使用时将其标记为删除。可以通过代码和配置文件来实现。

异常处理

在 ASP.NET Web Services 中,未处理的异常以 XML 格式的 SOAP <Fault> 元素返回给客户端。Web Services 可以抛出异常,并且一旦在客户端反序列化,就可以看到异常消息。这可以通过使用为此目的提供的 `SoapException` 类来抛出和捕获 SOAP 异常。

在 Web Service 内部,我们通过提及错误消息、错误类型、参与者(导致错误的页面)和内部异常来抛出异常详细信息。例如

string strError = "a custom error message"; 
SoapException ex = 
  new SoapException(strError,SoapException.ClientFaultCode); 
throw ex;

在客户端,可以通过这种方式访问它

catch (SoapException soapExc) { 
    lblExceptionMessage.Text = soapExc.Message; 
}

与 ASP.NET 应用程序不同,*Global.asax* 不能用于捕获 Web Services 中的应用程序级别错误。

WCF 允许我们隐藏错误详细信息并仅显示所需信息。它以两种方式处理错误:异常对象和 SOAP 故障。

故障进一步分类为

  • 已声明的
  • 未声明的

在已声明的故障中,操作使用 `[FaultContract]` 属性声明。

以下是 WCF 中使用 `FaultContract` 的示例

  1. 在接口内部,定义一个 `DataContract`,它将用作 `FaultContract` 的类型。这里,我定义了一个自定义类 `ConnectionError`。
  2. [DataContract] 
    public class ConnectionError 
    { 
        [DataMember] 
        public string strErrorMessage="Could not connect to database"; 
    }
  3. 定义一个具有上述定义的类型 `FaultContract` 属性的操作契约。
  4. public interface IService1
    {
        [FaultContract(typeof(ConnectionErrorFault))]
        [OperationContract]
        DataTable GetDataTable();
    }

    在 `DataContract` 内部,定义一个属性,用于设置异常消息。

    [DataContract]
    public class ConnectionErrorFault
    {
        [DataMember]
        private string m_Reason = string.Empty;
        public string Reason
        {
            get
            {
                return m_Reason;
            }
            set
            {
                m_Reason = value;
            }
        }
    }
  5. 在 *.svc* 文件内部,实现此函数并抛出 `ConnectionErrorFault` 类型的异常。
  6. public DataTable GetDataTable() 
    { 
        try 
        { 
            DataSet ds; 
            string connectionString = "Persist Security Info=False; 
            Integrated Security=SSPI;database=test;server=mypc"; 
            SqlConnection myConnection = new SqlConnection(connectionString); 
            // Create a SqlDataAdapter. 
            SqlDataAdapter myAdapter = new SqlDataAdapter(); 
            myAdapter.TableMappings.Add("Table", "table1"); 
            myConnection.Open(); 
            SqlCommand myCommand = new SqlCommand("SELECT * FROM table1", myConnection); 
            myCommand.CommandType = CommandType.Text; 
            myAdapter.SelectCommand = myCommand; 
            ds = new DataSet("table1"); 
            myAdapter.Fill(ds); 
            return ds.Tables[0]; 
        } 
        catch (Exception) 
        { 
            ConnectionErrorFault fault=new ConnectionErrorFault(); 
            fault.Reason = "Could not connect to database"; 
            throw new FaultException<ConnectionErrorFault>(fault, fault.Reason); 
        }
    }

    此代码将生成 `ConnectionErrorFault` 类型的异常;它将显示自定义消息。它将隐藏其他不必要的详细信息。

  7. 在客户端代码内部,使用相同的故障异常来显示适当的消息。
  8. try
    { 
        ServiceReference2.Service1Client obj1 = new ServiceReference2.Service1Client(); 
        GridView1.DataSource = obj1.GetDataTable(); 
        GridView1.DataBind(); 
    } 
    catch (FaultException<connectionerrorfault> ex) 
    { 
        Label1.Text= ex.Reason.ToString(); 
    }

    异常的 `Reason` 属性用于在客户端获取值。

Remoting 中的异常默认以非 XML 格式存在,因此可以使用普通的 .NET 方式捕获它们。出于测试目的,在我的示例中,我从 Remoting 类抛出一个字符串异常并在客户端捕获它。这是一个简单的异常代码

public string AnException() 
{ 
    throw new Exception("an error is thrown back"); 
}

因此,当我们在客户端调用它时,它将在客户端的 try catch 部分捕获异常,并显示消息

try 
{ 
    Label1.Text =objRemotingClass.AnException(); 
} 
catch (Exception exp) 
{ 
    Label1.Text += "<BR>" + exp.Message; 
}

.NET 还为我们提供了一种更好的方式来在 Remoting 应用程序中实现异常处理。我们可以通过覆盖 `GetObjectData` 方法并实现序列化来使用自定义异常。本文将不深入探讨其细节。

托管

.NET Remoting 应用程序的托管可以在 Windows Forms、控制台应用程序、Windows Services 和 IIS 中完成。如果我们在 IIS 上实现 Remoting,它仍然无法利用 IIS 内置的安全选项。

Web Services 通过将 ASMX 文件指向虚拟目录来托管在 IIS 中。

与 Web Services 和 .NET Remoting 不同,WCF 提供了更多托管选项。它将旧的托管选项与新的托管选项(如在 WAS 上托管)结合起来。WCF 应用程序还可以通过以下方式托管

  1. 自托管
  2. Windows 服务
  3. IIS 和 WAS

A. 自托管

顾名思义,WCF 服务将手动启动,并将运行到其设置的运行时间。这种方法仅适用于测试服务的目的。这可以通过使用控制台应用程序或 Windows Form 创建的进程来访问服务来实现。

通过调用主机的 `Open()` 和 `Close()` 方法,我们可以控制服务的执行时间。

例如,在 Windows Form 中,我们可以通过这种方式调用服务

public static void Main() 
{ 
   Uri AddressofService = new Uri ("https://:5850/"); 
   SerivceHost host = 
      new SerivceHost (typeof (WcfService1), AddressofService); 
   host.Open( ); 
   // 
   //business logic implementation 
   host.Close( ); //stop the wcf service. 
}

在这种托管格式中,我们必须实现自定义安全选项,并且不能利用 IIS 提供的内置安全性。

B. Windows 服务

WCF 服务可以由 Windows 服务托管,该服务将负责激活该服务。Windows 服务本身由安装程序文件安装。Windows 服务的 `Start()` 和 `Stop()` 方法用于控制 WCF 服务。

以下是通过 Windows 服务使用 WCF 服务的步骤

  1. 创建新的 Windows 服务。
  2. 添加对 System.ServiceModel 的引用。
  3. 添加对 WCF 服务的引用。
  4. 在 `OnStart` 事件内部,打开 WCF 服务。
  5. 在 `OnStop` 事件内部,停止服务。

以下是 Windows 服务的代码

using System.ServiceModel;
using WindowsService1.WCFServiceReference1;
namespace WindowsService1
{
    public partial class Service1 : ServiceBase
    {
        ServiceHost WCFHost;
        public Service1()
        {
            InitializeComponent();
        }
        protected override void OnStart(string[] args)
        {
            Type WCFService = typeof(Service1Client);
            WCFHost = new ServiceHost(WCFService);
            WCFHost.Open();
        }
        protected override void OnStop()
        {
            if (WCFHost != null)
                WCFHost.Close();
        }
    }
}
  1. 要添加安装程序文件,右键单击 Windows 服务设计器并选择 *添加安装程序*。
  2. 设置属性,例如启动类型:自动或手动。
  3. 要运行安装程序,请从 *安装和部署项目* 中添加一个安装程序项目。
  4. 将 Windows 服务添加为项目输出。
  5. 添加自定义操作。选择应用程序文件夹,然后选择 Windows 服务的主输出。

现在,您可以在编译并运行安装文件后,将 WCF 服务作为 Windows 服务安装。

使用 Windows 服务方法也有一些优点和缺点。一旦安装,其属性可以像 Windows 服务一样配置。它将自动启动,可以指定有权启动服务的用户等。另一方面,它需要在托管计算机上运行安装程序文件。它不提供像 IIS 那样的安全选项。此外,如果 Windows 服务由于用户权限而无法启动,则它将不允许 WCF 服务启动。

C. IIS 和 WAS

WCF 托管支持 IIS 5.1 及更高版本。Windows 2000 带有 5.0 版本,WCF 不能在 Win2000 中托管。如果我们在 IIS 中托管,我们不需要创建服务主机,因为这种托管需要 *.svc* 文件本身。

在使用服务时,我们还可以利用 IIS 中的内置安全性。要在 IIS 5.1 和 6.0 下托管,服务必须使用 HTTP 协议。

在这种情况下,所有会话级别的值都保存在内存中。IIS 会定期回收其控制下的应用程序域和进程。因此,服务在回收时可能会显示意外输出。此间隔时间在应用程序池设置下提及,并且回收选项可以自定义。

Windows 激活服务 (WAS) 是在 Windows Vista 和 Windows Longhorn 服务器机器中引入的。WAS 的发明提供了利用 TCP、命名管道和 MSMQ 等协议的功能。因此,通过 IIS 7.0,我们可以使用其提供的任何这些协议托管 WCF。

© . All rights reserved.