Как вы программно настраиваете известные типы WCF?
-
12-09-2019 - |
Вопрос
Мое клиент-серверное приложение использует 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, а не о стиле.
ПРАВКА 2 Эта страница MSDN состояния страницы:
Вы также можете добавлять типы в ReadOnlyCollection, доступ к которому осуществляется через свойство KnownTypes DataContractSerializer.
К сожалению, это все, что там сказано, и это не имеет особого смысла, учитывая, что KnownTypes - это свойство, доступное только для чтения, а значение свойства равно ReadOnlyCollection
.
Решение
Добавить [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
}
}
и тогда вы можете передать обратно любые типы, которые вам нужны.
Другие советы
Есть 2 дополнительных способа решить вашу проблему:
Я.Используйте 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 {...}
ПРИМЕЧАНИЕ:Вы должны использовать этот атрибут с обеих сторон:клиентская сторона и сервисная сторона!
Мне нужно было сделать это, чтобы наследование работало должным образом.Я не хотел поддерживать список производных типов.
[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();
}
Я знаю, что первая строка функции излишня, но это просто означает, что я могу вставить ее в любой базовый класс без изменений.
Web .Конфигурация
<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; }
}
немного излишне, но работает и является своего рода доказательством на будущее
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);