5
\$\begingroup\$

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
            }
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$
  • \$\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 to await method((T)proxy); \$\endgroup\$ – 303 Jul 24 '14 at 19:02
  • \$\begingroup\$ @303: Ensure that you're using ServiceMethodAsync as your argument type. \$\endgroup\$ – Stephen Cleary Jul 24 '14 at 20:21
  • \$\begingroup\$ Duh! Missed that somehow. Thank you very much for your help! \$\endgroup\$ – 303 Jul 24 '14 at 22:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.