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

创建和消费简单 WCF 服务而不使用配置文件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (25投票s)

2013年8月25日

CPOL

5分钟阅读

viewsIcon

89695

downloadIcon

4966

本文介绍了如何根据提供的参数创建简单的WCF服务并托管它,以及如何创建客户端来使用此服务。

引言

本文介绍了如何根据指定的绑定和指定的端口创建简单的WCF服务宿主。所有这些都可以动态完成。

背景

通常,我们在开发WCF服务时,习惯使用配置文件(根据环境不同,可能是web.config/app.config)。使用配置文件确实是一个非常好的主意。这一切看起来都很自动化。而且使用配置文件是默认的做事方式。

使用代码 

在本文中,我使用了三个项目

  • WCF 服务 (WCFMathLib.dll):实际的服务逻辑,它定义了一个服务契约接口(Service Contract)、操作契约(OperationContract)并实现它们,然后向外界公开几个函数供使用。
  • 宿主应用程序 (ConWCFMathHost.exe):定义了根据命令行提供的参数来宿主所述服务的逻辑。
  • 客户端应用程序 (ConWCFMathClient.exe):将使用此服务的客户端应用程序。

第一部分:WCF 服务 (WCFMathLib.dll)

实现业务逻辑的实际服务。它定义了一个服务契约(ServiceContract)以及服务提供的一些操作,如下所示。我们称之为IMathInterface.cs

要创建此项目,您可以从项目向导选项中选择一个类库项目。我们将其命名为“WCFMathLib”。它已经包含了一个文件Class1.cs,将该文件重命名为MathService.cs,再添加一个文件(IMathService.cs)来定义接口,尽管您也可以在此文件中定义接口。下面是两个文件的列表。

服务契约定义

// IMathInterface.cs 
[ServiceContract]
public interface IMathService
{
    [OperationContract]
    double AddNumber(double dblX, double dblY);
    [OperationContract]
    double SubtractNumber(double dblX, double dblY);
    [OperationContract]
    double MultiplyNumber(double dblX, double dblY);
    [OperationContract]
    double DivideNumber(double dblX, double dblY);
}

服务契约实现

// MathInterface.cs 
public class MathService : IMathService
{
    public double AddNumber(double dblX, double dblY)
    {
        return (dblX + dblY);
    }

    public double SubtractNumber(double dblX, double dblY)
    {
        return (dblX - dblY);
    }

    public double MultiplyNumber(double dblX, double dblY)
    {
        return (dblX * dblY);
    }

    public double DivideNumber(double dblX, double dblY)
    {
        return ((dblY == 0 ) ? 0 : dblX / dblY);
    }
}
ServiceContract 属性

服务契约:描述了哪些相关操作可以绑定在一起作为一个单一的功能单元,供客户端在服务上执行。

OperationContract 属性

操作契约指定该操作由服务公开;服务定义了操作的参数和返回类型。正如我们所见,这里没有什么特别之处,只是一个服务契约定义及其实现。

第二部分:WCF 服务宿主 (ConWCFMathHost.exe)

宿主应用程序被开发为基于控制台的应用程序,它根据提供的参数来宿主我们的服务(WCFMathLib.MathService)。参数的提供形式如下。它接受两个参数:ConWCFMathHost.exe TCP/HTTP portAdr <return>。

  • 参数 1:绑定的可接受值为 HTTP 和 TCP。
  • 参数 2:端口号,一个整数值,指定端口值。

使用 HTTP 绑定宿主服务

private static void StartHTTPService( int nPort)
{
    string strAdr = "https://:" + nPort.ToString() + "/MathService";
    try
    {
        Uri adrbase = new Uri(strAdr);
        m_svcHost = new ServiceHost(typeof(MathService), adrbase);
                                
        ServiceMetadataBehavior mBehave = new ServiceMetadataBehavior();
        m_svcHost.Description.Behaviors.Add(mBehave);
        m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange), 
          MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
                
        BasicHttpBinding httpb = new BasicHttpBinding();
        m_svcHost.AddServiceEndpoint(typeof(IMathService), httpb, strAdr);
        m_svcHost.Open();
        Console.WriteLine("\n\nService is Running as >> " + strAdr );
    }
    catch (Exception eX)
    {
        m_svcHost = null;
        Console.WriteLine("Service can not be started as >> [" + 
          strAdr + "] \n\nError Message [" + eX.Message + "]");
    }
} 

