2009-04-21 9 views
48

Mon application client/serveur utilise WCF pour la communication, ce qui a été génial. Cependant, un défaut de l'architecture actuelle est que je dois utiliser une configuration de type connue pour certains types transmis. J'utilise un mécanisme Pub/Sub interne et cette exigence est inévitable. Le problème est qu'il est facile d'oublier d'ajouter le type connu, et si vous le faites, WCF échoue silencieusement avec peu d'indices sur ce qui ne va pas.Comment configurer les types connus WCF par programme?

Dans mon application, je connais l'ensemble des types qui vont être envoyés. Je voudrais effectuer la configuration par programme, plutôt que déclarative dans le fichier App.config qui contient actuellement quelque chose comme ceci:

<system.runtime.serialization> 
    <dataContractSerializer> 
    <declaredTypes> 
     <add type="MyProject.MyParent, MyProjectAssembly"> 
     <knownType type="MyProject.MyChild1, MyProjectAssembly"/> 
     <knownType type="MyProject.MyChild2, MyProjectAssembly"/> 
     <knownType type="MyProject.MyChild3, MyProjectAssembly"/> 
     <knownType type="MyProject.MyChild4, MyProjectAssembly"/> 
     <knownType type="MyProject.MyChild5, MyProjectAssembly"/> 
     </add> 
    </declaredTypes> 
    </dataContractSerializer> 
</system.runtime.serialization> 

Au lieu de cela, je voudrais faire quelque chose comme ceci:

foreach (Type type in _transmittedTypes) 
{ 
    // How would I write this method? 
    AddKnownType(typeof(MyParent), type); 
} 

Can quelqu'un s'il vous plaît expliquer comment je pourrais le faire?

EDIT Veuillez comprendre que j'essaie de définir les types connus dynamiquement au moment de l'exécution plutôt que de manière déclarative dans config ou en utilisant des attributs dans le code source.

Il s'agit essentiellement d'une question sur l'API WCF, pas une question de style.

EDIT 2This MSDN page états de page:

Vous pouvez également ajouter des types à la ReadOnlyCollection, accessible par la propriété KnownTypes du DataContractSerializer.

Malheureusement, c'est tout ce qu'il dit et il n'a pas de sens terriblement bien étant donné que KnownTypes est une propriété en lecture seule, et la valeur de la propriété est un ReadOnlyCollection.

+0

Sur votre édition 2: Je suppose qu'ils veulent dire que vous pouvez passer supplémentaire types connus par le constructeur DataContractSerializer. Cela ne vous aidera pas beaucoup dans votre cas, car WCF fait lui-même son sérialiseur. –

Répondre

64

Ajouter à votre [ServiceKnownType][ServiceContract] Interface:

[ServiceKnownType("GetKnownTypes", typeof(KnownTypesProvider))] 

puis créez une classe appelée KnownTypesProvider:

internal static class KnownTypesProvider 
{ 
    public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider) 
    { 
     // collect and pass back the list of known types 
    } 
} 

et vous pouvez passer en arrière tout type dont vous avez besoin.

+0

Peut-être que ma question n'était pas claire. Ce n'est pas "programmatique" - c'est toujours déclaratif. J'ai besoin de pouvoir * ajouter * des types connus, pas * les * obtenir *, au moment de l'exécution. –

+7

@ Drew Noakes - Huh? Dans la méthode GetKnownTypes, qui n'est que du code, vous pouvez renvoyer les types connus à ce moment précis. L'attribut est juste là pour dire à WCF quelle méthode appeler pour obtenir les types connus. C'est aussi programmatique que vous pouvez l'avoir dans WCF, je pense (à moins de modifier par programme le fichier de configuration et de le recharger). –

+0

D'accord avec Miki et Kurt, c'est aussi bon que ce que vous obtiendrez en WCF. –

17

Il y a 2 façons supplémentaires pour résoudre votre problème:

I. Utilisation KnownTypeAttribute (string):

[DataContract] 
[KnownType("GetKnownTypes")] 
public abstract class MyParent 
{ 
    static IEnumerable<Type> GetKnownTypes() 
    { 
     return new Type[] { typeof(MyChild1), typeof(MyChild2), typeof(MyChild3) }; 
    } 
} 

