Search

Locations of visitors to this page

Categories

On this page

WCF Behavior Ordering
Integrating Forms Authentication and Federated Security
MetadataExchangeClient message logging
Enabling InstanceProvider for Singleton Services
JSON vs XML encoder performance
Serializing Faults using XmlSerializer
Calculating WCF Message Size
SOAP message size optimization: Encoding vs compression
Windows authentication for IIS hosted WCF Service
Per Endpoint UserName Authentication
Setting ProtectionLevel from config file

Archive

Blogroll

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 49
This Year: 5
This Month: 0
This Week: 0
Comments: 69

Sign In

 Tuesday, June 02, 2009
Tuesday, June 02, 2009 1:39:43 PM (GMT Standard Time, UTC+00:00) ( WCF )

WCF behaviours are applied based on a particular evaluation order. Exact details are available here 

The ServiceHost applies behaviors in the following order:

1.     Contract

2.     Operation

3.     Endpoint

  1. Service

Please note within any collection of behaviors, ordering is NOT guaranteed. Because of that sometimes when you configure some aspects of runtime in your custom behavior (for example, TransactionRequired property of the operation) – they get overwritten by default WCF behavior. For example, following will not work and TransactionRequired will always be set to false (default value).

class TestBv : Attribute, IOperationBehavior

{

    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation){}

 

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)

    {

        dispatchOperation.TransactionRequired = true;

    }

 

    public void Validate(OperationDescription operationDescription){}

}

Why? Because here WCF is executing default OperationBehavior after the execution of your custom behavior. Please note, missing OperationBehaviorAttribute doesn’t mean that behavior won’t be applied at all rather it mean a default OperationBehavior will be applied.

So how to fix this?

Use explicit ordering: Remove the bahavior from the collection and apply it at a particular time yourself. Here is the modified behavior which will work correctly.

class TestBv : Attribute, IOperationBehavior

{

    IOperationBehavior orignal;

 

    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation){}

 

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)

    {

        orignal.ApplyDispatchBehavior(operationDescription, dispatchOperation);

        dispatchOperation.TransactionRequired = true;

    }

 

    public void Validate(OperationDescription operationDescription)

    {

        this.orignal = operationDescription.Behaviors.Find<OperationBehaviorAttribute>();

        if (orignal != null)

             operationDescription.Behaviors.Remove(orignal);

    }

}

HTH...

Comments [0] | | # 
 Wednesday, April 15, 2009
Wednesday, April 15, 2009 8:42:24 PM (GMT Standard Time, UTC+00:00) ( Federation/STS | WCF | WCF Security )

Here I have talked about a technique to flow Forms Authentication cookie to a WCF service and using it as an authentication credential for the service.  For this to work – service must be running in ASP.net compatibility mode and Forms Authentication must be configured in web.config.

Here is diagrammatic view of a simple case.

In the above diagram, website authenticates users using Forms authentication, which creates a cookie after successful authentication. When ASP.net sites calls the backend service (in response to users action) – it simply passes the Form Auth cookie as part of the call. Now because our service is running in asp.net compatibility mode and Forms Authentication module is also configured in web.config, cookie will successfully be validated and we will see correct identity in the HttpContext.User

Looking good so far. Yes?

I think, this is acceptable when you only have one to two services – however when we talk about SOA – we generally mean larger number of services and this is where this approach becomes clunky primarily due to following two reasons:

·         Every service MUST run under asp.net compatibility mode, which is slower than the default native mode. So you incur some performance plenty.

·         For the cookie flow to work, both participants MUST use the same <machineKey>.  As your share single key among more and more participants – the overall security of solution is getting weaker as key will be duplicated across many places.

So there are some scalability & security issues in the above architecture.

 Let’s see how we can improve this architecture by introducing an STS into picture. The goal here is to extract the authentication mechanism (FormsAuth cookie based) out of the services and move it to STS. Service will simply require a token from STS – it is STS’s responsibility to authenticate user using some mechanism (Forms Auth being the step one).

Here you can see ONLY STS is running in asp.net compatibility mode and services are running under native mode which will result in better performance.

Also now we are only sharing keys (<machineKey>) between two participants (STS  & Website) regardless of the number of services. Services are simply configured to require a SAML token from the STS and are agnostic to the actual client authentication mechanism. We can easily change cookie based authentication with Kerberos without impacting services at all.

