Question

Is there a way to know whether the currently executing WCF method is a OneWay method?

I'm using httpBinding and the question relates to the server side.

I searched in properties for OperationContext on MSDN and couldn't find it.

EDIT:

I used the following check:
HttpContext.Current.Response.StatusCode != 
        (int)System.Net.HttpStatusCode.Accepted;

In case of OneWay calls the status code will be 202, but it's not a good way.

Are there any better ways?

Was it helpful?

Solution

The WCF way to solve this is to:

  1. Create a custom context object that contains the data you want
  2. Create a custom behavior that populates the data
  3. Apply the behavior to your service

This requires plugging into multiple WCF extension points. Its not hard once you get the hang of it, but it is a lot of typing because of all the interfaces you need to implement (even when the method implementations are empty). Here's an example.

First define a Simple Service:

[ServiceContract]
public interface ISimple
{
    [OperationContract(IsOneWay = true)]
    void OneWay();

    [OperationContract]
    void Default();
}

[OneWayContract]
public class SimpleService : ISimple
{
    //[OneWayOperation]     // uncomment to Add context data on the operation level instead on contract.
    public void OneWay()
    {
        Console.WriteLine("OneWay() is marked IsOneWay:" + OneWayContext.Current.IsOneWay);
    }

    public void Default()
    {
        Console.WriteLine("Default() is marked IsOneWay:" + OneWayContext.Current.IsOneWay);
    }
}

It uses a custom context object to store the information you need. In this case a bool that is true if the operation IsOneWay. Notice that since you are wrapping the WCF InstanceContext, unit testing without actually hosting the service is possible. Method to create a custom context taken from this blog:

public class OneWayContext : IExtension<InstanceContext>
{
    public OneWayContext()
    {
        // if not set, default to false.
        IsOneWay = false;
    }

    public bool IsOneWay { get; set; }

    public static OneWayContext Current
    {
        get
        {
            OneWayContext context = OperationContext.Current.InstanceContext.Extensions.Find<OneWayContext>();
            if (context == null)
            {
                context = new OneWayContext();
                OperationContext.Current.InstanceContext.Extensions.Add(context);
            }
            return context;
        }
    }

    public void Attach(InstanceContext owner) { }
    public void Detach(InstanceContext owner) { }
}

Create an OperationInvoker to add the custom context to the OperationContext. Note that inserting a WCF OperationInvoker means putting it in the call stack. So all calls that it doesn't handle, it needs to pass on to the framework's "inner" OperationInvoker.

public class OneWayBehavior : IOperationInvoker
{
    IOperationInvoker innerOperationInvoker;
    public readonly bool isOneWay;

    public OneWayBehavior(IOperationInvoker innerOperationInvoker, bool isOneWay)
    {
        this.isOneWay = isOneWay;
        this.innerOperationInvoker = innerOperationInvoker;
    }

    public object[] AllocateInputs()
    {
        return innerOperationInvoker.AllocateInputs();
    }

    public object Invoke(object instance, object[] inputs, out object[] outputs)
    {
        // Everytime the operation is invoked, add IsOneWay information to the context.
        OneWayContext.Current.IsOneWay = this.isOneWay;

        return innerOperationInvoker.Invoke(instance, inputs, out outputs);
    }

    public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
    {
        return innerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
    }

    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
    {
        return innerOperationInvoker.InvokeEnd(instance, out outputs, result);
    }

    public bool IsSynchronous
    {
        get { return innerOperationInvoker.IsSynchronous; }
    }
}

Now apply the new behavior to the contract. The [OneWayContract] attribute applies a WCF Contract behavior, which applies an operation behavior. You could also apply the behavior at the operation level.

Note that the OperationDescription provides all the information that you need with WCF structures that are already populated. No resorting to reflection. No dependency on binding.

public class OneWayOperationAttribute : Attribute, IOperationBehavior
{
    public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

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

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
        // grab "IsOneWay" from the operation description and pass on the behavior's constructor.
        dispatchOperation.Invoker = new OneWayBehavior(dispatchOperation.Invoker, operationDescription.IsOneWay);
    }

    public void Validate(OperationDescription operationDescription)
    {
    }
}

public class OneWayContractAttribute : Attribute, IContractBehavior
{
    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

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

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        foreach (OperationDescription operation in contractDescription.Operations)
        {
            operation.OperationBehaviors.Add(new OneWayOperationAttribute());
        }
    }

    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {
    }
}

Now run a quick test.

public static class Program
{
    static void Main(string[] args)
    {
        ServiceHost simpleHost = new ServiceHost(typeof(SimpleService), new Uri("http://localhost/Simple"));
        simpleHost.Open();

        ChannelFactory<ISimple> factory = new ChannelFactory<ISimple>(simpleHost.Description.Endpoints[0]);
        ISimple proxy = factory.CreateChannel();

        proxy.OneWay();

        proxy.Default();

        Console.WriteLine("Press ENTER to close the host once you see 'ALL DONE'.");
        Console.ReadLine();

        ((ICommunicationObject)proxy).Shutdown();

        simpleHost.Shutdown();
    }
}

