none
Problem sharing custom collection types between service and client

    Question

  • I have an http wcf service that exposes a method:

    public PermissionCollection GetPermissions(){ ... }

    PermissionCollection is defined as follows:

    [CollectionDataContract]
    public class PermissionCollection : List<PermissionItem> { ... }

    The PermissionCollection class is contained in a project that both the client + service have (this application is for a closed environment on a local network - it is a winforms application that uses a wcf service (hosted within IIS) to load and save its data).

    When I generate the service proxy in the client application, visual studio insists on creating a new PermissionCollection object in the clients namespace - and deserialises the object to this.

    I followed an example here : http://www.codeproject.com/KB/WCF/WCFCollectionTypeSharing.aspx
    which explained how to use svcutil to generate a proxy that used the shared collection type, however this is not an ideal solution. I would rather other developers on the application didn't have to run this every time they need to regenerate the proxy. Is there no way to get Visual studio to generate the proxy correctly? Perhaps there is a better way of setting up the service or the datacontracts so that it will correctly use the shared class on the client every time?

    Any suggestions?


    Friday, December 12, 2008 10:34 AM

Answers

  • I removed my earlier post because I think I have found a better solution...

    You can edit the WCF mapping file (Reference.svcmap) and add a custom collection mapping :-


    <CollectionMappings> 
        <CollectionMapping TypeName="MyNamespace.MyCustomCollection`1" Category="List" /> 
    </CollectionMappings> 


    When you now open the 'Configure Service Reference' dialog box you will see a new entry '( Custom )' in the collection type drop down. If you select 'Update Service Reference' to regenerate your proxy with this entry selected, you should find that the custom collection is mapped to the type in your shared assembly rather than an auto-generated collection type.

    If you need to transfer more than one collection type, you can add multiple CollectionMapping entries to apply different mappings for each specific type.

    For example:-

    Create a custom collection class with the following definition:-

    Note the use of the [CollectionDataContract] attribute.

    [CollectionDataContract]  
    public class MyCustomCollection<T> : IEnumerable<T>  
    {  
        public void Add(T item)  
        {  
            ...  
        }  
        public IEnumerator<T> GetEnumerator()  
        {  
            ...  
        }  
        // Rest of implementation...  


    Create a service contract definition, as follows:-

    Note that the class MyItem which replaces type parameter T must be marked as a data contract. 

    [ServiceContract]  
    [ServiceKnownType("GetKnownTypes"typeof(KnownTypesHelper))]  
    public interface IMyService  
    {  
        [OperationContract]  
        MyCustomCollection<MyItem> GetItems();  
    }  
     
    internal static class KnownTypesHelper  
    {  
        public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)  
        {  
            var knownTypes = new List<Type>  
                                 {  
                                     typeof(MyCustomCollection <MyItem>)  
                                 };  
            return knownTypes;  
        }  


    In the client, add a normal reference to the assembly containing the MyCustomCollection and MyItem classes.

    Add the service reference.

    Now edit the Reference.svcmap file and add the following custom collection mapping.

    <CollectionMappings>    
        <CollectionMapping TypeName="MyNamespace.MyCustomCollection`1" Category="List" />    
    </CollectionMappings>   


    Open the ‘Configure Service  Reference’ dialog and select the (custom) collection type entry. Check the ‘Reuse types in referenced assemblies’ check box.

    Now update the Service Reference.

    The definition for the operation GetItems() in the client proxy (Reference.cs) should be:-

    public object GetItems()  
    {  
        return base.Channel.GetItems();  


    Furthermore, there should be no auto-generated client side collection type - normally this would be called something like MyCustomCollectionOfMyItem.

    You should be able to call the service operation in the client code, as follows:-

    using (var client = new ServiceClient())  
    {  
        var items = (MyCustomCollection<MyItem>) client.GetItems();  


    Note that you should be allowed to cast the result of GetItems() directly to the custom collection type in the shared assembly.
    I have tested this, (it is actually a more elegant solution to a problem I was also having a while ago), and it appears to work.

    Hope this finally answers your question?

    • Marked as answer by davidc01 Tuesday, December 16, 2008 12:11 PM
    • Edited by Dave Wheeler Tuesday, December 16, 2008 12:13 PM
    Tuesday, December 16, 2008 11:21 AM

All replies

  • You could try not using svutil.exe from the commandline, but add the service as a Service Reference to the client. This also will generate the client proxy. In the future you only have to click 'Update reference' after making some changes to service
    Friday, December 12, 2008 10:55 AM
  • Sorry, perhaps I wasn't very clear in my original question - the problem is that when I add the service as a service reference in visual studio it generates the collection in the client rather than using the shared object library.

    I can only get it to use the shared library by using svcutil (as described in the article I linked to).
    i.e. using the /reference arg to reference the shared library dll, and the /collectionType arg to tell it to use the custom collection that I define in the shared lib.

    What I would like to do is get this functionality within visual studio and be able to use the update reference functionality that it provides.
    Friday, December 12, 2008 12:15 PM
  • In Visual Studio 2008, right click on your service reference under Solution Explorer and select 'Configure Service Reference'. In the dialog box, choose the desired data type mapping for your collection classes. Then enable the 'Reuse types in all reference assemblies' checkbox. You can enable type reuse in all referenced assemblies, or select the ones you want individually (obviously, you will need to add the assembly that contains your shared classes as a normal reference in your client project).

    Now when you perform an 'Update Service Reference' Visual Studio should update your proxy using the shared types in the shared assembly, rather than auto-generating these.

    Hope this solves your problem?

    Dave

    Friday, December 12, 2008 6:29 PM
  • Unfortunately this does not fix the problem.
    When going to the "Configure Service Reference" dialog, you are only presented with default collection types that .Net provides, and there does not appear to be a way to add custom ones (i.e. in my case "PermissionCollection"). Even if there was one here, it would only fix the problem if that was the only collection type I was transferring (what if i wanted to pass a "UserCollection" in the same service).

    I am assuming that the only way to achieve this is with svcutil....

    Perhaps instead of this there is a way to set up the server differently so that it advertises itself differently and allows the client to automatically use the shared lib? (rather than forcing it with svcutil)
    Monday, December 15, 2008 2:47 PM
  • I have just tried using the SvcUtil with /collectionType and it works, but I understand that its does not solve your original problem of not wanting to run this everytime you need to regenerate your proxy :-(

    I will have another look at the 'Configure Service Reference' options, as I'm sure I have managed to share assemblies containing custom collections in the past using this method...
    Monday, December 15, 2008 4:08 PM
  • Did you intend to remove your longer post - or is it a forum bug? I didn't get a chance to fully read it (thank you for the in-depth response by the way). 
    Tuesday, December 16, 2008 11:18 AM
  • I removed my earlier post because I think I have found a better solution...

    You can edit the WCF mapping file (Reference.svcmap) and add a custom collection mapping :-


    <CollectionMappings> 
        <CollectionMapping TypeName="MyNamespace.MyCustomCollection`1" Category="List" /> 
    </CollectionMappings> 


    When you now open the 'Configure Service Reference' dialog box you will see a new entry '( Custom )' in the collection type drop down. If you select 'Update Service Reference' to regenerate your proxy with this entry selected, you should find that the custom collection is mapped to the type in your shared assembly rather than an auto-generated collection type.

    If you need to transfer more than one collection type, you can add multiple CollectionMapping entries to apply different mappings for each specific type.

    For example:-

    Create a custom collection class with the following definition:-

    Note the use of the [CollectionDataContract] attribute.

    [CollectionDataContract]  
    public class MyCustomCollection<T> : IEnumerable<T>  
    {  
        public void Add(T item)  
        {  
            ...  
        }  
        public IEnumerator<T> GetEnumerator()  
        {  
            ...  
        }  
        // Rest of implementation...  


    Create a service contract definition, as follows:-

    Note that the class MyItem which replaces type parameter T must be marked as a data contract. 

    [ServiceContract]  
    [ServiceKnownType("GetKnownTypes"typeof(KnownTypesHelper))]  
    public interface IMyService  
    {  
        [OperationContract]  
        MyCustomCollection<MyItem> GetItems();  
    }  
     
    internal static class KnownTypesHelper  
    {  
        public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)  
        {  
            var knownTypes = new List<Type>  
                                 {  
                                     typeof(MyCustomCollection <MyItem>)  
                                 };  
            return knownTypes;  
        }  


    In the client, add a normal reference to the assembly containing the MyCustomCollection and MyItem classes.

    Add the service reference.

    Now edit the Reference.svcmap file and add the following custom collection mapping.

    <CollectionMappings>    
        <CollectionMapping TypeName="MyNamespace.MyCustomCollection`1" Category="List" />    
    </CollectionMappings>   


    Open the ‘Configure Service  Reference’ dialog and select the (custom) collection type entry. Check the ‘Reuse types in referenced assemblies’ check box.

    Now update the Service Reference.

    The definition for the operation GetItems() in the client proxy (Reference.cs) should be:-

    public object GetItems()  
    {  
        return base.Channel.GetItems();  


    Furthermore, there should be no auto-generated client side collection type - normally this would be called something like MyCustomCollectionOfMyItem.

    You should be able to call the service operation in the client code, as follows:-

    using (var client = new ServiceClient())  
    {  
        var items = (MyCustomCollection<MyItem>) client.GetItems();  


    Note that you should be allowed to cast the result of GetItems() directly to the custom collection type in the shared assembly.
    I have tested this, (it is actually a more elegant solution to a problem I was also having a while ago), and it appears to work.

    Hope this finally answers your question?

    • Marked as answer by davidc01 Tuesday, December 16, 2008 12:11 PM
    • Edited by Dave Wheeler Tuesday, December 16, 2008 12:13 PM
    Tuesday, December 16, 2008 11:21 AM
  • Hmm, that does look promising, thanks. I will give it a go.
    Tuesday, December 16, 2008 11:40 AM
  • That worked perfectly. Thank you very much for your help, it is very much appreciated.

    By the way, did you find any documentation about the .svcmap file? I could only find random blog posts on msdn.
    Tuesday, December 16, 2008 12:11 PM
  • Just by the way - using the CollectionMapping in svcmap means that I actually don't need to wrap the list in another collection anymore. I can just specify [CollectionDataContract] as before.

    The [ServiceKnownType (.. etc and the KnownTypesHelper appear to be unnecessary in this case.

    In this way, sharing the collection type between client and server is as simple as (going back to my initial example code):

    The operation in the contract defined as:

    [OperationContract]
    public PermissionCollection GetPermissions();

    PermissionCollection is defined in the shared project as follows:

    [CollectionDataContract]
    public class PermissionCollection : List<PermissionItem> { ... }

    And then finally in svcmap on the client:
    <CollectionMappings>
          <CollectionMapping TypeName="MySharedNamespace.PermissionCollection " Category="List" />
    </CollectionMappings>


    Tuesday, December 16, 2008 12:22 PM
  • Well spotted! ServiceKnownTypes is unnecessary and can be safely removed from the example code above.

    Just for clarity, the ServiceKnownTypes attribute is only required in cases where  the deserializer would have no way of knowing the type of items being stored in the collection and how to deserialize them. For example, if your GetPermissions() operation  was modified to return a Hashtable, which can normally hold any object type, you would want to use KnownTypes to specify the type of items to include with the client generated code i.e. your PermissionItem class.

    Regarding documentation on .svcmap files, like you, I only found the random blog posts. Maybe somebody from Microsoft could point us at some official documentation, if they read this thread?

    I'm pleased that we were able to resolve your issue ;-)

    Tuesday, December 16, 2008 2:05 PM