Now Step 1 isn’t really possible out of box – why?

Because it’s the wsFederationHttpBinding, which talks to STS rather your proxy (configured to use wsFederationHttpBinding). So setting cookie on the proxy will NOT send it to STS and the solution is to extend WCF security framework to enable cookie flow to the STS.

In next post, I will show how to implement Step 1 while still leveraging wsFederationHttpBinding. For now please see this post for background.

 

 

Comments [1] | | # 
 Monday, April 13, 2009
Monday, April 13, 2009 9:39:22 PM (GMT Standard Time, UTC+00:00) ( WCF )

Is to possible to add a custom behavior in the channel factory created by MetadataExchangeClient?

For example, is it possible to hook a message inspector to the MetadataExchangeClient  so that generated message can be inspected/logged?

 

It turns that default ctor of MetadataExchangeClient picks the endpoint information (including behavior configuration) from the config file. You can easily achieve this by creating a behavior extension and using it on MEX endpoint. Here are steps:

Step 1: create appropriate config entries

<system.serviceModel>

  <client>

    <endpoint address="http://localhost:9090/mex" binding="mexHttpBinding" contract="IMetadataExchange" behaviorConfiguration="epBv"/>

  </client>

  <behaviors>

    <endpointBehaviors>

      <behavior name="epBv">

        <customBehaviorToEnableMessageLogging/>

      </behavior>

    </endpointBehaviors>

  </behaviors>

</system.serviceModel>

Step2: Create a behavior extension

public class customBehaviorToEnableMessageLogging : BehaviorExtensionElement

{

    public override Type BehaviorType

    {

        get { return typeof(InspectorInstallerBehavior); }

    }

 

    protected override object CreateBehavior()

    {

        return new InspectorInstallerBehavior();

    }

}

Step3: Create an endpoint behavior to install the message inspector

 

public class InspectorInstallerBehavior : IEndpointBehavior

{

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)

    {

        clientRuntime.MessageInspectors.Add(new MyInspector());

    }

}

 

Comments [0] | | # 
 Thursday, December 18, 2008
Thursday, December 18, 2008 12:25:21 AM (GMT Standard Time, UTC+00:00) ( WCF )

Many people like to use Dependency Injection containers (Unity, Windsor etc) to create WCF service objects and the common approach is to use WCF’s InstanceProvider extensibility hook to delegate instance creation to a DI container. By default this approach however doesn’t work for singleton services as InstanceProvider is never called. There is very simple fix to this problem which I’m going to explain in this post but first little background information.

WCF instancing revolve around following 3 objects:

 

InstanceContextProvider is very early in dispatch pipeline and has access to channel and message. This provider has the flexibility to reuse instance contexts based on either sessions or any other unique id in messages. When InstanceContextProvider returns an existing InstanceContext, dispatcher sees whether the returned InstanceContext has an active instance, if yes then that active instance gets used and InstanceProvider is never invoked. If there is no active instance then InstanceProvider is invoked to get one.

So when you configure your service’s InstanceContextMode=Single, there will always be cached instance and your provided will never be called. A simple fix is to overwrite the InstanceContext with a brand new one thus destroying the cached object.

    dispatchRuntime.SingletonInstanceContext = new InstanceContext(serviceHostBase);

This above change will result in your InstanceProvider to be called (only first time) to get the required service instance. Here is complete code.

 

    [ServiceContract]

    public interface ICalc

    {

        [OperationContract]

        int Add(int a, int b);

    }

    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]

    public class Calc : ICalc

    {

        public int Add(int a, int b)

        {

            Console.WriteLine(this.GetHashCode());

            return a + b;

        }

    }

 

    class Program

    {

        static void Main(string[] args)

        {

            ServiceHost h = new ServiceHost(typeof(Calc));

            h.Description.Behaviors.Add(new MyBv());

            h.Open();

            var cf = new ChannelFactory<ICalc>("*");

            cf.Open();

            var proxy = cf.CreateChannel();

            for (int i = 0; i < 10; i++)

            {

                proxy.Add(11, 2);

            }

            Console.ReadLine();

        }

    }

 

 

    public class MyBv : IServiceBehavior

    {

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

        {

            var cd = serviceHostBase.ChannelDispatchers[0] as ChannelDispatcher;

            var dispatchRuntime = cd.Endpoints[0].DispatchRuntime;

            dispatchRuntime.SingletonInstanceContext = new InstanceContext(serviceHostBase);

            dispatchRuntime.InstanceProvider = new MyInstanceProvider();

        }

    }

    public class MyInstanceProvider : IInstanceProvider

    {

 

        public object GetInstance(InstanceContext instanceContext, System.ServiceModel.Channels.Message message)

        {

            var c =  new Calc();

            return c;

        }

 

    }