Output should be:

Default() is marked IsOneWay:False
OneWay() is marked IsOneWay:True
Press ENTER to close the host once you see 'ALL DONE'.

Notice that all our abstractions are maintained. The service only depends on the context object provided by a behavior, which is clearly marked as a dependency on the service by the attribute.

The example puts [OneWayContract] on the service class. But you should also be able to apply it to the [ServiceContract].


For completeness sake this is a copy of the entire code sample as one console app that you can paste and run.

using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace ConsoleWCF
{
    [ServiceContract]
    public interface ISimple
    {
        [OperationContract(IsOneWay = true)]
        void OneWay();

        [OperationContract]
        void Default();
    }

    [OneWayContract]
    public class SimpleService : ISimple
    {
        //[OneWayOperation]     // uncomment to Add context data on the operation level instead on contract.
        public void OneWay()
        {
            Console.WriteLine("OneWay() is marked IsOneWay:" + OneWayContext.Current.IsOneWay);
        }

        public void Default()
        {
            Console.WriteLine("Default() is marked IsOneWay:" + OneWayContext.Current.IsOneWay);
        }
    }

    public class OneWayBehavior : IOperationInvoker
    {
        IOperationInvoker innerOperationInvoker;
        public readonly bool isOneWay;

        public OneWayBehavior(IOperationInvoker innerOperationInvoker, bool isOneWay)
        {
            this.isOneWay = isOneWay;
            this.innerOperationInvoker = innerOperationInvoker;
        }

        public object[] AllocateInputs()
        {
            return innerOperationInvoker.AllocateInputs();
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            // Everytime the operation is invoked, add IsOneWay information to the context.
            OneWayContext.Current.IsOneWay = this.isOneWay;

            return innerOperationInvoker.Invoke(instance, inputs, out outputs);
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return innerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            return innerOperationInvoker.InvokeEnd(instance, out outputs, result);
        }

        public bool IsSynchronous
        {
            get { return innerOperationInvoker.IsSynchronous; }
        }
    }

    public class OneWayOperationAttribute : Attribute, IOperationBehavior
    {
        public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

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

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            // grab "IsOneWay" from the operation description and pass on the behavior's constructor.
            dispatchOperation.Invoker = new OneWayBehavior(dispatchOperation.Invoker, operationDescription.IsOneWay);
        }

        public void Validate(OperationDescription operationDescription)
        {
        }
    }

    public class OneWayContractAttribute : Attribute, IContractBehavior
    {
        public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

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

        public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
        {
            foreach (OperationDescription operation in contractDescription.Operations)
            {
                operation.OperationBehaviors.Add(new OneWayOperationAttribute());
            }
        }

        public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
        {
        }
    }

    public class OneWayContext : IExtension<InstanceContext>
    {
        public OneWayContext()
        {
            // if not set, default to false.
            IsOneWay = false;
        }

        public bool IsOneWay { get; set; }

        public static OneWayContext Current
        {
            get
            {
                OneWayContext context = OperationContext.Current.InstanceContext.Extensions.Find<OneWayContext>();
                if (context == null)
                {
                    context = new OneWayContext();
                    OperationContext.Current.InstanceContext.Extensions.Add(context);
                }
                return context;
            }
        }

        public void Attach(InstanceContext owner) { }
        public void Detach(InstanceContext owner) { }
    }



    public static class Program
    {
        static void Main(string[] args)
        {
            ServiceHost simpleHost = new ServiceHost(typeof(SimpleService), new Uri("http://localhost/Simple"));
            simpleHost.Open();

            ChannelFactory<ISimple> factory = new ChannelFactory<ISimple>(simpleHost.Description.Endpoints[0]);
            ISimple proxy = factory.CreateChannel();

            proxy.OneWay();

            proxy.Default();

            Console.WriteLine("Press ENTER to close the host once you see 'ALL DONE'.");
            Console.ReadLine();

            ((ICommunicationObject)proxy).Shutdown();

            simpleHost.Shutdown();
        }
    }

    public static class Extensions
    {
        static public void Shutdown(this ICommunicationObject obj)
        {
            try
            {
                obj.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Shutdown exception: {0}", ex.Message);
                obj.Abort();
            }
        }
    }
}

OTHER TIPS

as Tim suggested, use reflection. The following snippet should work for you

 Type serviceInterface = typeof(IService1);
 MethodInfo mi =  serviceInterface.GetMethod((System.Reflection.MethodBase.GetCurrentMethod().Name);
 Attribute attr = mi.GetCustomAttribute(typeof(OperationContractAttribute));
 Console.WriteLine(((OperationContractAttribute)attr).IsOneWay);

You could also use stackframe to get the current method name, but I used reflection all along.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top