VB.NET Deserialize XML sub nodes by attribute text - vb.net

Trying to deserialize an XML file but having trouble with sub nodes.
I need to collect these by there ID value eg ConNum, class, recid.
Currently I can get one value back but it's giving me the name of the ID and not the value.
eg: xData.TRAN_DATEX.theTarget = ConNum where I require 20190910 instead.
Here's the XML:
<?xml version="1.0" encoding="UTF-8"?>
<targets>
<target id="ConNum">20190910</target>
<target id="class">Third</target>
<target id="recid">123 </target>
</targets>
Here's my class:
Imports System.Xml.Serialization
<Serializable, XmlRoot("targets")>
Public Class XmlFile
<XmlElement("target")> Public Property TRAN_DATEX As myTarget
End Class
<Serializable, XmlRoot("target")>
Public Class myTarget
<XmlAttribute("id")> Public theTarget As String
End Class
And here is the deserialize method:
Dim fFile As FileInfo = New FileInfo("C:\Temp\TARGETS.metadata")
Dim s As New XmlSerializer(GetType(XmlFile))
Using sr As New StreamReader(fFile.FullName)
xData = s.Deserialize(sr)
Stop
End Using

theTarget is getting the value of the id attribute. You want the XmlText of that element:
<Serializable, XmlRoot("target")>
Public Class myTarget
<XmlAttribute("id")> Public theTarget As String
<XmlText> Public Property theValue As String
End Class
Then, instead of xData.TRAN_DATEX.theTarget, you can use xData.TRAN_DATEX.theValue
Edit: In response to the comment.
As there are multiple <target> elements, TRAN_DATEX will need to be a list:
<Serializable, XmlRoot("targets")>
Public Class XmlFile
<XmlElement("target")> Public Property TRAN_DATEX As New List(Of myTarget)
End Class
LINQ can be used to access the data that is required:
Dim reqValueTarget = xData.TRAN_DATEX.FirstOrDefault(Function(x) x.theTarget = "ConNum")
If reqValueTarget IsNot Nothing then
Dim reqValue = reqValueTarget.theValue
End If

Related

How to deal with complex json data in vb.net webservice

I am (trying) to create a RestApi in VB.net and have hit a dead end when it comes to posting data to my endpoint. If i use a simple json file post it via postman and bind it to my data class it is fine but once the Data class becomes more complex it no longer works. By that i mean a class like below works
<First>
<Last>
<AddressLine1>
<City>
<State>
but
<UserInfo>
<Name>
<First>
<Last>
</Name>
<Address>
<AddressLine1>
<City>
<State>
</Address>
</UserInfo>
does not. So here are my 2 question
A) Is there a way to have the build in Parser handle more complex data classes like my above since this doesnt work
Public Function PostValue(<FromBody()> ByVal value As User_Info) As String
If ModelState.IsValid Then
Console.Write(value.address.CITY)
Return "OK"
Else
Return "Error"
End If
End Function
b) what is the alternate way to access the json posted in body so i can manually
assign the values to my complex data class ?
Your sample is not JSON. It's very easy if you just use the built-in functionality.
Here is what your class would look like
<DataContract()>
Class UserInfo
<DataMember>
Public Name As New Name
<DataMember>
Public Address As New Address
End Class
<DataContract()>
Class Name
<DataMember>
Public First As String
<DataMember>
Public Last As String
End Class
<DataContract()>
Class Address
<DataMember>
Public AddressLine1 As String
<DataMember>
Public City As String
<DataMember>
Public State As String
End Class
Here's an example that save to JSON and loads it back.
Imports System.IO
Imports System.Runtime.Serialization.Json
Imports System.Runtime.Serialization
Dim ui As New UserInfo
' Initialize
ui.Name.First = "f"
ui.Name.Last = "l"
ui.Address.AddressLine1 = "a"
ui.Address.City = "c"
ui.Address.State = "s"
' Write to stream
Dim stream As New MemoryStream
Dim ser As New DataContractJsonSerializer(GetType(UserInfo))
ser.WriteObject(stream, ui)
' Show data
stream.Position = 0
Dim sr = New StreamReader(stream)
Dim jsonData As String = sr.ReadToEnd()
Console.WriteLine(jsonData)
' Bring it back
Dim ui2 As UserInfo
stream.Position = 0
ui2 = ser.ReadObject(stream)
Console.WriteLine(ui.Name.First)
Console.WriteLine(ui.Name.Last)
Console.WriteLine(ui.Address.AddressLine1)
Console.WriteLine(ui.Address.City)
Console.WriteLine(ui.Address.State)
This would be the JSON generated
{
"Address":
{
"AddressLine1":"a",
"City":"c",
"State":"s"
},
"Name":
{
"First":"f",
"Last":"l"
}
}

