WCF, information retrieval from a request, timesta

2019-07-26 23:03发布

问题:

I do have a WCF restful service that is running perfectly.

I do want to retrieve as much information as possible whenever someone sends a GET or POST request to my service

I am using the following to retrieve most of my information:

OperationContext context = OperationContext.Current;
MessageProperties messageProperties = context.IncomingMessageProperties;

RemoteEndpointMessageProperty endpointProperty =
          messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;

I do need help in finding the timestamp of the request.

Thank you

Edit: for some people the question making unrelated questions, hence portion of the question was removed.

回答1:

I found myself in a similar position recently and after reading the great answer from @carlosfigueira, I decided to dig a little deeper and figure out how to make his example work from an IIS web.config file.

It turns out that Carlos has his own blog at Microsoft and has a few posts that cover this subject in great detail. The diagram at the top of this page, was very helpful and clearly indicated that the MessageEncoder was the closest I was going to get to reading the client's message directly off the wire when the service is hosted through IIS.

This is essentially done using the same process Carlos detailed, but to get it done in a web.config file you have to create a class derived from BindingElementExtensionElement to give IIS the required hooks to create your BindingElement/Encoder/Factory as shown in the examples below.

(I hate to duplicate what Carlos has already posted, but I did tweak his code a bit in the process of figuring all this out. So, rather than chance posting code that doesn't work with his example, I'll just repost my tweaks here)


Step 1) Create your custom MessageEncoder (and correlating MessageEncoderFactory) that wraps the original MessageEncoder that your binding would normally use and make the wrapping MessageEncoder add the timestamp to the Message created by the original/wrapped MessageEncoder.

NOTE: This is the first two of three classes that will essentially wrap objects and implement their WCF interfaces by passing calls through to the wrapped/inner object (ie. "return inner.property/method") allowing the custom MessageEncoder to precisely match the behavior of the MessageEncoder we're essentially trying to extend/alter. So, although this is a lengthy answer, it's really not that complicated once you have a grasp of all the pieces involved.

using System;
using System.IO;
using System.ServiceModel.Channels;

namespace WCF.Sample
{
    public class TimestampedMsgEncoder : MessageEncoder
    {
        public const String TimestampProp = "MsgReceivedTimestamp";

        private MessageEncoder inner;

        public TimestampedMsgEncoder(MessageEncoder inner) { this.inner = inner; }

        public override Message ReadMessage(ArraySegment<Byte> buffer, BufferManager bufferManager, String contentType)
        {
            DateTime MsgReceived = DateTime.Now;
            Message Msg = inner.ReadMessage(buffer, bufferManager, contentType);
            Msg.Properties.Add(TimestampProp, MsgReceived);
            return Msg;
        }

        public override Message ReadMessage(Stream stream, Int32 maxSizeOfHeaders, String contentType)
        {
            DateTime MsgReceived = DateTime.Now;
            Message Msg = inner.ReadMessage(stream, maxSizeOfHeaders, contentType);
            Msg.Properties.Add(TimestampProp, MsgReceived);
            return Msg;
        }

        #region Pass-through MessageEncoder implementations

        public override String ContentType { get { return inner.ContentType; } }

        public override String MediaType { get { return inner.MediaType; } }

        public override MessageVersion MessageVersion { get { return inner.MessageVersion; } }

        public override Boolean IsContentTypeSupported(String contentType) { return inner.IsContentTypeSupported(contentType); }

        public override void WriteMessage(Message message, Stream stream) { inner.WriteMessage(message, stream); }

        public override ArraySegment<Byte> WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset) { return inner.WriteMessage(message, maxMessageSize, bufferManager, messageOffset); }

        #endregion Pass-through MessageEncoder implementations
    }

    public class TimestampedMsgEncoderFactory : MessageEncoderFactory
    {
        protected readonly MessageEncoderFactory inner;

        protected TimestampedMsgEncoderFactory() { }

        public TimestampedMsgEncoderFactory(MessageEncoderFactory inner)
        {
            this.inner = inner;
        }

        public override MessageEncoder Encoder { get { return new TimestampedMsgEncoder(inner.Encoder); } }

        public override MessageVersion MessageVersion { get { return inner.MessageVersion; } }
    }
}


