Read XMLDocument with same Element names - vb.net

I have to read XML document data which is here:
<root>
<cartype>Mercedes</cartype>
<production>2005</production>
<fuel>Diesel</fuel>
<color>Red</color>
<services>
<service>
<year>2006</year>
<km>47800</km>
<city>Stuttgart</city>
</service>
<service>
<year>2007</year>
<km>92125</km>
<city>FFM</city>
</service>
<service>
<km>180420</km>
<year>2009</year>
<city>Hannover</city>
</service>
</services>
<condition>Good</condition>
</root>
Then I read it as such:
Dim cartype As String
Dim fuel As String
Dim production As String
Dim color As String
Dim serviceyear() As String = {"", "", ""}
Dim servicekm() As String = {"", "", ""}
Dim sevicecity() As String = {"", "", ""}
Dim doc As XmlDocument = New XmlDocument()
doc.Load("mercedes.xml")
Dim root As XmlElement = doc.DocumentElement
Dim node As XmlNode = root.SelectSingleNode("production")
If Not node Is Nothing Then production = node.InnerText
node = root.SelectSingleNode("cartype")
If Not node Is Nothing Then cartype = node.InnerText
node = root.SelectSingleNode("fuel")
If Not node Is Nothing Then fuel = node.InnerText
node = root.SelectSingleNode("color")
If Not node Is Nothing Then color = node.InnerText
node = root.SelectSingleNode("services/service/year") '' first service year
If Not node Is Nothing Then serviceyear(0) = node.InnerText
node = root.SelectSingleNode("services/service/year") '' second service year
If Not node Is Nothing Then serviceyear(1) = node.InnerText
Reading nodes with unique element names is OK but I don't know how to read all "services" in array since showed code reads only first service. Services may be from 0 to undefined number of it. Function have to be as fast is possible since large number of xml's have to be readed in what is possible less time.

To read a variable number of <service> elements you will need to use SelectNodes instead of SelectSingleNode:
Dim services = root.SelectNodes("services/service")
You can then iterate over the <service> nodes:
For Each service In services
If service("year") IsNot Nothing Then
Dim year = service("year").InnerText
End If
Next
Personally, I would use LINQ to XML to parse the file (but that may be because I am obsessed with all things LINQ!). Combined with VB.NET's support for XML Literals it makes for some really nice looking code (IMHO).
Here is a complete example you can paste into LINQPad.
Sub Main
' Use XElememt.Load(fileName) to load from file
Dim xml =
<root>
<cartype>Mercedes</cartype>
<production>2005</production>
<fuel>Diesel</fuel>
<color>Red</color>
<services>
<service>
<year>2006</year>
<km>47800</km>
<city>Stuttgart</city>
</service>
<service>
<year>2007</year>
<km>92125</km>
<city>FFM</city>
</service>
<service>
<km>180420</km>
<year>2009</year>
<city>Hannover</city>
</service>
</services>
<condition>Good</condition>
</root>
Dim history = New ServiceHistory() With {
.CarType = xml.<cartype>.Value,
.Production = xml.<production>.Value,
.Fuel = xml.<fuel>.Value,
.Color = xml.<color>.Value,
.Condition = xml.<condition>.Value,
.Services = (
From svc In xml.<services>.<service>
Select New Service() With {
.Year = svc.<year>.Value,
.KM = svc.<km>.Value,
.City = svc.<city>.Value
}
).ToList()
}
history.Dump()
End Sub
' Define other methods and classes here
Public Class ServiceHistory
Public Property CarType As String
Public Property Production As String
Public Property Fuel As String
Public Property Color As String
Public Property Condition As String
Public Property Services As List(Of Service)
End Class
Public Class Service
Public Property Year As String
Public Property KM As String
Public Property City As String
End Class
This gives you the following:

Try linq it will be easier to parse the xml:
It have to be something like that:
xelement = XElement.Load(file);
services = xelement.Element("services").Elements("service");

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"
}
}

How to read attribute value of xml

