none
How to simulate SoapHeaders in WCF? RRS feed

  • Question

  • Hi

    Using classic .asmx web services, I used to a create a SoapHeader as follows and append it so it's available when I call each web method.   Each method used to have it's own set of defined parameters.

    However, I'm struggling in how to implement the same behavior in WCF.  From what I gather, you can define a Message Contract and use the Message Inspector's AfterReceiveRequest method to parse the header (AccountId/Password in my case).

    What is also confusing is if you use Message Contracts, it seems you have to then pass the contract as the single parameter for each method doing away with being able to pass parameters (i.e.  PerformSearch(searchvalue 1, searchvalue 2, ...).  I know you can append different MessageBodyMembers for each of the parameters and them access them in the WCF operation.

    Is that new pattern? Or is there a way to duplicate what I've done below but in WCF? Thanks!, Dave

    Note. The AuthHeader below was available through the proxy.  When I created Message Contract but it won't get generated with the proxy when I "Add a Service Reference". 

    public class AuthHeader : SoapHeader 
    {  
    public string AccountId; 
    public string Password;
    }
    
    public class MyWebServices: System.Web.Services.WebService
    {  public AuthHeader authHeader;  [WebMethod]   [SoapHeader("authHeader")]  public DataSet GetCompanyById(string CompanyId)     {     this.AuthenticateClient(authHeader); //Checks AccountId dand Password against a database..  return Company dataset from DB...; }

    In my proxy on I'd populate the auth header and make the sevice webmethod and allows me to call the method with a parameter list

    myServices= new MyWebServices(); AuthHeader authHeader = new AuthHeader(); authHeader.AccountId = "ABC"
    authHeader.Password = "123"; myServices.AuthHeaderValue = authHeader; myServices.Credentials =System.Net.CredentialCache.DefaultCredentials;

    myservices.GetCompanyById("123456"); 


    Wednesday, February 26, 2014 10:04 PM

All replies

  • The first step we need to take is to define the SOAP header. Our custom header is derived from the System.Web.Services.Protocols.SoapHeader type.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Web.Services.Protocols;
    using System.Xml.Serialization;
    
    namespace DPE.Samples.Web.Services
    {
        [XmlRoot("Keystone", Namespace = "urn:com-sample-dpe:soapextension")]
        public class CustomSoapHeader : SoapHeader
        {
            private string _castMemberID;
    
            public string CastMemberID
            {
                get { return _castMemberID; }
                set { _castMemberID = value; }
            }    
        }
    }
    

    The next step is to define the SoapExtension, the mechanism that will insert the above SoapHeader into an outgoing SOAP request.  I am not going into much detail here, other than to state that I started by ripping off the SoapExtension example in the SDK documentation.  I admit, the SoapExtension model itself is a little bizarre and hard to get started with.  The best way I can suggest to get started is to play with a few, maybe even work with Clemens' C# Wizard for SoapExtensions.  If you intend on working with SoapExtensions in the near future (you should really work with WCF's amazing extensibility model instead), make sure to read Steve Maine's rant on ChainStream.  Find a working example, then gut it to make it do what you want it to do.

    using System;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.IO;
    using System.Net;
    using System.Diagnostics;
    
    namespace DPE.Samples.Web.Services
    {
        public class CustomHeaderExtension : SoapExtension
        {
            Stream oldStream;
            Stream newStream;
            string filename;
    
            public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
            {
                return null;
            }
    
            public override object GetInitializer(Type WebServiceType)
            {            
                return "C:\\" + WebServiceType.FullName + ".log";
            }
    
            public override void Initialize(object initializer)
            {
                filename = (string)initializer;
            }
    
            // Save the Stream representing the SOAP request or SOAP response into
            // a local memory buffer.
            public override Stream ChainStream(Stream stream)
            {
                oldStream = stream;
                newStream = new MemoryStream();
                return newStream;
            }
            
            public override void ProcessMessage(SoapMessage message)
            {
                switch (message.Stage)
                {
                    case SoapMessageStage.BeforeSerialize:
                        //Add the CustomSoapHeader to outgoing client requests
                        if (message is SoapClientMessage)
                        {
                            AddHeader(message);
                        }
                        break;
                    case SoapMessageStage.AfterSerialize:
                        newStream.Position = 0;
                        WriteOutput(message);
                        newStream.Position = 0;
                        Copy(newStream, oldStream);
                        break;
                    case SoapMessageStage.BeforeDeserialize:
                        Copy(oldStream, newStream);
                        WriteInput(message);
                        newStream.Position = 0;
                        break;
                    case SoapMessageStage.AfterDeserialize:
                        break;
                }
            }
    
            private void AddHeader(SoapMessage message)
            {
                CustomSoapHeader header = new CustomSoapHeader();
                header.CastMemberID = "gsupike@hotmail.com";
                header.MustUnderstand = false;
                message.Headers.Add(header);
            }
    
            [Conditional("DEBUG")]
            private void WriteOutput(SoapMessage message)
            {            
                FileStream fs = new FileStream(filename, FileMode.Append,
                    FileAccess.Write);
                StreamWriter w = new StreamWriter(fs);
    
                string soapString = (message is SoapServerMessage) ? "SoapResponse" : "SoapRequest";
                w.WriteLine("-----" + soapString + " at " + DateTime.Now);
                w.Flush();
                Copy(newStream, fs);
                w.Close();
            }
    
            [Conditional("DEBUG")]
            private void WriteInput(SoapMessage message)
            {            
                FileStream fs = new FileStream(filename, FileMode.Append,
                    FileAccess.Write);
                StreamWriter w = new StreamWriter(fs);
    
                string soapString = (message is SoapServerMessage) ?
                    "SoapRequest" : "SoapResponse";
                w.WriteLine("-----" + soapString +
                    " at " + DateTime.Now);
                w.Flush();
                newStream.Position = 0;
                Copy(newStream, fs);
                w.Close();            
            }
    
            private void Copy(Stream from, Stream to)
            {
                TextReader reader = new StreamReader(from);
                TextWriter writer = new StreamWriter(to);
                writer.WriteLine(reader.ReadToEnd());
                writer.Flush();
            }
        }
    }

    Notice in the SoapExtension above that I used the ConditionalAttribute.  If the DEBUG constant is defined, calls to this method are compiled in.  If it is not defined, then the calls are not compiled in, and the method will never be JIT compiled (because it is never called).  When you deploy a Release build, the DEBUG constant is not defined and you won't have logging turned on.

    The next thing that we need to do is to create an attribute that can be applied to the client proxy.  This attribute is how the SoapExtension framework knows to invoke the SoapExtension for the outbound message.

    using System;
    using System.Web.Services.Protocols;
    
    
    namespace DPE.Samples.Web.Services
    {
        // Create a SoapExtensionAttribute for the SOAP Extension that can be
        // applied to an XML Web service method.
        [AttributeUsage(AttributeTargets.Method)]
        public class CustomHeaderExtensionAttribute : SoapExtensionAttribute
        {
            public override Type ExtensionType
            {
                get { return typeof(CustomHeaderExtension); }
            }
    
    
            public override int Priority
            {
                get
                {
                    return 100;
                }
                set
                {
                    
                }
            }
        } 
    }
    

    Now that we created our SoapExtension and the attribute that is applied to the client proxy type, we can apply it to the generated proxy.  This is one of my favorite aspects of .NET 2.0 and Visual Studio 2005... the ability to affect client proxies without ever touching the generated source.  We can use a partial type to create a method that includes our custom SOAP header without ever touching the generated Reference.cs file.

    using System;
    using System.Web.Services.Protocols;
    using DPE.Samples.Web.Services;
    using System.Web.Services.Description;
    
    namespace ConsoleApplication1.localhost 
    {
        //Note the use of the partial class and the CustomHeaderExtensionAttribute.
        //This allows us to add the CustomHeaderExtension
        //without changing the existing proxy code, which allows us to 
        //apply the attribute to just a single method on the proxy.
        public partial class MyService : SoapHttpClientProtocol
        {
            [SoapDocumentMethod("http://tempuri.org/HelloWorld", RequestNamespace = "http://tempuri.org/", ResponseNamespace = "http://tempuri.org/", Use = SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
            [CustomHeaderExtension]
            public string CustomHelloWorld()
            {
                object[] results = this.Invoke("HelloWorld", new object[0]);
                return ((string)(results[0]));
            }
        }
    }
    
    Thursday, February 27, 2014 6:55 AM