Comments [0] | | # 
 Tuesday, December 16, 2008
Tuesday, December 16, 2008 2:05:16 PM (GMT Standard Time, UTC+00:00) ( WCF )

Tim has summarized that both JSON and XML has their unique place. JSON encoded data has smaller wire footprint and is easier to convert back into Javascript objects using javascript:eval function so it’s generally favoured in AJAX scenarios. I was asked about WCF encoding performance for JSON and XML (text). My initial guess was there shouldn’t be much difference in encoding performance so to validate my assumption I wrote following code to test the encoding performance and indeed it’s roughly the same.

 

To encode same message 300000 times it  took following number of ticks (roughly equal).

 

XML encoding ticks = 63810463

JSON encoding ticks = 68490315

I used following code in my testing.

 

namespace encodingPerformance

{

    [DataContract]

    public class Data

    {

        [DataMember]

        public string Name { get; set; }

        [DataMember]

        public string Address { get; set; }

        [DataMember]

        public double Salary { get; set; }

        [DataMember]

        public string Email { get; set; }

        [DataMember]

        public string SpouseName { get; set; }

        [DataMember]

        public int FamilyCount { get; set; }

        [DataMember]

        public int Age { get; set; }

    }

    class Program

    {

        static void Main(string[] args)

        {

            var d = new Data

            {

                Address = "15 ********* aveneu.",

                Age = 29,

                Email = "uzhadaf@ gamicl.com",

                FamilyCount = 3,

                Name = "******* rAhmeD2",

                Salary = 111212121.121,

                SpouseName = "Sigrasdf"

 

            };

 

            const int ITER = 3000000;

 

            var ms = new MemoryStream();

            var tbe = new TextMessageEncodingBindingElement();

            tbe.MessageVersion = MessageVersion.None;

 

            var msg = Message.CreateMessage(MessageVersion.None, "", d);

            var buf = msg.CreateBufferedCopy(int.MaxValue);

 

            var sw = Stopwatch.StartNew();

            for (int i = 0; i < ITER; i++)

            {

                msg = buf.CreateMessage();

                tbe.CreateMessageEncoderFactory().Encoder.WriteMessage(msg, ms);

                ms.Seek(0, SeekOrigin.Begin);

            }

 

            sw.Stop();

            Console.WriteLine("XML encoding ticks = {0} ", sw.ElapsedTicks);

 

            msg = Message.CreateMessage(MessageVersion.None, "", d, new DataContractJsonSerializer(typeof(Data)));

            buf = msg.CreateBufferedCopy(int.MaxValue);

            var wbe = new WebMessageEncodingBindingElement();

            var prop = new WebBodyFormatMessageProperty(WebContentFormat.Json);

 

            // measure JSON encoding time....

            sw = Stopwatch.StartNew();

            for (int i = 0; i < ITER; i++)

            {

                msg = buf.CreateMessage();

                msg.Properties.Add(WebBodyFormatMessageProperty.Name, prop); //JSON encode

                wbe.CreateMessageEncoderFactory().Encoder.WriteMessage(msg, ms);

                ms.Seek(0, SeekOrigin.Begin);

            }

            sw.Stop();

            Console.WriteLine("JON encoding ticks = {0} ", sw.ElapsedTicks);

 

        }

    }

}

Comments [0] | | # 
 Friday, August 15, 2008
Friday, August 15, 2008 9:37:30 PM (GMT Standard Time, UTC+00:00) ( WCF )

Update (25/08/2008): .net Framework 3.5 SP1 has added the support for serializing faults using XML Serializer.

