Page 1 of 1

Rest API via C#

Posted: Thu Nov 15, 2018 9:59 am
by sbrown
Does anyone have a sample using c# to get an authorization token and login to Rest API?

Re: Rest API via C#

Posted: Sat Mar 16, 2019 3:27 am
by riyasathe
Not yet! I have a same question.

Re: Rest API via C#

Posted: Fri Mar 22, 2019 6:26 pm
by newclique
Here's how I do it in a console app. Be sure to add the necessary references and Newtonsoft nuget pkg:

Code: Select all

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Web;

using System.IO;
using Newtonsoft.Json;

namespace GrabBullhorn
{
    class Program
    {
        static string _ClientID = "your-id-here";
        static string _Secret = "your-secret-here";
        static string _Username = "whatever.restapi";
        static string _Password = "your web login pwd";
        static string _authCode = "";
        static string _stateCode = Guid.NewGuid().ToString(); //prob not necessary
        static BullhornTokens _tokens = null;

        static void Main(string[] args)
        {
            _authCode = getBHAuthCode();
            if (!string.IsNullOrEmpty(_authCode))
            {
                _tokens = getAccessToken();
                if (_tokens != null)
                {
                    //can make api calls for 10 minutes...

                    //...or make a refresh token call, get a refresh token (if your account is allowed one), and then make calls for hours
                    Console.WriteLine("Authentated successfully! You can now implement your API calls for entities, etc.");
                    Console.WriteLine("Press {Enter} to exit...");
                    Console.ReadLine();
                    Environment.Exit(0);
                }
            }
            Console.WriteLine("Unable to authenticate and generate an access token.\nPress {Enter} to exit...");
            Console.ReadLine();
            Environment.Exit(-1); //booo!
        }

        static string getBHAuthCode()
        {
            string url = $@"https://auth.bullhornstaffing.com/oauth/authorize?client_id={_ClientID}&response_type=code&action=Login&username={_Username}&password={_Password}&state={_stateCode}";
            
            var request = HttpWebRequest.CreateHttp(url);
            request.Method = "GET";
            var responseObj = (HttpWebResponse)request.GetResponse();
            string response = null;

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var vals = System.Web.HttpUtility.ParseQueryString(responseObj.ResponseUri.Query);
                    if (vals.HasKeys() && vals["code"] != null)
                    {
                        response = vals["code"];
                    }
                }
            }
            
            return response;
        }

        static BullhornTokens getAccessToken()
        {
            string url = $@"https://auth.bullhornstaffing.com/oauth/token?grant_type=authorization_code&code={_authCode}&client_id={_ClientID}&client_secret={_Secret}";
            var request = HttpWebRequest.CreateHttp(url);
            request.Method = "POST";
            request.Accept = "application/json";
            var responseObj = (HttpWebResponse)request.GetResponse();

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var content = streamReader.ReadToEnd();
                    var tokens = JsonConvert.DeserializeObject<BullhornTokens>(content);
                    return tokens;
                }
            }
            return null;
        }

    }

    public class BullhornTokens
    {
        public BullhornTokens()
        {
            this.TokensCreated = DateTime.Now;
        }
        public DateTime? TokensCreated { get; private set; }

        [JsonProperty(PropertyName="access_token")]
        public string AccessToken { get; set; }
        [JsonProperty(PropertyName ="expires_in")]
        public int ExpiresInSeconds { get; set; }
        [JsonProperty(PropertyName ="token_type")]
        public string TokenType { get; set; }
        [JsonProperty(PropertyName="refresh_token")]
        public string RefreshToken { get; set; }

        DateTime? _expiration;
        public DateTime? GetExipration()
        {
            if (_expiration.HasValue || !TokensCreated.HasValue)
                return _expiration;

            _expiration = TokensCreated.Value.AddSeconds(ExpiresInSeconds);
            return _expiration;
        }
    }
}

Re: Rest API via C#

Posted: Mon Apr 08, 2019 3:59 pm
by newclique
Oops, I left out two final steps. First, you have to get the REST Token (could this API be any more complex?) and the CorpID with yet another login call. Second, you have to use these new pieces of information to try to call the REST services themselves.