使用 TCP 绑定宿主服务

private static void StartTCPService(int nPort)
{
    string strAdr = "net.tcp://:" + nPort.ToString() + "/MathService/";
    try
    {
        Uri adrbase = new Uri(strAdr);
        m_svcHost = new ServiceHost(typeof(MathService), adrbase);
        NetTcpBinding tcpb = new NetTcpBinding();
 
        ServiceMetadataBehavior mBehave = new ServiceMetadataBehavior();
        m_svcHost.Description.Behaviors.Add(mBehave);
        m_svcHost.AddServiceEndpoint(typeof(IMetadataExchange), 
          MetadataExchangeBindings.CreateMexTcpBinding(), "mex");
                
        m_svcHost.AddServiceEndpoint(typeof(IMathService), tcpb, strAdr);
        m_svcHost.Open();
        Console.WriteLine("\n\nService is Running as >> " + strAdr );
    }
    catch (Exception eX)
    {
        m_svcHost = null;
        Console.WriteLine("Service can not be started as >> [" + 
          strAdr + "] \n\nError Message [" + eX.Message + "]");
    }
} 
 

由于没有配置文件,我们所要做的(请看粗体部分)基本上是以下几件事:

代码描述

创建所需的绑定(TCP/HTTP)

ServiceMetadataBehavior 控制服务元数据和相关信息的发布。虽然我们可以省略粗体部分,但就本项目而言,我们将手动创建一切,但始终添加一个IMetadataExchange端点到服务是一个好主意,因为它公开了用于返回服务元数据的方法。这在使用SVCUtil.exe工具从服务生成代理类和配置文件时非常有用

第三部分:客户端 (ConWCFMathClient.exe)

客户端应用程序被开发为基于控制台的应用程序,通过命令行以以下形式传递参数。您需要在命令行中提供六个参数

ConWCFMathClient <Host Machine> <TCP/HTTP> <Port#> <ADD/SUB/MUL/DIV> Num1 Num2
  • 参数 1:托管服务的计算机的名称/IP 地址。
  • 参数 2:托管服务的绑定,可接受的值是HTTP 和 TCP
  • 参数 3:端口号,一个整数值,指定端口值。
  • 参数 4:操作代码(可接受的值是ADD, SUB, MUL, DIV)。
  • 参数 5:操作数 1,操作的操作数 1 值。
  • 参数 6:操作数 2,操作的操作数 2 值。

导入接口定义

您可以定义接口,或者直接从服务中导入IMathService.cs文件。这只是定义了接口,以避免在编译客户端项目时出现错误。

// Listing of IMathService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
 
namespace ConWCFMathClient
{
    [ServiceContract]
    public interface IMathService
    {
        [OperationContract]
        double AddNumber(double dblX, double dblY);
        [OperationContract]
        double SubtractNumber(double dblX, double dblY);
        [OperationContract]
        double MultiplyNumber(double dblX, double dblY);
        [OperationContract]
        double DivideNumber(double dblX, double dblY);
    }
}    

调用服务的方法

创建了一个函数来调用服务的方法,在验证所有参数后将它们传递给方法。这是该方法

