2014-05-09 2 views
6

Je travaille sur un projet où une application Web hébergée sur un serveur Web appelle les services WCF hébergés sur le serveur de l'application. Proxy pour WCF appels est créé par ChannelFactory et les appels sont effectués via le canal, par exemple:Appel WCF asynchrone avec ChannelFactory et CreateChannel

(en omettant utilisant le bloc)

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

var users = channel.GetAllUsers(); 

Si je comprends appeler bien via le canal est async et le fil sur le serveur Web est inactif pendant la demande et attendez simplement une réponse.

Je voudrais faire appel async comme ceci:

var users = await channel.GetAllUsersAsync(); 

Est-il possible comment faire appel avec ChannelFactory et canaux async? Je n'en ai trouvé aucun. Je sais que je peux générer des méthodes asynchrones via svcutil/Ajouter une référence de service, mais je ne veux pas le faire. Aussi je ne veux pas changer l'interface de service sur le serveur d'application (IUserService) en ajoutant des méthodes asynchrones.

Existe-t-il un moyen d'appeler des méthodes async avec ChannelFactory? Merci.

Répondre

3

Malheureusement non, il n'y en a pas.

Les méthodes asynchrones que vous obtenez de svcutil sont générées dans le proxy en fonction de votre interface. Il n'y a rien dans le canal WCF brut comme ça. Le seul moyen est de modifier la référence du service pour avoir des appels asynchrones natifs, ce que vous ne voulez pas, ou de créer votre propre enveloppe autour du canal et de les implémenter vous-même comme le fait le proxy généré.

+0

Nous vous remercions de votre réponse. Avez-vous des astuces/liens sur la création de wrappers personnalisés autour d'un canal? J'ai fait une enquête mais je n'ai rien trouvé. – Michal

4

Malheureusement, ce n'est pas possible et il y a une bonne raison pour cela. CreateChannel renvoie un objet qui implémente l'interface fournie (IUserService dans votre exemple). Cette interface n'est pas asynchrone, donc il n'y a aucun moyen de renvoyer un objet avec les méthodes correctes.

Il existe deux solutions possibles:

  1. Créez votre propre proxy qui est en mesure d'appeler le service WCF. Cela signifie que vous devez écrire votre propre proxy (ou laissez svcutil le faire pour vous).
  2. Assurez-vous que IUserService est une interface async qui renvoie des tâches. Ceci est pris en charge dans WCF 4.5 et versions ultérieures. C'est ce que j'utilise souvent. L'inconvénient majeur est que cela rend votre service un peu compliqué et vous devez appeler les méthodes async (ce qui pourrait aussi être considéré comme un avantage).
6

Vous pouvez générer automatiquement nouvelle interface qui contient des versions de méthodes asynchrones à partir de l'interface d'origine en utilisant T4 et l'utiliser dans ChannelFactorywithout changing interface on server side.

J'utilisé NRefactory pour analyser original et générer de nouvelles C# code source et AssemblyReferences.tt utiliser les paquets de NuGet dans le modèle T4:

<#@ 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()#>​ 

Vous passez votre nom de fichier d'interface modèle:

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

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

Pour obtenir un nouveau:

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

Maintenant vous pouvez le mettre n usine à utiliser le canal de manière asynchrone:

var factory = new ChannelFactory<MyProject.Client.IUserService>("*"); 
var channel = factory.CreateChannel(); 
var users = await channel.GetAllUsersAsync(); 
Questions connexes