I decided to re-write my code responsible for WCF calls, as all of my methods had try
-catch
-finally
blocks. I read that it is bad idea to use a using()
statement as it does not close the WCF connection. After digging around, I found a solution based on delegates. Yesterday I decided to add an activator for calls that are made by async controllers in my MVC Web API app.
I am not sure about the code used for async. My doubts are based on this article.
Note - some code removed for brevity.
Here is the code for activators:
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Threading.Tasks;
using System.Web;
namespace MyProject.Concrete
{
public class SvcActivator<T>
{
public delegate void ServiceMethod(T proxy);
private BasicHttpBinding binding
{
get
{
return new BasicHttpBinding
{
MaxReceivedMessageSize = int.MaxValue,
MaxBufferPoolSize = int.MaxValue
};
}
}
public static void Use(ServiceMethod method, string url)
{
var channelFactory = new ChannelFactory<T>(binding);
var endpoit = new EndpointAddress(url);
IClientChannel proxy = (IClientChannel)channelFactory.CreateChannel(endpoit);
bool success = false;
try
{
method((T)proxy);
proxy.Close();
success = true;
}
catch (Exception)
{
throw;
}
finally
{
if (!success)
{
proxy.Abort();
}
}
}
public static Task UseAsync(ServiceMethod method, string url, HttpBindingBase binding)
{
var channelFactory = new ChannelFactory<T>(binding);
var endpoit = new EndpointAddress(url);
IClientChannel proxy = (IClientChannel)channelFactory.CreateChannel(endpoit);
bool success = false;
return Task.Run(() =>
{
try
{
method((T)proxy);
proxy.Close();
success = true;
}
catch (Exception)
{
throw;
}
finally
{
if (!success)
{
proxy.Abort();
}
}
});
}
}
}
Here is sample DAL:
using System;
using System.Configuration;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using MyProject.OrdersService;
namespace MyProject.Concrete
{
public class OrdersDAL : CommonDAL
{
public Order GetOrder(int id)
{
Order order = null;
SvcActivator<IOrdersService>.Use(svc =>
{
order = svc.GetOrder(id);
}, "url_goes_here");
return order;
}
public async Task<Order> GetOrderAsync(int id)
{
Order order = null;
await SvcActivator<IOrdersService>.UseAsync(svc =>
{
order = svc.GetOrder(id);
}, "url_goes_here");
return order;
}
}
}
And finally, here is sample API controller:
namespace MyProject.Controllers
{
public class OrdersController : CommonController
{
private OrdersDAL ordersDAL;
public OrdersController()
{
ordersDAL = new OrdersDAL();
}
//sync call
[HttpGet, Route("orders/{id}")]
public HttpResponseMessage GetOrder(int id)
{
try
{
var order = ordersDAL.GetOrder(id);
if (order == null)
{
return RequestHelper.CreateResponse(Request, HttpStatusCode.NotFound, "Order not found.");
}
return RequestHelper.CreateResponse(Request, HttpStatusCode.OK, order);
}
catch (Exception ex)
{
//Error logging here
}
}
//async method call
[HttpGet, Route("orders/{id}")]
public async Task<HttpResponseMessage> GetOrder(string id)
{
try
{
var order = await ordersDAL.GetOrderAsync(id);
if (order == null)
{
return RequestHelper.CreateResponse(Request, HttpStatusCode.NotFound, "Order not found.");
}
return RequestHelper.CreateResponse(Request, HttpStatusCode.OK, order);
}
catch (Exception ex)
{
//Error logging here
}
}
}
}
1 Answer 1
You don't want to use Task.Run
from WebAPI (or other service frameworks) because that interferes with the ASP.NET thread pool heuristics.
Instead, use the naturally-asynchronous methods that are available in your WCF client proxy. (Side note: if the proxy is from a very old Visual Studio version, you may need to recreate it).
Then you can look at making your SvcActivator
truly asynchronous. First, you'll need an asynchronous delegate type:
public delegate Task ServiceMethodAsync(T proxy);
Next, your UseAsync
method:
public static async Task UseAsync(ServiceMethodAsync method, string url)
{
var channelFactory = new ChannelFactory<T>(binding);
var endpoit = new EndpointAddress(url);
IClientChannel proxy = (IClientChannel)channelFactory.CreateChannel(endpoit);
bool success = false;
try
{
await method((T)proxy);
proxy.Close();
success = true;
}
catch (Exception)
{
throw;
}
finally
{
if (!success)
{
proxy.Abort();
}
}
}
Your DAL can use it as such:
public async Task<Order> GetOrderAsync(int id)
{
Order order = null;
await SvcActivator<IOrdersService>.UseAsync(async svc =>
{
order = await svc.GetOrderAsync(id);
}, "url_goes_here");
return order;
}
The end result is that you are using async
all the way, rather than wrapping a synchronous WCF call (GetOrder
) within a Task.Run
.
-
\$\begingroup\$ Hi Stephen, thank you for your reply. I've made change to the code, but it will not compile. I am getting error
Cannot await 'void'
and it points toawait method((T)proxy);
\$\endgroup\$303– 3032014年07月24日 19:02:05 +00:00Commented Jul 24, 2014 at 19:02 -
\$\begingroup\$ @303: Ensure that you're using
ServiceMethodAsync
as your argument type. \$\endgroup\$Stephen Cleary– Stephen Cleary2014年07月24日 20:21:42 +00:00Commented Jul 24, 2014 at 20:21 -
\$\begingroup\$ Duh! Missed that somehow. Thank you very much for your help! \$\endgroup\$303– 3032014年07月24日 22:24:54 +00:00Commented Jul 24, 2014 at 22:24
Explore related questions
See similar questions with these tags.