[XmlSerializerFormat(SupportFaults=true)]: by setting SupportFaults=true will result in the use of Xml Serializer for fault serialization as well. The default value is false to maintain backward compatibility. The approach mentioned in this article is still valid for scenarios where you want to use DataContractSerializer for input & output messages but XML Serializer for faults (a rare requirement though).

------------------------------------------------------------------------------------------------------------------------------------------

 

Today someone asked me how to serialize the TDetail part of FaultException<TDetail> using XmlSerializer. The scenario was to interoperate with an existing schema using some of the XSD features (attributes etc.), which are currently not supported by DataContractSerializer. The solution is to Subclass the MessageFault class and use XmlSerializer to serialize the TDetail.

public class XmlSerializerMessageFault : MessageFault

{

    FaultCode code;

    FaultReason reason;

    object details;

    public XmlSerializerMessageFault(FaultCode code, FaultReason reason, object details)

    {

        this.details = details;

        this.code = code;

        this.reason = reason;

    }

    public override FaultCode Code

    {

        get { return code; }

    }

    public override bool HasDetail

    {

        get { return (details != null); }

    } 

    protected override void OnWriteDetailContents(System.Xml.XmlDictionaryWriter writer)

    {

        var ser = new XmlSerializer(details.GetType());

        ser.Serialize(writer, details);

        writer.Flush();

    } 

    public override FaultReason Reason

    {

        get { return reason; }

    }

}

Fault was created from a global error handler (IErrorHandler).

public class ErrorHandlerEx : IErrorHandler

{

    public bool HandleError(Exception error)

    {

        return true;

    } 

    public void ProvideFault(

        Exception error, MessageVersion version, ref Message fault)

    {

        if (error is FaultException)

        {

            // extract our FaultContract object from the exception object.

            var detail = error.GetType().GetProperty("Detail").GetGetMethod().Invoke(error, null);

            // create a fault message containing our FaultContract object

            var msgFault = new XmlSerializerMessageFault(FaultCode.CreateSenderFaultCode("systemDown", "http://zamd.net"), new FaultReason("System is down"), detail);

            fault = Message.CreateMessage(version, msgFault, "http://zamd.net/action");

        }

    }

}

Xml output

<Fault xmlns="http://schemas.microsoft.com/ws/2005/05/envelope/none">

  <Code>

    <Value>Sender</Value>

    <Subcode>

      <Value xmlns:a="http://zamd.net">a:systemDown</Value>

    </Subcode>

  </Code>

  <Reason>

    <Text xml:lang="en-GB">System is down</Text>

  </Reason>

  <Detail>

    <GreaterThan3Fault ErrorCode="90192" Location="ISB" xmlns="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

      <FaultMessage>Count cannot be greate than 3. Please try again later.</FaultMessage>

    </GreaterThan3Fault>

  </Detail>

</Fault>

Comments [2] | | # 
Friday, August 15, 2008 2:46:31 PM (GMT Standard Time, UTC+00:00) ( WCF )

Here I have talked about various encoding options and their impact on the message size (byte stream produced by WCF). The question is how you can measure the size of the byte stream produced by WCF?

Size of the byte stream depends on the encoder being used and different encoder produced byte stream of different sizes.

I have written a MessageInspector, which does message/stream rewriting to calculate the size of the final byte stream. Once you have configured this inspector in your client it will print the difference in the size of message created by text encoder vs your configured encoder (MTOM, Binary or GZip).  Send method of Inspector looks like following:

public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)

{

    var mb = request.CreateBufferedCopy(int.MaxValue);

    request = mb.CreateMessage();

    var ms = new MemoryStream();

 

    // Dump message size based on text encoder.

    using (var memWriter = XmlDictionaryWriter.CreateTextWriter(ms))

    {

        mb.CreateMessage().WriteMessage(memWriter);

        memWriter.Flush();

        Console.WriteLine("Message size using text encoder {0}", ms.Position);

    }

    ms = new MemoryStream();

    if (gzipEncoding != null)

    {// GZip Special case

        var encoder = gzipEncoding.CreateMessageEncoderFactory().CreateSessionEncoder();

        encoder.WriteMessage(mb.CreateMessage(), ms);

        Console.WriteLine("GZip encoded size {0}", ms.Position);

        return null;

    }

    // just wrap the message - and wrapper will do the trick.

    request = new WrappingMessage(request);

    return null;

}

 

public class WrappingMessage : Message