II. Utilisez constructeur DataContractSerializer

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | 
       AttributeTargets.Interface)] 
public class MyHierarchyKnownTypeAttribute : Attribute, IOperationBehavior, IServiceBehavior, IContractBehavior 
{ 
    private void IOperationBehavior.AddBindingParameters(
      OperationDescription description, 
      BindingParameterCollection parameters) 
    { 
    } 

    void IOperationBehavior.ApplyClientBehavior(
      OperationDescription description, 
      ClientOperation proxy) 
    { 
     ReplaceDataContractSerializerOperationBehavior(description); 
    } 

    private void IOperationBehavior.ApplyDispatchBehavior(
      OperationDescription description, 
      DispatchOperation dispatch) 
    { 
     ReplaceDataContractSerializerOperationBehavior(description); 
    } 

    private void IOperationBehavior.Validate(OperationDescription description) 
    { 
    } 

    private void IServiceBehavior.AddBindingParameters(
      ServiceDescription serviceDescription, 
      ServiceHostBase serviceHostBase, 
      System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, 
      BindingParameterCollection bindingParameters) 
    { 
     ReplaceDataContractSerializerOperationBehavior(serviceDescription); 
    } 

    private void IServiceBehavior.ApplyDispatchBehavior(
      ServiceDescription serviceDescription, 
      ServiceHostBase serviceHostBase) 
    { 
     ReplaceDataContractSerializerOperationBehavior(serviceDescription); 
    } 

    private void IServiceBehavior.Validate(ServiceDescription serviceDescription, 
      ServiceHostBase serviceHostBase) 
    { 
    } 

    private void IContractBehavior.AddBindingParameters(
      ContractDescription contractDescription, 
      ServiceEndpoint endpoint, 
      BindingParameterCollection bindingParameters) 
    { 
    } 

    private void IContractBehavior.ApplyClientBehavior(
      ContractDescription contractDescription, 
      ServiceEndpoint endpoint, ClientRuntime clientRuntime) 
    { 
     ReplaceDataContractSerializerOperationBehavior(contractDescription); 
    } 

    private void IContractBehavior.ApplyDispatchBehavior(
      ContractDescription contractDescription, 
      ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime) 
    { 
     ReplaceDataContractSerializerOperationBehavior(contractDescription); 
    } 

    private void IContractBehavior.Validate(ContractDescription contractDescription, 
      ServiceEndpoint endpoint) 
    { 
    }  

    private static void ReplaceDataContractSerializerOperationBehavior(
      ServiceDescription description) 
    { 
     foreach (var endpoint in description.Endpoints) 
     { 
      ReplaceDataContractSerializerOperationBehavior(endpoint); 
     } 
    } 

    private static void ReplaceDataContractSerializerOperationBehavior(
      ContractDescription description) 
    { 
     foreach (var operation in description.Operations) 
     { 
      ReplaceDataContractSerializerOperationBehavior(operation); 
     } 
    } 

    private static void ReplaceDataContractSerializerOperationBehavior(
      ServiceEndpoint endpoint) 
    { 
     // ignore mex 
     if (endpoint.Contract.ContractType == typeof(IMetadataExchange)) 
     { 
      return; 
     } 
     ReplaceDataContractSerializerOperationBehavior(endpoint.Contract); 
    } 

    private static void ReplaceDataContractSerializerOperationBehavior(
      OperationDescription description) 
    { 
     var behavior = 
     description.Behaviors.Find<DataContractSerializerOperationBehavior>(); 
     if (behavior != null) 
     { 
      description.Behaviors.Remove(behavior); 
      description.Behaviors.Add(
       new ShapeDataContractSerializerOperationBehavior(description)); 
     } 
    } 