VB.NET Serealize structure that contains lists

I'm searching a way to serialize a structure that contains different kind of object such as Strings, Integer and Collections.
I have this data structure:
Dim database as New mainStruct
<Serializable()> Structure mainStruct
Public name As String
Public address As String
Public Shared bookings As IList(Of booking) = New List(Of booking)
End Structure
<Serializable()> Structure booking
Public id As Integer
Public category As String
Public description As String
End Structure
running the serializer:
Dim fs As FileStream = New FileStream("x.bin", FileMode.OpenOrCreate)
Dim serial As New XmlSerializer(GetType(mainStruct))
serial.Serialize(fs, database)
fs.Close()
the output only contains all non-collection variables such as:
<?xml version="1.0"?>
<mainStruct xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<name>foo</name>
<address>bar</address>
</mainStruct>
How i can serialize bookings Collection, without creating separate files using GetType(List(Of booking))?
Thanks!
Shared members are not serialized because serialization is all about saving a class/structure instance, and shared members are not part of an instance.
Add an instance property to your structure and you should be good to go:
Public Property bookingList As IList(Of booking)
Get
Return mainStruct.bookings
End Get
Set(value As IList(Of booking)
mainStruct.bookings = value
End Set
End Property
If you want the tag to be called <bookings> you can just apply an XmlArray attribute to the property:
<XmlArray("bookings")> _
Public Property bookingList As IList(Of booking)
...same code as above...
End Property

Error generating service reference in VS 2015

OK, this worked just fine in VS 2013. It's only when I started work anew on the project after my upgrade to 2015 that the problem has showed up.
In a nutshell, I'm unsure how to tell the WCF Proxy Generator to specify a CLR namespace for a property type; apparently this is required now.
Here's my contract:
<ServiceContract>
Friend Interface IService
<OperationContract> Function CheckFiles() As List(Of String)
<OperationContract> Function CreateBackup(AllFiles As List(Of String)) As BackupResult
End Interface
Here's the class being returned:
Public Class BackupResult
Public Property DbService As New DbService
Public Property TmpFolder As System.IO.DirectoryInfo ' <== Problem here '
Public Property Chunks As Integer
End Class
And just for clarity, here's the class for the DbService property (although its only relevance for this question is to show that it doesn't have any System.IO references).
Public Class DbService
Public Property ErrorMessage As String = String.Empty
Public Property HasError As Boolean = False
End Class
My problem is that the proxy generator doesn't seem to be able to see that DirectoryInfo is in the System.IO namespace—it keeps generating it in the service's namespace. (When I comment out the CreateBackup() function, rerun the service and update the reference, the QbBackup.DirectoryInfo class isn't generated. I don't get the warning shown below and everything works—like it did in 2013—but of course without the property I need.)
Here's the generated code:
Namespace QbServer
' ... '
' '
' Other generated code here '
' '
' ... '
' '
' Note the generated DirectoryInfo class and '
' the BackupResult.TmpFolder property of type '
' QbServer.DirectoryInfo, when the namespace '
' should be System.IO instead '
' '
<System.Diagnostics.DebuggerStepThroughAttribute(),
System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0"),
System.Runtime.Serialization.DataContractAttribute(Name:="BackupResult", [Namespace]:="http://schemas.datacontract.org/2004/07/Service"),
System.SerializableAttribute()>
Partial Public Class BackupResult
Inherits Object
Implements System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged
<System.NonSerializedAttribute()>
Private extensionDataField As System.Runtime.Serialization.ExtensionDataObject
<System.Runtime.Serialization.OptionalFieldAttribute()>
Private ChunksField As Integer
<System.Runtime.Serialization.OptionalFieldAttribute()>
Private DbServiceField As QbServer.DbService
<System.Runtime.Serialization.OptionalFieldAttribute()>
Private TmpFolderField As QbServer.DirectoryInfo
<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()>
Public Property Chunks() As Integer
Get
Return Me.ChunksField
End Get
Set
If (Me.ChunksField.Equals(Value) <> True) Then
Me.ChunksField = Value
Me.RaisePropertyChanged("Chunks")
End If
End Set
End Property
<System.Runtime.Serialization.DataMemberAttribute()>
Public Property DbService() As QbServer.DbService
Get
Return Me.DbServiceField
End Get
Set
If (Object.ReferenceEquals(Me.DbServiceField, Value) <> True) Then
Me.DbServiceField = Value
Me.RaisePropertyChanged("DbService")
End If
End Set
End Property
<System.Runtime.Serialization.DataMemberAttribute()>
Public Property TmpFolder() As QbServer.DirectoryInfo
Get
Return Me.TmpFolderField
End Get
Set
If (Object.ReferenceEquals(Me.TmpFolderField, Value) <> True) Then
Me.TmpFolderField = Value
Me.RaisePropertyChanged("TmpFolder")
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
<System.Diagnostics.DebuggerStepThroughAttribute(),
System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")>
Public Class DirectoryInfo
End Class
End Namespace
And here's the warning I'm getting in Visual Studio 2015:
Custom tool warning: Cannot import wsdl:portType
Detail: An exception was thrown while running a WSDL import extension: System.ServiceModel.Description.DataContractSerializerMessageContractImporter
Error: ISerializable type with data contract name 'DirectoryInfo' in namespace 'http://schemas.datacontract.org/2004/07/System.IO' cannot be imported. The data contract namespace cannot be customized for ISerializable types and the generated namespace 'QbServer' does not match the required CLR namespace 'System.IO'. Check if the required namespace has been mapped to a different data contract namespace and consider mapping it explicitly using the namespaces collection.
XPath to Error Source: //wsdl:definitions[#targetNamespace='http://tempuri.org/']/wsdl:portType[#name='IService'] ConsoleTest D:\Dev\Customers\OIT\Active\ConsoleTest\Service References\QbServer\Reference.svcmap 1
This all results in the proxy classes not being generated.
I've been reading this and this, but they seem to pertain to custom namespaces at the service level. I need to know how to tell the generator to recognize the property type as a CLR type and NOT generate a DirectoryInfo class of its own.
The class System.IO.DirectoryInfo is not supported by the DataContractSerializer. Instead you could try using the XmlSerializer, but you'll likely run into other issues.
A simple solution is to add a string property which captures the data needed to recreate the correct objects. You can keep the original property as well, just be sure to mark it with the [NonSerialized] attribute.
Alternatively you can use the OnSerializing and OnDeserializing attributes to ensure that the DirectoryInfo value is stored in the string field and so that the DirectoryInfo is restored after deserialization.
For more information see:
https://blogs.msdn.microsoft.com/carlosfigueira/2011/09/05/wcf-extensibility-serialization-callbacks/

