Reputation: 7377
I'm having a problem getting the helper method specified in my ServiceKnownType attribute to run. For simplicity's sake, I have two assemblies: one with my service interface and interfaces for my data contracts, and another with my service implementation and concrete data contracts.
Here's a simplified/stripped down version of my service and its implementation.
MyService.Interface.dll
// IMyService.cs
[ServiceContract]
IMyService
{
[OperationContract]
IList<IMyData> GetData();
}
// IMyData.cs
public interface IMyData
{
int Foo { get; set; }
}
MyService.dll (with a reference to MyService.Interface.dll)
// MyService.svc.cs
public class MyService: IMyService
{
public IList<IMyData> GetData()
{
// Instantiate List<IMyData>, add a new MyData to the list
// return the List<IMyData>.
}
}
// MyData.cs
[DataContract]
public class MyData : IMyData
{
[DataMember]
public int Foo { get; set; }
}
The root of the problem is that to serialize the result of GetData()
, the service must be informed about the concrete MyData
class and the concrete List<IMyData>
generic class because the service definition uses interface types not the concrete types.
The simple answer is to decorate IMyService with:
[ServiceKnownType(typeof(MyData))]
[ServiceKnownType(typeof(List<IMyData>))]
However, MyData
is defined in an assembly that is not referenced within MyService.Interface.dll (and cannot be due to a circular reference.)
My next thought was to use the "helper method" form of ServiceKnownType
on MyService itself:
[ServiceKnownType("GetDataContracts", MyService)]
public class MyService: IMyService
{
public static IEnumerable<Type> GetDataContracts(ICustomeAttributeProvider provider)
{
// create a List<Type>, add MyData to it, and return it.
}
//...
}
As far as I can see that ought to work except that GetDataContracts
is never called. I tried moving it into a separate static class (parallel to MyService and nested within it) but in no case can I get a breakpoint to stop there.
EDIT: I meant to also say that adding the known types through web.config doesn't work either because I have generic types can't be added that way. You can only add simple, concrete types via web.config:
<knownType type="TypeName, Assembly" />
My concrete List<IMyData>
doesn't have a fully-qualified type name in an assembly.
Upvotes: 2
Views: 885
Reputation: 7377
Fixed. The answer was to add the ServiceKnownType, using the helper method form, to the service interface, not the service implementation, and to add a helper class that reflects the types I need instead of adding them by referring to the concrete types in code. (Recall, I couldn't do that because I can't add a reference to that assembly.)
[ServiceContract]
[ServiceKnownType("GetDataContractTypes", typeof(MyServiceHelper))]
public interface IMyService
{ ... }
And I added a new MyServiceHelper class to Nightingale.Interface, but it's not public so I'm not concerned about unnecessarily exposing a class from an assembly that I want to be "pure" interface only.
// Not public outside of this assembly.
static class MyServiceHelper
{
public static IEnumerable<Type> GetDataContractTypes(ICustomAttributeProvider paramIgnored)
{
// Get the assembly with the concrete DataContracts.
Assembly ass = Assembly.Load("MyService"); // MyService.dll
// Get all of the types in the MyService assembly.
var assTypes = ass.GetTypes();
// Create a blank list of Types.
IList<Type> types = new List<Type>();
// Get the base class for all MyService data contracts
Type myDataContractBase = ass.GetType("MyDataContractBase", true, true);
// Add MyService's data contract Types.
types.Add(assTypes.Where(t => t.IsSubclassOf(myDataContractBase)));
// Add all the MyService data contracts to the service's known types so they can be serialized.
return types;
}
}
This particular solution works for me because all of my DataContract classes extend a common base class. It could be reworked to load all Types from the assembly that have the DataContract attribute which would result in the same set, in my case.
Upvotes: 2