Step 2) Create a class derived from MessageEncodingBindingElement that will be added to your CustomBinding and will (once again) wrap an "inner" object that is the type of object your usual binding would use (TextMessageEncodingBindingElement in the case of the BasicHttpBinding).

using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
using System.Configuration;

namespace WCF.Sample
{
    public class TimestampedTextMsgEncodingBindingElement : MessageEncodingBindingElement, IWsdlExportExtension, IPolicyExportExtension
    {
        private readonly TextMessageEncodingBindingElement inner;

        public TimestampedTextMsgEncodingBindingElement(TextMessageEncodingBindingElement inner)
        {
            this.inner = inner;
        }

        public TimestampedTextMsgEncodingBindingElement()
        {
            inner = new TextMessageEncodingBindingElement();
        }

        public TimestampedTextMsgEncodingBindingElement(MessageVersion messageVersion, Encoding writeEnconding)
        {
            inner = new TextMessageEncodingBindingElement(messageVersion, writeEnconding);
        }

        public override MessageEncoderFactory CreateMessageEncoderFactory()
        {
            return new TimestampedMsgEncoderFactory(inner.CreateMessageEncoderFactory());
        }

        #region Pass-through MessageEncoderBindingElement implementations

        public override BindingElement Clone()
        {
            return new TimestampedTextMsgEncodingBindingElement((TextMessageEncodingBindingElement)inner.Clone());
        }

        public override MessageVersion MessageVersion { get { return inner.MessageVersion; } set { inner.MessageVersion = value; } }
        public Encoding WriteEncoding { get { return inner.WriteEncoding; } set { inner.WriteEncoding = value; } }
        public Int32 MaxReadPoolSize { get { return inner.MaxReadPoolSize; } set { inner.MaxReadPoolSize = value; } }
        public Int32 MaxWritePoolSize { get { return inner.MaxWritePoolSize; } set { inner.MaxWritePoolSize = value; } }
        public XmlDictionaryReaderQuotas ReaderQuotas { get { return inner.ReaderQuotas; } set { inner.ReaderQuotas = value; } }

        public override Boolean CanBuildChannelListener<TChannel>(BindingContext context)
        {
            return context.CanBuildInnerChannelListener<TChannel>();
            //return inner.CanBuildChannelFactory<TChannel>(context);
        }

        public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
        {
            context.BindingParameters.Add(this);
            return context.BuildInnerChannelListener<TChannel>();
            //return inner.BuildChannelListener<TChannel>(context);         
        }

        public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
        {
            ((IWsdlExportExtension)inner).ExportContract(exporter, context);
        }

        public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
        {
            ((IWsdlExportExtension)inner).ExportEndpoint(exporter, context);
        }

        public void ExportPolicy(MetadataExporter exporter, PolicyConversionContext context)
        {
            ((IPolicyExportExtension)inner).ExportPolicy(exporter, context);
        }

        #endregion Pass-through MessageEncoderBindingElement implementations
    }
}


Step 3) Create a class derived from BindingElementExtensionElement that will allow you to use the TimestampedTextMsgEncodingBindingElement class we just created (above) in the web.config file.

This is the first (only) class we're creating that has a purpose other than wrapping some standard framework class to effectively extend/alter it. Still, it's a pretty simple class to implement. A minimal implementation only has the BindingElementType property and the CreateBindingElement method.

However, if you will want to use attributes to customize the MessageEncoder behavior from the web.config file then it does need to have special properties decorated with ConfigurationProperty attributes to let IIS know that they accept values from element attributes in the web.config file ... which must then be marshaled to the inner MessageEncoder accordingly.

(If you know you're message encoder will always use the same configuration then it's probably easier just to hard code things like UTF8, and SOAP11 - I just provided a way to implement the attributes here as an example)

using System;
using System.Xml;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
using System.Configuration;

