Why is WCF serializing a Enum as a string? - vb.net

Code on the server
<DataContract(Namespace:="http://schema.aam.us.com/2010/6", Name:="TradeStatus")>
Public Enum TradeStatus
NewOrder = 100
SendToProvider = 101
ProviderSubmitted = 102
ProviderAccepted = 103
ExecutionPending = 104
Executed = 105
TicketsCreated = 106 'TERMINAL STATE
End Enum
<DataContract(Namespace:="http://schema.aam.us.com/2010/6", Name:="StatusUpdate")> _
Public Class StatusUpdate
Public Sub New(ByVal tradeStatus As TradeStatus, ByVal additionalInformation As String)
Me.TradeStatus = tradeStatus
Me.AdditionalInforamtion = additionalInformation
End Sub
<DataMember(IsRequired:=True)> _
Public Property AdditionalInforamtion() As String
<DataMember(IsRequired:=True)> _
Public Property TradeStatus() As TradeStatus
End Class
Generated code
<System.Diagnostics.DebuggerStepThroughAttribute(), _
System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0"), _
System.Runtime.Serialization.DataContractAttribute(Name:="StatusUpdate", [Namespace]:="http://schema.aam.us.com/2010/6"), _
System.SerializableAttribute()> _
Partial Public Class StatusUpdate
Inherits Object
Implements System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged
<System.NonSerializedAttribute()> _
Private extensionDataField As System.Runtime.Serialization.ExtensionDataObject
Private AdditionalInforamtionField As String
Private TradeStatusField As String
<Global.System.ComponentModel.BrowsableAttribute(false)> _
Public Property ExtensionData() As System.Runtime.Serialization.ExtensionDataObject Implements System.Runtime.Serialization.IExtensibleDataObject.ExtensionData
Get
Return Me.extensionDataField
End Get
Set
Me.extensionDataField = value
End Set
End Property
<System.Runtime.Serialization.DataMemberAttribute(IsRequired:=true)> _
Public Property AdditionalInforamtion() As String
Get
Return Me.AdditionalInforamtionField
End Get
Set
If (Object.ReferenceEquals(Me.AdditionalInforamtionField, value) <> true) Then
Me.AdditionalInforamtionField = value
Me.RaisePropertyChanged("AdditionalInforamtion")
End If
End Set
End Property
<System.Runtime.Serialization.DataMemberAttribute(IsRequired:=true, EmitDefaultValue:=false)> _
Public Property TradeStatus() As String
Get
Return Me.TradeStatusField
End Get
Set
If (Object.ReferenceEquals(Me.TradeStatusField, value) <> true) Then
Me.TradeStatusField = value
Me.RaisePropertyChanged("TradeStatus")
End If
End Set
End Property
Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Protected Sub RaisePropertyChanged(ByVal propertyName As String)
Dim propertyChanged As System.ComponentModel.PropertyChangedEventHandler = Me.PropertyChangedEvent
If (Not (propertyChanged) Is Nothing) Then
propertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(propertyName))
End If
End Sub
End Class

Enums are serialized by default. Like primites and Collection classes you do not need to mark them with [DataContract]. However, that does not mean that WCF does not let you customize the serialization behavior, so in the spirit of interoperability you can change how the enum will be serialized. As part of that customizability if you mark it with DataContract but do not mark EnumMembers you are changing the default serialization scheme. See more on Enum serialization here Enum Serialization
EDIT: Got to thinking a bit more about this and now I started wondering about the underlying cause... turns out it is the WSDL's fault.
By default if you don't put [DataContract] then WCF by default serializes the enum as if it had the [DataContract] and [EnumMembers] attribute. So if you take the following example
[DataContract]
public enum FileType {
[EnumMember]
Text,
[EnumMember]
Pdf,
[EnumMember]
Word
}
it will generate the following WSDL
<xs:simpleType name="FileType">
<xs:restriction base="xs:string">
<xs:enumeration value="Text" />
<xs:enumeration value="Pdf" />
<xs:enumeration value="Word" />
</xs:restriction>
</xs:simpleType>
<xs:element name="FileType" nillable="true" type="tns:FileType" />
So now if you take out the [EnumMember] attributes like so
[DataContract]
public enum FileType {
Text,
Pdf,
Word
}
your WSDL will look like this:
<xs:simpleType name="FileType">
<xs:restriction base="xs:string" />
</xs:simpleType>
<xs:element name="FileType" nillable="true" type="tns:FileType" />
So the second one looks just like the first one except without the enumeration elements. Now what is the difference between the second one and just a WSDL describing a simple string? None. That is why the WCF Proxy gen gives you a string instead of the Enum.

