关于.NET:使用IErrorHandler和TCP消息安全性导致超时

Using IErrorHandler and TCP Message Security causes a timeout

我有一个带有自定义ISeviceBehavior的WCF服务,用于返回客户端的特定错误。当我使用TCP消息安全性启用此代码时,将收到服务超时。

下面您可以看到完整的客户端和服务器代码来重现错误。

服务器代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;

namespace TestWCFServer
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("SERVER");

            NetTcpBinding binding = new NetTcpBinding();
            binding.Security.Mode = SecurityMode.Message; //If you remove this line the code works!!!!

            Uri address = new Uri("net.tcp://localhost:8184/");

            // Create the ServiceHost.
            using (ServiceHost host = new ServiceHost(typeof(HelloWorldService)))
            {
                host.AddServiceEndpoint(typeof(IHelloWorldService), binding, address);

                host.Description.Behaviors.Add(new MyErrorhandlerBehavior());

                host.Open();

                Console.WriteLine("The service is ready at {0}", address);
                Console.WriteLine("Press  to stop the service.");
                Console.ReadLine();

                // Close the ServiceHost.
                host.Close();
            }
        }
    }



    [ServiceContract]
    public interface IHelloWorldService
    {
        [OperationContract]
        string SayHello(string name);
    }

    public class HelloWorldService : IHelloWorldService
    {
        public string SayHello(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

            return string.Format("Hello, {0}", name);
        }
    }

    class MyErrorhandlerBehavior : IServiceBehavior, IErrorHandler
    {
        #region IServiceBahvior
        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection endpoints, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher chanDisp in serviceHostBase.ChannelDispatchers)
            {
                chanDisp.ErrorHandlers.Add(this);
            }
        }

        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }
        #endregion

        #region IErrorHandler Members
        public bool HandleError(Exception error)
        {
            return true;
        }

        public void ProvideFault(Exception error,MessageVersion ver, ref Message msg)
        {
            FaultException fe = new FaultException(error.Message);
            MessageFault fault = fe.CreateMessageFault();
            msg = Message.CreateMessage(ver, fault,"net.tcp://localhost:8184/fault");
        }
        #endregion
    }
}

客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace TestWCFClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("CLIENT");

            try
            {
                NetTcpBinding binding = new NetTcpBinding();
                binding.Security.Mode = SecurityMode.Message; //If you remove this line the code works!!!!

                Uri address = new Uri("net.tcp://localhost:8184/");
                EndpointAddress endpoint = new EndpointAddress(address);

                HelloWorldServiceClient client = new HelloWorldServiceClient(binding, endpoint);

                Console.WriteLine("Calling client with a valid parameter...");
                Console.WriteLine(client.SayHello("Davide"));
                Console.WriteLine("OK");

                Console.WriteLine("Calling client with an invalid parameter...");
                Console.WriteLine(client.SayHello(null)); //This call causes the timeout when Security is set to Message
                Console.WriteLine("OK");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }


            Console.WriteLine("Press enter to exit");
            Console.ReadLine();
        }
    }


    [ServiceContract]
    public interface IHelloWorldService
    {
        [OperationContract]
        string SayHello(string name);
    }

    class HelloWorldServiceClient : System.ServiceModel.ClientBase, IHelloWorldService
    {
        public HelloWorldServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress address) :
            base(binding, address)
        { }

        public string SayHello(string name)
        {
            return base.Channel.SayHello(name);
        }
    }
}

如果我在客户机和服务器上删除binding.Security.Mode = SecurityMode.Message;行,异常就会被正确地转换,客户机可以毫无问题地看到它。

在wcf日志中,我看到以下消息:没有为具有"net.tcp://localhost:8184/fault"操作的消息指定签名消息部分。安全协议无法保护传出消息。

你知道如何解决这个问题吗?似乎我必须签署/加密错误消息,但我不知道如何…如果我使用传输安全,代码将按预期工作。

谢谢!


回答可能有点晚,但我也有同样的问题,我发现了:

您需要在接口方法上定义FaultContract以及OperationContract。看看这里。

此外,错误契约对象需要与操作命名空间具有相同的命名空间。

Soooooooooo很痛苦,但最终发现并开始工作。


在我的例子中,问题出现在名称空间中。我将providefault方法替换为:

1
2
3
4
5
6
public void ProvideFault(Exception error,MessageVersion ver, ref Message    
{
        FaultException fe = new FaultException(error.Message);
        MessageFault fault = fe.CreateMessageFault();
        msg = Message.CreateMessage(ver, fault, null);
}

注意null参数。

还应考虑使用错误的命名空间时,异常未正确序列化。