WCF筆記(6)消息攔截與篡改

要對SOAP消息進行攔截和修改,我們需要實現兩個接口,它們都位於System.ServiceModel.Dispatcher (程序集System.ServiceModel)。下面分別價紹。

接口一:IClientMessageInspector

從名字中我們可以猜測,它是用來攔截客戶消息的,而看看它的方法,你就更加肯定當初的猜測了。

  • BeforeSendRequest:向服務器發送請求前攔截或修改消息(事前控制)
  • AfterReceiveReply:接收到服務器的回覆消息後,在調用返回之前攔截或修改消息(事後諸葛亮)

接口二:IDispatchMessageInspector

剛纔那個接口是針對客戶端的,而這個是針對服務器端的。

  • AfterReceiveRequest:接收客戶端請求後,在進入操作處理代碼之前攔截或修改消息(欺上)
  • BeforeSendReply:服務器向客戶端發送回覆消息之前攔截和修改消息(瞞下)。

雖然實現了這兩個接口,但你會有新的疑問,怎麼用?把它們放到哪兒才能攔截消息?因此,下一步就是要實現IEndpointBehavior按口(System.ServiceModel.Description命名空間,程序集System.ServiceModel),它有四個方法,而我們只需要處理兩個就夠了。

下面是MSDN的翻譯版本說明:

使用 ApplyClientBehavior 方法可以在客戶端應用程序中修改、檢查或插入對終結點中的擴展。

使用 ApplyDispatchBehavior 方法可以在服務應用程序中修改、檢查或插入對終結點範圍執行的擴展。

我想不用額外解釋了,說白了就是一個在客戶攔截和修改消息,另一個在服務器端攔截和修改消息。

在實現這兩個方法時,和前面我們實現的IClientMessageInspector和IDispatchMessageInspector聯繫起來就OK了。

做完了IEndpointBehavior的事情後,把它插入到服務終結點中就行了,無論是服務器端還是客戶端,這一步都必須的,因爲我們實現的攔截器是包括兩個端的,因此,較好的做法是把這些類寫到一個獨立的類庫(dll)中,這樣一來,服務器端和客戶端都可以引用它。


示例代碼

1、實現消息攔截器

using System;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;

namespace MyMessageIntercept
{
    /// <summary>
    /// 消息攔截器【實現客戶端和服務端的消息攔截】
    /// </summary>
    public class MessageIntercept:IClientMessageInspector,IDispatchMessageInspector 
    {
        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            Console.WriteLine("客戶端接收到的回覆:{0}\n", reply);
        }

        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            Console.WriteLine("客戶端發送請求前的SOAP消息:{0}\n", request);
            return null;
        }

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            Console.WriteLine("服務器端接收到的請求:{0}\n", request);
            return null;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            Console.WriteLine("服務器將作出以下回復:{0}\n", reply);
        }
    }

    /// <summary>
    /// 將自定義的消息攔截器(MessageIntercept)插入到終結點行爲
    /// </summary>
    public class MyEndPointBehavior : IEndpointBehavior
    {   
        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            //不需要
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
           //植入"竊聽器"客戶端
            clientRuntime.ClientMessageInspectors.Add(new MessageIntercept());
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            //植入"竊聽器"服務端
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MessageIntercept());
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            //不需要
        }
    }
}

2、實現服務端

using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Runtime.Serialization;
using MyMessageIntercept;

namespace Server
{
    class Program
    {
        //設置服務並啓動
        static void Main()
        {
            //服務基地址
            Uri baseUri = new Uri("http://localhost:3000/Service");
            //聲明服務器主機
            using (ServiceHost host = new ServiceHost(typeof(MyService), baseUri))
            {
                //添加綁定和終結點
                WSHttpBinding binding = new WSHttpBinding();
                host.AddServiceEndpoint(typeof(IService), binding, "test");
                //添加服務描述
                host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
                //把自定義的"MyEndPointBehavior"插入到終結點中
                foreach (var endPoint in host.Description.Endpoints)
                {
                    endPoint.EndpointBehaviors.Add(new MyEndPointBehavior());
                }
                //啓動服務
                try
                {
                    host.Open();
                    Console.WriteLine("服務已啓動");
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                Console.ReadKey();
                //關閉服務
                host.Close();
            }
        }
    }

    /// <summary>
    /// 定義服務協定
    /// </summary>
    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        int AddInt(int a, int b);
        [OperationContract]
        Student GetStudent();
        [OperationContract]
        CalculateResultResponse CaculateNumber(CalculateRequest request);
    }

    /// <summary>
    /// 實現服務協定
    /// </summary>
    public class MyService : IService
    {
        public int AddInt(int a, int b)
        {
            return a + b;
        }

        public Student GetStudent()
        {
            Student st = new Student
                {
                    Name = "小明",
                    Age = 23
                };
            return st;
        }

