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?
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();
}
}