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

管理 WCF 服务操作顺序的初学者教程

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2013年3月25日

CPOL

6分钟阅读

viewsIcon

23101

downloadIcon

296

本文讨论了 WCF 服务中操作序列的管理。

引言

在本文中,我们将讨论如何在 WCF 服务中强制和管理序列操作。我们将看到如何配置 WCF 服务,使其以特定顺序调用方法,并且调用超出该顺序的函数将导致异常。

背景

我们已经了解了如何根据应用程序需求配置 WCF 服务实例(WCF 实例管理初学者教程[^])。现在,如果我们将 WCF 服务配置为通过将 InstanceContextMode 设置为 PerSession 来以有状态模式工作,则会引出另一个问题。问题是如何配置服务,以便对这个有状态 WCF 服务强制执行特定操作顺序。

为什么这个顺序很重要?顺序很重要,因为如果我们的服务为每个客户端维护状态,那么当实例创建时,我们可能需要进行一些资源获取的初始化。服务的其他函数将使用这些资源来执行其操作。我们还希望确保在调用特定函数时(可能是函数序列中的最后一个函数)完成资源清理。

现在我们可以很好地定义一个约定,即客户端应该首先调用某个函数,然后调用服务的其他函数,以便初始函数负责所有初始化,最终函数负责资源清理。但是,如果任何客户端在没有调用初始化函数或在调用清理函数之后调用某个中间函数怎么办?这可能会导致一些不一致的行为。WCF 为我们提供了一种强制执行此约定的方法,并确保如果任何客户端在没有调用初始化函数或在调用清理函数之后调用任何函数,则客户端调用将失败。

理解 SessionMode 属性

在查看特定序列配置之前,让我们了解一个非常重要的属性 SessionModeSessionMode 属性作为 ServiceContract 属性的一部分,它指定服务将如何实现会话。SessionMode 可以设置为三个可能的值

  1. 允许
  2. 不允许
  3. 必需

Allowed 指定如果所使用的协议(即绑定)支持会话并且 InstanceContextMode 指定为 PerSession,则服务将允许会话。

NotAllowed 指定即使绑定支持会话和/或 InstanceContextMode 指定为 PerSession,服务也不允许会话。

Required 选项指定服务将支持会话,并且底层绑定/协议必须支持会话。

配置操作序列

如上所述,如果我们需要在 WCF 服务中强制执行操作顺序,WCF 提供了一种将特定函数标记为应首先调用以启动实例创建过程的函数的方法。这可以通过将 OperationContractIsInitiating 属性设置为 true 来完成。

同样,要将函数标记为操作序列中的最终函数,我们需要将 OperationContractIsTerminating 属性设置为 true。此函数的 IsInitiating 属性应设置为 false

所有其他函数都应将 OperationContractIsInitiating 属性设置为 false。只有在调用了标记为 IsInitiating 为 true 的函数且尚未调用标记为 IsTerminating 为 true 的函数时,才能调用所有这些函数。否则,调用此函数将导致异常。

使用代码

现在让我们创建一个示例服务来实际看看。

创建测试服务

让我们创建一个具有三个操作的简单服务。将有一个函数 (Function1) 启动实例创建,一个函数 (Function3) 终止实例。还将有一个函数只能在调用 Function1 且尚未调用 Function3 时调用。否则调用此函数将导致异常。

此外,让我们将 ServiceContractSessionMode 属性设置为 SessionMode.Required,以便此服务不能在无会话场景/绑定中使用。让我们看看 ServiceContractOperationContact 来实现同样的目的。

[ServiceContract(SessionMode=SessionMode.Required)]
public interface ISampleService
{
    // This function will INITIATE the session
    [OperationContract(IsInitiating=true)]
    string Function1();

    // This function will work only if session has been INITIATED and not yet TERMINATED
    [OperationContract(IsInitiating = false)]
    string Function2();

    // This function will TERMINATE the session
    [OperationContract(IsInitiating = false, IsTerminating=true)]
    string Function3();
}

服务实现将把 ServiceBehaviorInstanceContextMode 属性设置为 InstanceContextMode.PerSession。函数将简单地返回一些虚拟字符串以显示已调用哪个方法。这将确保为每个客户端创建一个服务实例。

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class SampleService : ISampleService
{
    public string Function1()
    {
       return "This function will INITIATE the session";
    }

    public string Function2()
    {
        return "This function will work when Session has been INITIATED and not yet TERMINATED";
    }

    public string Function3()
    {
        return "This function will TERMINATE the session";
    }
}

创建宿主

现在,由于 ServiceContractSessionMode 属性设置为 Required,我们必须选择支持会话的绑定。因此,让我们在控制台应用程序中自宿主此服务,该应用程序将通过 TCP 公开服务。由于 netTcpBinding 支持会话,因此使用此绑定宿主此服务将支持会话。

