10

I work on project where web application hosted on web server calls WCF services hosted on the app server. Proxy for WCF calls is created by ChannelFactory and calls are made via channel, example:

(omitting using block)

var factory = new ChannelFactory<IUserService>(endpointConfigurationName);
var channel = factory.CreateChannel();

var users = channel.GetAllUsers();

If I understand it well call via channel is async and thread on the web server is idle during request and just wait for a response.

I would like to make call async like this:

var users = await channel.GetAllUsersAsync();

Is there a way how to make call with ChannelFactory and channels async? I didn't find any. I know that I can generate async methods via svcutil / Add service reference but I do not want to do that. Also I don't want to change interface of service on app server (IUserService) by adding async methods.

Is there any way how to call methods async with ChannelFactory? Thanks.

Michal
  • 1,324
  • 2
  • 16
  • 38

3 Answers3

8

You can automatically generate new interface that contains async versions of methods from original interface using T4 and use it in ChannelFactory without changing interface on server side.

I used NRefactory to parse original and generate new C# source code and AssemblyReferences.tt to use nuget packages in T4 template:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ include file="AssemblyReferences.tt" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="ICSharpCode.NRefactory.CSharp" #>
<#@ output extension=".cs"#>
<# 
var file = System.IO.File.ReadAllText(this.Host.ResolvePath("IUserService.cs")); 
if(!file.Contains("using System.Threading.Tasks;"))
{ #>
using System.Threading.Tasks;
<# } #>
<#
CSharpParser parser = new CSharpParser();
var syntaxTree = parser.Parse(file);


foreach (var namespaceDeclaration in syntaxTree.Descendants.OfType<NamespaceDeclaration>())
{
    namespaceDeclaration.Name += ".Client";
}


foreach (var methodDeclaration in syntaxTree.Descendants.OfType<MethodDeclaration>())
{
    if (methodDeclaration.Name.Contains("Async"))
        continue;

    MethodDeclaration asyncMethod = methodDeclaration.Clone() as MethodDeclaration;
    asyncMethod.Name += "Async"; 

    if (asyncMethod.ReturnType.ToString() == "void")
        asyncMethod.ReturnType = new SimpleType("Task");
    else
        asyncMethod.ReturnType = new SimpleType("Task", typeArguments: asyncMethod.ReturnType.Clone());

    methodDeclaration.Parent.AddChild(asyncMethod, Roles.TypeMemberRole);
}

#>
<#=syntaxTree.ToString()#>​

You pass your interface file name to template:

using System.Collections.Generic;
using System.ServiceModel;

namespace MyProject
{
    [ServiceContract]
    interface IUserService
    {
        [OperationContract]
        List<User> GetAllUsers();
    }
}

To get new one:

using System.Threading.Tasks;
using System.Collections.Generic;
using System.ServiceModel;

namespace MyProject.Client
{
    [ServiceContract]
    interface IUserService
    {
        [OperationContract]
        List<User> GetAllUsers ();

        [OperationContract]
        Task<List<User>> GetAllUsersAsync ();
    }
}

Now you can put it in factory to use channel asynchroniously:

var factory = new ChannelFactory<MyProject.Client.IUserService>("*");
var channel = factory.CreateChannel();
var users = await channel.GetAllUsersAsync();
Community
  • 1
  • 1
Leonid Vasilev
  • 11,910
  • 4
  • 36
  • 50
  • hey i was able to get the T4 working, but i'm a complete noob at T4s... how do i get the generated C# code to run to allow me to access `MyProject.Client.IUserService` ? if i do `string content = myT4.TransformText();` i get the new interface, but i'm not really sure where to go from there – philr Jun 04 '18 at 16:47
  • @philr T4 is used in the IDE, not at runtime. You right-click the *.tt file in Visual Studio and go to Run Custom Tool, which will generate a *.cs file that you can copy-paste where it needs to go in your solution. Honestly this answer isn't all that useful, as it only reduces some copy-pasting; you still have to write the code for `GetAllUsersAsync()` or whatever your equivalent function is. – ketura Jan 03 '19 at 16:21
7

Unfortunately no, there is not.

The async methods you get from svcutil are generated in the proxy based on your interface. There is nothing in the raw WCF channel like this.

The only way is to alter the service reference to have native async calls, which you don't want, or to create your own wrapper around the channel and implement them yourself like the generated proxy does.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • thank you for your response. Do you have any tip/link about how to create custom wrappers around channel? I made some investigation but I didn't found anything. – Michal May 14 '14 at 09:20
5

Unfortunately, this isn't possible and there is a pretty good reason for it. CreateChannel returns an object that implements the provided interface (IUserService in your example). This interface is not async-aware, so there is no way it could return an object with the correct methods.

There are two possible solutions:

  1. Create your own proxy that is able to call the WCF service. This means you need to write your own proxy (or let svcutil do it for you).
  2. Make sure IUserService is an async interface that returns tasks. This is supported in WCF 4.5 and later. This is what I often use. The major drawback is that it makes your service a bit complicated and you are required to invoke the methods async (which might also be considered an advantage).
Ramon de Klein
  • 5,172
  • 2
  • 41
  • 64