        public CalculateResultResponse CaculateNumber(CalculateRequest request)
        {
            CalculateResultResponse result = new CalculateResultResponse();
            switch (request.Operation)
            {
                case "+":
                    result.CalculateResult = request.NumberA + request.NumberB;
                    break;
                case "-":
                    result.CalculateResult = request.NumberA - request.NumberB;
                    break;
                case "*":
                      result.CalculateResult = request.NumberA * request.NumberB;
                    break;
                case "/":
                    result.CalculateResult = request.NumberA / request.NumberB;
                    break;
                default:
                    throw new ArgumentException("運算操作符只允許'+'、'-'、'*'、'/'.");
            }
            return result;
        }

    }

    [DataContract]
    public class Student
    {
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public int Age { get; set; }
    }

    [MessageContract]
    public class CalculateRequest
    {
        /// <summary>
        /// 操作符
        /// </summary>
        [MessageHeader]
        public string Operation { get; set; }

        /// <summary>
        /// 第一個數
        /// </summary>
        [MessageBodyMember]
        public int NumberA { get; set; }

        /// <summary>
        /// 第二個數
        /// </summary>
        [MessageBodyMember ]
        public int NumberB { get; set; }
    }
    
    /// <summary>
    /// 計算返回的結果
    /// </summary>
    [MessageContract]
    public class CalculateResultResponse
    {
        /// <summary>
        /// 計算結果
        /// </summary>
        [MessageBodyMember]
        public int CalculateResult { get; set; }
    }
}

3、實現客戶端

using System;
using MyMessageIntercept;

namespace Client
{
    class Program
    {
        private static void Main()
        {
            WS.ServiceClient sc = new WS.ServiceClient();
            //記得在客戶端也要插入自定義的"MyEndPointBehavior"
            sc.Endpoint.EndpointBehaviors.Add(new MyEndPointBehavior());
            try
            {
                // 1、調用帶元數據參數和返回值的操作  
                Console.WriteLine("10和55相加的結果是:{0}\n", sc.AddInt(10, 55));
                //2、調用帶有數據協定的操作
                WS.Student st = sc.GetStudent();
                Console.WriteLine("-----學生信息--------\n姓名:{0}\n年齡:{1}\n", st.Name, st.Age);
                //3、調用帶有消息協定的操作
                Console.WriteLine("12乘以56的結果是:{0}\n", sc.CaculateNumber("*", 12, 56));
                sc.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }
    }
}

消息攔截效果圖




修改消息,只需要對消息攔截器(MessageIntercept)類做修改

using System;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;

namespace MyMessageIntercept
{
    /// <summary>
    /// 消息攔截器【實現客戶端和服務端的消息攔截】
    /// </summary>
    public class MessageIntercept:IClientMessageInspector,IDispatchMessageInspector 
    {
        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            //Console.WriteLine("客戶端接收到的回覆:\n{0}", reply);  
        }

        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            //Console.WriteLine("客戶端發送請求前的SOAP消息:\n{0}", request); 
            // (客服端)插入驗證信息  
            System.ServiceModel.Channels.MessageHeader hdUserName = System.ServiceModel.Channels.MessageHeader.CreateHeader("user", "myTest", "admin");
            System.ServiceModel.Channels.MessageHeader hdPassWord = System.ServiceModel.Channels.MessageHeader.CreateHeader("pw", "myTest", "123");
            request.Headers.Add(hdUserName);
            request.Headers.Add(hdPassWord);
            return null;  
        }

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            //Console.WriteLine("服務器端:接收到的請求:\n{0}", request); 
            // (服務端)栓查驗證信息  
             string un = request.Headers.GetHeader<string>("user", "myTest");
             string ps = request.Headers.GetHeader<string>("pw", "myTest");
            if (un == "admin" && ps == "123")
            {
                Console.WriteLine("用戶名和密碼正確.");
            }
            else
            {
                throw new Exception("驗證失敗!");
            }
            return null; 
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            //Console.WriteLine("服務器即將作出以下回復:\n{0}", reply); 
        }
    }

    /// <summary>
    /// 將自定義的消息攔截器(MessageIntercept)插入到終結點行爲
    /// </summary>
    public class MyEndPointBehavior : IEndpointBehavior
    {   
        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            //不需要
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
           //植入"竊聽器"客戶端
            clientRuntime.ClientMessageInspectors.Add(new MessageIntercept());
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            //植入"竊聽器"服務端
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MessageIntercept());
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            //不需要
        }
    }
}
注意:添加對System.Runtime.Serialization的引用。
前面我們說過,如果安裝證書進行身份驗證會相當麻煩,而可以通過修改SOAP消息頭來驗證,但是,上次的做法會有一個麻煩,那就是每次調用操作協定都要手動修改一次,這一次,我們直接在終結點級別進行修改和驗證,就省去了許多功夫。


發佈了88 篇原創文章 · 獲贊 6 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章