Code: Select all

_tokens = getAccessToken();
                if (_tokens != null)
                {
                    _RESTInfo = getBHRESTLoginInfo();
                    if (_RESTInfo != null)
                    {
                        //can make api calls for 10 minutes...
                        var url = new Uri(_RESTInfo.REST_URL);
                        string corptoken = url.Segments[url.Segments.Count()-1];
                        corptoken = corptoken.Remove(corptoken.Length - 1, 1);
                        string fieldlist = "id,title";
                        //can make api calls for 10 minutes...
                        var request = HttpWebRequest.CreateHttp($@"https://rest.bullhornstaffing.com/rest-services/{corptoken}/search/JobOrder?BhRestToken={_RESTInfo.REST_Token}&fields={fieldlist}");
                        
                        request.Method = "GET";
                        var responseObj = (HttpWebResponse)request.GetResponse();
                        string response = null;

                        using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
                        {
                            if (responseObj.StatusCode == HttpStatusCode.OK)
                            {
                                var vals = System.Web.HttpUtility.ParseQueryString(responseObj.ResponseUri.Query);
                                if (vals.HasKeys() && vals["code"] != null)
                                {
                                    response = vals["code"];
                                }
                            }
                        }
                        //...or make a refresh token call, get a refresh token (if your account is allowed one), and then make calls for hours
                        Console.WriteLine("Authentated successfully! You can now implement your API calls for entities, etc.");
                        
I had to implement one more class to hold the de-serialized JSON of this rest login call:

Code: Select all

    public class BullhornRESTLoginInfo
    {
        [JsonProperty(PropertyName = "BhRestToken")]
        public string REST_Token { get; set; }
        [JsonProperty(PropertyName ="restUrl")]
        public string REST_URL { get; set; }
    }
Be warned: The examples in the API guide do not seem to be accurate. Currently, following those guides, I am getting this back from a call asking for all of my current JobOrder entities (using the exact format and path listed in their API guide):

Code: Select all

{"error":"This URL is reserved for login purposes."}
So, I dunno if it is me or it is their documentation. I'll post back when I actually get some data. Here's the URL format I am using (with my tokens, obviously):

Code: Select all

https://rest.bullhornstaffing.com/rest-services/{corptoken}/search/JobOrder?BhRestToken={_RESTInfo.REST_Token}&fields={fieldlist}

Re: Rest API via C#

Posted: Mon Apr 08, 2019 4:18 pm
by newclique
Ugh. My last post had errors due to the API guide not being very clear about needing to use the returned REST_URL from the rest-login call to make all future calls. I'm sure this was obvious to some but the examples that show {corpToken} really threw me because I thought that meant I needed to use their exact example URLs and just tack on my token. IMHO, the API guide should {parameterize} everything in the URL that you need to sub out for yourself, e.g. {Returned REST_URL}/{your query here}

If this does't give someone enough information to finish the C# code, let me know and I will gladly post the finished product.

Re: Rest API via C#

Posted: Thu May 16, 2019 4:23 am
by redaw77
Hi, Thank you for posting this, its very helpful. I agree the BH documentation is a bit confusing, especially as it seem to constantly change.

I was just trying to use the code, but there is no method 'getBHRESTLoginInfo'. do you have this to share? Any help would be greatly appreciated.

Thanks

Re: Rest API via C#.net

Posted: Fri Sep 27, 2019 10:59 am
by newclique
Alrighty, scratch all that code above. This is how I am now doing it. There's one class with all the "meat" and then a driver (program.cs) to test it out as a console app. Just replace the class variables with your own keys and credentials.

------------------------

Code: Select all

namespace GrabBullhorn
{
    public class MPAuth
    {
        static string _ClientID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
        static string _Secret = "????????????????????????"; //24 characters - can't remember where this came from...maybe the REST signup?
        static string _Username = "username.restapi";  //log into the UI and register for REST to get this
        static string _Password = "************";  //REST password, I think
        static string _stateCode = Guid.NewGuid().ToString();
        static BullhornRESTLoginInfo _RESTInfo = null;

        /// <summary>
        /// Call this third. 
        /// </summary>
        /// <returns></returns>
        public static BullhornRESTLoginInfo getBHRESTLoginInfo(BullhornTokens tokens)
        {
            string url = $@"https://rest.bullhornstaffing.com/rest-services/login?version=2.0&access_token={tokens.AccessToken}";
            var request = HttpWebRequest.CreateHttp(url);
            request.Method = "POST";
            request.Accept = "application/json";
            var responseObj = (HttpWebResponse)request.GetResponse();

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var content = streamReader.ReadToEnd();
                    var restTokens = JsonConvert.DeserializeObject<BullhornRESTLoginInfo>(content);
                    return restTokens;
                }
            }
            return null;
        }

        /// <summary>
        /// Call this first. Send the results to getAccessToken().
        /// </summary>
        /// <returns></returns>
        public static string getBHAuthCode()
        {
            string url = $@"https://auth.bullhornstaffing.com/oauth/authorize?client_id={_ClientID}&response_type=code&action=Login&username={_Username}&password={_Password}&state={_stateCode}";

            var request = HttpWebRequest.CreateHttp(url);
            request.Method = "GET";
            var responseObj = (HttpWebResponse)request.GetResponse();
            string response = null;

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var vals = System.Web.HttpUtility.ParseQueryString(responseObj.ResponseUri.Query);
                    if (vals.HasKeys() && vals["code"] != null)
                    {
                        response = vals["code"];
                    }
                }
            }

            return response;
        }

        /// <summary>
        /// Call this second.
        /// </summary>
        /// <param name="authCode">Obtain this from a successful call to getBHAuthCode().</param>
        /// <returns></returns>
        public static BullhornTokens getAccessToken(string authCode)
        {
            string url = $@"https://auth.bullhornstaffing.com/oauth/token?grant_type=authorization_code&code={authCode}&client_id={_ClientID}&client_secret={_Secret}";
            var request = HttpWebRequest.CreateHttp(url);
            request.Method = "POST";
            request.Accept = "application/json";
            var responseObj = (HttpWebResponse)request.GetResponse();

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var content = streamReader.ReadToEnd();
                    var tokens = JsonConvert.DeserializeObject<BullhornTokens>(content);
                    return tokens;
                }
            }
            return null;
        }
    }

    public class BHServiceData
    {
        public delegate void MessageDelegate(string message);

        public static event MessageDelegate OnServiceMessage;

        public static List<BHJobOrder> GetOpenJobOrders(BullhornRESTLoginInfo RESTInfo)
        {
            //can make api calls for 10 minutes...
            string query = "isOpen:1+AND+isPublic:1+AND+isDeleted:0";
            string fieldlist = "address,costCenter,dateAdded,dateClosed,dateEnd,employmentType,branchCode,id,isInterviewRequired,isOpen,isPublic,payRate,publicDescription,salary,status,title,type";
            var request = HttpWebRequest.CreateHttp($@"{RESTInfo.REST_URL}search/JobOrder?fields={fieldlist}&query={query}&BhRestToken={RESTInfo.REST_Token}");

            //broadcast what we're calling
            if (OnServiceMessage != null)
                OnServiceMessage("CALLING: " + request.RequestUri.ToString());

            request.Method = "GET";
            var responseObj = (HttpWebResponse)request.GetResponse();
            List<BHJobOrder> response = null;

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var content = streamReader.ReadToEnd();
                    var data = JsonConvert.DeserializeObject<BHResults>(content);
                    response = JsonConvert.DeserializeObject<List<BHJobOrder>>(data.data.ToString());
                }
            }
            return response;
        }
    }

    /// <summary>
    /// This class' values allow you to make REST calls to the REST_URL.
    /// </summary>
    public class BullhornRESTLoginInfo
    {
        /// <summary>
        /// Include this in the query path of all REST calls as .../path?BhRestToken=xxxxx
        /// </summary>
        [JsonProperty(PropertyName = "BhRestToken")]
        public string REST_Token { get; set; }
        /// <summary>
        /// Make all REST calls to the URL contained in this property.
        /// </summary>
        [JsonProperty(PropertyName = "restUrl")]
        public string REST_URL { get; set; }
    }

    public class BullhornTokens
    {
        public BullhornTokens()
        {
            this.TokensCreated = DateTime.Now;
        }
        public DateTime? TokensCreated { get; private set; }

        [JsonProperty(PropertyName = "access_token")]
        public string AccessToken { get; set; }
        [JsonProperty(PropertyName = "expires_in")]
        public int ExpiresInSeconds { get; set; }
        [JsonProperty(PropertyName = "token_type")]
        public string TokenType { get; set; }
        [JsonProperty(PropertyName = "refresh_token")]
        public string RefreshToken { get; set; }

        DateTime? _expiration;
        public DateTime? GetExipration()
        {
            if (_expiration.HasValue || !TokensCreated.HasValue)
                return _expiration;

            _expiration = TokensCreated.Value.AddSeconds(ExpiresInSeconds);
            return _expiration;
        }
    }
    public class BHResults
    {
        //{"total":1,"start":0,"count":1,"data":[{...}]
        public int count { get; set; }
        public dynamic data { get; set; }
        public int total { get; set; }
        public int start { get; set; }
    }
    public class BHAddress
    {
        public string address1 { get; set; }
        public string city { get; set; }
        public string state { get; set; }
        public string zip { get; set; }
        public int countryID { get; set; }
    }
    public class BHJobOrder
    {
        public BHAddress address { get; set; }
        public string costCenter { get; set; }
        [JsonConverter(typeof(JSTimeConverter))]
        public DateTime? dateAdded { get; set; }
        [JsonConverter(typeof(JSTimeConverter))]
        public DateTime? dateClosed { get; set; }
        [JsonConverter(typeof(JSTimeConverter))]
        public DateTime? dateEnd { get; set; }
        public string employmentType { get; set; }
        public string branchCode { get; set; }
        public int id { get; set; }
        public bool isInterviewRequired { get; set; }
        public bool isOpen { get; set; }
        public bool isPublic { get; set; }
        public float payRate { get; set; }
        public string publicDescription { get; set; }
        public float salary { get; set; }
        public string status { get; set; }
        public string title { get; set; }
        public int type { get; set; }
    }

    public class JSTimeConverter : DateTimeConverterBase
    {
        private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteRawValue(((DateTime)value - _epoch).TotalMilliseconds.ToString()); // + "000"); not sure about the necessity of the zeroes
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.Value == null) { return null; }
            var ticks = (Convert.ToInt64(reader.Value.ToString()) * 10000) + 621355968000000000;
            return new DateTime(ticks);
        }
    }
}
And here's the driver:
----------------------------------