XmlSerializer returns nothing in my Windows 8 Store App

I have successfully deserialized my xml file using XmlSerializer in .Net but trying to serialize that data back to an xml file is becoming frustrating. When I try to serialize my classes I only get the root tag of the xml with no child elements. How can I serialize all my objects to get the correct xml with data? I have seen somewhere where someone suggested to add the classes to be serialized in a collection and then serialize that collection but I can't wrap my head around that or is there a simpler way of doing it? Any help is appreciated! Here is my code:
Public Shared Function SerializeXml() As Byte()
Dim serializer As New XmlSerializer(GetType(Data))
Dim nameSpaces As XmlSerializerNamespaces = New XmlSerializerNamespaces()
Dim mStream As New MemoryStream()
Dim result As Byte()
Dim target As New Data()
nameSpaces.Add(String.Empty, String.Empty)
serializer.Serialize(mStream, target, nameSpaces)
result = mStream.ToArray()
Return result
And here is a generic sample of the xml with attributes:
<?xml version"1.0">
<RootTag>
<ChildTag Label="Label1" Value="Value1"/>
<ChildTag Label="Label2" Value="Value2"/>
</RootTag>
Edit: Here is my Data class:
Imports System.Xml.Serialization
<XmlRoot("DATA", [Namespace]:="", IsNullable:=False)>
Public Class Data
Inherits Model
<XmlElement("CONFIGURATION")>
Public Property Configuration() As DataConfiguration
Get
Return Me._Configuration
End Get
Set(value As DataConfiguration)
Me._Configuration = value
End Set
End Property
Private _Configuration As DataConfiguration
<XmlElement("FIELD")>
Public Property Field() As Field
Get
Return Me._Field
End Get
Set(value As Field)
Me._Field = value
End Set
End Property
Private _Field As Field
<XmlElement("LIST")>
Public Property ListRoot() As List(Of ListRoot)
Get
Return Me._ListRoot
End Get
Set(value As List(Of ListRoot))
Me._ListRoot = value
End Set
End Property
Private _ListRoot As List(Of ListRoot)
End Class
This is your issue here, <XmlRoot("DATA", [Namespace]:="", IsNullable:=False)>. The IsNullable property, when set to false, will omit the XML for items if they are equal to nothing. If you set the IsNullable to True, then it will emit a tag like this <ListRoot xsi:nil = "true" />. In your code example, since you just created a new Data class like this Dim target As New Data(), all of the members are Nothing by default. Since you've set the IsNullable = False, you should only see that root tag, and that would be a valid serialization of the data.