{

    Message innerMsg;

    MessageBuffer msgBuffer;

    public WrappingMessage(Message inner)

    {

        this.innerMsg = inner;

        msgBuffer = innerMsg.CreateBufferedCopy(int.MaxValue);

        innerMsg = msgBuffer.CreateMessage();

    }

    public override MessageHeaders Headers

    {

        get { return innerMsg.Headers; }

    }

 

    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)

    {

        innerMsg.WriteBodyContents(writer);

    }

 

    public override MessageProperties Properties

    {

        get { return innerMsg.Properties; }

    }

 

    public override MessageVersion Version

    {

        get { return innerMsg.Version; }

    }

 

    protected override void OnWriteMessage(XmlDictionaryWriter writer)

    {

        // write message to the actual stream using encoder..

        base.OnWriteMessage(writer);

        writer.Flush();

        // write message to MemoryStream (using encoder) to get it's size.

        var copy = msgBuffer.CreateMessage();

        DumpEncoderSize(writer, copy);

    }

    private static void DumpEncoderSize(System.Xml.XmlDictionaryWriter writer, Message copy)

    {

        var ms = new MemoryStream();

        string configuredEncoder = string.Empty;

        if (writer is IXmlTextWriterInitializer)

        {

            var w = (IXmlTextWriterInitializer)writer;

            w.SetOutput(ms, Encoding.UTF8, true);

            configuredEncoder = "Text";

        }

        else if (writer is IXmlMtomWriterInitializer)

        {

            var w = (IXmlMtomWriterInitializer)writer;

            w.SetOutput(ms, Encoding.UTF8, int.MaxValue, "", null, null, true, false);

            configuredEncoder = "MTOM";

        }

        else if (writer is IXmlBinaryWriterInitializer)

        {

            var w = (IXmlBinaryWriterInitializer)writer;

            w.SetOutput(ms, null, null, false);

            configuredEncoder = "Binary";

        }

        copy.WriteMessage(writer);

        writer.Flush();

        var size = ms.Position;

        Console.WriteLine("Message size using configured ({1}) encoder {0}",  size,configuredEncoder);

    }

 

}

I have attached complete solution with this post.

Download: MessageSize.zip

Comments [0] | | # 
 Wednesday, June 18, 2008
Wednesday, June 18, 2008 12:13:13 PM (GMT Standard Time, UTC+00:00) ( WCF )

Sometimes there is a requirement to reduce the size of the messages sent between client and service. Encoder is a WCF component which transforms a SOAP message (Infoset) into a byte stream and vice versa. Kenny has written about the different encoders available in WCF and I highly recommend reading his post.

His post is focused on the performance characteristics of various encoders for various types of messages (with or without binary data).  Here I will talk about other aspect of the encoders. i.e size of the encoded byte stream.

Usually there are few requirements which derive your message size optimization options.

  1. Whether you own both side of communication (client & service) or not?
  2.  Do you require XML(Infoset) representation on the wire?
  3. Deployment options

 Let’s talk about various options in the light of above requirements.

If you own both sides then the recommendation is to use binary encoding which is the default encoding with NetTcpBinding & NetNamedPipeBinding. The XML structure on the wire will not preserved with this encoding however you will get huge savings in message size without writing any additional code.

If byte stream produced by binary encoding still doesn’t meet your bandwidth constraint then you can think of applying compression on this byte stream. There is a compression encoder available as part of WCF samples which you can use as a base to write your own compression encoder. Again this is only useful if wire format is not important to you. 

If you have a scenario in which message flows through intermediaries (e.g IBM DataPower) and certain policies are applied on the message (based on SOAP headers) then above options will not work. In these scenarios you can devise a scheme to only compress SOAP body and replace the actual body with compressed string (quite similar to XML encryption & Digital Signature) while preserving SOAP headers. Pablo has created a protocol (WS-Compression) to achieve this exact requirement. His sample is not updated to the latest version of WCF but you can get the idea by looking at the code. Also you can achieve this same functionality by extending WCF service model layer. For example, by writing a message inspector and doing compression there. 

I did a quick test of various encoding and compression choices and here are my findings. In my tests I have used a large dataset, consisting of 91 customer records, each having 1-20 orders and each order consist of 5-10 products.  