namespace WCF.Sample
{
    public class TimestampedTextMsgEncodingExtension : BindingElementExtensionElement
    {
        private MessageVersion _MessageVersion = MessageVersion.Soap11;
        private Encoding _Encoding = Encoding.UTF8;
        private Int32 _MaxReadPoolSize = -1;
        private Int32 _MaxWritePoolSize = -1;
        private XmlDictionaryReaderQuotas _ReaderQuotas = null;

        public override Type BindingElementType { get { return typeof(TimestampedTextMsgEncodingBindingElement); } }

        protected override BindingElement CreateBindingElement()
        {
            TimestampedTextMsgEncodingBindingElement eb = new TimestampedTextMsgEncodingBindingElement(_MessageVersion, _Encoding);
            if (_MaxReadPoolSize > -1) eb.MaxReadPoolSize = _MaxReadPoolSize;
            if (_MaxWritePoolSize > -1) eb.MaxWritePoolSize = MaxWritePoolSize;
            if (_ReaderQuotas != null) eb.ReaderQuotas = _ReaderQuotas;

            return eb;
        }

        [ConfigurationProperty("messageVersion", DefaultValue = "Soap11", IsRequired = false)]
        public String messageVersion
        {
            set
            {
                switch (value)
                {
                    case "Soap11":
                        _MessageVersion = MessageVersion.Soap11;
                        break;

                    case "Soap12":
                        _MessageVersion = MessageVersion.Soap12;
                        break;

                    case "Soap11WSAddressing10":
                        _MessageVersion = MessageVersion.Soap11WSAddressing10;
                        break;

                    case "Soap12WSAddressing10":
                        _MessageVersion = MessageVersion.Soap12WSAddressing10;
                        break;

                    case "Soap11WSAddressingAugust2004":
                        _MessageVersion = MessageVersion.Soap11WSAddressingAugust2004;
                        break;

                    case "Soap12WSAddressingAugust2004":
                        _MessageVersion = MessageVersion.Soap12WSAddressingAugust2004;
                        break;

                    default:
                        throw new NotSupportedException("\"" + value + "\" is not a supported message version.");
                }
            }
        }

        [ConfigurationProperty("writeEncoding", DefaultValue = "Utf8TextEncoding", IsRequired = false)]
        public String writeEncoding
        {
            set
            {
                switch (value)
                {
                    case "Utf8TextEncoding":
                        _Encoding = Encoding.UTF8;
                        break;

                    case "Utf16TextEncoding":
                        _Encoding = Encoding.Unicode;
                        break;

                    case "UnicodeFffeTextEncoding":
                        _Encoding = Encoding.BigEndianUnicode;
                        break;

                    default:
                        _Encoding = Encoding.GetEncoding(value);
                        break;
                }
            }
        }

        [ConfigurationProperty("maxReadPoolSize", IsRequired = false)]
        public Int32 MaxReadPoolSize { get { return _MaxReadPoolSize; } set { _MaxReadPoolSize = value; } }

        [ConfigurationProperty("maxWritePoolSize", IsRequired = false)]
        public Int32 MaxWritePoolSize { get { return _MaxWritePoolSize; } set { _MaxWritePoolSize = value; } }

        [ConfigurationProperty("readerQuotas", IsRequired = false)]
        public XmlDictionaryReaderQuotas ReaderQuotas { get { return _ReaderQuotas; } set { _ReaderQuotas = value; } }
    }
}


Step 4) Update your web.config file:

  • Add/Update the configuration\system.serviceModel\extensions\bindingElementExtensions section of your web.config to include a fully qualified reference to the TimestampedTextMsgEncodingExtension we just created in the code above
  • Create/update an endpoint with a binding of "customBinding" that points to a bindingConfiguration (HttpNoAuthTimestampEncoding)
  • Add/update the bindingConfiguration section (HttpNoAuthTimestampEncoding) under bindings\customBinding to use an element with the name we assigned to our custom MessageEncoder extension (timestampedTextMsgEncoding) in the "bindingElementExtensions" section to indicate that traffic should flow through that encoder.
