2009-08-31 6 views
5

J'ai une application où les types de partage client et serveur, et l'interopérabilité ne fait pas partie de nos préoccupations. Je prévois d'avoir un référentiel unique pour tous les objets activés sur le Web, et je pensais à une interface générique pour mon service exposé.wcf exposant des génériques

quelque chose comme T GetObject (int id)

mais WCF ne marche pas comme ça depuis son essayant d'exposer son schéma (que je ne se soucient vraiment)

est-il possible de faire une telle chose avec WCF ?, je peux utiliser n'importe quel type de liaison ne doit pas être httpbinding ou wsbinding ...

+0

Voir aussi http://stackoverflow.com/questions/6223886/generic-service-interface –

Répondre

2

Je suppose que cela est possible, bien que je ne suis pas sûr que vous le souhaitez. Je prendrais l'approche suivante (non testée, je ne sais pas si ça marche).Tout d'abord créer la structure de projet suivante dans votre solution:

  • ServiceInterfaces
  • ServiceImplementations (références ServiceInterfaces et ModelClasses)
  • ModelClasses
  • Host (références ServiceInterfaces et ServiceImplementations)
  • Client (références ServiceInterfaces et ModelClasses

Dans ServiceInterfaces vous avez une interface comme celui-ci (je sautais les espaces de noms, etc pour rendre l'exemple plus court):

[ServiceContract] 
public interface IMyService<T> 
{ 
    T GetObject(int id); 
} 

En ServiceImplementations vous avez une classe qui implémente IMyService<T>:

public class MyService<T> : IMyService<T> 
{ 
    T GetObject(int id) 
    { 
     // Create something of type T and return it. Rather difficult 
     // since you only know the type at runtime. 
    } 
} 

Dans Host vous avez la configuration correcte pour votre service dans un fichier App.config (ou Web.config) et le code suivant pour héberger votre service (étant donné qu'il s'agit d'une application autonome) :

ServiceHost host = new ServiceHost(typeof(MessageManager.MessageManagerService)) 
host.Open(); 

Et enfin Client vous utilisez une ChannelFactory<TChannel> classe pour définir un proxy:

Binding binding = new BasicHttpBinding(); // For the example, could be another binding. 
EndpointAddress address = new EndpointAddress("http://localhost:8000/......"); 
IMyService<string> myService = 
    ChannelFactory<IMyService<string>>.CreateChannel(binding, address); 
string myObject = myService.GetObject(42); 

Encore une fois, je ne sais pas si cela fonctionne. L'astuce consiste à partager vos interfaces de service (dans ServiceInterfaces) et les objets de modèle de domaine (dans ModelClasses) entre l'hôte et le client. Dans mon exemple, j'utilise une chaîne pour renvoyer de la méthode de service, mais il peut s'agir de n'importe quel type de contrat de données du projet ModelClasses.

+0

Je peux référencer les mêmes assemblages sur le client et utiliser l'onglet avancé de l'utlity de référence du service (je devine svcutil) pour partager les types, cela fonctionne, mais maintenant je veux faire est abstrait, je suppose que je cherche un type de remotint solution, mais devrait être possible sur wcf ... –

+0

Vous ne devriez pas utiliser la boîte de dialogue 'Ajouter une référence de service' dans Visual Studio pour mon approche. Ça ne va pas marcher. C'est pourquoi j'utilise la classe ChannelFactory . Il génère une implémentation proxy de votre interface de service à la volée, sans besoin de métadonnées. –

+0

Cela probablement ne fonctionnera pas (je ne peux pas le tester maintenant), puisque vous définissez un service avec des opérations qui renvoient un type générique "T" - mais afin de fonctionner correctement, ce type "T" doit être un DataContract, afin que la pile WCF puisse sérialiser et désérialiser les objets de ce type. Rappelez-vous: tout ce que vous passez entre le client et le serveur sont des ** messages ** - il n'y a pas de référence d'objet, ni de référence d'interface, ou quoi que ce soit de référentiel! Vous ** devez ** pouvoir sérialiser votre requête dans un format de message, et la désérialiser à l'autre extrémité - cela ne fonctionnera pas avec les génériques :-( –

8

Non, vous ne pouvez pas. Que vous souhaitiez ou que vous ayez besoin de l'interopérabilité ou non, le fondement le plus fondamental de la WCF est l'échange de messages.

Le client envoie un message au serveur et reçoit une réponse. Ce message est tout ce qui passe entre le client et le serveur, et doit être sérialisé dans un format XML ou binaire. C'est pourquoi toute donnée transmise doit être atomique (comme int, string) ou un DataContract - une description de la pile de service WCF sur la façon de sérialiser et de désérialiser de tels objets.

Vous ne pouvez pas passer d'interfaces, ou d'autres "tromperies" - tout ce qui se passe entre le client et le serveur doit être exprimable dans le schéma XML, fondamentalement.

J'ai donc peur que ce que vous essayez d'accomplir soit tout à fait contraire à ce que propose WCF. Le monde et les paradigmes de SOA (Service-Oriented Apps) sont assez différents et pas toujours 100% en phase avec l'idée et les mécanismes de la POO.

Marc

+0

Donc, si quelqu'un voulait faire cela, quelle pile technologique MS offre-t-il, est-ce plus approprié pour l'accès à distance (si c'est encore en vie) ? Ms étend le support pour les types de partage (netcontractserializer etc) –

+0

Je ne sais pas s'il existe une solution actuelle pour les objets distants - ce qui est essentiellement ce que vous essayez de faire. Cette approche a sa part de problèmes, que MS essaie de résoudre avec WCF - mais pour la résoudre, il faut passer à un modèle entièrement basé sur les messages, qui ne fonctionne pas bien avec les interfaces et les génériques. Oui, il supporte le partage de types CONCRETE - mais certainement pas d'interfaces, et pas vraiment de génériques. –

0

Vous pouvez le faire si vous utilisez ServiceKnownTypesDiscovery.

Par exemple:

[ServiceKnownType("GetKnownTypes", typeof(ServiceKnownTypesDiscovery))] 
public interface ISomeService 
{ 
    [OperationContract] 
    object Request(IRequestBase parameters); 
} 

où GetKnownTypes pourrait être déclarée comme ceci:

public static class ServiceKnownTypesDiscovery 
{ 
    public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider) 
    { 
     var types = new List<Type>(); 

     foreach (var asmFile in Directory.GetFiles(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory, "*.dll")) 
     { 
      Assembly asm = Assembly.LoadFrom(asmFile); 
      types.AddRange(asm.GetTypes().Where(p=> Attribute.IsDefined(p,typeof(DataContractAttribute)))); 
     } 

     return types; 
    } 
} 

Dans ce cas, tout déclaré avec [DataContract] (aussi longtemps qu'ils sont découvrable sur le serveur et la côté client) peut être sérialisé.

J'espère que cela a aidé!

+1

Mais ... votre solution n'utilise pas de génériques –

0

En suivant l'exemple précédent, vous pouvez déclarer un DataContract avec un objet comme DataMember. Vous pouvez ensuite ajouter une méthode d'extension pour obtenir et définir un type générique sur le membre de données d'objet. Vous pouvez également rendre ceci interne, de cette façon vous seriez obligé d'utiliser les méthodes d'extension pour obtenir et définir la valeur.

Bien sûr, cela ne fonctionne que si vous générez le client en utilisant svcutil (ou Visual Studio) et que vous référencez l'assembly contenant le contrat de données et la classe avec les méthodes d'extensions.

Espérons que cela aide ...

Questions connexes