Как программно настроить известные типы WCF?

В моем клиент-серверном приложении для связи используется 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);
}

Может кто-нибудь объяснить, как я могу это сделать?

ИЗМЕНИТЬ. Пожалуйста, поймите, что я пытаюсь установить известные типы динамически во время выполнения, а не декларативно в конфигурации или с использованием атрибутов в исходном коде.

Это в основном вопрос об API WCF, а не о стиле.

ИЗМЕНИТЬ 2 На этой странице MSDN указано:

Вы также можете добавлять типы в ReadOnlyCollection, доступ к которым осуществляется через свойство KnownTypes DataContractSerializer.

К сожалению, это все, что в нем говорится, и это не имеет особого смысла, учитывая, что KnownTypes является свойством только для чтения, а значение свойства - ReadOnlyCollection.


person Drew Noakes    schedule 21.04.2009    source источник
comment
При редактировании 2: я думаю, они означают, что вы можете передавать дополнительные известные типы через конструктор DataContractSerializer. Это не сильно поможет в вашем случае, поскольку WCF сам делает свой сериализатор.   -  person Kurt Schelfthout    schedule 21.04.2009


Ответы (5)


Добавьте [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
    }
}

а затем вы можете передать любые типы, которые вам нужны.

person Miki Watts    schedule 21.04.2009
comment
Возможно, мой вопрос был непонятен. Это не «программно» - это все еще декларативно. Мне нужна возможность добавлять известные типы, а не получать их во время выполнения. - person Drew Noakes; 21.04.2009
comment
@ Дрю Ноукс - А? В методе GetKnownTypes, который представляет собой всего лишь код, вы можете вернуть известные на данный момент типы. Атрибут нужен только для того, чтобы указать WCF, какой метод вызывать для получения известных типов. Я думаю, это так же программно, как и в WCF (если не считать программного редактирования файла конфигурации и его перезагрузки). - person Kurt Schelfthout; 21.04.2009
comment
Согласны с Мики и Куртом, это все, что вам нужно в WCF. - person Rob; 21.04.2009
comment
Я приношу извинения. На самом деле это правильный ответ, я просто неверно истолковал его как средство доступа к существующим известным типам. Глядя на это снова, я понятия не имею, о чем я думал. - person Drew Noakes; 21.04.2009
comment
@Drew: этот ответ показывает именно это. Типы, возвращаемые методом GetKnownTypes, добавляются в список известных типов, поддерживаемых службой. Вы можете использовать отражение для создания этого списка типов на основе любых критериев, которые вам подходят. Имейте в виду, что любые новые типы требуют регенерации клиентского прокси для их использования. Я не думаю, что это возможно. - person Dan C.; 21.04.2009
comment
@DanC - список известных типов также должен быть доступен на стороне отправителя, поэтому, по-видимому, узел службы должен быть воссоздан и для новых типов. - person Drew Noakes; 21.04.2009
comment
Правильно (однако вы можете обойти это, при условии, что вы можете определить, когда вы получаете новый тип и воссоздаете хост). Во всяком случае, я использовал вышеуказанный шаблон для загрузки типов при запуске службы (просто хотел избавиться от необходимости декларативно указывать все производные классы). Если вы хотите динамически добавлять типы во время работы службы, вы, вероятно, столкнетесь с гораздо большим количеством проблем. - person Dan C.; 21.04.2009
comment
Просто хотел снова вмешаться и сказать большое спасибо за этот ответ. Я только что начал использовать это, и он сработал отлично с первого раза. Я немного отредактировал ваш ответ, чтобы расширить возвращаемый тип до IEnumerable<Type> и указать, что класс может быть внутренним и по-прежнему работать нормально. Еще раз спасибо. - person Drew Noakes; 06.08.2009
comment
Для чего нужен параметр ICustomAttributeProvider provider? - person Michael Freidgeim; 23.06.2011
comment
@MichaelFreidgeim Параметр ICustomAttributeProvider является частью сигнатуры метода. Вам не обязательно использовать значение параметра, но параметр должен присутствовать для Reflection, чтобы найти метод в классе, указанном в атрибуте ServiceKnownType. Напротив, для указания имени метода в атрибуте KnownType требуется пустой список параметров. - person Suncat2000; 26.12.2018

Есть 2 дополнительных способа решить вашу проблему:

I. Используйте KnownTypeAttribute (строка):

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

II. Использовать конструктор 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 {...}

ПРИМЕЧАНИЕ. Этот атрибут необходимо использовать с обеих сторон: на стороне клиента и на стороне службы!

person Sergey Teplyakov    schedule 20.01.2010
comment
Отлично! Я искал этот код около 8 лет! К сожалению, я не уверен, будет ли он работать на всех платформах, для которых я хочу его реализовать. - person Christian Findlay; 09.09.2016

Мне нужно было сделать это, чтобы наследование работало правильно. Я не хотел поддерживать список производных типов.

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

Я знаю, что первая строка функции избыточна, но это просто означает, что я могу вставить ее в любой базовый класс без изменений.

person Paul McNamara    schedule 28.01.2011
comment
Хорошо, спасибо. Но я думаю, что ToList ‹Type› () бессмысленен. Почему ты этим пользуешься? - person Karel Kral; 04.01.2012

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

The Order is the abstract base class


[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; }
}
person Amit Bagga    schedule 17.12.2010
comment
Для чего нужен параметр ICustomAttributeProvider provider? Это обязательно? - person Michael Freidgeim; 23.06.2011
comment
@MichaelFreidgeim: Я считаю, что это необходимо не из-за интерфейса, а из-за внутренней реализации с использованием отражения. Как обычно в обратных вызовах сериализации и т. Д. - person abatishchev; 23.06.2012

немного перебор, но работает и является своего рода доказательством на будущее

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);
person aeroson    schedule 04.11.2016