cid:image002.png@01C8D13A.DE114040

Encoding

Size

Bytes

Kb

Standard Text Encoding

1318179

1288

Standard Binary Encoding

259141

253

Gzip Compression using the compressing encoder        

131242

128

Gzip Compression using a custom compression channel

103428

101

 

 

 

 

 

As you can see there is huge size difference between text and binary encoding of xml messages. Note that in my testing there wasn’t any binary data in SOAP messages at all. If there would be some binary data then text encoded messages would be even bigger because of base64 encoding of binary data. So with binary encoding you can save lot of bandwidth without doing any custom coding and that’s why this should be the first choice.

In addition to using a compression encoder you can also use IIS compression if you are hosting in IIS or WPAS (Windows Server 2008). IIS compression is pretty much similar to compression encoder option with the only different that in the former case compression is done by IIS while in later case it is done by a WCF encoder. IIS compression has the disadvantage that you need to configure compression separate to your WCF configuration (IIS metabase).  Using a compression encoder or a custom compression channel also enables you to use a compression algorithm of your choice (based on your compression requirement) while IIS limits you to GZip compression only. In conclusion, the need for xml structure on the wire will be the deciding factor on what option will best suit you.

Comments [0] | | # 
 Friday, February 15, 2008
Friday, February 15, 2008 6:26:16 PM (GMT Standard Time, UTC+00:00) ( WCF )

Security settings for this service require 'Anonymous' Authentication but it is not enabled for the IIS application that hosts this service.

I have seen people struggling while configuring integrated windows authentication for their IIS hosted WCF service and are getting above exception. There are two settings required to make this work.

·         First enable Integrated Windows Authentication on IIS

·         Set the clientCredentialType to Windows

Here is a sample binding to enable windows authentication in IIS.

<bindings>

  <basicHttpBinding>

    <binding name="basicHttpBinding IMyService">

      <security mode="TransportCredentialOnly">

        <transport clientCredentialType="Windows"/>

      </security>

    </binding>

  </basicHttpBinding>

</bindings>

However even after making these two changes you might still be getting the exception and the prime reason is that one of the service endpoints (most likely the MEX endpoint) still requires anonymous access while it is disabled in IIS.

Why mostly MEX endpoint?

Because the default settings of mexHttpBinding allows anonymous access by setting clientCredentialsType to None. So if you have a mex endpoint and you are using out of the box mexHttpBinding you will be getting the above exception.

A simple fix is to use the same secured binding, in this case basicHttpBinding IMyService, for the mex endpoint as well or create a new binding and disable the anonymous access for mex endpoint as well.

Comments [6] | | # 
Friday, February 15, 2008 4:52:59 PM (GMT Standard Time, UTC+00:00) ( WCF )

In this post I will explain an extensibility mechanism to support multiple userName/password validation modes in the same service. 

When you use UserName authentication in WCF there are couple of different mode for the actual UserName/Password validation. These modes are:

·         Windows: Credentials are verified against a windows account (either local or domain).

·         Custom: Credentials are verified by calling your custom UserNamePasswordValidator component.

·         Membership: Credentials are verified by using SqlMembershipProvider.

You can configure userName authentication and the actual validation mode using the following service behavior:

<serviceCredentials>

  <serviceCertificate findValue="01 0a 27" storeName="My" x509FindType="FindByThumbprint"/>

  <userNameAuthentication userNamePasswordValidationMode="Custom"

                          customUserNamePasswordValidatorType="UserNameValidationSamples"/>

</serviceCredentials>

Using this behavior however will results in the same validation mode (custom in above case) being used for all the endpoints. Sometimes there is a requirement to support different mode of UserName/Password validation for different endpoints belonging to the same service. Out of the box WCF doesn't provide this functionality so I have created a custom endpoint behavior and a behavior extension to achieve this functionality.

The implementation of my custom behavior clones the ServiceCredentials behavior and overrides the value of userNamePasswordValidationMode with the endpoint's mode value.

namespace UserNameValidationSample

{

    //behavior configuration extension element

    public class UserNameValidationBehaviorElement : BehaviorExtensionElement

    {

        public UserNameValidationBehaviorElement()

        {

        }

        public override Type BehaviorType

        {

            get { return typeof(UserNameValidationBehavior); }

        }