private static void Evaluate(string strServer, string strBinding, 
          int nPort, string strOper, double dblVal1, double dblVal2)
{
    ChannelFactory<IMathService> channelFactory = null; 
    EndpointAddress ep = null;

    string strEPAdr = "http://" + strServer + 
           ":" + nPort.ToString() + "/MathService";
    try
    {
        switch (strBinding)
        {
            case "TCP":
                NetTcpBinding tcpb = new NetTcpBinding();
                channelFactory = new ChannelFactory<IMathService>(tcpb);
                
                // End Point Address
                strEPAdr = "net.tcp://" + strServer + ":" + 
                             nPort.ToString() + "/MathService";
                break;

            case "HTTP":
                BasicHttpBinding httpb = new BasicHttpBinding();
                channelFactory = new ChannelFactory<IMathService>(httpb);
                
                // End Point Address
                strEPAdr = "http://" + strServer + ":" + 
                          nPort.ToString() + "/MathService";
                break;
        }
                
        // Create End Point
        ep = new EndpointAddress(strEPAdr);
                
        // Create Channel
        IMathService mathSvcObj = channelFactory.CreateChannel(ep);
        double dblResult = 0;

        // Call Methods
        switch (strOper)
        {
            case "ADD": dblResult = mathSvcObj.AddNumber(dblVal1, dblVal2); break;
            case "SUB": dblResult = mathSvcObj.SubtractNumber(dblVal1, dblVal2); break;
            case "MUL": dblResult = mathSvcObj.MultiplyNumber(dblVal1, dblVal2); break;
            case "DIV": dblResult = mathSvcObj.DivideNumber(dblVal1, dblVal2); break;
        }
                
        //  Display Results.
        Console.WriteLine("Operation {0} ", strOper );
        Console.WriteLine("Operand 1 {0} ", dblVal1.ToString ( "F2"));
        Console.WriteLine("Operand 2 {0} ", dblVal2.ToString("F2"));
        Console.WriteLine("Result {0} ", dblResult.ToString("F2"));
        channelFactory.Close();
    }
    catch (Exception eX)
    {
         // Something unexpected happended .. 
         Console.WriteLine("Error while performing operation [" + 
           eX.Message + "] \n\n Inner Exception [" + eX.InnerException + "]");
    }
}
 

代码描述

在调用任何方法之前,您需要做两件事:创建一个通道工厂对象,使用指定的绑定,通过

对于 HTTP 绑定
BasicHttpBinding httpb = new BasicHttpBinding();
channelFactory = new ChannelFactory<IMathService>(httpb); 
对于 TCP 绑定
NetTcpBinding tcpb = new NetTcpBinding();
channelFactory = new ChannelFactory<IMathService>(tcpb);                       
HTTP 绑定的端点地址
strEPAdr = "http://" + strServer + ":" + nPort.ToString() + "/MathService";
TCP 绑定的端点地址
strEPadr = strEPAdr = "net.tcp://" + strServer + ":" + nPort.ToString() + "/MathService";   

通过指定的端点地址创建通道

// Create End Point
ep = new EndpointAddress(strEPAdr);
                
// Create Channel
IMathService mathSvcObj = channelFactory.CreateChannel(ep);                    
   

通过通道调用方法并显示输出

double dblResult = 0;

// Call Methods
switch (strOper)
{
    case "ADD": dblResult = mathSvcObj.AddNumber(dblVal1, dblVal2); break;
    case "SUB": dblResult = mathSvcObj.SubtractNumber(dblVal1, dblVal2); break;
    case "MUL": dblResult = mathSvcObj.MultiplyNumber(dblVal1, dblVal2); break;
    case "DIV": dblResult = mathSvcObj.DivideNumber(dblVal1, dblVal2); break;
}
                
//  Display Results.
Console.WriteLine("Operation {0} ", strOper );
Console.WriteLine("Operand 1 {0} ", dblVal1.ToString ( "F2"));
Console.WriteLine("Operand 2 {0} ", dblVal2.ToString("F2"));
Console.WriteLine("Result {0} ", dblResult.ToString("F2"));

// Close channel        
channelFactory.Close(); 

关注点

  • 无配置文件。
  • 未使用 SvcUtil 生成代理。

输出

使用 TCP 端点运行服务

客户端

使用 HTTP 端点运行服务

注意:在 HTTP 模式下运行服务时,您需要以管理员模式打开命令提示符。

客户端

关注点   

在本文中,我用非常简单的方式描述了所有三种契约类型,也解释了每种契约类型的适用性。何时应该使用。希望您会喜欢阅读它,就像我喜欢写它一样。

非常感谢您的阅读。

历史 

初始版本。

© . All rights reserved.