Search

Locations of visitors to this page

Categories

On this page

Adding Dynamic methods to a WCF Service
Authenticated messaging in WF 4
Using Request Headers for Metadata Address

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

 Friday, February 05, 2010
Friday, February 05, 2010 4:25:46 PM (GMT Standard Time, UTC+00:00) ( WCF Extensibility )

Someone asked me if it’s possible to add a Ping method to every service contract without repeatedly defining it in each service contract?  The answer is yes & and it’s fairly simply too.

Here I have a LoginService, which require that implicit Ping method (DateTime Ping(){})like all other service.

 

[ServiceContract]

class LoginService

{

    [OperationContract]

    public bool Login(string userName, string password)

    {

        return true;

    }

}

This is how you would configure your host for these implicit methods.

class Program

{

    static void Main(string[] args)

    {

        var sh = new ServiceHost(typeof(LoginService), new Uri("http://localhost:9001"));

 

        GeneratePingMethod(sh);

        sh.Open();

 

        Console.ReadLine();

        sh.Close();

    }

}

 

Please note “GeneratePingMethod” adds the Ping method to every endpoint.  I’m simply creating the operation using the WCF description API and adding it into the description tree. PingImplementationBehavior plugs in a custom OperationInvoker which calls the actual Ping implementation.

 

private static void GeneratePingMethod(ServiceHost sh)

{

    foreach (var endpoint in sh.Description.Endpoints)

    {

        var cd = endpoint.Contract;

        var od = new OperationDescription("Ping", cd);

        var inputMsg = new MessageDescription(cd.Namespace + cd.Name + "/Ping", MessageDirection.Input);

        var outputMsg = new MessageDescription(cd.Namespace + cd.Name + "/PingResponse", MessageDirection.Output);

        var retVal = new MessagePartDescription("PingResult", cd.Namespace); ;

        retVal.Type = typeof(DateTime);

 

        outputMsg.Body.ReturnValue = retVal;

 

 

        od.Messages.Add(inputMsg);

        od.Messages.Add(outputMsg);

 

        od.Behaviors.Add(new DataContractSerializerOperationBehavior(od));

        od.Behaviors.Add(new PingImplementationBehavior());

 

        cd.Operations.Add(od);

    }

}

 

class PingImplementationBehavior : IOperationBehavior

{

    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)

    { }

 

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)

    { }

 

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)

    {

        dispatchOperation.Invoker = new PingInvoker();

    }

 

    public void Validate(OperationDescription operationDescription)

    { }

}

Again a fairly simple invoker which is tightly coupled to the signature of Ping method.

class PingInvoker : IOperationInvoker

{

    public object[] AllocateInputs()

    {

        return new object[0];

    }

 

    public object Invoke(object instance, object[] inputs, out object[] outputs)

    {

        outputs = new object[0];

        return Ping();

    }

 

    public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)

    {

        throw new NotImplementedException();

    }

 

    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)

    {

        throw new NotImplementedException();

    }

 

    public bool IsSynchronous

    {

        get { return true; }

    }

 

    public static DateTime Ping()

    {

        return DateTime.Now;

    }

}

Comments [0] | | # 
 Tuesday, January 19, 2010
Tuesday, January 19, 2010 4:44:20 PM (GMT Standard Time, UTC+00:00) ( WF 4 )

Authenticated messaging is one limitation in WF 4 as currently there is no API for setting up credentials to be used with a Send activity. Having said that, ChannelFactory used by Send activity still picks up the behaviour configuration from the config file.

So you can potentially write a custom behaviour to supply additional credentials to the Send activity. Let’s see a simple clientCredentialsAdapter behaviour which will enable you to setup userName/Password in config file. It’s almost always a bad idea to keep unencrypted password in config files so it is recommended that you should use config encryption to encrypt the behaviour section.

<behavior name="credentialAdapter">

  <clientCredentials>

    <serviceCertificate>

      <authentication certificateValidationMode="None"/>

      <defaultCertificate findValue="f7ad5a9dcc35f21ffc691925515f48eb44f5e07a" x509FindType="FindByThumbprint" storeLocation="CurrentUser" storeName="My"/>

    </serviceCertificate>

  </clientCredentials>

 

  <clientCredentialsAdapter>

    <userName userName="configUser" password="p@ssw0rd!"/>

  </clientCredentialsAdapter>

</behavior>

At this point, you can configure this behaviour on an endpoint used by a Send activity. This change will enable Send activity to use this userName/Password for securing outgoing messages.

public class ClientCredentialsAdapterBehavior : IEndpointBehavior

{

    ClientCredentialsAdapterBehaviorElement configElement;

 

    public ClientCredentialsAdapterBehavior(ClientCredentialsAdapterBehaviorElement configElement)

    {

        this.configElement = configElement;

    }

 

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters){}

 

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

    {

        var clientCredentials = endpoint.Behaviors.Find<ClientCredentials>();

        Adapt(clientCredentials);

    }

 

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }

 

    public void Validate(ServiceEndpoint endpoint){}

 

    void Adapt(ClientCredentials orignalCredentials)

    {

        orignalCredentials.UserName.UserName = configElement.UserName.UserName;

        orignalCredentials.UserName.Password = configElement.UserName.Password;

    }

}

So that was simpleJ What about a scenario where you have userName/Password as part of workflow state and you want to use that instead?

Well, in that case there is more work involved which I’ll discuss in next post.

Comments [0] | | # 
 Thursday, January 14, 2010
Thursday, January 14, 2010 7:44:42 PM (GMT Standard Time, UTC+00:00) ( )

WCF by default adds the listen address of the endpoints in the WSDL so in a scenario where the private address of the server is different from the public address you get incorrect endpoint address in WSDL.

For example if a service is listening on http://localhost:7721/ and you access its metadata using a virtual IP http://102.212.0.117/?wsdl, you get following:

Now this behavior might not work in certain scenarios and you want the metadata generation to dynamically pick the address from the request headers, similar to ASMX web services.

UseRequestHeadersForMetadataAddressBehavior enables exactly this. This opt-in behavior sets the address of endpoints dynamically, based on the request header.  

Like all other WCF behaviors, this behavior can be enabled either in the code or config.

sh.Description.Behaviors.Add(new UseRequestHeadersForMetadataAddressBehavior());

<system.serviceModel>

  <behaviors>

    <serviceBehaviors>

      <behavior>

        <useRequestHeadersForMetadataAddress/>

      </behavior>

    </serviceBehaviors>

  </behaviors>

</system.serviceModel>

This behavior is added in .NET 4.0 and there is a QFE available for 3.5 SP1: http://support.microsoft.com/kb/971842

Comments [2] | | #