Ran across a weird problem with my WCF references this morning. In a nutshell, I have a duplex service set up so that a server can notify a client by sending objects in a data contract. When a client connects, it runs a function on the server to return a List(Of NewItem) based on what's in the shared queue of the service class. The trouble is, when I update my service reference in the client, it says the function returns a NewItem object, not a List(Of NewItem) object. I can go into the reference and manually change it to a List object and it'll transfer just fine. Any ideas why the service reference generator would arbitrarily change my return type?
Here's the relevant code:
<ServiceContract(
CallbackContract:=GetType(INotifyCallback),
SessionMode:=ServiceModel.SessionMode.Required)>
Public Interface INotifyService
<OperationContract()>
Function GetNewServerItems() As List(Of NewItem)
End Interface
<DataContract>
<Serializable>
Public Class NewItem
<DataMember()>
Public Property ItemNum As String
<DataMember()>
Public Property Timestamp As DateTime
End Class
<ServiceBehavior(
ConcurrencyMode:=ServiceModel.ConcurrencyMode.Single,
InstanceContextMode:=ServiceModel.InstanceContextMode.Single)>
Public Class NotifyService
Implements INotifyService
Shared _server_items As New List(Of NewItem)
Public Function GetNewServerItems() As List(Of NewItem)
Return _server_items
End Function
End Class
And in Reference.vb (simplified):
<System.ServiceModel.OperationContractAttribute(Action:="http://tempuri.org/INotifyService/GetNewServerItems", ReplyAction:="http://tempuri.org/INotifyService/GetNewServerItemsResponse")> _
Function GetNewServerItems() As NotifyGateway.NewItem()
You and I fell into the same trap: we both misread the VB.NET code generated for the proxy method as returning a single item. In fact, it is returning an array.
WSDL uses XML Schema to describe the shape of the request and response. XML Schema has no concept of a "list", only of a sequence. By default, when it sees a sequence of items, "Add Service Reference" generates an array. You can change this to generate a "List", simply by changing the collection type on the "Advanced" tab of the "Add Service Reference" or "Configure Service Reference" dialogs.
When adding a reference the way you did, you should change the "Collection type" in the Advanced Service Reference Settings (Add Service Reference dialog) to "system.Collections.Generic.List". By default it is "System.Array".
I would not recomend though using the Add Reference way because you lose control over the configuration of your WCF service, and VS adds a lot of garbage that you do not need, and that would make your client/service link harder to maintain.
Hope this helps.
There is another, less well-known, but more powerful mechanism for preserving the collection types without resorting to using the default collection type setting.
This mechanism is especially useful if you have CollectionDataContract classes that you use in your code on both sides of a WCF pipe.
The solution involves editing the svcmap that is generated as part of the WCF service reference and adding specific CollectionMapping entries for each of the collection types that you want to serialize. The primary thing to watch out for here is generics: if WCF finds more than one match for a given class, it will refuse to generate the code.
To actually make the changes:
1) Show all of the files in the project which contains your WCF service reference.
2) Expand your service reference
3) Double-click on the Reference.svmap file to edit it.
4) Add your entries in the CollectionMappings section (if it does not exist, you can add it).
For example, in order to transfer generic Dictionaries and Lists, and to transfer StringCollections, you can have the following entries:
<CollectionMappings>
<CollectionMapping TypeName="System.Collections.Generic.Dictionary`2" Category="Dictionary" />
<CollectionMapping TypeName="System.Collections.Generic.List`1" Category="List" />
<CollectionMapping TypeName="System.Collections.Specialized.StringCollection" Category="List" />
<CollectionMappings>
The first item with a category of Dictionary will serve as the default Dictionary collection type and the first item with a category of List will serve as the default Collection type.
We have over 200 collection classes that we use this way for Silverlight and Windows Forms clients and it is an extremely efficient way to reuse your own code on both sides of a WCF pipe.
One important note: to reuse your classes, you need to ensure that the "Reuse types in referenced assemblies" flag in the service reference configuration is checked or that that the GenerateInternalTypes flag in the reference.svmap is set to false (these are the same thing).
Related
I'm kind of confused here. I have a WCF web service
<OperationContract()>
Function StartStep(ByVal Key As Guid, WorkOrders As List(Of Guid), UserUID As Guid, Comment As String) As Boolean
But when I generate my web reference on my client, WorkOrders comes out as an observableCollection. I created a few new web services and all of the lists are doing that. A list is a fairly common data type, I assumed it would be handled properly by WCF.. could this be because I have another function that returns an ObservableCollection and that's screwing something up? Would I have to use a KnownType or something?
In the "Advanced" tab of the "Add Service Reference" dialog, you get to say how you want collections handled on the client. I guess you have it set to default to ObservableCollection, but you can select List.
Neither "List", nor "ObservableCollection", nor any other .NET type are transferred over the wire. In all cases, XML or some other such data representation is sent. The setting I mentioned earlier is about deciding how the client will interpret this XML data.
I am creating an XML web service that passes an array of custom types. In my consuming code I am referencing the code as a web reference which I have given the namespace MYWS. Now in code I am trying to assign the results of my web service call to an array of my type like so :
'instance to make a call to my web service
Dim srv As New MYDWS.ServiceNameWSSoapClient
'array to hold the results
Dim arr() As MyClass
'assign the web service call results
arr = srv.myWebMethod()
When I do this the complier complains, saying:
Value of 1 dimensional array of my.namespace.MyClass cannot be
converted to 1 dimensional array of my.namespace.MYWS.MyClass because
my.namespace.MYSW.MyClass is not derived from my.namespace.MyClass
Now I understand the message, the thing is they are the same class. The class is declared in my calling code by the web service references a dll from that project. How do I tell the compiler that these are the same type? Any help would be very much appreciated. Thanks!
The upshot is that you have a namespace mismatch. If you right-click on MyClass in your example and select Go To Definition, where does it take you? I suspect that you may end up in a locally defined class.
The solution is to change
Dim arr() As MyClass
to
Dim arr() As MYWS.MyClass
Update based on information in comments
The problem with using the web service is that you cannot cast it to a local class.
You have a couple of options depending on exactly what you need out of the local class.
If you only need methods to act on the data in the class or you need additional properties, you can create a partial class in your environment that extends the class created by the web service. For example:
Namespace MYWS
Public Partial Class MyClass
Public Property SomeAdditionalData As String
Public Sub SomeMethod
' Perform some operations on the class members
End Sub
End Class
End Namespace
However, if you have calculations or other work embedded in the class, then you will need to get the data using the web service class, then copy the data from that class into your local class. If the properties have the same names, you could ease this task using reflection.
As another option, if you have control over the web service, you could change it to a WCF service. This will allow you to reuse the exact same class code on both ends of the communication pipe.
Found a solution to the problem. In the web.config I found this:
<add key="net.mydom.mydom" value="http://localhost:7452/dir/mysvc.asmx"/>
which was what the system automatically entered when I registered the web service. I got the error messages on screen, but everything compiled and ran w/o problem.
When I manually changed to this:
<add key="net.mydom" value="http://localhost:7452/dir/mysvc.asmx"/>
The error messages went away and everything continued to function as expected.
(That only took my 7 years to figure out...)
UPDATE:
Well, not quite the fix, but it must be close. After awhile, the problem came back, when I switched back to to:
<add key="net.mydom.mydom" value="http://localhost:7452/dir/mysvc.asmx"/>
it went away again...sure to come back at any time...
UPDATE
If I explicitly add:
imports net.mydom
to the top of my code, the message goes away again (even though I was explicitly using the full net.mydom. when typing the variables.
I made a simple PasteBin demo example of what my code looks like: http://pastebin.com/GpDhPRVm
My actual Process object is extremely complex that includes adding collections of Tasks, Documents, Workflows, etc into a Process object and setting properties through methods, etc.
My PasteBin example is as simple as I can make it to show where things break down. Including the Process object in the CreateNewProcess method in my service (shown in code below), allows the service user to automatically "see" the Process object, properties and enumerators in their instance on the client side.
<ServiceContract()>
Public Interface ICreateProcess
<OperationContract()>
Sub CreateNewProcess(ByVal newprocess As Process)
End Interface
However, it does not allow them to use any of the methods like the 'AddTask' method (shown in PasteBin example) and it also doesn't expose the Task or TaskCollection objects. Even if I decorate the methods with DataContract, OperationContract, etc they still are not visible to the client service.
This is my major issue: I need the client service to be able to fully "build" the Process object and then pass it in to the CreateNewProcess method.
Another small note: (to avoid comments asking me about it) is that I made sure that all of my properties are simple types that are interoperable since this needs to be able to work for Java - not just .NET clients.
Ok, I figured it out with a little help from a buddy of mine.
Can't use methods at all outside the .svc service class; only properties, enums and sub objects will translate down to the client. (I figured this)
Then, instead of using a Collection, you have to use a generic List type.
So, instead of this:
Public Tasks As New TaskCollection() 'where TaskCollection inherits from Collection
I needed to do this:
Public Tasks As List(Of Task)
And the client will just have to build their own array of Tasks and assign it to the t.Tasks property.
This works like a charm now: http://pastebin.com/rt8HwsXY
I have two .NET 3.5 WCF services build with VS2008.
I have two WCF clients in Silverlight to consume these services. The clients are generated with the 'Add Service Reference'. I am using Silverlight 4.
ONE of the proxies is generated with Specified properties for each property. This is a 'message-in' class for my service method :
// properties are generated for each of these fields
private long customerProfileIdField;
private bool customerProfileIdFieldSpecified;
private bool testEnvField;
private bool testEnvFieldSpecified;
Now my other service (still with a Silverlight client) does NOT generate Specified properties.
Now I don't care about 'tenets of good SOA'. I just want to get rid of these damn properties because in the context of what I'm doing I absolutely hate them.
There has to be some difference between the two services - but I don't want to have to completely rip them apart to find out the difference.
A similar question before had the answer 'you cant do it' - which is definitely not true because I have it - I just don't know what I did differently.
Edit: I am now in a situation where I regenerate my Silverlight 4 proxy to my 3.5 WCF service (all on the same localhost machine) that sometimes I get 'Specified' properties and sometimes I don't. I no longer think (as I suspected originally) that this is due solely to some endpoint configuration or service level [attribute]. Theres certain triggers in the message itself that cause Specified to be generated (or not). There may be many factors involved or it may be something very simple.
try this in your WCF service where the property is declared
[DataMember(IsRequired=true)]
public bool testEnvField { get; set; }
IsRequired=true will negate the need for the testEnvFieldSpecified property
These extra Specified properties are generated for value types which are being specified as optional in either the contract or the attribute markup.
As value types have a value by default, the extra Specified flags are being added for these properties, to allow the client (and server) to distinguish between something explicitly not specified or explicitly specified - which may well be set to the default value. Without it, integers would always end up being 0 (and being serialized) even if you don't set them (because of the mapping to int) in your client code. So when you do, you need to also make sure that you set the Specified flag to true, otherwise these properties will not get serialized.
So to prevent these flags being generated for value types, you would have to change the contract to make these value type properties mandatory, instead of optional.
Hope that makes sense.
OK I've found one thing so far that will cause Specified properties to be generated:
The presence of an XTypedElement in the message.
These are used by Linq2XSD. I was returning an element from a Linq2XSD model.
This triggered Specified properties to be generated EVERYTHING in all my classes :
public XTypedElement Foo { get; set; }
This however didn't :
public XElement Foo { get; set; }
Still curious as to why this is, and if there are any other things that trigger this.
NOTE: I realize this is an old question. I'm adding this here because this question comes up as a top result on Google, and it's helpful information for whoever comes looking.
Try adding this line into your operation contract declaration:
[XmlSerializerFormat]
It should look something like this:
namespace WebServiceContract
{
[ServiceContract(Namespace = "http://namespace")]
[XmlSerializerFormat] //This line here will cause it to serialize the "optional" parameters correctly, and not generate the extra
interface InterfaceName
{
/*...Your web service stuff here...*/
}
}
I found that if I put a DataTable in a service DataContract then the generated client will use xml serializer and thus generate the *IsSpecified members.
I got a class called "Board" and one of its property's is an ObservableCollection. When i send the ObservableCollection through WCF (from server to client) end call it from my proxy, it's turned into an Array, which is no good for me.
Can i keep the ObservableCollection after being sent, or do i have to kick the Array till it becomes an ObservableCollection again?
Check out the 'Configure Service Reference' option in the context menu in VS for the reference. You can choose the collection type that is transmitted across the service. By default I think it is set to array but there are several choices (I believe list and observablecollection are options).
EDIT: I just checked, and unfortunately observable collection is not one of the choices. It looks like you'll have to pick from:
Array
ArrayList
LinkedList
List
Collection
BindingList
By default - no, you cannot do anything about it. WCF will serialize your structures into something that can be represented with XML schema. XML Schema has no knowledge of anything but raw, and fairly simplistic data structures. You can only transfer concrete, raw data - no "magic" behavioral addon.
There is one solution to the problem, IF you own both ends of the wire: you could put your service and data contracts into a separate class library assembly, and share those between server and client. In that case, you only ever have one single implementation of your data contract - your ObservableCollection.
If you share that assembly between your service (implementation) class, and the client (add the reference to that assembly before you "Add Service Reference" from Visual Studio!), then your client should pick up that ObservableCollection and continue to use that (instead of creating a XML schema compatible Array on the client side).
Thank you both for the answer.
I will look at both solutions when i continue the project, and will start with try and change the Collection send through the wcf service.
I'll let you know what works for me...