如何以编程方式配置 WCF 已知类型?

Posted

技术标签:

【中文标题】如何以编程方式配置 WCF 已知类型?【英文标题】:How do you configure WCF known types programmatically? 【发布时间】:2010-10-20 18:29:24 【问题描述】:

我的客户端/服务器应用程序正在使用 WCF 进行通信,这非常棒。然而,当前架构的一个缺点是我必须对某些传输类型使用已知类型配置。我使用的是内部 Pub/Sub 机制,这个要求是不可避免的。

问题是很容易忘记添加已知类型,如果你这样做了,WCF 会默默地失败,几乎没有什么线索可以知道出了什么问题。

在我的应用程序中,我知道要发送的类型集。我想以编程方式执行配置,而不是通过App.config 文件以声明方式执行配置,该文件目前包含以下内容:

<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>

相反,我想做这样的事情:

foreach (Type type in _transmittedTypes)

    // How would I write this method?
    AddKnownType(typeof(MyParent), type);

有人可以解释一下我该怎么做吗?

编辑请理解,我试图在运行时动态设置已知类型,而不是在配置中以声明方式或在源代码中使用属性。

这基本上是关于 WCF API 的问题,而不是样式问题。

EDIT 2 This MSDN page 页面状态:

您还可以将类型添加到 ReadOnlyCollection,通过 DataContractSerializer 的 KnownTypes 属性访问。

不幸的是,这就是它所说的全部内容,并且鉴于 KnownTypes 是一个只读属性,并且该属性的值为ReadOnlyCollection,这并没有多大意义。

【问题讨论】:

在您的编辑 2 中:我猜他们的意思是您可以通过 DataContractSerializer 构造函数传入额外的已知类型。不过,这对您的情况没有多大帮助,因为 WCF 自己制作了它的序列化程序。 【参考方案1】:

[ServiceKnownType] 添加到您的[ServiceContract] 界面:

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

然后创建一个名为KnownTypesProvider的类:

internal static class KnownTypesProvider

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

然后你可以传回你需要的任何类型。

【讨论】:

也许我的问题不清楚。这不是“以编程方式” - 它仍然是声明性的。我需要能够在运行时添加已知类型,而不是获取它们。 @Drew Noakes - 嗯?在 GetKnownTypes 方法中,这只是代码,您可以在该时间点返回已知类型。该属性只是用来告诉 WCF 调用什么方法来获取已知类型。我认为,这就像你可以在 WCF 中以编程方式使用它一样(缺少以编程方式编辑配置文件并重新加载它)。 我很抱歉。这实际上是一个有效的答案,我只是将其误读为访问现有已知类型的一种方式。再看一遍,我不知道我在想什么。 只是想再次插话,非常感谢您的回答。我刚刚开始使用它,它第一次完美运行。我稍微编辑了您的答案以将返回类型扩大到IEnumerable&lt;Type&gt;,并表明该类可以是内部的并且仍然可以正常工作。再次感谢。 参数“ICustomAttributeProvider provider”的作用是什么?【参考方案2】:

还有 2 种额外的方法可以解决您的问题:

我。使用 KnownTypeAttribute(string):

[DataContract]
[KnownType("GetKnownTypes")]
public abstract class MyParent

    static IEnumerable<Type> GetKnownTypes()
    
        return new Type[]  typeof(MyChild1), typeof(MyChild2), typeof(MyChild3) ;
    

二。使用构造函数 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 ...

注意:您必须在双方都使用此属性:客户端和服务端!

【讨论】:

太棒了!我一直在寻找这个代码大约 8 年!不幸的是,我不确定它是否适用于我想要实现的所有平台。【参考方案3】:

我需要这样做才能让继承正常工作。我不想维护派生类型的列表。

 [KnownType("GetKnownTypes")]
 public abstract class BaseOperationResponse
 

    public static Type[] GetKnownTypes()
    
        Type thisType = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType;
        return thisType.Assembly.GetTypes().Where(t => t.IsSubclassOf(thisType)).ToArray();
    

我知道函数的第一行是多余的,但这只是意味着我可以将它粘贴到任何基类中而无需修改。

【讨论】:

很好,谢谢。但我认为 ToList() 是无价值的。你为什么用它?【参考方案4】:

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


Order 是抽象基类


[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; 

【讨论】:

参数“ICustomAttributeProvider provider”的作用是什么?需要吗? @MichaelFreidgeim:我认为这是必需的,不是由于接口,而是由于使用反射的内部实现。通常在序列化回调等中。【参考方案5】:

有点矫枉过正,但可以工作并且是一种未来的证明

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

【讨论】:

以上是关于如何以编程方式配置 WCF 已知类型?的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式使用证书身份验证配置 WCF 服务客户端

WCF路由如何以编程方式添加备份列表

如何以编程方式将客户端连接到 WCF 服务?

从.NET调用WCF服务时如何以编程方式添加soap标头?

WCF - 如何使用 HTTP(S) 上的二进制编码以编程方式创建自定义绑定

以编程方式添加终结点标头安全 WCF