Code: Select all

using System;
using System.Collections.Generic;

namespace GrabBullhorn
{
    class Program
    {
        static void Main(string[] args)
        {
            string _authCode = MPAuth.getBHAuthCode();
            if (!string.IsNullOrEmpty(_authCode))
            {
                BullhornTokens _tokens = MPAuth.getAccessToken(_authCode);
                if (_tokens != null)
                {
                    BullhornRESTLoginInfo _RESTInfo = MPAuth.getBHRESTLoginInfo(_tokens);
                    if (_RESTInfo != null)
                    {
                        BHServiceData.OnServiceMessage += new BHServiceData.MessageDelegate(msg => Console.WriteLine(msg));
                        try
                        {
                            List<BHJobOrder> jobs = BHServiceData.GetOpenJobOrders(_RESTInfo);

                            //...or make a refresh token call, get a refresh token (if your account is allowed one), and then make calls for hours
                            Console.WriteLine("Authentated successfully! You can now implement your API calls for entities, etc.");
                        }catch (Exception ex)
                        {
                            Console.WriteLine("ERROR: " + ex.Message);
                        }
                        Console.WriteLine("Press {Enter} to exit...");
                        Console.ReadLine();
                        Environment.Exit(0);
                    }
                }
            }
            Console.WriteLine("Unable to authenticate and generate an access token.\nPress {Enter} to exit...");
            Console.ReadLine();
            Environment.Exit(-1); //booo!
        }
    }
}