    public class ShapeDataContractSerializerOperationBehavior 
      : DataContractSerializerOperationBehavior 
    { 
     public ShapeDataContractSerializerOperationBehavior(
       OperationDescription description) 
      : base(description) { } 

     public override XmlObjectSerializer CreateSerializer(Type type, 
       string name, string ns, IList<Type> knownTypes) 
     { 
      var shapeKnownTypes = 
       new List<Type> { typeof(Circle), typeof(Square) }; 
      return new DataContractSerializer(type, name, ns, shapeKnownTypes); 
     } 

     public override XmlObjectSerializer CreateSerializer(Type type, 
       XmlDictionaryString name, XmlDictionaryString ns, 
       IList<Type> knownTypes) 
     { 
      //All magic here! 
      var knownTypes = 
       new List<Type> { typeof(MyChild1), typeof(MyChild2), typeof(MyChild3) }; 
      return new DataContractSerializer(type, name, ns, knownTypes); 
     } 
    } 
} 

[ServiceContract()] 
[MyHierarchyKnownTypeAttribute] 
public interface IService {...} 

REMARQUE: Vous devez utiliser cet attribut sur les deux côtés: côté client et côté service!

+0

+1 pour l'exemple complet de code! –

+0

Excellent! Je cherchais ce code depuis peut-être 8 ans! Malheureusement, je ne suis pas sûr que cela fonctionne sur toutes les plateformes pour lesquelles je veux l'implémenter. –

3

Web.Config

<applicationSettings> 
<HostProcess.Properties.Settings> 
<setting name="KnowTypes" serializeAs="Xml"> 
<value> 
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
    <string>a.AOrder,a</string> 
    <string>b.BOrder,b</string> 
    <string>c.COrder,c</string> 
</ArrayOfString> 
</value> 
</setting> 
</HostProcess.Properties.Settings> 

static class Helper 
{ 
    public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider) 
    { 
     System.Collections.Generic.List<System.Type> knownTypes = 
     new System.Collections.Generic.List<System.Type>(); 
     // Add any types to include here. 
     Properties.Settings.Default.KnowTypes.Cast<string>().ToList().ForEach(type => 
      { 
       knownTypes.Add(Type.GetType(type)); 
      }); 

     return knownTypes; 
    } 
} 


[ServiceContract] 
[ServiceKnownType("GetKnownTypes", typeof(Helper))] 
public interface IOrderProcessor 
{ 
    [OperationContract] 
    string ProcessOrder(Order order); 
} 

L'Ordre est la classe de base abstraite


[DataContract] 
public abstract class Order 
{ 
    public Order() 
    { 
     OrderDate = DateTime.Now; 
    } 
    [DataMember] 
    public string OrderID { get; set; } 
    [DataMember] 
    public DateTime OrderDate { get; set; } 
    [DataMember] 
    public string FirstName { get; set; } 
    [DataMember] 
    public string LastName { get; set; } 
} 
+0

Quel est l'objectif du paramètre "Fournisseur ICustomAttributeProvider"? Est-ce nécessaire? –

+0

@MichaelFreidgeim: Je pense que c'est nécessaire, pas en raison d'une interface mais de la mise en œuvre interne en utilisant la réflexion. Comme d'habitude dans les callbacks de sérialisation, etc. – abatishchev

13

que je devais faire cela pour permettre l'héritage de fonctionner correctement. Je ne voulais pas avoir à maintenir la liste des types dérivés. Je sais que la première ligne de la fonction est overkill mais cela signifie simplement que je peux la coller dans n'importe quelle classe de base sans modification.

+1

Nice, thanks.Mais je pense que ToList () est sans effort. Pourquoi l'utilisez-vous? –

+0

Bon point! Je devrais ranger ça :) –

+0

Cela devrait être la réponse acceptée. –

0

un peu exagéré, mais des œuvres et est une sorte de future preuve

var knownTypes = 
    AppDomain.CurrentDomain 
    .GetAssemblies() 
    .Where(a => 
    { 
     var companyAttribute = a.GetCustomAttribute<AssemblyCompanyAttribute>(); 
     if (companyAttribute == null) return false; 
     return companyAttribute.Company.ToLower().Contains("[YOUR COMPANY NAME]"); 
    }) 
    .SelectMany(a => a.GetTypes()).Where(t => t.IsSerializable && !t.IsGenericTypeDefinition); 

var serializer = new DataContractSerializer(type, knownTypes); 
Questions connexes