Why doesn't XmlSerializer work for shared class variables?

I have the following class which I want to serialize to XML:
<Serializable()> _
Public Class Settings
Public Shared var1 As Boolean = False
Public var2 As Boolean = False
End Class
I create a new instance and serialize it using my own method...
SaveSerialXML(PathToFile, New Settings, GetType(Settings))
...however the shared (static) variable is not included in the output:
<?xml version="1.0" encoding="utf-8"?>
<Settings>
<var2>false</var2>
</Settings>
Does anyone know of a way to serialize shared members of a class to XML?
Serialization is about serializing instances. Shared variables do not belong to an instance.
You can cheat add a redirect instance property for serialization.
For example:
Public Property Var1Instance As Boolean
Get
Return Var1
End Get
Set(value As Boolean)
Var1 = value
End Set
End Property
I've often found myself wanting to implement my own Xml serialization and not use the .NET XmlSerializer.
I do so by adding an Xml property to my class that directly handles the serialization via Xml Literals. That property could be a String or an XElement depending on how I'm going to use it.
Here's how the Xml String property would look for your sample class:
Public Property Xml() As String
Get
Return <Settings>
<var1><%= var1 %></var1>
<var2><%= var2 %></var2>
</Settings>.ToString
End Get
Set(ByVal value As String)
Dim xValue = XElement.Parse(value)
var1 = xValue...<var1>.Value = "true"
var2 = xValue...<var2>.Value = "true"
End Set
End Property
This places all the serialization logic in one place and avoids the use of attributes to control how an instance gets serialized. The other benefit I've found is that this allows me to deserialize a class that does not have a default constructor.
In production scenarios I would add an extension method that converts a String to a Boolean instead of comparing to "true" since valid Xml Boolean values include true, false, 1 and 0.