创建的终结点的详细信息如下

  • 地址: net.tcp:///TestService
  • 绑定: netTcpBinding
  • 契约: SampleServiceNamespace.SampleService

宿主服务的代码将如下所示

static void Main(string[] args)
{
    using (ServiceHost host = new ServiceHost(typeof(SampleServiceNamespace.SampleService)))
    {
        host.Open();

        Console.WriteLine("Service up and running at:");
        foreach (var ea in host.Description.Endpoints)
        {
            Console.WriteLine(ea.Address);
        }

        Console.ReadLine();
        host.Close();
    }
}

我们可以运行宿主以访问我们的测试服务。运行宿主将如下所示

 

注意:请参阅宿主应用程序的 app.config 文件以查看完整配置。

测试客户端和测试操作序列

现在我们将创建一个简单的测试客户端,它将调用 WCF 服务函数。首先,让我们尝试直接调用 Function2。现在,由于 Function1 被标记为 IsInitiating=true,直接调用 Function2 将导致异常。

static void Main(string[] args)
{
    using (ServiceReference1.SampleServiceClient client = new ServiceReference1.SampleServiceClient())
    {
        // let try to call the function that required session to be present
        Console.WriteLine("Scenario 1: let try to call the function that required session to be present");
        try
        {   
            string result = client.Function2();
            Console.WriteLine(result);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

如果我们尝试调用 Function3 也会出现同样的情况,因为 Function3 被标记为 IsTerminating=true,在不调用 Function1 的情况下调用它也会导致异常。

static void Main(string[] args)
{
    using (ServiceReference1.SampleServiceClient client = new ServiceReference1.SampleServiceClient())
    {
        // Let us now try to call the terminating function without initating session       
        Console.WriteLine("Scenario 2: Let us now try to call the terminating function without initating session");
        try
        {
            string result = client.Function3();
            Console.WriteLine(result);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

现在让我们尝试有效的操作序列。我们将调用 Function1(标记为 IsInitiating=true),然后我们将调用 Function2(应在 Function1 之后和 Function3 之前调用),最后我们将调用 Function3(标记为 IsTerminating=false)。

static void Main(string[] args)
{
    using (ServiceReference1.SampleServiceClient client = new ServiceReference1.SampleServiceClient())
    {
        // Now let us call the functions in proper order        
        Console.WriteLine("Scenario 3:  Now let us call the functions in proper order");
        try
        {
            string result1 = client.Function1();
            Console.WriteLine(result1);

            string result2 = client.Function2();
            Console.WriteLine(result2);

            string result3 = client.Function3();
            Console.WriteLine(result3);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

现在最后,让我们尝试在 Function3 调用完成后调用 Function2。由于 Function3 已标记为 IsTerminating=true,在此之后调用 Function2 将导致异常。

static void Main(string[] args)
{
    using (ServiceReference1.SampleServiceClient client = new ServiceReference1.SampleServiceClient())
    {
        // Now let us call the function2 again when the session has been terminated        
        Console.WriteLine("Scenario 4:  Now let us call the function2 again when the session has been terminated");
        try
        {
            string result1 = client.Function1();
            Console.WriteLine(result1);

            string result2 = client.Function2();
            Console.WriteLine(result2);

            string result3 = client.Function3();
            Console.WriteLine(result3);

            // This should now give us the exception
            string result2_alt = client.Function2();
            Console.WriteLine(result2_alt);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

因此,通过设置 IsInitatingIsTerminating 属性,我们强制执行了 WCF 服务中的操作序列。

关于持久 WCF 服务的说明

WCF 还提供了一个将服务标记为持久服务的选项。持久服务是一种将客户端会话信息持久存储在数据库等永久存储中的服务,这样即使服务类实例关闭,一旦新的同一客户端实例出现,它也可以重新获取状态信息。这在我们需要长时间运行有状态服务时非常有用。

可以将服务标记为 DurableService 以使其成为持久服务。函数也应标记为 DurableOperation 以使其成为持久操作。DurableOperation 支持类似类型的属性来维护操作顺序。对于持久操作,将 CanCreateInstance 设置为 true 将使该操作成为序列中的第一个操作,将 CompleteInstance 设置为 true 将使该操作成为序列中的终止操作。

注意:这是对持久服务的一个非常高级的概述。创建持久服务需要在服务端进行更多配置。

关注点

在本文中,我们了解了如何在有状态 WCF 服务中强制执行操作的顺序/序列。本文是从初学者的角度编写的(但需要了解一些 WCF 服务和实例模式的知识)。我希望这能提供一些信息。

历史

  • 2013 年 3 月 25 日:第一个版本。
© . All rights reserved.