        protected override object CreateBehavior()

        {

            return new UserNameValidationBehavior(this.Mode);

        }

        [ConfigurationProperty("mode")]

        public UserNamePasswordValidationMode Mode

        {

            get

            {

                return (UserNamePasswordValidationMode)base["mode"];

            }

            set

            {

                base["mode"] = value;

            }

        }

        ConfigurationPropertyCollection properties = null;

        protected override ConfigurationPropertyCollection Properties

        {

            get

            {

                if (this.properties == null)

                {

                    ConfigurationPropertyCollection propertys = new ConfigurationPropertyCollection();

                    propertys.Add(

                                    new ConfigurationProperty("mode", typeof(UserNamePasswordValidationMode), null,

                                                                              ConfigurationPropertyOptions.IsRequired));

                    this.properties = propertys;

                }

                return this.properties;

            }

        } 

    }

    public class UserNameValidationBehavior : IEndpointBehavior

    {

        UserNamePasswordValidationMode mode;

        public UserNameValidationBehavior(UserNamePasswordValidationMode mode)

        {

            mode = mode;

        }

        #region IEndpointBehavior Members

 

        public void AddBindingParameters(ServiceEndpoint endpoint,

                System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

        {

            // override the validation mode for this endpoint. 

            var sc = bindingParameters.Remove<ServiceCredentials>();

            if (sc != null)

            {

                var scCopy = sc.Clone();

                scCopy.UserNameAuthentication.UserNamePasswordValidationMode = mode; 

                bindingParameters.Add(scCopy);

            }

        } 

        public void ApplyClientBehavior(ServiceEndpoint endpoint,

                               System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)

        {} 

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint,

                 System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)

        {} 

        public void Validate(ServiceEndpoint endpoint)

        {} 

        #endregion

    }

}

This is how you define and use a the behavior from config file:

<system.serviceModel>

  <services>

    <service name="UserNameValidationSamples.CalculatorService" behaviorConfiguration="credBehavior">

      <endpoint address="http://localhost:8080/CalculatorService/Custom"

                binding="wsHttpBinding"

                bindingConfiguration="noSecureConversation"

                contract="UserNameValidationSamples.ICalculatorService"

                behaviorConfiguration="customValidation"

                />

      <endpoint address="http://localhost:8080/CalculatorService/Windows"

                binding="wsHttpBinding"

                bindingConfiguration="noSecureConversation"

                contract="UserNameValidationSamples.ICalculatorService"

                behaviorConfiguration="windowsValidation"

                />

    </service>

  </services> 

  <behaviors>

    <endpointBehaviors>

      <!-- my custom behavior -->

      <behavior name="customValidation">

        <userNameValidation mode="Custom"/>

        <!-- Custom, Windows, Membership-->

      </behavior> 

      <behavior name="windowsValidation">

        <userNameValidation mode="Windows"/>

        <!-- Custom, Windows, Membership-->

      </behavior> 

    </endpointBehaviors>

  </behaviors> 

  <!-- create a userNameValidation extension -->

  <extensions>

    <behaviorExtensions>

      <add name="userNameValidation" type="UserNameValidationSamples.UserNameValidationBehaviorElement"/>

    </behaviorExtensions>

  </extensions>

</system.serviceModel>

Source code of a VS2008 project is attached with this post.

Download: usernamesamples.zip

Comments [0] | | # 
 Sunday, February 10, 2008
Sunday, February 10, 2008 10:34:23 PM (GMT Standard Time, UTC+00:00) ( WCF )

Protectionlevel enum defines the level of security in terms of encryption & signature. When programing at the Service Model layer, ProtectionLevel can be set at various levels:

·         ServiceContract level: all messages/faults of a service contract will have same protection level

·         OperationContract level: An operation can override the protection level specified at the contract level. So messages/faults of an operation can have different protection requirements.

·         MessageContract level: Individual messages/faults can also specify their own protection level, overriding the one specified at higher level.

If your only requirement is to be able to set protection level from config file, then a simple option could be to access the description object model and update the contract protection level there before opening the service host or proxy.

ServiceHost svc = new ServiceHost(typeof(CalculatorService));

svc.Description.Endpoints[0].Contract.ProtectionLevel = GetProtectionLevelFromConfig();

If however, you want to set different protection level for different endpoints, then you have to do some more work.

