c# - WCF 自定义 header /自定义行为类不可用

标签 c# wcf

我是 WCF 的新手,所以我很肯定这是我做错的事情。我正在关注此链接 Sample link

基本上,我正在尝试实现“IClientMessageInspector”,以便对我的 WCF 的每次调用都必须包含我的 WCF 方法执行正确事件所需的自定义属性。

在我的 Console Consumer 应用程序中,我尝试添加我的自定义端点行为类。但是,当我去添加类(class)时,它不可用。这是我的消费者应用程序,您会注意到我遇到问题的代码行。

程序.cs

class Program
    {
        static void Main(string[] args)
        {
            using (Service1Client s1c = new Service1Client())
            {
                //cannot add 'CustomBehavior' this is my issue
                s1c.ChannelFactory.Endpoint.EndpointBehaviors.Add(
            }
        }
    }

我的服务 Service1.svc.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WCFHeaderCalls
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
    // NOTE: In order to launch WCF Test Client for testing this service, please select Service1.svc or Service1.svc.cs at the Solution Explorer and start debugging.
    [CustomBehavior]
    public class Service1 : IService1
    {
        public string GetData(int value)
        {
            return string.Format("You entered: {0}", value);
        }

        public CompositeType GetDataUsingDataContract(CompositeType composite)
        {
            if (composite == null)
            {
                throw new ArgumentNullException("composite");
            }
            if (composite.BoolValue)
            {
                composite.StringValue += "Suffix";
            }
            return composite;
        }
    }
}

IService1.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WCFHeaderCalls
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
    [ServiceContract]
    public interface IService1
    {

        [OperationContract]
        string GetData(int value);

        [OperationContract]
        CompositeType GetDataUsingDataContract(CompositeType composite);

        // TODO: Add your service operations here
    }


    // Use a data contract as illustrated in the sample below to add composite types to service operations.
    [DataContract]
    public class CompositeType
    {
        bool boolValue = true;
        string stringValue = "Hello ";

        [DataMember]
        public bool BoolValue
        {
            get { return boolValue; }
            set { boolValue = value; }
        }

        [DataMember]
        public string StringValue
        {
            get { return stringValue; }
            set { stringValue = value; }
        }
    }
}

上下文类.cs

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace WCFHeaderCalls
{
    /// <summary>
    /// This class will act as a custom context in the client side to hold the context information.
    /// </summary>
    public class ClientContext
    {
        public static string applicationKey;
        public static string methodKey;
        public static string accountName;
        public static string accountPassword;
    }

    /// <summary>
    /// This class will act as a custom context, an extension to the OperationContext.
    /// This class holds all context information for our application.
    /// </summary>
    public class ServerContext : IExtension<OperationContext>
    {
        public string EmployeeID;
        public string WindowsLogonID;
        public string KerberosID;
        public string SiteminderToken;

        // Get the current one from the extensions that are added to OperationContext.
        public static ServerContext Current
        {
            get
            {
                return OperationContext.Current.Extensions.Find<ServerContext>();
            }
        }

        #region IExtension<OperationContext> Members
        public void Attach(OperationContext owner)
        {
        }

        public void Detach(OperationContext owner)
        {
        }
        #endregion
    }


}

MessageInspector.cs

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace WCFHeaderCalls
{
    /// <summary>
    /// This class is used to inspect the message and headers on the server side,
    /// This class is also used to intercept the message on the
    /// client side, before/after any request is made to the server.
    /// </summary>
    public class CustomMessageInspector : IClientMessageInspector, IDispatchMessageInspector
    {
        #region Message Inspector of the Service

        /// <summary>
        /// This method is called on the server when a request is received from the client.
        /// </summary>
        /// <param name="request"></param>
        /// <param name="channel"></param>
        /// <param name="instanceContext"></param>
        /// <returns></returns>
        public object AfterReceiveRequest(ref Message request,
               IClientChannel channel, InstanceContext instanceContext)
        {
            // Create a copy of the original message so that we can mess with it.
            MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
            request = buffer.CreateMessage();
            Message messageCopy = buffer.CreateMessage();

            // Read the custom context data from the headers
            ServiceHeader customData = CustomHeader.ReadHeader(request);

            // Add an extension to the current operation context so
            // that our custom context can be easily accessed anywhere.
            ServerContext customContext = new ServerContext();

            if (customData != null)
            {
                customContext.KerberosID = customData.accountName;
                customContext.SiteminderToken = customData.accountPassword;
            }
            OperationContext.Current.IncomingMessageProperties.Add(
                     "CurrentContext", customContext);
            return null;
        }

        /// <summary>
        /// This method is called after processing a method on the server side and just
        /// before sending the response to the client.
        /// </summary>
        /// <param name="reply"></param>
        /// <param name="correlationState"></param>
        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            // Do some cleanup
            OperationContext.Current.Extensions.Remove(ServerContext.Current);
        }

        #endregion

        #region Message Inspector of the Consumer

        /// <summary>
        /// This method will be called from the client side just before any method is called.
        /// </summary>
        /// <param name="request"></param>
        /// <param name="channel"></param>
        /// <returns></returns>
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            // Prepare the request message copy to be modified
            MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
            request = buffer.CreateMessage();

            ServiceHeader customData = new ServiceHeader();

            customData.accountName = ClientContext.accountName;
            customData.accountPassword = ClientContext.accountPassword;

            CustomHeader header = new CustomHeader(customData);

            // Add the custom header to the request.
            request.Headers.Add(header);

            return null;
        }

        /// <summary>
        /// This method will be called after completion of a request to the server.
        /// </summary>
        /// <param name="reply"></param>
        /// <param name="correlationState"></param>
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {

        }

        #endregion
    }
}