Simple - this is the way it's meant to work.
XML Schema has no concept of an enum, in the sense of a name/value pair. The closest it has is the ability to indicate that a particular simple type may have one of several values - string values in this case.
Note that ASMX web services and the XML Serializer do exactly the same thing.
OK, Jonathan and I are both right and both wrong.
When serializing an enum, WCF adds .NET-specific information to the XML Schema. This permits another .NET implementation to treat the enum as an enum, complete with preservation of the enum values.
However, no other platform is going to understand this information. As a result, an enum will simply be treated as a string which can only take on one of several values.

If you include the DataContract attribute, then you need to tag at least one value with the EnumMember attribute. Otherwise it can't see any of the values and turns the whole field into a string.
If you do not include the DataContract attribute, then you don't need the EnumMember attribute either.
EDIT: Example of correct code
<DataContract(Namespace:="http://schema.aam.us.com/2010/6", Name:="TradeStatus")>
Public Enum TradeStatus
<EnumMember> NewOrder = 100
<EnumMember> SendToProvider = 101
<EnumMember> ProviderSubmitted = 102
<EnumMember> ProviderAccepted = 103
<EnumMember> ExecutionPending = 104
<EnumMember> Executed = 105
<EnumMember> TicketsCreated = 106 'TERMINAL STATE
End Enum

Because there is a defined Name for that specific value.
When there is not a defined name for a given value, then the serializer will write out an integer and the deserializer will read it just fine:
TradeStatus didntMakeCompileCutoffDate = (TradeStatus)dbRecord.TS; // value 999
It would be nice if the serializer allowed an option to write a particular enum field as an integer because this would allow the list of valid enum names to increase without having to recompile the deserializer code. Currently the deserializer will throw an exception (and reject the whole contract) due to the unrecognized enum name.
It may be possible to work around this issue by using the [EnumMember(Value = "123")] and specify the equivalent integers for each of the enum names known at compile time. From MSDN Enumeration Types in Data Contracts
A 2nd work around is to specify the contract enum as a property that uses an integer backing store and serialize the backing store. SO Link

Related

When adding a web service reference, I can't get all properties of an object

I'm using VB.Net in Visual Studio 2012 for a project with a web service I can't change at all. The problem is that VB does not generate all the properties it should generate.
I added the web service as a reference service. According to the XSD, collectionRAEEDataType class should include properties as receiver, referenceNumber, sigCode. But, when I try to access them, two of them are not shown: sigCode and responsabilitySystemData.
I've contacted the support email of the web service and they tell me that yes, that in the Java service the class is generated with those fields:
public class CollectionRAEEDataType {
protected String sigCode;
protected RegisteredInfoDataType responsabilitySystemData;
...
}
But in Reference.vb I get this:
Partial Public Class collectionRAEEDataType
Inherits Object
Implements System.ComponentModel.INotifyPropertyChanged
Private itemField As Object
Private receiverField As receiverType
Private referenceNumberField As String
Private assignmentOfficeIdField As String
'''<remarks/>
<System.Xml.Serialization.XmlElementAttribute("responsabilitySystemData", GetType(registeredInfoDataType), Form:=System.Xml.Schema.XmlSchemaForm.Unqualified, Order:=0), _
System.Xml.Serialization.XmlElementAttribute("sigCode", GetType(collectionRAEEDataTypeSigCode), Form:=System.Xml.Schema.XmlSchemaForm.Unqualified, Order:=0)> _
Public Property Item() As Object
Get
Return Me.itemField
End Get
Set
Me.itemField = value
Me.RaisePropertyChanged("Item")
End Set
End Property
'''<remarks/>
<System.Xml.Serialization.XmlAttributeAttribute()> _
Public Property receiver() As receiverType
Get
Return Me.receiverField
End Get
Set
Me.receiverField = value
Me.RaisePropertyChanged("receiver")
End Set
End Property
...
As you can see, the property receiver is ok, but responsabilitySystemData and sigCode are not properties.
Do you know how could I solve this problem?
Thanks a lot.
I answer myself. I had to instantiate the Item field with the type I wanted, registeredInfoDataType or collectionRAEEDataTypeSigCode.
For example,
MyElement.Item = New registeredInfoDataType()
Thank you.