<configuration>
  <system.web>
    <compilation strict="false" explicit="true" targetFramework="4.5" />
    <trust level="Full" />
  </system.web>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
  </system.webServer>
  <system.serviceModel>
    <extensions>
      <bindingElementExtensions>
        <add name="timestampedTextMsgEncoding" type="WCF.Sample.TimestampedTextMsgEncodingExtension, WCFSample" />
      </bindingElementExtensions>
    </extensions>
    <services>
      <service behaviorConfiguration="WCF.Sample.SampleBehavior" name="WCF.Sample.Service">
        <endpoint address="basic" contract="WCF.Sample.IService" binding="basicHttpBinding" bindingConfiguration="HttpNoAuth" />
        <endpoint address="timestamp" contract="WCF.Sample.IService" binding="customBinding" bindingConfiguration="HttpNoAuthTimestampEncoding" />
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="HttpNoAuth" >
          <security mode="None" >
            <transport clientCredentialType="None" />
          </security>
        </binding>
      </basicHttpBinding>
      <customBinding>
        <binding name="HttpNoAuthTimestampEncoding">
          <timestampedTextMsgEncoding writeEncoding="Utf8TextEncoding" messageVersion="Soap11" />
          <httpTransport authenticationScheme="None" />
        </binding>
      </customBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="WCF.Sample.SampleBehavior">
          <useRequestHeadersForMetadataAddress />
          <serviceMetadata httpGetEnabled="True" />
          <serviceDebug includeExceptionDetailInFaults="False" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>


Step 5) Update your WCF service method(s) with code to pull the timestamp property we added to the incoming Message. Since we recorded the DateTime before we sent the client's message to the inner MessageEncoder, this should be essentially the earliest opportunity we've had to get a DateTime since the last bit of the POST message was transferred from the client.

DateTime SOAPMsgReceived = (DateTime)System.ServiceModel.OperationContext.Current.IncomingMessageProperties[TimestampedMsgEncoder.TimestampProp];


