VS 2010 Help using JSON API that requires Authentication with JWT token-VBForums
Page 1 of 2 12 LastLast
Results 1 to 40 of 55

Thread: Help using JSON API that requires Authentication with JWT token

  1. #1

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Help using JSON API that requires Authentication with JWT token

    A program I wrote years ago used theTVdb.com XML API to collect data of TV show episodes. They recently updated the API to JSON and will discontinue the XML API soon. I need to convert the program to use the new API, but I can’t figure out how to get anything to work. The old API required an API key which was passed directly in the WebRequest. The new API requires this same API key along with a userkey and username to be sent for authentication which returns a JWT token which needs to be used for all other requests. The GET requests return the response JSON data and HTTP status code. There is a test API website at https://api.thetvdb.com/swagger which I can work with, but I can’t figure out how to make requests in my program.
    I have left many messages on TheTVdb forums, but I never get any responses there. Hopefully someone here can get me started since I don’t know where to begin. If I can have someone walk me through to the point where I can login and get a valid token I should be able move forward from there.
    Authentication:
    https://api.thetvdb.com/login

    Schema:
    {
    "apikey":"XXXXXXXXXXXXXXXX",
    "username":" XXXXXXXXXXXXXXXX ",
    "userkey":" XXXXXXXXXXXXXXXX "
    }
    Thanks,
    Jim

  2. #2
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    This is the important part of the documentation you might've missed:
    The token is then used in all subsequent requests by providing it in the Authorization header. The header will look like: Authorization: Bearer <yourjwttoken>.
    So you need to add that header to your requests after you get the JWT token.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  3. #3

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    I am just reading up on JWT tokens now. I don't have any workable code yet, Not even how to get the JWT token.

    Jim

    Quote Originally Posted by Sitten Spynne View Post
    This is the important part of the documentation you might've missed:


    So you need to add that header to your requests after you get the JWT token.

  4. #4
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    The documentation lays out a really clear roadmap. I assumed you already had some working code since you said you'd been using the XML API? It sounds like you have a working program, so you already have code that can send GET or POST requests. You'll just be changing that code a little bit.

    Obtain a JWT token by POSTing to the /login route in the Authentication section with your API key and credentials.
    That means you need to go find the /login endpoint in the Authentication and read how to use it.

    It looks like to do that you need to send a POST request to the /login endpoint, giving it JSON with this schema:
    Code:
    {
        "apikey" : "your api key",
        "userkey" : "your user key",
        "username" : "your username"
    }
    You will either get a 200 OK response with this JSON:
    Code:
    {
        "token" : "the token"
    }
    Or a 401 Unauthorized response with some JSON that doesn't really matter.

    Armed with that token, you have to include it in the headers for your future requests.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  5. #5

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    I wrote the program years ago when the API was in XML and it has been working great, however they are changing the API to JSON with JWT and I am lost! I did just realize one big problem that explains a lot… I am using Visual Studio 2010 and the highest version of .NET it supports is 4.0 while JWT wasn’t introduced until version 4.5! I don’t know if there is a way to install the JWT library in .NET 4.0 or not. I guess I have to upgrade my VS before I continue.

    Quote Originally Posted by Sitten Spynne View Post
    The documentation lays out a really clear roadmap. I assumed you already had some working code since you said you'd been using the XML API? It sounds like you have a working program, so you already have code that can send GET or POST requests. You'll just be changing that code a little bit.


    That means you need to go find the /login endpoint in the Authentication and read how to use it.

    It looks like to do that you need to send a POST request to the /login endpoint, giving it JSON with this schema:
    Code:
    {
        "apikey" : "your api key",
        "userkey" : "your user key",
        "username" : "your username"
    }
    You will either get a 200 OK response with this JSON:
    Code:
    {
        "token" : "the token"
    }
    Or a 401 Unauthorized response with some JSON that doesn't really matter.

    Armed with that token, you have to include it in the headers for your future requests.

  6. #6
    PowerPoster techgnome's Avatar
    Join Date
    May 2002
    Posts
    31,285

    Re: Help using JSON API that requires Authentication with JWT token

    Quote Originally Posted by JimAvanti View Post
    I wrote the program years ago when the API was in XML and it has been working great, however they are changing the API to JSON with JWT and I am lost! I did just realize one big problem that explains a lot… I am using Visual Studio 2010 and the highest version of .NET it supports is 4.0 while JWT wasn’t introduced until version 4.5! I don’t know if there is a way to install the JWT library in .NET 4.0 or not. I guess I have to upgrade my VS before I continue.
    I don't think you need to. You're not using the JWT Token... just capturing the value so you can return it in future calls. That's why it is a token not an object. That's a distinct difference. What you're getting is just a token. A series of data that on its own is useless. So you don't need the JWT library installed.

    -tg
    * I don't respond to private (PM) requests for help. It's not conducive to the general learning of others.*
    * I also don't respond to friend requests. Save a few bits and don't bother. I'll just end up rejecting anyways.*
    * How to get EFFECTIVE help: The Hitchhiker's Guide to Getting Help at VBF - Removing eels from your hovercraft *
    * How to Use Parameters * Create Disconnected ADO Recordset Clones * Set your VB6 ActiveX Compatibility * Get rid of those pesky VB Line Numbers * I swear I saved my data, where'd it run off to??? *

  7. #7

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    That would be good if I don't need to upgrade my VS. I was just about to upgrade it to v2013 today. So the library must be needed to create JWT tokens then I guess.

    Jim

    Quote Originally Posted by techgnome View Post
    I don't think you need to. You're not using the JWT Token... just capturing the value so you can return it in future calls. That's why it is a token not an object. That's a distinct difference. What you're getting is just a token. A series of data that on its own is useless. So you don't need the JWT library installed.

    -tg

  8. #8

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    The program currently uses a simple WebRequest that you pass the information you want and it creates a file of the XML code returned. I then either get the information I need from the XML data, or save it as show data that I can retrieve information from as the program needs it. I will need to modify this code to read the JSON format and save it as XML data, but for right now I just need to get the JSON to work. This is how I do the WebRequest:


    Code:
    Public Class GetWebRequest
    
        Public Shared Sub Main(ByVal Arg As String)
            Dim request As WebRequest = WebRequest.Create(Arg)
    
            Try
                Dim response As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)
                Dim oWrite As System.IO.StreamWriter
                oWrite = IO.File.CreateText(Form1.AppDataPath & "HTTP_Response.txt")
                Dim dataStream As Stream = response.GetResponseStream()
                Dim reader As New StreamReader(dataStream)
                Dim responseFromServer As String = reader.ReadToEnd()
                oWrite.WriteLine(responseFromServer)
                reader.Close()
                dataStream.Close()
                response.Close()
                oWrite.Close()
            Catch ex As Exception
                MsgBox("Error making WebRequest")
            End Try
    
        End Sub
    End Class
    This is an example of how I call the WebRequest and save the returned XML data:

    Code:
    Dim Arg As String = "http://thetvdb.com/api/" + TheTVdbKey + "/series/" + ShowID + "/all"
            GetWebRequest.Main(Arg)
            My.Computer.FileSystem.CopyFile(AppDataPath & "HTTP_Response.txt", AppDataPath & "Shows\" & ShowName & ".xml", True)
    I don’t know if I am currently doing the XML WebRequest the proper way, but it works.

    Jim




    Quote Originally Posted by Sitten Spynne View Post
    The documentation lays out a really clear roadmap. I assumed you already had some working code since you said you'd been using the XML API? It sounds like you have a working program, so you already have code that can send GET or POST requests. You'll just be changing that code a little bit.


    That means you need to go find the /login endpoint in the Authentication and read how to use it.

    It looks like to do that you need to send a POST request to the /login endpoint, giving it JSON with this schema:
    Code:
    {
        "apikey" : "your api key",
        "userkey" : "your user key",
        "username" : "your username"
    }
    You will either get a 200 OK response with this JSON:
    Code:
    {
        "token" : "the token"
    }
    Or a 401 Unauthorized response with some JSON that doesn't really matter.

    Armed with that token, you have to include it in the headers for your future requests.

  9. #9
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    No. You don't need a library. There are some that might help. But that's not the problem.

    I didn't realize it but JWT is a standard. It's built on top of HTTP. It uses HTTP. The API is HTTP. So if you want to have success, you need to understand a little bit about HTTP. I don't think you have this understanding. Let's make sure you do.

    HTTP is a "protocol". That means it's a set of rules that are used for computers to communicate with each other. It's important for every computer to follow the rules, or else other computers can't understand what it's saying. Think of protocols as sort of like human languages. If our protocol is "speak in Spanish" and you choose to speak Mandarin instead, it's not likely I'll understand you.

    When you use a type like WebClient or HttpWebRequest, you're using a class that generates the right text for an HTTP request and sends it on your behalf. They generally let you configure all aspects of the request, such as the headers. The JWT has to be added as a special header. That's all there is to it.

    So using an imaginary HTTP library and an imaginary JSON parser, this request looks like:
    Code:
    ' Returns the token or throws an exception.
    Public Function Login(ByVal username As String, ByVal userKey As String, ByVal apiKey As String) As String
        Dim credentials = New Credentials() With {
            .Username = username,
            .UserKey = userKey,
            .ApiKey = apiKey
        }
        Dim json = New ImaginaryJson()
        Dim credentialsJson = json.Serialize(credentials)
    
        Dim postRequest = New HttpPostRequest("https://example.com/login")
        postRequest.Headers("Content-Type") = "application/json"
        postRequest.Body = credentialsJson
        
        Dim response = postRequest.Send()
    
        If response.Status <> 200 Then
            Throw New ApplicationException("Could not login.")
        End If
    
        Dim tokenJson = response.Body
        Dim tokenObject = json.Deserialize(tokenJson)
        Return tokenObject.Token
    End Function
    It sets up the JSON that needs to be sent. It creates the type that will send the HTTP request. Headers are configured, the JSON is added, and the request is sent. The response is received, and if it indicates an error an exception is thrown. If there's no exception, the JWT is returned as a String.

    Now that you have that JWT, you can make all of the other requests. The code looks remarkably the same for every other kind of request, with one important difference: you need to add the JWT as a header:
    Code:
    Dim requestSpecificObject = New Something() With {
        <some properties>
    }
    Dim json = New ImaginaryJson()
    Dim requestJson = json.Serialize(requestSpecificObject)
    
    Dim postRequest = New HttpPostRequest("https://example.com/<the endpoint>")
    postRequest.Headers("Content-Type") = "application/json"
    postRequest.Headers("Authorization") = String.Format("Bearer {0}", theJwt)
    postRequest.Body = requestJson
    
    Dim response = postRequest.Send()
    
    If response.Status <> 200 Then
        Throw New ApplicationException("Could not do the thing.")
    End If
    
    Dim responseJson = response.Body
    Dim responseObject = json.Deserialize(tokenJson)
    Return responseObject
    That's it. If I knew which types you were using to deal with HTTP, I could help you figure out how to add the appropriate header. But you haven't revealed this, so the best I can do is work with imaginary types and ask you to figure out how it applies to your type.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  10. #10

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    The Test site for the API I need to use is https://api.thetvdb.com/swagger and it includes documentation (the site works for me, but you would need to be set up with an API key and user account to use it). I thought I understood HTTP o.k., but maybe I don’t.
    I get a lot of “Type not defined” with the code you gave me. I tried to find Imports or references I might be missing, but I don’t think that is the case.
    Dim credentials = New Credentials() With {
    .Username = username,
    .UserKey = userKey,
    .ApiKey = apiKey
    }
    Dim credentialsJson = json.Serialize(credentials)
    Dim postRequest = New HttpPostRequest("https://api.thetvdb.com/login")
    Dim tokenObject = json.Deserialize(tokenJson)

    There are similar Types not defined in the remaining sample code you gave me. Do you think my problem could be that my VB 2010 only supports up to .NET 4.0?




    [QUOTE=Sitten Spynne
    If I knew which types you were using to deal with HTTP, I could help you figure out how to add the appropriate header. But you haven't revealed this, so the best I can do is work with imaginary types and ask you to figure out how it applies to your type.[/QUOTE]

  11. #11
    PowerPoster techgnome's Avatar
    Join Date
    May 2002
    Posts
    31,285

    Re: Help using JSON API that requires Authentication with JWT token

    Here is a basic version of a method I use to invoke a webapi call... it uses a WebRequest to invoke a POST WebAPI call... this is live, honest to goodness running code (some things have been changed for sanitation - specifically the methods/properties of "CustomObject") and it works
    Code:
        Public Shared Sub SendData(endpointAddress As String, userName As String, password As String, objectID As Guid, revenueLookupId As String, customID As Guid, firstName As String, lastName As String, ByRef errorMessage As String, ByRef rawData As String)
            Dim request As HttpWebRequest = Nothing
            Dim postData() As Byte
            Dim response As HttpWebResponse = Nothing
            Dim replyStreamReader As StreamReader = Nothing
    
            request = WebRequest.Create(New Uri(endpointAddress))
            'Set login information
            request.Credentials = New NetworkCredential(userName, password)
            request.ContentType = "application/json"
            request.Method = "POST"   ' In this case, I have to send the data via POST
    
            'set content/payload
    'Here I have a custom class that I fill with the data and get back the JSON version of it ... 
    ' The data includes the parameters and some other data I get from the database
    'I didn't use a JSON serializer because of certain tech issues (this is actually implemented as a CLR SProc and called from inside SQL Server
    ' -- If it can work in there, it can work anywhere
    
            Dim jsonData As String = CustomObject.ToJson 'Custom function to return the data as a JSON string
            rawData = jsonData
            postData = UTF8Encoding.UTF8.GetBytes(jsonData)
            request.ContentLength = postData.Length
            Using requestStream As Stream = request.GetRequestStream
                requestStream.Write(postData, 0, postData.Length)
            End Using
    
            Try
                response = request.GetResponse()
                replyStreamReader = New StreamReader(response.GetResponseStream)
    
                'read back the stream into a string
                errorMessage = replyStreamReader.ReadToEnd()
    
                'Possible response codes:
                '200 - Success
                '400 - Bad request
                '409 - Duplicate transaction
    
                '404 - Transaction not found
    
            Catch ex As Exception
                errorMessage = ex.Message
            Finally
                If response IsNot Nothing Then
                    response.Close()
                End If
                If replyStreamReader IsNot Nothing Then
                    replyStreamReader.Dispose()
                End If
    
            End Try
    
        End Sub
    That said... one thing it doesn't do is send headers... but I think you can set them similar to SS's example... you may want to explore the object model some.

    -tg
    * I don't respond to private (PM) requests for help. It's not conducive to the general learning of others.*
    * I also don't respond to friend requests. Save a few bits and don't bother. I'll just end up rejecting anyways.*
    * How to get EFFECTIVE help: The Hitchhiker's Guide to Getting Help at VBF - Removing eels from your hovercraft *
    * How to Use Parameters * Create Disconnected ADO Recordset Clones * Set your VB6 ActiveX Compatibility * Get rid of those pesky VB Line Numbers * I swear I saved my data, where'd it run off to??? *

  12. #12
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    Oh, gosh, I started typing that up and while I was typing it you responded, so some things I said you weren't doing you did, I just didn't notice. But now I can see some of your code, and I can help out a little. First, though:

    I get a lot of “Type not defined” with the code you gave me. I tried to find Imports or references I might be missing, but I don’t think that is the case.
    That's why I said:
    So using an imaginary HTTP library and an imaginary JSON parser,
    I made up the syntax, because it takes a lot longer for me to go hunt down the right methods on everything. The idea was "ImaginaryJson" is "whatever you use to deal with JSON", and "HttpPostRequest" is "Whatever you use to deal with HTTP". "Credentials" was just a random type made up, because generally JSON libraries convert to and from objects.

    So you get "type not defined" errors because I made them all up. You weren't supposed to copy/paste it, you were meant to look at the one line that mattered and figure out where to put it in your code.

    Your code as-is needs restructuring to work with JSON and a JWT. The biggest difference is going to be you need to start with a login request to get the JWT, then you need to use other requests. I want to show you a good example, but it's going to have to wait until morning. My wife just got back from a week-long trip and I know I can't bang a good sample out in the next 10 minutes while she unpacks. I'll go the extra mile and use a real JSON parser if I wait until morning.

    And if someone else posts before then, well, then you get an answer anyway.

    techgnome's posted a very good example of making a web request. Not all of it is needed for every request, but that's what makes it neat: it shows off a lot of features. It's not manipulating headers, but HttpWebRequest.Headers is easy to deal with. Read that documentation and see if it makes sense while you wait.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  13. #13

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    I did assume the “ImaginaryJson” looked like something I needed to change, but I could not figure out for sure. It is crazy how few examples are out there for using VB.NET with JWT. I have been Google searching for weeks now with every example not able to compile, which is why I started thinking maybe my VS or .NET was too outdated to support it. It seems mostly everything I find that uses JWT uses either ASP.NET or Java.

    I hate to ask to be spoon-fed but I am not grasping the whole JSON thing and it looks like I need to start with something that works before I can begin to understand it enough to modify it. If you could get me a working example for what I am trying to do I would really appreciate it (Not that I don’t already appreciate what you have already done). I have been reading what I can find, but the whole token thing syntax confuses me.

    Thanks,

    Jim




    Quote Originally Posted by Sitten Spynne View Post
    I made up the syntax, because it takes a lot longer for me to go hunt down the right methods on everything. The idea was "ImaginaryJson" is "whatever you use to deal with JSON", and "HttpPostRequest" is "Whatever you use to deal with HTTP". "Credentials" was just a random type made up, because generally JSON libraries convert to and from objects.

    So you get "type not defined" errors because I made them all up. You weren't supposed to copy/paste it, you were meant to look at the one line that mattered and figure out where to put it in your code.

    Your code as-is needs restructuring to work with JSON and a JWT. The biggest difference is going to be you need to start with a login request to get the JWT, then you need to use other requests. I want to show you a good example, but it's going to have to wait until morning. My wife just got back from a week-long trip and I know I can't bang a good sample out in the next 10 minutes while she unpacks. I'll go the extra mile and use a real JSON parser if I wait until morning.

  14. #14
    PowerPoster techgnome's Avatar
    Join Date
    May 2002
    Posts
    31,285

    Re: Help using JSON API that requires Authentication with JWT token

    Don't worry about JWT... it's a SERVER side library... that's why the examples are for ASP.NET or Java... because it is used on the SERVER end of things. All you need to know is that it will return a string and you then have to send that string back in the header on subsequent calls.

    As for JSON itself, it's fairly straight forward.
    Let's use the ubiquitous car example.
    I have a Car class that represents my 2011 Ford F150... in VB it might look like this:
    Code:
    myCar.Make = "Ford"
    myCar.Model = "F150"
    myCar.Year = 2011
    The JSON equivalent for that would be:
    Code:
    {myCar:{"Make":"Ford","Model:F150","Year":"2011"}}
    The {} are used to delimit objects... the first object encountered is myCar... which is then made up of three objects (properties): Make, Model, and Year. That's what JSON looks like.

    So... what you need to do is, using code similar to what I posted, invoke the login API call... https://api.thetvdb.com/login -- pass it the data it needs in a JSON object... and get the response back. What you get back will be another JSON string. In that string will be the JWT token... extract it. Put it into a string variable so you can get to it again later. Then, later when you need to invoke another API call, again, it will be code similar to what I posted, BUT with adding the JWT token to the header, again, it's just a string value, before making the API call...

    that's all there is to it. No smoke, no mirrors, maybe a little bit of fog, but that should clear up. There's nothing special about the token. Nothing. It's just a value. Period.

    -tg
    * I don't respond to private (PM) requests for help. It's not conducive to the general learning of others.*
    * I also don't respond to friend requests. Save a few bits and don't bother. I'll just end up rejecting anyways.*
    * How to get EFFECTIVE help: The Hitchhiker's Guide to Getting Help at VBF - Removing eels from your hovercraft *
    * How to Use Parameters * Create Disconnected ADO Recordset Clones * Set your VB6 ActiveX Compatibility * Get rid of those pesky VB Line Numbers * I swear I saved my data, where'd it run off to??? *

  15. #15

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    The steps you are telling me I need to take makes sense to me in English, but I am having a hard time with the syntax. The Fog is thicker than London’s! I don’t know what to do in place of CustomObject

    Code:
    Public Sub SendData(ByVal endpointAddress As String, ByVal username As String, ByVal userKey As String, ByVal apiKey As String)
            Dim request As HttpWebRequest = Nothing
            Dim postData() As Byte = Nothing
            Dim response As HttpWebResponse = Nothing
            Dim replyStreamReader As StreamReader = Nothing
            Dim errorMessage As String
    
            request = WebRequest.Create(New Uri(endpointAddress))
            'Set login information
            request.Credentials = New NetworkCredential(username, userKey, apiKey)
    
            request.ContentType = "application/json"
            request.Method = "POST"   
    
            Dim jsonData As String = CustomObject.ToJson 'Custom function to return the data as a JSON string
            rawData = jsonData
            postData = UTF8Encoding.UTF8.GetBytes(jsonData)
            request.ContentLength = postData.Length
            Using requestStream As Stream = request.GetRequestStream
                requestStream.Write(postData, 0, postData.Length)
            End Using
    
            Try
                response = request.GetResponse()
                replyStreamReader = New StreamReader(response.GetResponseStream)
    
                'read back the stream into a string
                errorMessage = replyStreamReader.ReadToEnd()
                TextBox_Results.Text = response.ToString
    
                'Possible response codes:
                '200 - Success
                '400 - Bad request
                '409 - Duplicate transaction
                '404 - Transaction not found
    
            Catch ex As Exception
                errorMessage = ex.Message
            Finally
                If response IsNot Nothing Then
                    response.Close()
                End If
                If replyStreamReader IsNot Nothing Then
                    replyStreamReader.Dispose()
                End If
            End Try
        End Sub

  16. #16
    PowerPoster techgnome's Avatar
    Join Date
    May 2002
    Posts
    31,285

    Re: Help using JSON API that requires Authentication with JWT token

    it can be what ever you need it to be. A string, and object, a call to a function... what ever. It just depends on what you need to send to the API.

    Let's say you;re trying to call the login API... the lines would look like this:
    Code:
            Dim jsonData As String = "{""apikey"":""XXXXXXXXXXXXXXXX"",""username"":"" XXXXXXXXXXXXXXXX"",""userkey"":""XXXXXXXXXXXXXXXX""}"
            postData = UTF8Encoding.UTF8.GetBytes(jsonData)
    That will then setup the JSON to send to the login....

    you then call it and get your token back:
    Code:
                response = request.GetResponse()
                replyStreamReader = New StreamReader(response.GetResponseStream)
    
                'read back the stream into a string
                yourJWTToeken = replyStreamReader.ReadToEnd()
    there... badda boom, badda bing...

    -tg\
    * I don't respond to private (PM) requests for help. It's not conducive to the general learning of others.*
    * I also don't respond to friend requests. Save a few bits and don't bother. I'll just end up rejecting anyways.*
    * How to get EFFECTIVE help: The Hitchhiker's Guide to Getting Help at VBF - Removing eels from your hovercraft *
    * How to Use Parameters * Create Disconnected ADO Recordset Clones * Set your VB6 ActiveX Compatibility * Get rid of those pesky VB Line Numbers * I swear I saved my data, where'd it run off to??? *

  17. #17
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    I'm a day late, but I ended up working from home a day more than I expected. I think, looking at your code and confusion, we need to talk about some basics and build our way up. I can't actually use the API because I don't want to register for that site. But I'm going to build something that ought to work with only a small bit of tweaking.

    The first thing we need to talk about is JSON. You seem a little flustered by it. Let's get it straight. JSON is just a text format, it's XML with different symbols. However, XML tends to be very "wordy", and JSON tends to be very compact. People like JSON for representing objects. That's one of the things XML can do. So if we have this class:
    Code:
    Public Class Person
        Public Property Name As String
    End Class
    This is one of the several different ways XML might represent an object of that type:
    Code:
    <Person>
        <Name>Joe Random</Name>
    </Person>
    This is the only sensible way JSON represents that object:
    Code:
    {
        "Name" : "Joe Random"
    }
    No one sane hand-writes JSON or XML unless it's for some emergency hotfix. We use libraries to write it for us. Low-level libraries like XmlTextWriter let you write out individual elements, but those are a pain in the butt to work with. When you're in a hurry, you want a "serialization" library. These libraries can "read" an object and convert it to the string format, and they can parse the string and spit out the right object. They exist for both XML and JSON.

    Microsoft hasn't made a decent JSON serialization library for .NET because they spent a lot of money trying to force people to use XML. But there's a very good one called "Newtonsoft JSON" that everyone uses. It has some quirks and advanced features that I'm not going to talk about. You should read its documentation for at least an hour, but this is the whirlwind tour.

    Start a new console project. You are going to need to add a NuGet package, and I'm not sure how well VS 2010 supported that. If it does, you should be able to right-click your project and find either "Add a NuGet Package" or "Manage NuGet Packages" somewhere in the menu. In the dialog that displays, search for "Newtonsoft JSON". The correct author is "James Newton-King" and it's at version 10.0.2 at this moment. Add this package. (I remember the dialog being a little tricky, make sure it's added.)

    (What's happening here is a bit of a modern wonder. Used to, I'd have to send you to a site to download a DLL, you'd have to put it somewhere convenient, and add a reference. NuGet does all of these steps for you. It's quite useful.)

    Now, to use it, we have to define the object that we are serializing/deserializing. Let's make something relevant. The /login request needs an object with the properties "apikey", "userkey", and "username". Add this to your project:
    Code:
    Public Class TvDbCredentials
        Public Property apikey As String
        Public Property username As String
        Public Property userkey As String
    End Class
    I didn't capitalize the names on purpose. JSON is case-sensitive, and the API used lower-case names. By default, Newtonsoft JSON capitalizes names exactly as you do. There are some ways to customize this I won't cover. So for this example we're just going to use non-standard casing for the property names.

    Now we can write a program that turns one of these objects into JSON. And we can have it turn the JSON back into an object to make sure we understand what we're doing.

    Code:
    Imports Newtonsoft.Json
    
    Module Module1
    
        Sub Main()
            Dim credentials As New TvDbCredentials()
            credentials.apikey = "apikey asdf"
            credentials.username = "username asdf"
            credentials.userkey = "userkey asdf"
    
            Dim serializedJson As String = JsonConvert.SerializeObject(credentials)
            Console.WriteLine("The object as JSON:")
            Console.WriteLine(serializedJson)
    
            Dim deserializedObject As TvDbCredentials = JsonConvert.DeserializeObject(Of TvDbCredentials)(serializedJson)
            Console.WriteLine("The JSON as an object:")
            Console.WriteLine(deserializedObject.apikey)
            Console.WriteLine(deserializedObject.username)
            Console.WriteLine(deserializedObject.userkey)
        End Sub
    
    End Module
    That's it. When you're doing things the simplest way, the Shared JsonConvert class is what you use for converting between JSON and objects. If you call SerializeObject(), it will make JSON with a name/value pair for every property of the object.

    DeserializeObject() is a little more complicated. First, you need to tell it what kind of object you want it to give you. Then, it parses the JSON. If it finds a name/value pair in the JSON that matches the name of a property on the object, it tries to set the property to that value. As configured, it won't fail if it can't find a property on the object, or if the JSON is missing some of the object's properties. You can find out how to get those features in the documentation, we don't need them for the rest of the tutorial.

    This is a good place for me to stop typing, because I need to get some of my real work done. Play with the JSON library a little bit and get familiar with it. Read the documentation and see if there's any fancy features you might like. The next post is going to assume you're comfy with seeing it and discuss how I'd go about implementing this API.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  18. #18
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    OK. We know how to convert objects to and from JSON. That's important to this project because any web API is mostly about sending and receiving JSON objects.

    I like to work in layers. To log in, I need to send some particular JSON to the server, and it's going to give me some JSON back. I don't want to muck about with HTTP types every time I do that. So I'm going to write a "RestClient" class to handle the HTTP bits for me. Let's have a look at the first draft:
    Code:
    Imports System.IO
    Imports System.Net
    
    Public Class RestClient
    
        Public Function Post(ByVal url As String, ByVal payload As String) As String
    
            ' Configure the request headers.
            Dim request As HttpWebRequest = CType(HttpWebRequest.Create(url), HttpWebRequest)
            request.Method = "POST"
            request.ContentType = "application/json"
    
            ' Send the request plus the JSON payload.
            Using requestStream As Stream = request.GetRequestStream()
                Using writer As New StreamWriter(requestStream, Text.Encoding.UTF8)
                    writer.Write(payload)
                End Using
            End Using
    
            ' Read the response and its JSON payload.
            Using response As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)
                If response.StatusCode <> HttpStatusCode.OK Then
                    ' This needs to be more sophisticated, you can flesh it out later.
                    Throw New ApplicationException("There was an error with the request.")
                End If
    
                Using responseStream As Stream = response.GetResponseStream()
                    Using reader As New StreamReader(responseStream, Text.Encoding.UTF8)
                        Return reader.ReadToEnd()
                    End Using
                End Using
            End Using
        End Function
    
    End Class
    This method takes the URL and some JSON. It sets up an HTTP POST request and sends the JSON along with it. It then reads the response, throws an exception if there was an error, or returns the JSON payload of the response.

    Why am I leaving the JSON as a string here? Why am I not using objects? Life is easiest if each method does only one thing. This method "sends a request and returns the response". That's about as close to "one thing" as an HTTP exchange can get. If I made a more generic version that worked with objects, it would, "Serialize an object to JSON, then send it as part of a REST request, then attempt to deserialize the response and return the deserialized object." That's more complicated. More complicated things are more likely to fail, and when they fail they're harder to debug.

    So I've got this RestClient class. How's this help me do something with the API? Well, I want to make using the API pretty easy. So I'd like a class that doesn't look like HTTP at all, and instead works with the objects I need to make to represent the JSON. When I look at the /login endpoint, I can describe it as "a function that takes some credentials and returns a JWT". In VB terms:
    Code:
    Public Function Login(ByVal credentials As TvDbCredentials) As String
    But wait. The '/login/ endpoint doesn't return a String. It returns a JSON string representing an object with a 'token' property (unless there's an error.) So I need to make an object to represent it:
    Code:
    Public Class LoginResult
        Public Property token As String
    End Class
    Armed with that, I can write a function that converts the TvDbCredentials to JSON, sends it to the /login endpoint, and returns the token from the result:
    Code:
    Imports Newtonsoft.Json
    
    Public Class TvDbApi
    
        Public Function Login(ByVal credentials As TvDbCredentials) As String
            Dim restClient As New RestClient()
            Dim url As String = "https://example.com/login"
            Dim payloadJson As String = JsonConvert.SerializeObject(credentials)
    
            Dim responseJson As String = restClient.Post(url, payloadJson)
            Dim responseObject As LoginResult = JsonConvert.DeserializeObject(Of LoginResult)(responseJson)
            Return responseObject.token
        End Function
    
    End Class
    See how small, neat, and easy to understand it is with the HTTP logic removed? At this point you ought to be able to get a JWT with a small test application:
    Code:
    Module Module1
    
        Sub Main()
            Dim credentials As New TvDbCredentials()
            credentials.apikey = "apikey asdf"
            credentials.username = "username asdf"
            credentials.userkey = "userkey asdf"
    
            Dim tvDbApi As New TvDbApi()
            Dim jwt As String = tvDbApi.Login(credentials)
            Console.WriteLine(jwt)
        End Sub
    
    End Module
    See the layers at work? You can log in and get the JWT without thinking about JSON or HTTP now. This is another good place for me to stop. Try this out. Make sure to change the URL from 'example.com' to the right one for TvDb, and make sure you know if you should be using https or just http. If the program prints a JWT, you're doing great and can continue. If there is an error, we have to fix that before moving on.

    The next post is going to try out a random entry point to demonstrate how we can take the code we already have and adjust it to include a JWT.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  19. #19
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    We know how to convert objects to and from JSON. We know how to send HTTP POST requests. We've written the /login endpoint for the API. We can get a JWT. Now we have to write code to work with an endpoint that uses it.

    I want to write the code for /search/series. It seems relatively easy, and its response object isn't very complicated. I'll have to write a little bit more code to support this at each layer. But first let's talk about the endpoint.

    It is a GET function that takes a name, imdbId, or zap2itId and, optionally, a language. It will search for a series using these parameters and return results in the indicated language, if they exist. It returns status 404 if the search turns up no results. For simplicity, I'm only going to implement the 'name' parameter.

    We don't have code to send a GET request yet. They behave a little different from POST requests. You don't send a message body along with these, so if they take any parameters they are specified via the URL, and you need to perform 'URL encoding' on them. Also, this request has to take a JWT. So we've got our work cut out for us.

    First, let's make some code to help build a URL with parameters. Newer HTTP libraries in .NET do this for you, but HttpWebRequest offers no help. A URL with GET parameters looks like this:
    Code:
    http://example.com?firstParam=asdf&secondParam=hello%20world
    That %20? That stands for a space. There are a lot of characters not valid in URLs, and they have special codes for representation. The URI class has EscapeUriString() and EscapeDataString() to help, but you have to be careful which one you use. Instead of spending a day explaining, I'm just going to show you the right way. Add this to RestClient.vb:
    Code:
    Private Function CreateEncodedParameterizedUrl(ByVal baseUrl As String, ByVal parameters As Dictionary(Of String, String)) As String
        Dim urlBuilder As New Text.StringBuilder()
        urlBuilder.Append(baseUrl)
    
        Dim parameterIndex As Integer = 0
        urlBuilder.Append("?")
        For i As Integer = 0 To parameters.Count() - 1
            Dim key As String = parameters.Keys(i)
            Dim value As String = parameters(key)
            Dim encodedValue As String = Uri.EscapeDataString(value)
    
            urlBuilder.AppendFormat("{0}={1}", key, encodedValue)
    
            If i < parameters.Count - 1 Then
                urlBuilder.Append("&")
            End If
        Next
    
        Return urlBuilder.ToString()
    End Function
    It's a little bit of a mess. This is the kind of code I like to make MS write for me. There's some newer types that do, but alas, I've already started down the road using HttpWebRequest. The rules are that the first parameter is of the format "?name=value". Every parameter after that is "&name=value". Technically every name and value should be encoded, but I've never seen someone use a parameter name that had special characters. So I'm just encoding the values. Why don't I build the whole thing, then encode it? Because the "://" after "http" would get encoded too! I told you it was tricky.

    Anyway, if you look at that code you'll see it one-by-one appends the right start character, then each name/value pair with the value encoded. Now we can build a GET request that uses a JWT. It reuses some code from POST, for the sake of brevity I'll live with duplication. Add this to RestClient.vb:
    Code:
    Public Function GetWithJwt(ByVal url As String, ByVal jwt As String, Optional ByVal parameters As Dictionary(Of String, String) = Nothing) As String
        Dim encodedUrl As String = url
        If parameters IsNot Nothing Then
            encodedUrl = CreateEncodedParameterizedUrl(url, parameters)
        End If
    
        ' Configure the request headers.
        Dim request As HttpWebRequest = CType(HttpWebRequest.Create(url), HttpWebRequest)
        request.Method = "GET"
    
        ' Build the JWT string and add the right header.
        Dim jwtHeaderValue As String = String.Format("Bearer {0}", jwt)
        request.Headers.Add(HttpRequestHeader.Authorization, jwtHeaderValue)
    
        ' Read the response and its JSON payload.
        Using response As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)
            If response.StatusCode <> HttpStatusCode.OK Then
                ' This needs to be more sophisticated, you can flesh it out later.
                Throw New ApplicationException("There was an error with the request.")
            End If
    
            Using responseStream As Stream = response.GetResponseStream()
                Using reader As New StreamReader(responseStream, Text.Encoding.UTF8)
                    Return reader.ReadToEnd()
                End Using
            End Using
        End Using
    End Function
    Let's pay attention to how it compared to the Rest() method already implemented.

    First, there's extra work to decide if the URL needs encoded parameters. I made the parameters optional because not every GET request requires parameters. The next big change is fewer headers need to be configured: the method is set to GET, and Content-Encoding isn't needed.

    The next 2 lines are how I think the JWT is added. It's very simple, just building a string and adding a header.

    Everything past that is identical: the request is sent, the response is returned.

    Now we have the ability to send an HTTP GET method with URL parameters and a JWT. That's what we needed to implement /search/series! I don't want to have to think about HTTP when calling that method, so I'm thinking it needs to look like this:
    Code:
    Public Function SearchSeriesByName(ByVal jwt As String, ByVal searchTerm As String) As SearchSeriesResult
    Ah. Right. I forgot something. I need an object that represents the search result. That JSON is defined in the documentation. This object should do the trick:
    Code:
    Public Class SearchSeriesResult
        Public Property aliases() As String
        Public Property banner As String
        Public Property firstAired As String
        Public Property id As Long
        Public Property network As String
        Public Property overview As String
        Public Property seriesName As String
        Public Property status As String
    End Class
    Now I write the relevant code in the TvDbApi class:
    Code:
    Imports Newtonsoft.Json
    
    Public Class TvDbApi
    
        Public Function Login(ByVal credentials As TvDbCredentials) As String
            ...
        End Function
    
        Public Function SearchSeriesByName(ByVal jwt As String, ByVal searchTerm As String) As SearchSeriesResult
            Dim restClient As New RestClient()
            Dim url As String = "https://example.com/search/series"
            Dim parameters As New Dictionary(Of String, String)()
            parameters.Add("name", searchTerm)
    
            Dim responseJson As String = restClient.GetWithJwt(url, jwt, parameters)
            Dim responseObject As SearchSeriesResult = JsonConvert.DeserializeObject(Of SearchSeriesResult)(responseJson)
            Return responseObject
        End Function
    
    End Class
    Calling this should be relatively easy from the context of the main program:
    Code:
    Dim credentials As New TvDbCredentials()
    credentials.apikey = "apikey asdf"
    credentials.username = "username asdf"
    credentials.userkey = "userkey asdf"
    
    Dim tvDbApi As New TvDbApi()
    Dim jwt As String = tvDbApi.Login(credentials)
    Dim searchResults As SearchSeriesResult = tvDbApi.SearchSeriesByName(jwt, "Twin Peaks")
    
    Console.WriteLine("First aired: {0}", searchResults.firstAired)
    No muss, no fuss. We already know how to get the JWT, so it just gets included with this next call. See how minor a detail the JWT actually is?

    Now you're on your own to implement the rest of this API. You might notice I didn't write a way to include a JWT with Post(). That's because when I was implementing Login(), I didn't need a JWT because I didn't have one. But if you needed PostWithJwt(), you have the tools to do it now. It doesn't look like this API actually uses any other POST methods. It does use HEAD, GET, and PUT, though, so you'll need to implement those.

    This is how I write web API clients. First, I study the documentation to see what I need to do to use a particular endpoint. Then, I ask if my current RestClient class can satisfy those capabilities. If not, I implement a new RestClient method that can. When RestClient has enough features, I look at my "API" type and decide how to put a friendly function around the HTTP. Usually that involves making a new JSON object for inputs and outputs. The API method never does anything complex, it passes the URL, JSON, and any parameters to the RestClient and gets JSON back. Then it deserializes the JSON to an object and returns something.

    That way, my application worries about "What things am I doing" rather than "Oh wait to search for series I first need to configure a GET request..."

    If this code doesn't work in some way, post as many details about what's going wrong as you can. I can try to remote debug it. I left out any real attempt at handling errors, so it'll faceplant if you do something the API doesn't like. Error handling complicates tutorials.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  20. #20

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    SS,
    I had no time to look at this today either (Got too busy with work), but I am going to give it a hour or so now. I got close to something that works last night using Techgnome’s example (Syntax finally compiled and I got back a code of 401 -Authentication error). I do already have Newtonsoft JSON package installed. I am going to put that code aside for now and go through your steps instead. It seems you put a lot of time into explaining things for me and I want to make sure it sinks in. Thanks for all your help – both of you! Hopefully my next message will to be to let you know it is working!

    Jim




    Quote Originally Posted by Sitten Spynne View Post
    I'm a day late, but I ended up working from home a day more than I expected. I think, looking at your code and confusion, we need to talk about some basics and build our way up. I can't actually use the API because I don't want to register for that site. But I'm going to build something that ought to work with only a small bit of tweaking.

    No one sane hand-writes JSON or XML unless it's for some emergency hotfix. We use libraries to write it for us. Low-level libraries like XmlTextWriter let you write out individual elements, but those are a pain in the butt to work with. When you're in a hurry, you want a "serialization" library. These libraries can "read" an object and convert it to the string format, and they can parse the string and spit out the right object. They exist for both XML and JSON.

    Microsoft hasn't made a decent JSON serialization library for .NET because they spent a lot of money trying to force people to use XML. But there's a very good one called "Newtonsoft JSON" that everyone uses. It has some quirks and advanced features that I'm not going to talk about. You should read its documentation for at least an hour, but this is the whirlwind tour.

    This is a good place for me to stop typing, because I need to get some of my real work done. Play with the JSON library a little bit and get familiar with it. Read the documentation and see if there's any fancy features you might like. The next post is going to assume you're comfy with seeing it and discuss how I'd go about implementing this API.

  21. #21
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    Up-front, the two reasons you might be getting 401 are:

    • You are using the /login endpoint, but have sent either the wrong trio of credentials or your JSON is formatted wrong. Or maybe it doesn't want UTF-8, or doesn't expect it unless you set Content-Encoding.
    • You are using anything else, but not properly including the JWT.


    One hint, because it'd be useful here: never post just what the error says. Post the code, any inputs you gave it, AND what the error says. That way we can mentally work through the code and try to connect dots that end in the same place you did.

    I don't ever trust "I'm using so and so's code from this post". It's almost always true that the code got copied, pasted, and tweaked slightly. The tweaks are usually where the problem lies, but we can't see the tweaks if the code isn't posted
    Last edited by Sitten Spynne; Jun 16th, 2017 at 04:18 PM.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  22. #22

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    Hmmm, I have your code working up to this point, but I am getting the same error as with TechGnome’s example. “System.Net.WebException” Returned an error: “The Remote Server Returned an error: (401) Unautorized.” When executing the code:

    Code:
    Using response As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)
    I use the same credentials on the API test site and it gives me a valid token that I can use to search with. I need to look at this a little deeper. Things are making more sense to me though.
    Jim



    Quote Originally Posted by Sitten Spynne View Post
    OK. We know how to convert objects to and from JSON. That's important to this project because any web API is mostly about sending and receiving JSON objects.

    I like to work in layers. To log in, I need to send some particular JSON to the server, and it's going to give me some JSON back. I don't want to muck about with HTTP types every time I do that. So I'm going to write a "RestClient" class to handle the HTTP bits for me. Let's have a look at the first draft:
    See the layers at work? You can log in and get the JWT without thinking about JSON or HTTP now. This is another good place for me to stop. Try this out. Make sure to change the URL from 'example.com' to the right one for TvDb, and make sure you know if you should be using https or just http. If the program prints a JWT, you're doing great and can continue. If there is an error, we have to fix that before moving on.

    The next post is going to try out a random entry point to demonstrate how we can take the code we already have and adjust it to include a JWT.

  23. #23

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    Credentials seem correct. They work on the API test site. I get the following response header so it looks like UTF-8 is correct too. I have to stop for now, but I will continue later tonight.
    {
    "date": "Fri, 16 Jun 2017 21:30:42 GMT",
    "content-type": "application/json; charset=utf-8",
    "transfer-encoding": "chunked",
    "connection": "keep-alive",
    "vary": "Accept-Language",
    "x-powered-by": "Thundar!",
    "x-thetvdb-api-version": "2.1.2",
    "server": "cloudflare-nginx",
    "cf-ray": "3700df2e0e825a32-BOS",
    "": ""
    }



    Quote Originally Posted by Sitten Spynne View Post
    Up-front, the two reasons you might be getting 401 are:

    • You are using the /login endpoint, but have sent either the wrong trio of credentials or your JSON is formatted wrong. Or maybe it doesn't want UTF-8, or doesn't expect it unless you set Content-Encoding.
    • You are using anything else, but not properly including the JWT.


    One hint, because it'd be useful here: never post just what the error says. Post the code, any inputs you gave it, AND what the error says. That way we can mentally work through the code and try to connect dots that end in the same place you did.

    I don't ever trust "I'm using so and so's code from this post". It's almost always true that the code got copied, pasted, and tweaked slightly. The tweaks are usually where the problem lies, but we can't see the tweaks if the code isn't posted

  24. #24
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,126

    Re: Help using JSON API that requires Authentication with JWT token

    Quote Originally Posted by JimAvanti View Post
    Hmmm, I have your code working up to this point, but I am getting the same error as with TechGnome’s example. “System.Net.WebException” Returned an error: “The Remote Server Returned an error: (401) Unautorized.” When executing the code:

    Code:
    Using response As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)
    I use the same credentials on the API test site and it gives me a valid token that I can use to search with. I need to look at this a little deeper. Things are making more sense to me though.
    Jim
    To (hopefully) save you some grief, the code will be 'bom'ing in the Post Function (RestClient Class) on the line:
    Code:
    Using writer As New StreamWriter(requestStream, Text.Encoding.UTF8)
    Text.Encoding.UTF8 emits a BOM, and that's screwing up the POST data.

    You could create the StreamWriter without specifying an Encoding, in which case it actually defaults to using UTF8 without a BOM.

    Or if you want to be explicit (some people do ), create an instance of the UTF8Encoding Class that does not emit a BOM and pass that to the StreamWriter's Constructor:
    Code:
    Dim UTF8WithoutBOM As New System.Text.UTF8Encoding(False)
    Using writer As New StreamWriter(requestStream, UTF8WithoutBOM)

  25. #25

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    I took out the Text.Encoding.UTF8 like you said and it is now working. I am able to get the token! Onto the next step...

    Thank You,
    Jim

    Quote Originally Posted by Inferrd View Post
    To (hopefully) save you some grief, the code will be 'bom'ing in the Post Function (RestClient Class) on the line:
    Code:
    Using writer As New StreamWriter(requestStream, Text.Encoding.UTF8)
    Text.Encoding.UTF8 emits a BOM, and that's screwing up the POST data.

    You could create the StreamWriter without specifying an Encoding, in which case it actually defaults to using UTF8 without a BOM.

    Or if you want to be explicit (some people do ), create an instance of the UTF8Encoding Class that does not emit a BOM and pass that to the StreamWriter's Constructor:
    Code:
    Dim UTF8WithoutBOM As New System.Text.UTF8Encoding(False)
    Using writer As New StreamWriter(requestStream, UTF8WithoutBOM)

  26. #26

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    Sitten Spynne,
    I was able to get a valid JWT and use it on a search. I am still getting some errors with the GetWithJwt function though.
    It looks like this line (Which gives me error 405 Method not Allowed):
    Code:
    Dim request As HttpWebRequest = CType(HttpWebRequest.Create(url), HttpWebRequest)
    Should actually be:
    Code:
    Dim request As HttpWebRequest = CType(HttpWebRequest.Create(encodedUrl), HttpWebRequest)
    If I search for the full name of the show and it finds an exact match the program works, but all the SearchSeriesResult properties return nothing (or a zero in the case of id).

    If I search for a name of a show that isn’t in the database exactly as typed (Such as “Twin Peak” rather than “Twin Peaks” I get error 404 “Not Found”. I get that this is not an error in the code, but simply the way the search works. The old API would allow you to type any part of a show and it would return a whole list of shows containing the search string so that you can select the one you want from a list. I guess I have to leave a message to the people at theTVdb.com about that one.
    I am finishing up for the night. Thanks again for all your help.
    Jim

  27. #27

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    Sitten Spynne,
    I was able to get a valid JWT and use it on a search. I am still getting some errors with the GetWithJwt function though.
    It looks like this line (Which gives me error 405 Method not Allowed):
    Code:
    Dim request As HttpWebRequest = CType(HttpWebRequest.Create(url), HttpWebRequest)
    Should actually be:
    Code:
    Dim request As HttpWebRequest = CType(HttpWebRequest.Create(encodedUrl), HttpWebRequest)
    If I search for the full name of the show and it finds an exact match the program works, but all the SearchSeriesResult properties return nothing (or a zero in the case of id).

    If I search for a name of a show that isn’t in the database exactly as typed (Such as “Twin Peak” rather than “Twin Peaks” I get error 404 “Not Found”. I get that this is not an error in the code, but simply the way the search works. The old API would allow you to type any part of a show and it would return a whole list of shows containing the search string so that you can select the one you want from a list. I guess I have to leave a message to the people at theTVdb.com about that one.
    I am finishing up for the night. Thanks again for all your help.

    Jim

  28. #28

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    It Seems this JSON Class:
    Code:
    Public Class SearchSeriesResult
        Public Property aliases() As String
        Public Property banner As String
        Public Property firstAired As String
        Public Property id As Long
        Public Property network As String
        Public Property overview As String
        Public Property seriesName As String
        Public Property status As String
    End Class
    Actually returns an Array of results. That may be why I am getting nothing back as a result. What do you think?

    Jim

  29. #29
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    Quote Originally Posted by JimAvanti View Post
    Sitten Spynne,
    I was able to get a valid JWT and use it on a search. I am still getting some errors with the GetWithJwt function though.
    It looks like this line (Which gives me error 405 Method not Allowed):
    Code:
    Dim request As HttpWebRequest = CType(HttpWebRequest.Create(url), HttpWebRequest)
    Should actually be:
    Code:
    Dim request As HttpWebRequest = CType(HttpWebRequest.Create(encodedUrl), HttpWebRequest)
    If I search for the full name of the show and it finds an exact match the program works, but all the SearchSeriesResult properties return nothing (or a zero in the case of id).

    If I search for a name of a show that isn’t in the database exactly as typed (Such as “Twin Peak” rather than “Twin Peaks” I get error 404 “Not Found”. I get that this is not an error in the code, but simply the way the search works. The old API would allow you to type any part of a show and it would return a whole list of shows containing the search string so that you can select the one you want from a list. I guess I have to leave a message to the people at theTVdb.com about that one.
    I am finishing up for the night. Thanks again for all your help.

    Jim
    Look at you, you're catching my errors. Fast learner!

    I'm not sure how to search for a partial show name. I'd sort of expect that by default, but the documentation didn't make that clear. Maybe they've got a wildcard format? I hope there's an answer to that.

    Also:
    It seems this JSON class [...] returns an Array of results
    This is spot on and I missed it from the documentation:
    An array of results that match the provided query.
    I was looking for [] around the "model" if it was an array. Whoops!

    I forgot to mention it, but 90% of "writing a client" for one of these is being almost right and hacking out all the little wrinkles
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  30. #30

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    SS:
    I don’t know how you could have written all that code without even having a valid APIkey to test it out!

    I already spent half the day trying to figure out how to Deserialize the JSON response into an Array of SearchSeriesResult with no luck. I made a new copy of your SearchSeriesName function that returns a JSON string instead of a Deserialized object so that I could see what was going on.


    Code:
        Public Function SearchSeriesByName(ByVal jwt As String, ByVal searchTerm As String) As String 
            Dim restClient As New RestClient()
            Dim url As String = "https://api.thetvdb.com/search/series"
            Dim parameters As New Dictionary(Of String, String)()
            parameters.Add("name", searchTerm)
            Dim responseJson As String = restClient.GetWithJwt(url, jwt, parameters)
            Return responseJson
        End Function
    I then try calling with the following code to display the JSON string followed by my attempt to load an array of the Deserialized string and display an item. I am having no luck.
    Code:
           Dim searchResults As String = tvDbApi.SearchSeriesName(jwt, TextBox2.Text)
            TextBox1.Text = TextBox1.Text & vbCrLf & searchResults
    
            Dim responseObject = JsonConvert.DeserializeObject(Of List(Of SearchSeriesResult))(searchResults)
            TextBox1.Text = TextBox1.Text & vbCrLf & responseObject(1).id
    If I wanted to convert the JSON to XML how would I go about that? Would I convert the JSON response string or the Deserialized array (Once I have a valid array).

    Jim




    Quote Originally Posted by Sitten Spynne View Post
    Look at you, you're catching my errors. Fast learner!

    I'm not sure how to search for a partial show name. I'd sort of expect that by default, but the documentation didn't make that clear. Maybe they've got a wildcard format? I hope there's an answer to that.

    Also:

    This is spot on and I missed it from the documentation:


    I was looking for [] around the "model" if it was an array. Whoops!

    I forgot to mention it, but 90% of "writing a client" for one of these is being almost right and hacking out all the little wrinkles

  31. #31

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    I know we already got beyond this point, but I am still curious why Text.Encoding.UTF8 creates an error.

    Jim

    Quote Originally Posted by Inferrd View Post
    To (hopefully) save you some grief, the code will be 'bom'ing in the Post Function (RestClient Class) on the line:
    Code:
    Using writer As New StreamWriter(requestStream, Text.Encoding.UTF8)
    Text.Encoding.UTF8 emits a BOM, and that's screwing up the POST data.

    You could create the StreamWriter without specifying an Encoding, in which case it actually defaults to using UTF8 without a BOM.

    Or if you want to be explicit (some people do ), create an instance of the UTF8Encoding Class that does not emit a BOM and pass that to the StreamWriter's Constructor:
    Code:
    Dim UTF8WithoutBOM As New System.Text.UTF8Encoding(False)
    Using writer As New StreamWriter(requestStream, UTF8WithoutBOM)

  32. #32
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    I can write code that works without an API key because accessing JSON-based REST APIs involves a fairly common bag of tricks. It was designed by hundreds of web developers who needed to be able to conveniently interoperate with each other. It's easy to just see HTTP interaction as analagous to writing and reading text files. So even when I don't have a server, I can think about the Strings I might receive and parse them.

    Converting JSON to XML won't help you if you don't know how to interpret the JSON. The easiest way to convert from JSON to XML is to convert JSON to objects, then convert objects to XML. But since you can't convert JSON to objects, you won't get the objects as XML. The only other approach is to "parse the JSON token-by-token and emit XML in response". To get there, you have to learn how to read JSON. If you know how to read JSON, you know how to parse JSON to objects. See the circle there?

    Your code snippets don't quite show me enough for me to be able to draw a conclusion. I see your SearchSeriesByName(), and it looks right. The next code is probably your level above that. I didn't know, offhand, if the JSON library can deserialize an array into a List(Of T), but I knew how to check: write a small program! When you don't know what's going on, this is often the best way to play around without breaking what you've got.

    Let's imagine a much simpler SearchSeriesResult, with just the 'id' property, to keep things easier. If the search results are an array of those, they could look like:
    Code:
    [{ "id" : 1 }, { "id" : 2 }, { "id" : 3 }]
    So I'm going to try deserializing that:
    Code:
    Imports Newtonsoft.Json
    
    Module Module1
    
        Sub Main()
            Dim jsonInput As String = "[{ ""id"" : 1 }, { ""id"" : 2 }, { ""id"" : 3 }]"
    
            Dim results = JsonConvert.DeserializeObject(Of List(Of SearchSeriesResult))(jsonInput)
            For Each r In results
                Console.WriteLine(r.id)
            Next
        End Sub
    
    End Module
    
    Public Class SearchSeriesResult
        Public Property id As Long
    End Class
    I get the results I expect: 1, 2, and 3 are printed to the console.

    So I'm left with an easy conclusion: the string you get back from SearchSeriesByName() probably can't be parsed to a List(Of SearchSeriesResult). That means it doesn't look like what that should look like. But I can't see enough things to understand what went wrong, I'm very in the dark. You have to post as much detail as possible, since I can't use the server. Here's what I need to see:

    • Your exact SearchSeriesByName() code. (You posted this.)
    • Your exact code that calls/uses SearchSeriesByName(). (You posted this.)
    • The actual JSON you get back and store in responseJson. You're currently putting this in TextBox1.Text. If I could see it, I could tell if you're not interpreting the JSON right.
    • Your exact code for SearchSeriesResult, even if you think it hasn't changed since #28.
    • Exactly what "having no luck" means. Is there an exception? Is the list empty? Is no JSON appearing at all?


    I know we already got beyond this point, but I am still curious why Text.Encoding.UTF8 creates an error.
    Oh. Agh. See, this is why I warned you I wasn't testing the code. Things like this tend to slip my mind and don't show up until you run them against a real server.

    There are literally half a dozen ways to represent text as bytes. You need to know which one is being used before trying to convert to or from bytes, because most of them are not compatible with each other. Worse, not all computers put their bytes in the same order. For example, some systems would say two bytes are "0x00 0xAD" and another might argue it should be "0xAD 0x00". To help make heads or tails of this, the "Byte Order Mark" or BOM is sometimes used.

    The BOM is a simple protocol. Text streams with a BOM put some special bytes at the start of the stream that indicate which encoding and byte order is needed to properly interpret the text. For example, UTF-8's BOM is 0xEF, 0xBB, 0xBF (or opposite for different byte order.) Tools that read text are supposed to check for a BOM before interpreting the text, and use the appropriate encoding if the BOM is present.

    But not all tools expect a BOM, and since they're specifically designed to not look like valid text it can confuse the heck out of tools that don't expect a BOM. It sounds like this server does NOT expect to get a BOM, which is fairly reasonable since you can set the Content-Encoding header to do the same thing.

    Unfortunately, .NET's UTF-8 encoding sends a BOM by default. So if the code I wrote was just trying to send one character, it'll actually send four (or more) bytes: the BOM and that one character. The server is expecting to get JSON that starts with a {, but it gets a weird character instead and decides to bail.

    But if you take Inferrd's advice and use this:
    Code:
    Dim UTF8WithoutBOM As New System.Text.UTF8Encoding(False)
    Using writer As New StreamWriter(requestStream, UTF8WithoutBOM)
    That will create a UTF-8 encoding that doesn't send a BOM at the beginning. That would be my recommendation.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  33. #33
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,126

    Re: Help using JSON API that requires Authentication with JWT token

    Quote Originally Posted by JimAvanti View Post
    I know we already got beyond this point, but I am still curious why Text.Encoding.UTF8 creates an error.

    Jim

    Because 'System.Text.Encoding.UTF8' is a version of the UTF-8 encoding that happens to emit a BOM (Byte Order Mark) when used with a StreamWriter.

    For UTf-8, the BOM consists of three bytes with the values 0xEF, 0xBB, 0xBF. These bytes are prepended to the output, so they would be sent as part of the POST data before the actual JSON text, and the tvdb server considers that to be bad JSON.

    Remember, as I mentioned in the earlier post, you can create an instance of the UTF-8 encoding that does not emit a BOM, and that's done by default when you don't specify an encoding when you create the StreamWriter.


    Take a look at the following links for more info:
    https://en.wikipedia.org/wiki/Byte_order_mark

    https://stackoverflow.com/questions/...-8-without-bom



    Edit: I see I was too slow
    Last edited by Inferrd; Jun 19th, 2017 at 03:12 PM.

  34. #34

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    It looks to me like data is an array and also the name aliases within the array is an array.
    I did a search that would respond with something small ("Big Bang"):


    Code:
    {
      "data": [
        {
          "aliases": [
            "Big Bang"
          ],
          "banner": "graphical/80379-g15.jpg",
          "firstAired": "2007-09-24",
          "id": 80379,
          "network": "CBS",
          "overview": "What happens when hyperintelligent roommates Sheldon and Leonard meet Penny, a free-spirited beauty moving in next door, and realize they know next to nothing about life outside of the lab. Rounding out the crew are the smarmy Wolowitz, who thinks he's as sexy as he is brainy, and Koothrappali, who suffers from an inability to speak in the presence of a woman.",
          "seriesName": "The Big Bang Theory",
          "status": "Continuing"
        },
        {
          "aliases": [],
          "banner": "graphical/290555-g2.jpg",
          "firstAired": "2007-10-02",
          "id": 290555,
          "network": "Mega Channel",
          "overview": null,
          "seriesName": "Big Bang (Mega)",
          "status": "Ended"
        },
        {
          "aliases": [
            "Lyngbø and Hærland Big Bang"
          ],
          "banner": "",
          "firstAired": "2010-03-05",
          "id": 148641,
          "network": "NRK1",
          "overview": null,
          "seriesName": "Lyngbø \u0026 Hærlands Big Bang",
          "status": "Ended"
        },
        {
          "aliases": [],
          "banner": "",
          "firstAired": "2016-03-14",
          "id": 308935,
          "network": "RTÉ Two",
          "overview": "Comedy quiz hosted by Neil Delamere in which team captains PJ Gallagher and Aoibhinn Ni Shuilleabhain and a panel of comedians and scientists attempt to answer some of humanity's most pressing questions.",
          "seriesName": "Eureka! The Big Bang Query",
          "status": "Continuing"
        },
        {
          "aliases": [
            "Marginal Number Four Kiss Kara Tsukuru Big Bang",
            "Marginal#4 Kiss Kara Tsukuru Big Bang"
          ],
          "banner": "",
          "firstAired": "2017-01-12",
          "id": 321440,
          "network": "Tokyo MX",
          "overview": "On February 13, as snow flurries dance in the air, a new idol unit is born at Pythagoras Production. They are the idols who will deliver their kiss to the ends of the galaxy: \"Marginal #4.\" Its members, Atom Kirihara, Rui Aiba, L Nomura, and R Nomura, put all their effort into their entertainment career for the sake of the fan supporting them! They continue to mature as idols along with the senior unit pulling the entertainment industry forward, Lagrange Point, and the younger unit preparing to make their debut, Unicorn Jr. But when they step off the stage, they're ordinary high school boys with some chaotic student lives!\r\n\r\nThis is the tale of Margninal #4, a school slice-of-life about idols that shine brighter than the stars.",
          "seriesName": "MARGINAL #4 The Animation",
          "status": "Continuing"
        }
      ]
    }
    SearchSeriesResult class is the same as before:
    Code:
    Public Class SearchSeriesResult
        Public Property aliases() As String
        Public Property banner As String
        Public Property firstAired As String
        Public Property id As Long
        Public Property network As String
        Public Property overview As String
        Public Property seriesName As String
        Public Property status As String
    End Class
    Quote Originally Posted by Sitten Spynne View Post
    I can write code that works without an API key because accessing JSON-based REST APIs involves a fairly common bag of tricks. It was designed by hundreds of web developers who needed to be able to conveniently interoperate with each other. It's easy to just see HTTP interaction as analagous to writing and reading text files. So even when I don't have a server, I can think about the Strings I might receive and parse them.

    Here's what I need to see:

    • Your exact SearchSeriesByName() code. (You posted this.)
    • Your exact code that calls/uses SearchSeriesByName(). (You posted this.)
    • The actual JSON you get back and store in responseJson. You're currently putting this in TextBox1.Text. If I could see it, I could tell if you're not interpreting the JSON right.
    • Your exact code for SearchSeriesResult, even if you think it hasn't changed since #28.
    • Exactly what "having no luck" means. Is there an exception? Is the list empty? Is no JSON appearing at all?


  35. #35

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    When I try to deseriaize to an array:

    Code:
            Dim responseObject = JsonConvert.DeserializeObject(Of List(Of SearchSeriesResult))(searchResults)
            TextBox1.Text = TextBox1.Text & vbCrLf & responseObject(1).id
    I get the following error:

    Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[JSON_API.SearchSeriesResult]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
    To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.



    Quote Originally Posted by Sitten Spynne View Post


    • Your exact SearchSeriesByName() code. (You posted this.)
    • Your exact code that calls/uses SearchSeriesByName(). (You posted this.)
    • The actual JSON you get back and store in responseJson. You're currently putting this in TextBox1.Text. If I could see it, I could tell if you're not interpreting the JSON right.
    • Your exact code for SearchSeriesResult, even if you think it hasn't changed since #28.
    • Exactly what "having no luck" means. Is there an exception? Is the list empty? Is no JSON appearing at all?



  36. #36

    Thread Starter
    Member
    Join Date
    Feb 2014
    Posts
    33

    Re: Help using JSON API that requires Authentication with JWT token

    Oh,

    When you said BOM I thought you were just saying the command would bomb! I didn't know it was an acronym! Thanks for explaining it to me.

    Jim

    Quote Originally Posted by Inferrd View Post
    Because 'System.Text.Encoding.UTF8' is a version of the UTF-8 encoding that happens to emit a BOM (Byte Order Mark) when used with a StreamWriter.

    For UTf-8, the BOM consists of three bytes with the values 0xEF, 0xBB, 0xBF. These bytes are prepended to the output, so they would be sent as part of the POST data before the actual JSON text, and the tvdb server considers that to be bad JSON.

    Remember, as I mentioned in the earlier post, you can create an instance of the UTF-8 encoding that does not emit a BOM, and that's done by default when you don't specify an encoding when you create the StreamWriter.


    Take a look at the following links for more info:
    https://en.wikipedia.org/wiki/Byte_order_mark

    https://stackoverflow.com/questions/...-8-without-bom



    Edit: I see I was too slow

  37. #37
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    OK. This is a case of their API documentation not being 100% accurate. Welcome to development, this happens sometimes.

    If you look at JSON as "a string", you will have a hard time interpreting it. You have to understand what the symbols mean. There's not many symbols and rules to remember:
    • [] means an array. Arrays can contain values or objects, separated by commas.
    • {} means an object. Objects can contain properties, separated by commas.
    • A property has a name, enclosed in quotes, and a value. They are separated by a colon.
    • A value can be:
      • A string, "which must be enclosed in quotes".
      • A number, which doesn't have quotes.
      • An array.
      • An object.


    Building a VB object out of a JSON object is tough, compared to some other languages, because VB is very verbose and you can't really nest object declarations inside each other. When I read JSON, I tend to defer nested things until I can see the whole picture. Let's take a look at the JSON for /series/{id}. I won't copy/paste it here, for brevity, but I will show the parts I'm looking at.

    This API call returns an object with one property: "data". That property is an array that contains other objects:
    Code:
    {
        "data" : [{ ... }, ... ]
    }
    I want to call this type "SeriesSearchResults", since it represents many possible results.

    The one inside "data" looks really familiar:
    Code:
    {
        "added" : "string",
        ...
    }
    I want to call this type "SeriesSearchResult", since it's one result.

    Code:
    Public Class SearchSeriesResults
        Public Property data() As SearchSeriesResult()
    End Class
    
    Public Class SearchSeriesResult
        ... same as before ...
    End Class
    So now you're trying to deserialize:
    Code:
    Dim responseObject = JsonConvert.DeserializeObject(Of SearchSeriesResults)(searchResults)
    That's a heck of a lot more likely to work.

    Note the difference between what you read and what it was: there is a "data" property and THAT contains an array. If the result had been an array alone, it would've looked like:
    Code:
    [ { ... }, { ... } ]
    Especially since we now see their documentation isn't 100% accurate, it's important to know how to read JSON so you can figure out how it corresponds to objects.
    Last edited by Sitten Spynne; Jun 19th, 2017 at 04:41 PM. Reason: New post with factual information!
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  38. #38
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,126

    Re: Help using JSON API that requires Authentication with JWT token

    Quote Originally Posted by JimAvanti View Post
    Oh,

    When you said BOM I thought you were just saying the command would bomb! I didn't know it was an acronym! Thanks for explaining it to me.

    Jim

    Aye. Sorry for the confusion.

    I was going for the pun... I really ought to leave that sort of thing to Shaggy Hiker.

  39. #39
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,126

    Re: Help using JSON API that requires Authentication with JWT token

    @Sitten,

    You're looking at the wrong API. It's the Search API (search/series) that you were talking about last time. The Series API (get series info by ID) will come next, no doubt.


    Also, you have a typo in your Property declarations. The array brackets are in the wrong place: they should come after the Type name.

  40. #40
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,177

    Re: Help using JSON API that requires Authentication with JWT token

    Huh, OK. I'm going back and editing because it's still sort of almost right, the only difference is the inner type now will have different properties. I'm going fix it!

    Done. Thanks for pointing it out, and reminding me that there's 2 different places VB might want you to put parenthesis on array brackets, and four different contexts, and not all placements are all valid in all contexts.

    It's simple scenarios like that that make VB so easy to learn compared to C#, what with its one place for array brackets. Who has time to memorize one rule when you could be memorizing at least four?
    Last edited by Sitten Spynne; Jun 19th, 2017 at 04:42 PM.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

Page 1 of 2 12 LastLast

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width

Survey posted by VBForums.