Re: Rest API via C#

Posted: Thu Jan 23, 2020 12:10 am
by GeekInterface
I am getting a 302 redirect error when I try to get the BHAuthCode.

Error Message: System.Net.WebException: 'The remote server returned an error: (302) Found.'
Error Location: var responseObj = (HttpWebResponse)request.GetResponse();

Code: Select all

     public static string getBHAuthCode()
        {
            string url = $@"https://auth.bullhornstaffing.com/oauth/authorize?client_id={_ClientID}&response_type=code&action=Login&username={_Username}&password={_Password}&state={_stateCode}";
             var request = HttpWebRequest.CreateHttp(url);
            request.AllowAutoRedirect = true;
            request.Method = "GET";
            var responseObj = (HttpWebResponse)request.GetResponse();
            string response = null;

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var vals = System.Web.HttpUtility.ParseQueryString(responseObj.ResponseUri.Query);
                    if (vals.HasKeys() && vals["code"] != null)
                    {
                        response = vals["code"];
                    }
                }
            }

            return response;
        }

Re: Rest API via C#

Posted: Mon Feb 10, 2020 11:06 pm
by newclique
Just looking at it briefly I'd estimate that you are hitting an endpoint that is causing a redirect but you are not auto-following redirects. Not sure what version of .net you're using but if it's .net core then it possibly has different behavior than the .net standard 4.7 or so that I was using. You may try compiling a console app using 4.7.2 or something and see if you get different results.