How can i read attribute value of a xml.
I have this below xml and i am working with windows forms vb.net
I tried something like this below but does not work. I want to read connection string value
Dim xe As XElement = XElement.Load("..\\..\\KMMiddleTier.xml")
Dim name = From nm In xe.Elements("ConnectionKey") Where nm.Element("ConnectionKey").Attribute("Key") = "DB_DEV" Select nm.Element("ConnectionKey").Attribute("ConnectionString").Value.FirstOrDefault()
Dim xmlDoc as XMLDocument
Dim xmlNodeList As XmlNodeList = xmlDoc.SelectNodes("/KMMiddleTierSecurity/ConnectionKeys/ConnectionKey")
Dim strConnectionKey As String = xmlNodeList.Item(0).Attributes("ConnectionString").Value''''
This might help.

Linq to xml combine values

Consider the following MediaObject class
Public Class MediaObject
Public Path as String
Public File as String
Public Sub New(_path as String, _file as String)
Path = _path
File = _file
End Sub
End Class
I have the following XML (myxml):
<records>
<media>
<path>\\first\path</path>
<file>firstfile</file>
</media>
<media>
<path>\\second\path</path>
<file>secondfile</file>
</media>
<records>
To get a list of MediaObjects I use this :
Dim mobjects As New List(Of MediaObject)
Dim x As XDocument = XDocument.Parse(myxml)
mobjects = (From m In x.<records>.<media> Select media = New MediaObject(m.<path>.Value, m.<file>.Value)).ToList()
All is fine.
But now consider this new XML (where second file is an alternate of the first one) :
<records>
<media>
<path>\\first\path</path>
<file>firstfile</file>
<path>\\second\path</path>
<file>secondfile</file>
</media>
</records>
I can easily get either of properties but not both, i.e.
Dim mobjects As New List(Of MediaObject)
Dim x As XDocument = XDocument.Parse(myxml)
'here get only the paths
Dim r = (From m In x.<records>.<media> Select media = (From t In m.<path> Select New MediaObject(t.Value, Nothing)).ToList()).ToList()
mobjects = r(0)
How would I go to create a list of MediaObjects in this context ?
(let's consider path and file values in the xml are in sequence and go 2 by 2)
Thanks!
UPDATE:
Sorry I wasn't precise enough.
Here is the real world scenario.
There can many paths and files and it's guaranteed
paths come before files
there is the same number of paths and files
all paths come first, then all files
Sample:
<records>
<media>
<some_tags />
<path>\\first\path</path>
<path>\\second\path</path>
<might_be_something_here />
<file>firstfile</file>
<file>secondfile</file>
<more_tags />
</media>
</records>
PS: I cannot change the XML, which comes from another system...
Assuming you're simply pairing up every nth path and file, you can do this:
Dim paths = doc...<path>
Dim files = doc...<file>
Dim query = paths.Zip(files, Function(p, f)
New MediaObject(CType(p, String), CType(f, String))
)
You don't even have to worry about possible elements that are in between, they'll simply be ignored.

Retrieving data from xml using xmlDocument, xmlNode and xmlNodelist

This is a sample code in vb.net in which i retrieve the details of elements without attributes.
For Each standardItemInfoNode In ItemInfoNodes
baseDataNodes = ItemInfoNodes.ChildNodes
bFirstInRow = True
For Each baseDataNode As XmlNode In baseDataNodes
If (bFirstInRow) Then
bFirstInRow = False
Else
Response.Write("<br>")
End If
Response.Write(baseDataNode.Name & ": " & baseDataNode.InnerText)
Next
Next
How can i retrieve the details of the xml like having node with attributes and its child also having attributes. I need to retrieve all the attributes of node and its child node which are present in the middle of other xml tags.
I'm not sure exactly what you're asking, and I can't give you a specific example without knowing the format of the XML you are trying to process, but I think what you are looking for is the Attributes property of the XmlNode objects. Each XmlNode has an Attributes property that allows you to access all the attributes for that node. Here's the MSDN page that explains it (and provides a simple example):
http://msdn.microsoft.com/en-us/library/7f285y48.aspx
EDIT:
Using the example XML you posted in the comment, you could read the all the values and attributes like this:
Dim doc As XmlDocument = New XmlDocument()
doc.LoadXml("<EventTracker><StandardItem><Header1>Header1 Text</Header1> <Header2>Header2 Text</Header2></StandardItem><Item> <Events> <EventSub EventId='73' EventName='Orchestra' Description='0'> <Person PersonId='189323156' PersonName='Chandra' Address='Arunachal'/><Person PersonId='189323172' PersonName='Sekhar' Address='Himachal'/></EventSub> </Events> </Item> </EventTracker>")
Dim header1 As String = doc.SelectSingleNode("EventTracker/StandardItem/Header1").InnerText
Dim header2 As String = doc.SelectSingleNode("EventTracker/StandardItem/Header2").InnerText
For Each eventSubNode As XmlNode In doc.SelectNodes("EventTracker/Item/Events/EventSub")
Dim eventId As String = eventSubNode.Attributes("EventId").InnerText
Dim eventName As String = eventSubNode.Attributes("EventName").InnerText
Dim eventDescription As String = eventSubNode.Attributes("Description").InnerText
For Each personNode As XmlNode In eventSubNode.SelectNodes("Person")
Dim personId As String = personNode.Attributes("PersonId").InnerText
Dim personName As String = personNode.Attributes("PersonName").InnerText
Dim personAddress As String = personNode.Attributes("Address").InnerText
Next
Next
However, if you are loading all the data in the XML like this, I would recommend deserializing the XML into an EventTracker object. Or, as I said in my comment on your post, if the only purpose for reading the XML document is to transform it into another XML or HTML document, I would recommend using XSLT, instead.
If you want to test if an attribute exists, you can do something like this:
Dim attribute As XmlNode = personNode.Attributes.GetNamedItem("PersonId")
If attribute IsNot Nothing Then
Dim personId As String = attribute.InnerText
End If
However, this would be much easier with serialization since the deserialized object would simply have null properties for any elements that didn't exist.
You can use SelectSingleNode("XPath or NodeName") and loop through the Attributes.Item(index) on that node. You can also, if you know the childnode name in advance, loop through SelectSingleNode("XPath Or NodeName").SelectSingleNode("XPath or ChildName").Attributes.Item(index).

WCF REST deployment Error: "Resource Does not exist"

I am trying to access http://localhost/tempservicehost/tempservice.svc and I am getting the following error:
Error Description: 'Resource does not
exist'
This may be because an invalid URI or
HTTP method was specified. Please see
the service help page for constructing
valid requests to the service.
The funny thing is that http://localhost/tempservicehost/tempservice.svc/help is working fine. Not only that, all my endpoints are working fine.
I am using IIS 7.5 (Win 2008 R2). Application developed using .NET 4.0
Post Updated (following is the code):
<ServiceContract()>
Public Interface ITestSvc
<OperationContract()>
<Description("")>
<WebInvoke(Bodystyle:=WebMessageBodyStyle.Bare,
Method:="POST",
UriTemplate:="GetCodes")>
Function GetCodes(ByVal oReq As ReqGetCodes) As RespGetCodes
End Interface
Public Class TestSvc
Implements ITestSvc
Public Function GetCodes(ByVal oReq As ReqGetCodes) As RespGetCodes Implements ITestSvc.GetCodes
Dim o As New RespGetCodes
Dim lstGetCodes = New List(Of ClassGetCodes) From {
New ClassGetCodes With {.App_Code = "a1", .SystemFlag = True},
New ClassGetCodes With {.App_Code = "a2", .SystemFlag = False},
New ClassGetCodes With {.App_Code = "a3", .SystemFlag = True},
New ClassGetCodes With {.App_Code = "a4", .SystemFlag = True},
New ClassGetCodes With {.App_Code = "a5", .SystemFlag = False}
}
o.GetCodesArray = lstGetCodes.ToArray
Return o
End Function
End Class
Public Class TestWebHttpBehavior
Inherits WebHttpBehavior
Protected Overrides Sub AddServerErrorHandlers(ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal endpointDispatcher As System.ServiceModel.Dispatcher.EndpointDispatcher)
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear()
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(New TestErrorHandler)
End Sub
End Class
Public Class TestWcfSvcHostFactory
Inherits ServiceHostFactory
Protected Overrides Function CreateServiceHost(ByVal serviceType As Type, ByVal baseAddresses As Uri()) As ServiceHost
Dim result As New WebServiceHost2(serviceType, True, baseAddresses)
Dim sEnableBasicAuth As String = System.Configuration.ConfigurationManager.AppSettings.Get("EnableBasicAuthentication")
If String.IsNullOrEmpty(sEnableBasicAuth) OrElse String.Compare(sEnableBasicAuth, "false", True) <> 0 Then
result.Interceptors.Add(New TestRequestInterceptor(System.Web.Security.Membership.Provider, "Personify Authentication"))
End If
result.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.None
Dim bahavior As New TestWebHttpBehavior With {.AutomaticFormatSelectionEnabled = True}
result.Description.Endpoints(0).Behaviors.Add(bahavior)
Return result
End Function
End Class
Public Class TestRequestInterceptor
Inherits RequestInterceptor
Private m_provider As MembershipProvider
Private m_realm As String
Public Sub New(ByVal provider As MembershipProvider, ByVal realm As String)
MyBase.New(False)
Me.m_provider = provider
Me.m_realm = realm
End Sub
Protected ReadOnly Property Realm() As String
Get
Return m_realm
End Get
End Property
Protected ReadOnly Property Provider() As MembershipProvider
Get
Return m_provider
End Get
End Property
Public Overrides Sub ProcessRequest(ByRef requestContext As RequestContext)
Dim credentials As String() = ExtractCredentials(requestContext.RequestMessage)
If credentials.Length > 0 AndAlso AuthenticateUser(credentials(0), credentials(1)) Then
InitializeSecurityContext(requestContext.RequestMessage, credentials(0))
Else
Dim reply As Message = Message.CreateMessage(MessageVersion.None, Nothing)
Dim responseProperty As New HttpResponseMessageProperty() With {.StatusCode = HttpStatusCode.Unauthorized}
responseProperty.Headers.Add("WWW-Authenticate", String.Format("Basic realm=""{0}""", Realm))
reply.Properties(HttpResponseMessageProperty.Name) = responseProperty
requestContext.Reply(reply)
requestContext = Nothing
End If
End Sub
Private Function AuthenticateUser(ByVal username As String, ByVal password As String) As Boolean
If Provider.ValidateUser(username, password) Then
Return True
End If
Return False
End Function
Private Function ExtractCredentials(ByVal requestMessage As Message) As String()
Dim request As HttpRequestMessageProperty = DirectCast(requestMessage.Properties(HttpRequestMessageProperty.Name), HttpRequestMessageProperty)
Dim authHeader As String = request.Headers("Authorization")
If authHeader IsNot Nothing AndAlso authHeader.StartsWith("Basic") Then
Dim encodedUserPass As String = authHeader.Substring(6).Trim()
Dim encoding__1 As Encoding = Encoding.GetEncoding("iso-8859-1")
Dim userPass As String = encoding__1.GetString(Convert.FromBase64String(encodedUserPass))
Dim separator As Integer = userPass.IndexOf(":"c)
Dim credentials As String() = New String(1) {}
credentials(0) = userPass.Substring(0, separator)
credentials(1) = userPass.Substring(separator + 1)
Return credentials
End If
Return New String() {}
End Function
Private Sub InitializeSecurityContext(ByVal request As Message, ByVal username As String)
Dim principal As New GenericPrincipal(New GenericIdentity(username), New String() {})
Dim policies As New List(Of IAuthorizationPolicy)()
policies.Add(New PrincipalAuthorizationPolicy(principal))
Dim securityContext As New ServiceSecurityContext(policies.AsReadOnly())
If request.Properties.Security IsNot Nothing Then
request.Properties.Security.ServiceSecurityContext = securityContext
Else
request.Properties.Security = New SecurityMessageProperty() With { _
.ServiceSecurityContext = securityContext _
}
End If
End Sub
Private Class PrincipalAuthorizationPolicy
Implements IAuthorizationPolicy
Private m_id As String = Guid.NewGuid().ToString()
Private user As IPrincipal
Public Sub New(ByVal user As IPrincipal)
Me.user = user
End Sub
Public ReadOnly Property Id As String Implements System.IdentityModel.Policy.IAuthorizationComponent.Id
Get
Return Me.m_id
End Get
End Property
Public Function Evaluate(ByVal evaluationContext As System.IdentityModel.Policy.EvaluationContext, ByRef state As Object) As Boolean Implements System.IdentityModel.Policy.IAuthorizationPolicy.Evaluate
evaluationContext.AddClaimSet(Me, New DefaultClaimSet(Claim.CreateNameClaim(user.Identity.Name)))
evaluationContext.Properties("Identities") = New List(Of IIdentity)(New IIdentity() {user.Identity})
evaluationContext.Properties("Principal") = user
Return True
End Function
Public ReadOnly Property Issuer As System.IdentityModel.Claims.ClaimSet Implements System.IdentityModel.Policy.IAuthorizationPolicy.Issuer
Get
Return ClaimSet.System
End Get
End Property
End Class
End Class
The config is here:
<system.serviceModel>
<services>
<service behaviorConfiguration="TestWcfServiceBehavior"
name="TestServiceLib.TestSvc">
<endpoint address="" binding="webHttpBinding" behaviorConfiguration="EPrestBehavior"
name="EPrest" contract="TestServiceLib.ITestSvc" />
<endpoint address="mex" binding="mexHttpBinding" name="EPmex"
contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="EPrestBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="TestWcfServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
I Had a similar issue,
I found that if i navigated to an actual resource it was fine.
for example
http://localhost/tempservicehost/tempservice.svc/test/
assuming you have the following method as well
[OperationContract]
[WebGet(UriTemplate = "/test/")]
public Test TestMethod()
{
return new Test("Hello world");
}
what has happened here is that the standard 404 error that is normally returned has not been rendered the way you are expecting it to. (usually you get a blue header and a line saying service end point or similar)
If you want to customise your errors there is a question Custom Error Handling message for Custom WebServiceHost
That explains how to pick up error codes on the way through and modify the return.