How should I serialize a vb.NET object which has class attributes with JsonConvert?

I am trying to use Json.NET to serialize an object in vb.NET.
The object fails to serialize correctly. This seems to be because of some attributes on the class.
Below is the class definition – a very simple definition.
Here is the code to serialize:
Dim MyObject As New TestClass() With {.Property1 = "Hello", .Property2 = 3}
Dim Serialized As String = JsonConvert.SerializeObject(MyObject)
After the 2 lines above execute, the variable Serialized has the following value (which is not what I would expect or want):
"Namespace1.TestClass"
When I remove the class attributes completely (just the class attributes, not the property attributes) and then execute the same 2 lines of code, the variable Serialized has the following value (which is what I would expect):
{"Property1":"Hello","Property2":"3"}
This is only an example: we have many such classes with these kinds of attributes. We need to serialize them with Json.NET.
Removing the attributes is not possible, the classes I am dealing with are part of a system of existing applications and WCF based web services (i.e. part of our system needs our current serialization system for WCF kept in place, and another part of our system needs to serialize the same class with Json .. I won't go into the "why" details, just that we are serializing thousands of such objects for database writes and have speed and space isues).
I realize also that I can use JsonTextWriter to serialize, but then we have maintenance issues – every time we add/remove a property we have to remember to maintain the serialization code appropriately.
So how must I serialize this class correctly without removing the attributes?
I have not seen anything on the NewtonSoft site, nor anywhere else, which addresses this specific problem.
Here again is the class definition, along with the attributes.
<System.CodeDom.Compiler.GeneratedCodeAttribute ("System.Xml", "2.0.50727.3053"), _
System.SerializableAttribute(), _
System.Diagnostics.DebuggerStepThroughAttribute(), _
System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Xml.Serialization.XmlTypeAttribute ([Namespace]:="http://Namespace.com/SomePath/SomeXsd.xsd", TypeName:="TestClass"), _
System.ComponentModel.TypeConverterAttribute(GetType (System.ComponentModel.ExpandableObjectConverter))> _
Partial Public Class TestClass
Private _Property1 As String
Private _Property2 As Integer
<System.ComponentModel.Browsable(False), System.Xml.Serialization.XmlIgnoreAttribute()> _
Public Property Property1() As String
Get
Return Me._Property1
End Get
Set(ByVal value As String)
If (Me._Property1 <> value) Then
Me._Property1 = value
End If
End Set
End Property
<System.ComponentModel.Browsable(False), System.Xml.Serialization.XmlIgnoreAttribute()> _
Public Property Property2() As String
Get
Return Me._Property2
End Get
Set(ByVal value As String)
If (Me._Property2 <> value) Then
Me._Property2 = value
End If
End Set
End Property
End Class
The problem is the TypeConverterAttribute on your class. When Json.Net sees that, it will use the associated TypeConverter to convert the object to a string. In this case, it results in the class's type name being output.
You can override the unwanted behavior by adding a JsonObjectAttribute to the classes that have a TypeConverterAttribute applied. But since it appears that your classes are generated code, it might not be feasible to do that on an class-by-class basis unless you can modify the code generator. In that case, another alternative is to use a custom IContractResolver to force Json.Net to ignore the TypeConverter on classes that have it.
Here is the code you would need for the resolver:
Class TypeConverterIgnoringResolver
Inherits DefaultContractResolver
Protected Overrides Function CreateContract(objectType As Type) As JsonContract
If objectType.GetCustomAttributes(True) _
.OfType(Of System.ComponentModel.TypeConverterAttribute)() _
.Any() _
Then
Return MyBase.CreateObjectContract(objectType)
End If
Return MyBase.CreateContract(objectType)
End Function
End Class
You can use the resolver like this:
Dim MyObject As New TestClass() With {.Property1 = "Hello", .Property2 = 3}
Dim Settings As New JsonSerializerSettings
Settings.ContractResolver = New TypeConverterIgnoringResolver()
Dim Serialized As String = JsonConvert.SerializeObject(MyObject, Settings)
Console.WriteLine(Serialized)
Fiddle: https://dotnetfiddle.net/s6Ebmc
I guess you don't want to change your TestClass as it must have been generated by some tool, I would suggest to derive a new class from it:
Imports Newtonsoft.Json
<JsonObject()>
Public Class OtherClass
Inherits TestClass
End Class
and use the attribute <JsonObject()>. This should do the trick:
Dim MyObject As New OtherClass() With {.Property1 = "Hello", .Property2 = 3}
Dim Serialized As String = JsonConvert.SerializeObject(MyObject)
UPDATE:
Since you're already working with a Partial you can extend it creating a new one (in another folder):
Imports Newtonsoft.Json
<JsonObject()>
Partial Public Class TestClass
End Class

Make static member persistent

I have one class with a private static (shared, since I'm in VB.BET) field and its associated public static property, since it stores one variable that should be the same to all the instances of this class.
My Class looks like this:
Public MustInherit Class NitrogenController
Private _active As Boolean
Private Shared _controlInterval As TimeSpan
Private _lastControlTime As Date
Public Property Active() As Boolean
Public Shared Property ControlInterval() As System.TimeSpan
'other properies that must be persisted
Public Function Control() As Boolean
If Not Now > _lastControlTime.Add(_controlInterval) Or Not _active Then
Return False
Else
DoControl()
_lastControlTime = Now
Return True
End If
End Function
End Class
The problem arrives when trying to binary serialize these kind of objects, since this shared field is nos being properly stored and returns to its default value when deserializing.
I suppose this is the expected behaviour, so my question is... how can I make a shared field persistent? I have read some comments to similar questions that say that this is a bad design, but it really makes sense (AFAIK) in my case, since this variable should be the same to all the object, but can be changed by the user and therefore should be stored.
Can you suggest another way of doing it?
Thanks!
What you have read, in my opinion, is correct. This is, likely, a bad design. However, if you must, there are two ways to do this with the XmlSerializer. The easy way would be to simply add a public instance (non-shared) property which has a getter and setter which simply wrap the shared property, for instance:
Public MustInherit Class NitrogenController
Public Shared Property ControlInterval As TimeSpan
Public Property CurrentControlInterval() As TimeSpan
Get
Return ControlInterval
End Get
Set(value As TimeSpan)
ControlInterval = value
End Set
End Property
End Class
If you aren't satisfied with that method, the second, more involved, option would be to override the default serialization logic by implementing the ISerializable interface.

How to add a "sub property" to a class property

If, in code, I wanted to do something like the following, what would my class definition need to look like? (Keep in mind the fruit/language thing is just an example)
dim myfruit as new fruit()
myfruit.name = "apple"
myfruit.name.spanish = "manzana"
Here is the class I have, just not sure how to add the "sub property".
Public Class Fruit
Private _name As String
Public Property name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
End Class
In general, for you to have a "sub property", you'd need to make your Property a class itself. This would mean the subproperty is actually a property on the class exposed by the top level property.
Effectively, you'd change the name property from a string to a "Translations" class or similar, ie:
Public Class Fruit
Public Property Name As New Translations
End Class
Public Class Translations
Public Property Primary As String
public Property Spanish As String
End Class
However, this will likely break the code you're displaying, as the second line would need to have a different syntax, ie:
myfruit.Name.Primary = "green"
myfruit.Name.Spanish = "verde"
However, if the goal here is to just handle translation of your user interface, there are other options. For details, see Introduction to International Applications Based on the .NET Framework on MSDN.
I initially thought Reed´s answer was what I was after. In my application, I wanted to use the "sub-property" to set a property on a Form Label. (I was trying to emit only the Label properties I wanted available to a Custom Control.)
I tried this:
Public Class Fruit
Private _name As New Translations
Public Property Name As Translations
Get
Return _name
End Get
Set(value As Translations)
_name = value
_PrimaryCaps = _name.Primary.ToUpper
End Set
End Property
'Private variable is automatically added for unexpanded property
Public Property PrimaryCaps As String
End Class
Public Class Translations
Public Property Primary As String
Public Property Spanish As String
End Class
Then
Dim myFruit As New Fruit
myFruit.Name.Primary = "Apple"
myFruit.Name.Spanish = "Manzana"
Dim primaryCaps As String = myFruit.PrimaryCaps
Weirdly - to me at least - this doesn't work; myFruit.PrimaryCaps returns nothing rather than the hoped-for "APPLE". It appears that the Set for Name is never executed. (Placing the _PrimaryCaps assignment above the Get Return does work, however.)
(I realize that a PrimaryCaps property could be added to the Translations class but, again, this doesn't help if you're wanting to set a foreign variable from within an instance of Fruit.)
I don't know if this is "by-design", whether I've simply misunderstood the intended functionality or what. One thing I did alight on after further research was that this structure isn't very common at all in .NET; for example setting a control's size is done as follows:
oControl.Size = New Drawing.Size(20, 15)
rather than simply setting, say, the Width property directly:
oControl.Size.Width = 20
(The latter won't compile: "Expression is a value and therefore cannot be the target of an assignment.")
If anyone has any more insight than I on this, I'd love to hear it. I know this could simply be done by using an instance of Fruit, for example, but that's not the point.

Only first DataAnnotation validation being applied

I'm currently working on a Winforms application written in VB.NET and implementing the Entity Framework (4.4). I want to add validation attributes to my entities so that I can validate them on the UI - just as I do in MVC.
I have created my 'Buddy Class' which contains an IsValid method and points to a 'MetaData' class that contains the data annotations.
Imports System.ComponentModel.DataAnnotations
Imports System.Runtime.Serialization
Imports System.ComponentModel
<MetadataTypeAttribute(GetType(ProductMetadata))>
Public Class Product
Private _validationResults As New List(Of ValidationResult)
Public ReadOnly Property ValidationResults() As List(Of ValidationResult)
Get
Return _validationResults
End Get
End Property
Public Function IsValid() As Boolean
TypeDescriptor.AddProviderTransparent(New AssociatedMetadataTypeTypeDescriptionProvider(GetType(Product), GetType(ProductMetadata)), GetType(Product))
Dim result As Boolean = True
Dim context = New ValidationContext(Me, Nothing, Nothing)
Dim validation = Validator.TryValidateObject(Me, context, _validationResults)
If Not validation Then
result = False
End If
Return result
End Function
End Class
Friend NotInheritable Class ProductMetadata
<Required(ErrorMessage:="Product Name is Required", AllowEmptyStrings:=False)>
<MaxLength(50, ErrorMessage:="Too Long")>
Public Property ProductName() As Global.System.String
<Required(ErrorMessage:="Description is Required")>
<MinLength(20, ErrorMessage:="Description must be at least 20 characters")>
<MaxLength(60, ErrorMessage:="Description must not exceed 60 characters")>
Public Property ShortDescription As Global.System.String
<Required(ErrorMessage:="Notes are Required")>
<MinLength(20, ErrorMessage:="Notes must be at least 20 characters")>
<MaxLength(1000, ErrorMessage:="Notes must not exceed 1000 characters")>
Public Property Notes As Global.System.String
End Class
The first line in the IsValid method registers the MetaData class (only way I could find that actually worked - otherwise no annotations were honored!). I then use the System.ComponentModel.Validator.TryValidateObject method to perform the validation.
When I call the IsValid method on an instance with an empty (null/nothing) ProductName the validation fails and the ValidationResults collection is populated with the correct error message. So far so good.....
However, if I call IsValid on an instance with a ProductName which is longer than 50 characters the validation passes despite the MaxLength attribute!
Also, if I call IsValid on an instance with a valid ProductName (not empty and not more than 50 characters) but without a ShortDescription the validation passes even though there is a Required annotation on that property.
What am I doing wrong here?
Try the other method signature for TryValidateObject() and explicitly set validateAllProperties to true:
Dim validation = Validator.TryValidateObject(
Me, context, _validationResults, true)