Search code examples
wcfrestwrapperwebhttpbinding

Implementing RequestWrapper for RESTful WCF service


I've written a simple object called RequestWrapper that contains single method of type:

TResult WrapRequest<TResult>(Func<TResult> action)

It wraps any action with try..catch, error handling, logging, database connection, transaction (commit & rollback), etc.

Currently I use it like this: (example, not production code)

return RequestWrapper.WrapRequest(() =>
{
    Topic entity = GetRepository<Topic>().Find(uid);

    if (entity == null)
        throw new EntityNotFoundException("Topic not found.");

    return new Topic
    {
        Name = entity.Name,
        Posts = entity.Posts.Select(x => new Post
        {
            Body = x.Body,
        }).ToList()
    };
});

I simply wrap around every method of my RESTful web service (using WCF and WebHttpBinding).

My question is: How should I implement behavior that would do the wrapping for me automatically? Is it possible?


Solution

  • You can use a custom IOperationInvoker which wraps the original one does what you need. The code below shows a sample implementation of one, and you can find more information about invokers at http://blogs.msdn.com/b/carlosfigueira/archive/2011/05/17/wcf-extensibility-ioperationinvoker.aspx.

    public class StackOverflow_10156890
    {
        [ServiceContract]
        public interface ITest
        {
            [WebGet]
            [WrappedOperationBehavior]
            string Echo(string text);
            [WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest)]
            [WrappedOperationBehavior]
            int Divide(int x, int y);
        }
        public class Service : ITest
        {
            public string Echo(string text)
            {
                return text;
            }
            public int Divide(int x, int y)
            {
                return x / y;
            }
        }
        class RequestWrapperOperationInvoker : IOperationInvoker
        {
            IOperationInvoker originalInvoker;
    
            public RequestWrapperOperationInvoker(IOperationInvoker originalInvoker)
            {
                this.originalInvoker = originalInvoker;
            }
    
            public object[] AllocateInputs()
            {
                return this.originalInvoker.AllocateInputs();
            }
    
            public object Invoke(object instance, object[] inputs, out object[] outputs)
            {
                Console.WriteLine("Do initialization, etc. here");
                object result = null;
                try
                {
                    result = this.originalInvoker.Invoke(instance, inputs, out outputs);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Log exception: {0}: {1}", e.GetType().FullName, e.Message);
                    result = null;
                    outputs = null;
                }
                finally
                {
                    Console.WriteLine("Do finalization, etc. here");
                }
    
                return result;
            }
    
            public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
            {
                throw new NotSupportedException("Only synchronous operations supported");
            }
    
            public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
            {
                throw new NotSupportedException("Only synchronous operations supported");
            }
    
            public bool IsSynchronous
            {
                get { return true; }
            }
        }
        class WrappedOperationBehaviorAttribute : Attribute, 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 RequestWrapperOperationInvoker(dispatchOperation.Invoker);
            }
    
            public void Validate(OperationDescription operationDescription)
            {
            }
        }
        public static void Test()
        {
            string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
            ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
            var endpoint = host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "");
            endpoint.Behaviors.Add(new WebHttpBehavior());
            host.Open();
            Console.WriteLine("Host opened");
    
            WebClient c = new WebClient();
            Console.WriteLine(c.DownloadString(baseAddress + "/Echo?text=Hello%20world"));
    
            c = new WebClient();
            c.Headers[HttpRequestHeader.ContentType] = "application/json";
            Console.WriteLine(c.UploadString(baseAddress + "/Divide", "{\"x\":12,\"y\":0}"));
    
            Console.Write("Press ENTER to close the host");
            Console.ReadLine();
            host.Close();
        }
    }