Two final notes.

  1. If you created a new endpoint, make sure your clients are pointed to the address of that endpoint when you go to test (I chased my tail on this for about an hour before I realized I'd overlooked that detail).

  2. The web.config sample above does little to show the difficulty I had figuring out how to make the customBinding configuration (HttpNoAuthTimestampEncoding) match the functionality of the basicHttpBinding configuration (HttpNoAuth). The documentation on the customBinding element helped a little, but mostly it was a lot of web searches and trial and error. Of particular note is using <httpTransport> and <httpsTransport> to flip between HTTP and HTTPS. In contrast, basicHttpBinding uses "security\@mode=None" and "security\@mode=Transport".



回答2:

It depends on what you mean by the timestamp of the request. If you want to know what time the request left the client, then you don't have that information (even on ASP.NET, with the HttpContext.Timestamp property, that is not available) - the server only knows when it receives the request (and this is what HttpContext.Timestamp gives you, based on the documentation).

For WCF itself, there's no property that will tell when the operation context has created; you can add such property yourself in any layer of the WCF stack (in a custom encoder, a custom message inspector, a custom protocol channel, etc.), or even in the operation itself - and for most scenarios, the difference between them is insignificant.

For example, the code below shows an example of timestamping the request in three places; in most tests that I've run the difference between them is at most a couple of milliseconds.

public class StackOverflow_39082986
{
    const string TimestampPropertyName = "MyTimestampProperty";
    class MyTimestampProperty
    {
        public DateTime EncoderTimestamp;
        public DateTime InspectorTimestamp;
    }
    [ServiceContract]
    public interface ITest
    {
        [OperationContract]
        void DoSomething();
    }
    public class Service : ITest
    {
        public void DoSomething()
        {
            var myProp = (MyTimestampProperty)OperationContext.Current.IncomingMessageProperties[TimestampPropertyName];
            var now = DateTime.UtcNow;
            Console.WriteLine("Request timestamps:");
            var timeFormat = "yyyy-MM-dd HH:MM:ss.fffffff";
            Console.WriteLine("  From encoder  : {0}", myProp.EncoderTimestamp.ToString(timeFormat));
            Console.WriteLine("  From inspector: {0}", myProp.InspectorTimestamp.ToString(timeFormat));
            Console.WriteLine("  From operation: {0}", now.ToString(timeFormat));
        }
    }
    class MyInspector : IEndpointBehavior, IDispatchMessageInspector
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            MyTimestampProperty prop;
            if (request.Properties.ContainsKey(TimestampPropertyName))
            {
                prop = (MyTimestampProperty)request.Properties[TimestampPropertyName];
            }
            else
            {
                prop = new MyTimestampProperty();
                request.Properties.Add(TimestampPropertyName, prop);
            }

            prop.InspectorTimestamp = DateTime.UtcNow;
            return null;
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }
    }
    public class MyEncoderBindingElement : MessageEncodingBindingElement
    {
        private MessageEncodingBindingElement inner;
        public MyEncoderBindingElement(MessageEncodingBindingElement inner)
        {
            this.inner = inner;
        }
        public override MessageVersion MessageVersion
        {
            get { return this.inner.MessageVersion; }
            set { this.inner.MessageVersion = value; }
        }

        public override BindingElement Clone()
        {
            return new MyEncoderBindingElement((MessageEncodingBindingElement)this.inner.Clone());
        }

        public override MessageEncoderFactory CreateMessageEncoderFactory()
        {
            return new MyEncoderFactory(this.inner.CreateMessageEncoderFactory());
        }

        public override bool CanBuildChannelListener<TChannel>(BindingContext context)
        {
            return context.CanBuildInnerChannelListener<TChannel>();
        }

        public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
        {
            context.BindingParameters.Add(this);
            return context.BuildInnerChannelListener<TChannel>();
        }

        class MyEncoderFactory : MessageEncoderFactory
        {
            MessageEncoderFactory inner;
            public MyEncoderFactory(MessageEncoderFactory inner)
            {
                this.inner = inner;
            }

            public override MessageEncoder Encoder
            {
                get
                {
                    return new MyEncoder(this.inner.Encoder);
                }
            }

            public override MessageVersion MessageVersion
            {
                get { return this.inner.MessageVersion; }
            }
        }

        class MyEncoder : MessageEncoder
        {
            MessageEncoder inner;
            public MyEncoder(MessageEncoder inner)
            {
                this.inner = inner;
            }

            public override string ContentType
            {
                get { return this.inner.ContentType; }
            }

            public override string MediaType
            {
                get { return this.inner.MediaType; }
            }

            public override MessageVersion MessageVersion
            {
                get { return this.inner.MessageVersion; }
            }

            public override bool IsContentTypeSupported(string contentType)
            {
                return this.inner.IsContentTypeSupported(contentType);
            }

            public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
            {
                var result = this.inner.ReadMessage(buffer, bufferManager, contentType);
                result.Properties.Add(TimestampPropertyName, new MyTimestampProperty { EncoderTimestamp = DateTime.UtcNow });
                return result;
            }

            public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)
            {
                var result = this.inner.ReadMessage(stream, maxSizeOfHeaders, contentType);
                result.Properties.Add(TimestampPropertyName, new MyTimestampProperty { EncoderTimestamp = DateTime.UtcNow });
                return result;
            }

            public override void WriteMessage(Message message, Stream stream)
            {
                this.inner.WriteMessage(message, stream);
            }

            public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
            {
                return this.inner.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
            }
        }
    }
    static Binding GetBinding(bool server)
    {
        var result = new CustomBinding(new BasicHttpBinding());
        if (server)
        {
            for (var i = 0; i < result.Elements.Count; i++)
            {
                var mebe = result.Elements[i] as MessageEncodingBindingElement;
                if (mebe != null)
                {
                    result.Elements[i] = new MyEncoderBindingElement(mebe);
                    break;
                }
            }
        }

        return result;
    }
    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
        ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITest), GetBinding(true), "");
        endpoint.EndpointBehaviors.Add(new MyInspector());
        host.Open();
        Console.WriteLine("Host opened");

        ChannelFactory<ITest> factory = new ChannelFactory<ITest>(GetBinding(false), new EndpointAddress(baseAddress));
        ITest proxy = factory.CreateChannel();
        proxy.DoSomething();

        ((IClientChannel)proxy).Close();
        factory.Close();

        Console.Write("Press ENTER to close the host");
        Console.ReadLine();
        host.Close();
    }
}


标签: c# rest wcf http