Как вы настраиваете известные типы WCF программно? - PullRequest
51 голосов
/ 21 апреля 2009

Мое клиент-серверное приложение использует 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.

Ответы [ 5 ]

67 голосов
/ 21 апреля 2009

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

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

17 голосов
/ 20 января 2010

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

I. Использовать KnownTypeAttribute (string):

[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 {...}

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

13 голосов
/ 28 января 2011

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

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

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

3 голосов
/ 17 декабря 2010

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

Орден является абстрактным базовым классом


[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 голосов
/ 04 ноября 2016

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

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