Secondly, be sure you are hitting the correct endpoint to the the auth code. It looks like you are but check the documentation to see if they've maybe changed it and that's why you're getting a redirect (if that's what's happening).

Lastly, I think you have to go into your account and make sure you are authorized to make API calls. There a whole process to go through to get your keys, etc. You'll want to verify that stuff is correct.

These are just some things to check off the top of my head.

Re: Rest API via C#

Posted: Mon Mar 16, 2020 9:18 am
by bosolutions
I am a VB.NET developer and translated the C# code (quiet similar as VB) for the function to get an authentication code like this:

Public Function GetBHAuthCode() As String
Dim ReturnValue As String = ""
CurrState = Guid.NewGuid.ToString
Dim strAuth As String = ""
strAuth = AuthURL & "/oauth/authorize?client_id=" & CurrClientID & "&response_type=code&action=Login" _
& "&username=" & CurrUsername & "&password=" & CurrPassword & "&state=" & CurrState

Dim CurrRequest = HttpWebRequest.Create(strAuth)
CurrRequest.Method = WebRequestMethods.Http.Get
Dim CurrResponse As HttpWebResponse

Try
CurrResponse = CurrRequest.GetResponse()
If CurrResponse.StatusCode = HttpStatusCode.OK Then
Dim CurrValues = System.Web.HttpUtility.ParseQueryString(CurrResponse.ResponseUri.Query)
If CurrValues.HasKeys AndAlso Not CurrValues("code") = Nothing Then
ReturnValue = CurrValues("code")
End If
End If
Catch ex As Exception
End Try

Return ReturnValue
End Function

My problem: the CurrValues has indeed keys, i.e. the keys that have been passed by the request:
- client_id
- respons_type
- action
- username
- password
- state
but the request returns no additional CurrValue with a key "code"

Re: Rest API via C#

Posted: Tue Mar 24, 2020 11:45 pm
by newclique
Are you looking at the Request or the Response after you send the request? Remember, the value you want should be in the Response.

If that doesn't work, does it work for you if you use the C# version of the code?

PS VB is like smoking. It will eventually kill you :) lol Just Kidding!

Re: Rest API via C#

Posted: Wed Mar 25, 2020 4:57 am
by bosolutions
Thks a lot for the reply but in the mean time I figured out that the response in VB was not representative for the cause of the problem.
I have launched the same request in Postman and there I received the correct 'error' answer, i.e. 'invalid client_ID'.
I had encapsulated the variables in curly brackets what is not correct of course.
Thks again.
PS VB indeed kills slowly but I have lots of time :D , especially now with covid 19 :(

Re: Rest API via C#

Posted: Wed Mar 25, 2020 11:21 am
by newclique
Excellent. Glad you got it sorted. Postman was essential for me, too.

Good thing about COVID is the recovery rate is 99%! Hang in there :)