CustomHeader.cs

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace WCFHeaderCalls
{
    [DataContract]
    public class ServiceHeader
    {
        /// <summary>
        /// this is the application-GUID specify this per app (OI,office,etc..)
        /// </summary>
        [DataMember]
        public string applicationKey { get; set; }

        /// <summary>
        /// this is the method key you wish to implament
        /// </summary>
        [DataMember]
        public string methodKey { get; set; }

        /// <summary>
        /// this is your account name
        /// </summary>
        [DataMember]
        public string accountName { get; set; }

        /// <summary>
        /// this is your account password
        /// </summary>
        [DataMember]
        public string accountPassword { get; set; }
    }

    public class CustomHeader : MessageHeader
    {
        private const string CUSTOM_HEADER_NAME = "HeaderName";
        private const string CUSTOM_HEADER_NAMESPACE = "YourNameSpace";

        private ServiceHeader _customData;

        public ServiceHeader CustomData
        {
            get
            {
                return _customData;
            }
        }

        public CustomHeader()
        {
        }

        public CustomHeader(ServiceHeader customData)
        {
            _customData = customData;
        }

        public override string Name
        {
            get { return (CUSTOM_HEADER_NAME); }
        }

        public override string Namespace
        {
            get { return (CUSTOM_HEADER_NAMESPACE); }
        }

        protected override void OnWriteHeaderContents(
            System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(ServiceHeader));
            StringWriter textWriter = new StringWriter();
            serializer.Serialize(textWriter, _customData);
            textWriter.Close();

            string text = textWriter.ToString();

            writer.WriteElementString(CUSTOM_HEADER_NAME, "Key", text.Trim());
        }

        public static ServiceHeader ReadHeader(Message request)
        {
            Int32 headerPosition = request.Headers.FindHeader(CUSTOM_HEADER_NAME, CUSTOM_HEADER_NAMESPACE);
            if (headerPosition == -1)
                return null;

            MessageHeaderInfo headerInfo = request.Headers[headerPosition];

            XmlNode[] content = request.Headers.GetHeader<XmlNode[]>(headerPosition);

            string text = content[0].InnerText;

            XmlSerializer deserializer = new XmlSerializer(typeof(ServiceHeader));
            TextReader textReader = new StringReader(text);
            ServiceHeader customData = (ServiceHeader)deserializer.Deserialize(textReader);
            textReader.Close();

            return customData;
        }
    }
}

CustomBehavior.cs

using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace WCFHeaderCalls
{
    /// <summary>
    /// This custom behavior class is used to add both client and server inspectors to
    /// the corresponding WCF end points.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class)]
    public class CustomBehavior : Attribute, IServiceBehavior, IEndpointBehavior
    {
        #region IEndpointBehavior Members

        void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
            CustomMessageInspector inspector = new CustomMessageInspector();
            clientRuntime.MessageInspectors.Add(inspector);
        }

        void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {
            ChannelDispatcher channelDispatcher = endpointDispatcher.ChannelDispatcher;
            if (channelDispatcher != null)
            {
                foreach (EndpointDispatcher ed in channelDispatcher.Endpoints)
                {
                    CustomMessageInspector inspector = new CustomMessageInspector();
                    ed.DispatchRuntime.MessageInspectors.Add(inspector);
                }
            }
        }

        void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { }

        #endregion

        #region IServiceBehavior Members

        void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription desc, ServiceHostBase host)
        {
            foreach (ChannelDispatcher cDispatcher in host.ChannelDispatchers)
                foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints)
                    eDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomMessageInspector());
        }

        void IServiceBehavior.Validate(ServiceDescription desc, ServiceHostBase host) { }

        #endregion
    }
}

最后这是我的服务 web.config

<?xml version="1.0"?>
<configuration>

  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5"/>
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
      <add binding="basicHttpsBinding" scheme="https" />
    </protocolMapping>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <!--
        To browse web app root directory during debugging, set the value below to true.
        Set to false before deployment to avoid disclosing web app folder information.
      -->
    <directoryBrowse enabled="true"/>
  </system.webServer>

</configuration>

最佳答案

在您的消费者应用中生成的代理中,消息行为将无法通过 WSDL 获得。因此,您要么必须在客户端为消息检查器添加相同的代码(类),要么为服务和客户端之间的契约(Contract)创建一个共享项目(类库),通过它行为类在两者中都可用.

关于c# - WCF 自定义 header /自定义行为类不可用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37169550/

相关文章:

c# - WCF 中带有 'not recognized sequence' 消息的 CommunicationException

c# - 在 QnA azure 机器人中嵌入链接

c# - 从 Angular 2 调用 WCF 服务

c# - ServiceController.Start() 未达到 "Running"状态

c# - 当无法序列化枚举时,尽早失败或明确抛出

c# - Fluent断言: how to specify that collection should contain a certain number of elements matching predicate?

c# - Windows服务是否需要.Net Runtime

c# - : ASP.net 或 WCF 哪个更简单?

c# - 将 MySQL 代理连接到 C# 应用程序

wcf - HTTPContext 在 WCF 中是否已过时?