Specifically you have to create an endpoint behavior, from which you will access the ChannelProtectionRequirements parameter to specify endpoint specific protection requirements. Here is step by step procedure to do this:

1.       Create an endpoint behavior and add following code to AddBindingParameters method.

    public class ProtectionLevelBehavior : IEndpointBehavior

    {

        ProtectionLevel level;

 

        internal ProtectionLevelBehavior(ProtectionLevel level)

        {

            this.level = level;

        }

        #region IEndpointBehavior Members

 

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

        {

            var proReq =

                bindingParameters.Remove<ChannelProtectionRequirements>();

            proReq = new ChannelProtectionRequirements();

            MessagePartSpecification unProtectedSpec = new MessagePartSpecification();

            MessagePartSpecification protectedSpec = new MessagePartSpecification(true);

            //I'm setting same protection level for all the actions.

            // You could specify different protection level per action, if required.

            // Also note, I haven't implemented any support for custom SOAP headers.

            // However that can easily be added using the same mechansim.

            switch (level)

            {

                case ProtectionLevel.None:

                    proReq.OutgoingSignatureParts.AddParts(unProtectedSpec, "*");

                    proReq.IncomingSignatureParts.AddParts(unProtectedSpec, "*");

                    proReq.OutgoingEncryptionParts.AddParts(unProtectedSpec, "*");

                    proReq.IncomingEncryptionParts.AddParts(unProtectedSpec, "*");

                    break;

                case ProtectionLevel.Sign:

                    proReq.OutgoingSignatureParts.AddParts(protectedSpec, "*");

                    proReq.IncomingSignatureParts.AddParts(protectedSpec, "*");

                    proReq.OutgoingEncryptionParts.AddParts(unProtectedSpec, "*");

                    proReq.IncomingEncryptionParts.AddParts(unProtectedSpec, "*");

                    break;

                case ProtectionLevel.EncryptAndSign:

                    proReq.OutgoingSignatureParts.AddParts(protectedSpec, "*");

                    proReq.IncomingSignatureParts.AddParts(protectedSpec, "*");

                    proReq.OutgoingEncryptionParts.AddParts(protectedSpec, "*");

                    proReq.IncomingEncryptionParts.AddParts(protectedSpec, "*");

                    break;

            }

            // Add our protection requirement for this endpoint into the binding params.

            bindingParameters.Add(proReq);

        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)

        {

        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)

        {

        }

        public void Validate(ServiceEndpoint endpoint)

        {

        }

        #endregion

    }

2.       Now create a behavior extension so that this behavior can be enabled/disabled using configuration file. The behavior extension will read the configured protectionLevel from config file and will create an instance of above endpoint behavior to plug it into behavior collections.

    //behavior configuration extension element

        public class ProtectionLevelBehaviorElement : BehaviorExtensionElement

        {

            public ProtectionLevelBehaviorElement()

            {

            }

            public override Type BehaviorType

            {

                get { return typeof(ProtectionLevelBehavior); }

            }

 

            protected override object CreateBehavior()

            {

                return new ProtectionLevelBehavior(this.Level);

            }

            [ConfigurationProperty("level")]

            public ProtectionLevel Level

            {

                get

                {

                    return (ProtectionLevel)base["level"];

                }

                set

                {

                    base["level"] = value;

                }

            }

            ConfigurationPropertyCollection properties = null;

            protected override ConfigurationPropertyCollection Properties

            {

                get

                {

                    if (this.properties == null)

                    {

                        ConfigurationPropertyCollection propertys = new ConfigurationPropertyCollection();

                        propertys.Add(new ConfigurationProperty("level", typeof(ProtectionLevel), null, ConfigurationPropertyOptions.IsRequired));

                        this.properties = propertys;

                    }

                    return this.properties;

                }

            }

        }

3.      And this is how you will specify the protection level from config file using the above behavior extension.

  <behaviors>

    <endpointBehaviors>

      <behavior name="endpointSecurityConfig">

        <protectionLevel level="Sign"/>

        <!-- None, Sign, EncryptAndSign-->

      </behavior>

    </endpointBehaviors>

  </behaviors>

I have attached the source code with this post. Feel free to download and reuse.

Download: protectionlevelsample.zip

Comments [1] | | #