My client/server application is using WCF for communication, which has been great. However one shortcoming of the current architecture is that I must use known type configuration for certain transmitted types. I'm using an in-house Pub/Sub mechanism and this requirement is unavoidable.
The problem is that it's easy to forget to add the known type, and if you do, WCF fails silently with few clues as to what's going wrong.
In my application, I know the set of types that are going to be sent. I would like to perform the configuration programmatically, rather than declaratively through the App.config
file which currently contains something like this:
<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>
Instead, I'd like to do something like this:
foreach (Type type in _transmittedTypes)
{
// How would I write this method?
AddKnownType(typeof(MyParent), type);
}
Can someone please explain how I might do this?
EDIT Please understand that I'm trying to set the known types dynamically at run time rather than declaratively in config or using attributes in the source code.
This is basically a question about the WCF API, not a style question.
EDIT 2 This MSDN page page states:
You can also add types to the ReadOnlyCollection, accessed through the KnownTypes property of the DataContractSerializer.
Unfortunately that's all it says and it doesn't make terribly much sense given that KnownTypes is a readonly property, and the property's value is a ReadOnlyCollection
.
Add [ServiceKnownType]
to your [ServiceContract]
interface:
[ServiceKnownType("GetKnownTypes", typeof(KnownTypesProvider))]
then create a class called KnownTypesProvider
:
internal static class KnownTypesProvider
{
public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
{
// collect and pass back the list of known types
}
}
and then you can pass back whatever types you need.
There are 2 additional ways to solve your problem:
I. Use KnownTypeAttribute(string):
[DataContract]
[KnownType("GetKnownTypes")]
public abstract class MyParent
{
static IEnumerable<Type> GetKnownTypes()
{
return new Type[] { typeof(MyChild1), typeof(MyChild2), typeof(MyChild3) };
}
}
II. Use constructor 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 {...}
NOTE: You must use this attribute on both sides: client side and service side!
I needed to do this to allow inheritance to work properly. I didn't want to have to maintain the list of derived types.
[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();
}
I know the first line of the function is overkill but it just means I can paste it into any base class without modification.
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; }
}
a bit overkill, but works and is kind of future proof
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);
来源:https://stackoverflow.com/questions/771560/how-do-you-configure-wcf-known-types-programmatically