HttpClient.SendAsync is throwing "A task was canceled" issue - c#

We are making a PUT call to the service layer to update an entity information
HttpClient.SendAsync method is throwing "A task was canceled" issue even though the API call is successfully made to the backend service layer. I could see the logs from service layer.
Initially I thought it could be related to timeout, so I increased the timeout from 10 seconds to 30 seconds, still the program is waiting for 30 seconds and gets timed out with the error "A task was canceled". But the api call was successfully completed in Service Layer within even 5 seconds
Please find below my code
protected override void Execute(CodeActivityContext context)
{
string uuid = UUID.Get(context);
Console.WriteLine(uuid + ": Http Api Call - STARTED");
string endPoint = EndPoint.Get(context);
string contentType = ContentType.Get(context);
string acceptFormat = AcceptFormat.Get(context);
HttpMethod httpMethod = HttpApiMethod.Get(context);
string clientCertificatePath = ClientCertificatePath.Get(context);
string clientCertificatePassword = ClientCertificatePassword.Get(context);
double httpTimeOut = HttpTimeout.Get(context);
Dictionary<string, string> requestHeaders = RequestHeaders.Get(context);
Dictionary<string, string> pathParams = PathParams.Get(context);
Dictionary<string, string> queryParams = QueryParams.Get(context);
string requestBody = RequestBody.Get(context);
WebRequestHandler webRequestHandler = new WebRequestHandler();
webRequestHandler.MaxConnectionsPerServer = 1;
if (clientCertificatePath != null && clientCertificatePath.Trim().Length > 0)
{
X509Certificate2 x509Certificate2 = null;
if (clientCertificatePassword != null && clientCertificatePassword.Trim().Length > 0)
{
x509Certificate2 = new X509Certificate2(clientCertificatePath.Trim(),
clientCertificatePassword.Trim());
}
else
{
x509Certificate2 = new X509Certificate2(clientCertificatePath.Trim());
}
webRequestHandler.ClientCertificates.Add(x509Certificate2);
}
HttpClient httpClient = new HttpClient(webRequestHandler)
{
Timeout = TimeSpan.FromMilliseconds(httpTimeOut)
};
if (acceptFormat != null)
{
httpClient.DefaultRequestHeaders.Add("Accept", acceptFormat);
}
HttpResponseMessage httpResponseMessage = InvokeApiSync(httpClient, endPoint,
httpMethod, contentType,
requestHeaders, pathParams,
queryParams, requestBody,
uuid
);
HttpResponseMessageObject.Set(context, httpResponseMessage);
ResponseBody.Set(context, httpResponseMessage.Content.ReadAsStringAsync().Result);
StatusCode.Set(context, (int)httpResponseMessage.StatusCode);
Console.WriteLine(uuid + ": Http Api Call - ENDED");
}
private HttpResponseMessage InvokeApiSync(HttpClient httpClient,
string endPoint,
HttpMethod httpMethod,
string contentType,
Dictionary<string, string> requestHeaders,
Dictionary<string, string> pathParams,
Dictionary<string, string> queryParams,
string requestBody,
string uuid)
{
if (pathParams != null)
{
ICollection<string> keys = pathParams.Keys;
if (keys.Count > 0)
{
foreach (string key in keys)
{
endPoint = endPoint.Replace(":" + key, pathParams[key]);
}
}
}
if (queryParams != null)
{
List<string> keys = new List<string>(queryParams.Keys);
if (keys.Count > 0)
{
endPoint = string.Concat(endPoint, "?", keys[0], "=", queryParams[keys[0]]);
for (int index = 1; index < keys.Count; index++)
{
endPoint = string.Concat(endPoint, "&", keys[index], "=", queryParams[keys[index]]);
}
}
}
try
{
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(httpMethod, endPoint);
if (requestHeaders != null)
{
foreach (string key in requestHeaders.Keys)
{
httpRequestMessage.Headers.Add(key, requestHeaders[key]);
}
}
if (httpMethod.Equals(HttpMethod.Put) || httpMethod.Equals(HttpMethod.Post))
{
StringContent reqContent = null;
if (requestBody != null)
{
reqContent = new StringContent(requestBody);
}
else
{
reqContent = new StringContent("");
}
reqContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
httpRequestMessage.Content = reqContent;
}
HttpResponseMessage httpResponseMessage = httpClient.SendAsync(httpRequestMessage).Result;
return httpResponseMessage;
}
catch (Exception exception)
{
Console.WriteLine(uuid + " : HttpApi Error Has Occurred");
Console.WriteLine(uuid + " : " + exception.Message);
Console.WriteLine(uuid + " : " + exception.StackTrace);
throw exception;
}
}
Any help would be much appreciated. Thanks.!

Related

Connection reset issue in .net core api call (net::ERR_CONNECTION_RESET 200 (OK))

I have a .net core API service which is called from a angular client project.
When a user request a status of his payment, we will make call to this service api and this service will then call a payment gateway service to fetch the status of payment and the output result will return to the user.
When i try to integrate this i am facing this below error.
net::ERR_CONNECTION_RESET 200 (OK)
core.js:5967 ERROR Unknown Error
This above issue is not showing when i try to hit the service after putting one breakpoint. Its also returning the result.
This is how entire flow works
Client side call performs by user
this.dataservice.postFeed(method, JSON.stringify(this.initsearch)).subscribe(result => {
var response = result.body["data"];
console.log(response);
});
Server side code looks like
[HttpPost]
public async Task<IActionResult> Post([FromBody] ObjectModel searchValue)
{
ApiResponse<string> response = new ApiResponse<string>();
IBaseResult<string> result = await _adlerBo.GetPaymentStatus(searchValue);
response.Success = result.success;
response.Data = result.Data;
return Ok(response);
}
In BusinessObject.cs
public async Task<IBaseResult<string>> GetPaymentStatus(PaymentSearchModel requestModel){
string apiResponse = await PaymentStatusCheckUsingAPI(requestModel.orderid);
return apiResponse ;
}
private async Task<string> PaymentStatusCheckUsingAPI(string orderNumber)
{
string message = await PostPaymentRequestToGateway(statusApiUrl, authQueryUrlParam);
NameValueCollection param = await GetResponseMap(message);
string status = "";
string encResJson = "";
if (param != null && param.Count == 2)
{
for (int i = 0; i < param.Count; i++)
{
if ("status".Equals(param.Keys[i]))
{
status = param[i];
}
if ("enc_response".Equals(param.Keys[i]))
{
encResJson = param[i];
}
}
if (!"".Equals(status) && status.Equals("0"))
{
resJson = crypto.Decrypt(encResJson, workingKey);
}
else if (!"".Equals(status) && status.Equals("1"))
{
Console.WriteLine("failure response: " + encResJson);
}
}
return resJson;
}
private async Task<string> PostPaymentRequestToGateway(string queryUrl, string urlParam)
{
string message = "";
try
{
StreamWriter myWriter = null;// it will open a http connection with provided url
WebRequest objRequest = WebRequest.Create(queryUrl);//send data using objxmlhttp object
objRequest.Method = "POST";
//objRequest.ContentLength = TranRequest.Length;
objRequest.ContentType = "application/x-www-form-urlencoded";//to set content type
myWriter = new System.IO.StreamWriter(objRequest.GetRequestStream());
myWriter.Write(urlParam);//send data
myWriter.Close();//closed the myWriter object
// Getting Response
System.Net.HttpWebResponse objResponse = (System.Net.HttpWebResponse)objRequest.GetResponse();//receive the responce from objxmlhttp object
using (System.IO.StreamReader sr = new System.IO.StreamReader(objResponse.GetResponseStream()))
{
message = await sr.ReadToEndAsync();
//Response.Write(message);
}
}
catch (Exception exception)
{
Console.Write("Exception occured while connection." + exception);
}
return message;
}
private async Task<NameValueCollection> GetResponseMap(string message)
{
//await Task.Delay(2000); I did this with no Luck
NameValueCollection Params = new NameValueCollection();
if (message != null || !"".Equals(message))
{
string[] segments = message.Split('&');
foreach (string seg in segments)
{
string[] parts = seg.Split('=');
if (parts.Length > 0)
{
string Key = parts[0].Trim();
string Value = parts[1].Trim();
Params.Add(Key, Value);
}
}
}
return await Task.FromResult(Params);
}
Any idea how to fix this? Why its working when i put breakpoint and not otherwise.
Am i doing correct asynchronous implimentsion in my api?

client.ExecuteAsync<T> Delegate does not take 1 arguments

I'm trying to change my Restsharp Client to work async instead of sync.
Each of my API-Calls referes to the GetAsync<T> method. When I try now to change the Client to call ExecuteAsync<T> instead of Execute i got this error:
Delegate 'Action, RestRequestAsyncHandle>' does not take 1 Arguments
I'm using RestSharp Version 106.6.10 currently.
Here is my GetAsyncMethod:
public async Task<T> GetAsync<T>(string url, Dictionary<string, object> keyValuePairs = null)
{
try
{
// Check token is expired
DateTime expires = DateTime.Parse(Account.Properties[".expires"]);
if (expires < DateTime.Now)
{
// Get new Token
await GetRefreshTokenAsync();
}
// Get AccessToken
string token = Account.Properties["access_token"];
if (string.IsNullOrEmpty(token))
throw new NullReferenceException("AccessToken is null or empty!");
// Create client
var client = new RestClient()
{
Timeout = 3000000
};
//Create Request
var request = new RestRequest(url, Method.GET);
request.RequestFormat = DataFormat.Json;
request.AddHeader("Authorization", "Bearer " + token);
// Add Parameter when necessary
if (keyValuePairs != null)
{
foreach (var pair in keyValuePairs)
{
request.AddParameter(pair.Key, pair.Value);
}
}
// Call
var result = default(T);
var asyncHandle = client.ExecuteAsync<T>(request, restResponse =>
{
// check respone
if (restResponse.ResponseStatus == ResponseStatus.Completed)
{
result = restResponse.Data;
}
//else
// throw new Exception("Call stopped with Status: " + response.StatusCode +
// " Description: " + response.StatusDescription);
});
return result;
}
catch (Exception ex)
{
Crashes.TrackError(ex);
return default(T);
}
}
Here one of the calling Methods:
public async Task<List<UcAudit>> GetAuditByHierarchyID(int hierarchyID)
{
string url = AuthSettings.ApiUrl + "/ApiMethod/" + hierarchyID;
List<UcAudit> auditList = await GetAsync<List<UcAudit>>(url);
return auditList;
}
When I Change the T in ExecuteAsync<T> in one of my classes the error is gone. How can I change the method to work async with <T>???
With the info from Lasse Vågsæther Karlsen I found the solution.
This is the start:
var asyncHandle = client.ExecuteAsync<T>(request, restResponse =>
{
// check respone
if (restResponse.ResponseStatus == ResponseStatus.Completed)
{
result = restResponse.Data;
}
//else
// throw new Exception("Call stopped with Status: " + response.StatusCode +
// " Description: " + response.StatusDescription);
});
Worked for me :
client.ExecuteAsync<T>(request, (response, asyncHandle )=>
{
//check respone
if (response.StatusCode == HttpStatusCode.OK)
{
result = response.Data;
}
else
throw new Exception("Call stopped with Status: " + response.StatusCode +
" Description: " + response.StatusDescription);
});
Thank you!

Unity C# GraphQL Post Request Body Authentication

I am working on trying to connect to my GraphQL server my developers have setup from within Unity. I have found some scripts to help with this process, however, I am still not able to connect because i need to be logged into the system hosting graphql to be able to query externally, I cannot just use the endpoint url.
My developer has told me to do a POST request to mysystem/login and in the request body add {email: string, password: string}. I have tried a few different things and nothing is working. I have to log into the mysystem/login with email and password, then i will be able to connect to mygraphql endpoint from the code below.--i was assuming this portion would go where I have the //smt authentication notes. Any help on how to setup the post request and where it should be or how it should work would be much appreciated.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using SimpleJSON;
using UnityEngine;
using UnityEngine.Networking;
namespace graphQLClient
{
public class GraphQuery : MonoBehaviour
{
public static GraphQuery instance = null;
[Tooltip("The url of the node endpoint of the graphQL server being queried")]
public static string url;
public delegate void QueryComplete();
public static event QueryComplete onQueryComplete;
public enum Status { Neutral, Loading, Complete, Error };
public static Status queryStatus;
public static string queryReturn;
string authURL;
public static string LoginToken;
public class Query
{
public string query;
}
public void Awake()
{
if (instance == null)
{
instance = this;
}
else if (instance != this)
{
Destroy(gameObject);
}
DontDestroyOnLoad(gameObject);
StartCoroutine("PostLogin");
}
private void Start()
{
LoginToken = "";
}
public static Dictionary<string, string> variable = new Dictionary<string, string>();
public static Dictionary<string, string[]> array = new Dictionary<string, string[]>();
// Use this for initialization
// SMT Authentication to ELP
// SMT Needed for Authentication--- if (token != null) request.SetRequestHeader("Authorization", "Bearer " + token);
//In the request body: {email: string, password: string}
//You’ll either get a 401 with an empty response or 201 with {token: string, user: object}
IEnumerator PostLogin()
{
Debug.Log("Logging in...");
string authURL = "https://myapp.com/login";
string loginBody = "{\"email\":\"myemail.com\",\"password\":\"mypassowrd\"}";
var request = new UnityWebRequest(authURL, "POST");
byte[] bodyRaw = new System.Text.UTF8Encoding().GetBytes(loginBody);
request.uploadHandler = (UploadHandler)new UploadHandlerRaw(bodyRaw);
request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.Log("Login error!");
foreach (KeyValuePair<string, string> entry in request.GetResponseHeaders())
{
Debug.Log(entry.Value + "=" + entry.Key);
}
Debug.Log(request.error);
}
else
{
Debug.Log("Login complete!");
//Debug.Log(request.downloadHandler.text);
LoginToken = request.downloadHandler.text;
Debug.Log(LoginToken);
}
}
public static WWW POST(string details)
{
//var request = new UnityWebRequest(url, "POST");
details = QuerySorter(details);
Query query = new Query();
string jsonData = "";
WWWForm form = new WWWForm();
query = new Query { query = details };
jsonData = JsonUtility.ToJson(query);
byte[] postData = Encoding.ASCII.GetBytes(jsonData);
Dictionary<string, string> postHeader = form.headers;
//postHeader["Authorization"] = "Bearer " + downloadHandler.Token;
if (postHeader.ContainsKey("Content-Type"))
//postHeader["Content-Type"] = "application/json";
postHeader.Add("Authorization", "Bearer " + LoginToken);
else
//postHeader.Add("Content-Type", "application/json");
postHeader.Add("Authorization", "Bearer " + LoginToken);
WWW www = new WWW(url, postData, postHeader);
instance.StartCoroutine(WaitForRequest(www));
queryStatus = Status.Loading;
return www;
}
static IEnumerator WaitForRequest(WWW data)
{
yield return data; // Wait until the download is done
if (data.error != null)
{
Debug.Log("There was an error sending request: " + data.error);
queryStatus = Status.Error;
}
else
{
queryReturn = data.text;
queryStatus = Status.Complete;
}
onQueryComplete();
}
public static string QuerySorter(string query)
{
string finalString;
string[] splitString;
string[] separators = { "$", "^" };
splitString = query.Split(separators, StringSplitOptions.RemoveEmptyEntries);
finalString = splitString[0];
for (int i = 1; i < splitString.Length; i++)
{
if (i % 2 == 0)
{
finalString += splitString[i];
}
else
{
if (!splitString[i].Contains("[]"))
{
finalString += variable[splitString[i]];
}
else
{
finalString += ArraySorter(splitString[i]);
}
}
}
return finalString;
}
public static string ArraySorter(string theArray)
{
string[] anArray;
string solution;
anArray = array[theArray];
solution = "[";
foreach (string a in anArray)
{
}
for (int i = 0; i < anArray.Length; i++)
{
solution += anArray[i].Trim(new Char[] { '"' });
if (i < anArray.Length - 1)
solution += ",";
}
solution += "]";
Debug.Log("This is solution " + solution);
return solution;
}
}
}

Amazon MWS | RestSharp | The request signature we calculated does not match the signature you provided

I have written the following method to fetch all orders from Amazon. I'm using the RestSharp library to communicate with the API. Everytime I execute the request I get the following error:
<?xml version="1.0"?>
<ErrorResponse xmlns="https://mws.amazonservices.com/Orders/2013-09-01">
<Error>
<Type>Sender</Type>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.</Message>
</Error>
<RequestID>9f03f5b0-e4e4-4766-a554-00ff970b6b8c</RequestID>
</ErrorResponse>
That's very strange, because on the Amazon Scratchpad (https://mws.amazonservices.de/scratchpad/index.html) it is working and I also get the same signature (when I use the timestamp calculated on the Scratchpad) in my c# app.
C# Class
public async void FetchOrders()
{
RestClient client = new RestClient("https://mws.amazonservices.de");
client.DefaultParameters.Clear();
client.ClearHandlers();
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("AWSAccessKeyId", "xxxxxxxxxx");
parameters.Add("Action", "ListOrders");
parameters.Add("CreatedAfter", "2018-01-01T11:34:00Z");
parameters.Add("MarketplaceId.Id.1", "A1PA6795UKMFR9");
parameters.Add("SellerId", "xxxxxxxxx");
parameters.Add("SignatureVersion", "2");
parameters.Add("Timestamp", DateTime.UtcNow.ToString("s") + "Z");
parameters.Add("Version", "2013-09-01");
RestRequest request = new RestRequest("Orders/2013-09-01/", Method.POST);
string signature = AmzLibrary.SignParameters(parameters, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
request.AddParameter("Signature", signature);
foreach (KeyValuePair<string, string> keyValuePair in parameters)
{
request.AddParameter(keyValuePair.Key, keyValuePair.Value);
}
IRestResponse result = await client.ExecuteTaskAsync(request);
}
public static class AmzLibrary
{
public static string GetParametersAsString(IDictionary<String, String> parameters)
{
StringBuilder data = new StringBuilder();
foreach (String key in (IEnumerable<String>)parameters.Keys)
{
String value = parameters[key];
if (value != null)
{
data.Append(key);
data.Append('=');
data.Append(UrlEncode(value, false));
data.Append('&');
}
}
String result = data.ToString();
return result.Remove(result.Length - 1);
}
public static String SignParameters(IDictionary<String, String> parameters, String key)
{
String signatureVersion = parameters["SignatureVersion"];
KeyedHashAlgorithm algorithm = new HMACSHA1();
String stringToSign = null;
if ("2".Equals(signatureVersion))
{
String signatureMethod = "HmacSHA256";
algorithm = KeyedHashAlgorithm.Create(signatureMethod.ToUpper());
parameters.Add("SignatureMethod", signatureMethod);
stringToSign = CalculateStringToSignV2(parameters);
}
else
{
throw new Exception("Invalid Signature Version specified");
}
return Sign(stringToSign, key, algorithm);
}
private static String CalculateStringToSignV2(IDictionary<String, String> parameters)
{
StringBuilder data = new StringBuilder();
IDictionary<String, String> sorted =
new SortedDictionary<String, String>(parameters, StringComparer.Ordinal);
data.Append("POST");
data.Append("\n");
Uri endpoint = new Uri("https://mws.amazonservices.de/Orders/2013-09-01");
data.Append(endpoint.Host);
if (endpoint.Port != 443 && endpoint.Port != 80)
{
data.Append(":")
.Append(endpoint.Port);
}
data.Append("\n");
String uri = endpoint.AbsolutePath;
if (uri == null || uri.Length == 0)
{
uri = "/";
}
data.Append(UrlEncode(uri, true));
data.Append("\n");
foreach (KeyValuePair<String, String> pair in sorted)
{
if (pair.Value != null)
{
data.Append(UrlEncode(pair.Key, false));
data.Append("=");
data.Append(UrlEncode(pair.Value, false));
data.Append("&");
}
}
String result = data.ToString();
return result.Remove(result.Length - 1);
}
private static String UrlEncode(String data, bool path)
{
StringBuilder encoded = new StringBuilder();
String unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~" + (path ? "/" : "");
foreach (char symbol in System.Text.Encoding.UTF8.GetBytes(data))
{
if (unreservedChars.IndexOf(symbol) != -1)
{
encoded.Append(symbol);
}
else
{
encoded.Append("%" + String.Format("{0:X2}", (int)symbol));
}
}
return encoded.ToString();
}
private static String Sign(String data, String key, KeyedHashAlgorithm algorithm)
{
Encoding encoding = new UTF8Encoding();
algorithm.Key = encoding.GetBytes(key);
return Convert.ToBase64String(algorithm.ComputeHash(
encoding.GetBytes(data.ToCharArray())));
}
}
The problem was the "/" at the end of the Request. Remove it and everything works.
wrong
RestRequest request = new RestRequest("Orders/2013-09-01/", Method.POST);
right
RestRequest request = new RestRequest("Orders/2013-09-01", Method.POST);

how to send a http basic auth post?

I have been given the task to create a http post using basic auth.
I am developing in C# in an asp.net MVC application.
I have also been given this example.
{
POST /v2/token_endpoint HTTP/1.1
Authorization: Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=
Accept: application/json
Content-Type: application/x-www-form-urlencoded
User-Agent: Java/1.6.0_33
Host: api.freeagent.com
Connection: close
Content-Length: 127
grant_type=authorization_code&code=12P3AsFZXwXjd7SLOE1dsaX8oCgix&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth
}
My question is how do i code this in C#?
If more information is need just ask, thanks in advance
edit: I have made some progress but I have not added the grant_type
public void AccessToken(string code)
{
string url = #"https://api.freeagent.com/v2/token_endpoint";
WebClient client = new WebClient();
string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(ApiKey + ":" + ApiSecret));
client.Headers[HttpRequestHeader.Authorization] = "Basic " + credentials;
client.Headers[HttpRequestHeader.Accept] = "application/json";
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
client.Headers[HttpRequestHeader.UserAgent] = "Java/1.6.0_33";
client.Headers[HttpRequestHeader.Host] = "api.freeagent.com";
client.Headers[HttpRequestHeader.Connection] = "close";
client.Headers["grant_type"] = "authorization_code";
var result = client.DownloadString(url);
}
So how do i add: grant_type=authorization_code&code=12P3AsFZXwXjd7SLOE1dsaX8oCgix&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth to the post?
You can find here two samples how to make basic auth request with WebRequest and WebClient classes:
http://grahamrhay.wordpress.com/2011/08/22/making-a-post-request-in-c-with-basic-authentication/
http://anishshenoy57.wordpress.com/2013/01/22/basic-http-authentication-using-c/
Basically basic auth it's just Base64(username:password), so it's easy to implement it.
UPDATE1
Here is a sample based on your method:
public void AccessToken(string code)
{
string url = #"https://api.freeagent.com/v2/token_endpoint";
WebClient client = new WebClient();
string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(ApiKey + ":" + ApiSecret));
client.Headers[HttpRequestHeader.Authorization] = "Basic " + credentials;
client.Headers[HttpRequestHeader.Accept] = "application/json";
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
client.Headers[HttpRequestHeader.UserAgent] = "Java/1.6.0_33";
client.Headers[HttpRequestHeader.Host] = "api.freeagent.com";
client.Headers[HttpRequestHeader.Connection] = "close";
client.Headers["grant_type"] = "authorization_code";
string data = string.Format(
"grant_type=authorization_code&code={0}&redirect_uri=http%3A%2F%2Flocalhost%3A8080",
code);
var result = client.UploadString(url, data);
}
The only different in calling method in WebClient. DownloadString will do GET request, but for POST you need to use UploadString method
I too have struggled with this, but I have now made it to the other side!
The one that held me up the most was that if you specify a redirect URL in the Auth Token request, you must also specify it in the Access Token request with the same URL, even though it's not used.
I started off with someones partial wrapper, and have evolved it almost beyond recognition. Hopefully this wrapper (complete with auth and access token code) will help others. I've only used it to create invoices and invoice items so far, and the other functions are untested, but it should get you your tokens and to the point where you can debug the requests.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Web.Script.Serialization;
namespace ClientZone.External
{
public class FreeAgent
{
public enum Methods
{
POST = 1,
PUT
}
public enum Status
{
Draft = 1,
Sent,
Cancelled
}
private string _subDomain;
private string _identifier;
private string _secret;
private string _redirectURI;
public static string AuthToken = "";
private static string _accessToken = "";
private static string _refreshToken = "";
private static DateTime _refreshTime;
public FreeAgent(string identifier, string secret, string subdomain, string redirectURI)
{
_subDomain = subdomain;
_identifier = identifier;
_secret = secret;
_redirectURI = redirectURI; // If this was specified in the Auth Token call, you must specify it here, and it must be the same
}
public bool GetAccessToken()
{
try
{
string url = #"https://api.freeagent.com/v2/token_endpoint";
WebClient client = new WebClient();
string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(_identifier + ":" + _secret));
client.Headers[HttpRequestHeader.Host] = "api.freeagent.com";
client.Headers[HttpRequestHeader.KeepAlive] = "true";
client.Headers[HttpRequestHeader.Accept] = "application/json";
client.Headers[HttpRequestHeader.UserAgent] = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36";
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded;charset=UTF-8";
client.Headers[HttpRequestHeader.Authorization] = "Basic " + credentials;
System.Net.ServicePointManager.Expect100Continue = false;
client.Headers[HttpRequestHeader.AcceptEncoding] = "gzip, deflate";
client.Headers[HttpRequestHeader.AcceptLanguage] = "en-US,en;q=0.8";
var result = "";
if (!(_accessToken == "" && _refreshToken != ""))
{
string data = string.Format("grant_type=authorization_code&code={0}&redirect_uri={1}", AuthToken, _redirectURI);
result = client.UploadString(url, data);
}
else
{
// Marking the access token as blank and the refresh token as not blank
// is a sign we need to get a refresh token
string data = string.Format("grant_type=refresh_token&refresh_token={0}", _refreshToken);
result = client.UploadString(url, data);
}
JavaScriptSerializer json_serializer = new JavaScriptSerializer();
var results_list = (IDictionary<string, object>)json_serializer.DeserializeObject(result);
_accessToken = results_list["access_token"].ToString();
int secondsUntilRefresh = Int32.Parse(results_list["expires_in"].ToString());
_refreshTime = DateTime.Now.AddSeconds(secondsUntilRefresh);
if (results_list.Any(x => x.Key == "refresh_token"))
{
_refreshToken = results_list["refresh_token"].ToString();
}
if (_accessToken == "" || _refreshToken == "" || _refreshTime == new DateTime())
{
return false;
}
}
catch
{
return false;
}
return true;
}
private HttpStatusCode SendWebRequest(Methods method, string URN, string request, out string ResponseData)
{
if (_accessToken == "")
{
// The access token has not been retrieved yet
GetAccessToken();
}
else
{
if (_refreshTime != new DateTime() && _refreshTime < DateTime.Now) {
// The token has expired and we need to refresh it
_accessToken = "";
GetAccessToken();
}
}
if (_accessToken != "")
{
try
{
WebClient client = new WebClient();
string url = "https://api.freeagentcentral.com/v2/" + URN;
client.Headers[HttpRequestHeader.UserAgent] = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36";
client.Headers[HttpRequestHeader.Authorization] = "Bearer " + _accessToken;
client.Headers[HttpRequestHeader.ContentType] = "application/json";
client.Headers[HttpRequestHeader.Accept] = "application/json";
if (method == Methods.POST || method == Methods.PUT)
{
string data = request;
var result = client.UploadString(url, method.ToString(), data);
ResponseData = result;
return HttpStatusCode.Created;
}
else
{
var result = client.DownloadString(url);
ResponseData = result;
return HttpStatusCode.OK;
}
}
catch (WebException e)
{
if (e.GetType().Name == "WebException")
{
WebException we = (WebException)e;
HttpWebResponse response = (System.Net.HttpWebResponse)we.Response;
ResponseData = response.StatusDescription;
return response.StatusCode;
}
else
{
ResponseData = e.Message;
if (e.InnerException != null)
{
ResponseData = ResponseData + " - " + e.InnerException.ToString();
}
return HttpStatusCode.SeeOther;
}
}
}
else
{
ResponseData = "Access Token could not be retrieved";
return HttpStatusCode.SeeOther;
}
}
private int ExtractNewID(string resp, string URN)
{
if (resp != null && resp.Trim() != "")
{
JavaScriptSerializer json_serializer = new JavaScriptSerializer();
var results_list = (IDictionary<string, object>)json_serializer.DeserializeObject(resp);
if (results_list.Any(x => x.Key == "invoice"))
{
var returnInvoice = (IDictionary<string, object>)results_list["invoice"];
if (returnInvoice["created_at"].ToString() != "")
{
string returnURL = returnInvoice["url"].ToString();
if (returnURL.Contains("http"))
{
return int.Parse(returnURL.Remove(0, ("https://api.freeagentcentral.com/v2/" + URN).Length + 1));
}
else
{
return int.Parse(returnURL.Remove(0, URN.Length + 2));
}
}
}
}
return -1;
}
public int CreateContact(string firstName, string lastName, string emailAddress, string street, string city, string state, string postcode, string country)
{
StringBuilder request = new StringBuilder();
request.Append("{\"contact\":{");
request.Append("\"first-name\":");
request.Append(firstName);
request.Append("\",");
request.Append("\"last-name\":");
request.Append(lastName);
request.Append("\",");
request.Append("\"email\":");
request.Append(emailAddress);
request.Append("\",");
request.Append("\"address1\":");
request.Append(street);
request.Append("\",");
request.Append("\"town\":");
request.Append(city);
request.Append("\",");
request.Append("\"region\":");
request.Append(state);
request.Append("\",");
request.Append("\"postcode\":");
request.Append(postcode);
request.Append("\",");
request.Append("\"country\":");
request.Append(country);
request.Append("\"");
request.Append("}");
string returnData = string.Empty;
HttpStatusCode responseCode = SendWebRequest(Methods.POST, "contacts", request.ToString(), out returnData);
if (responseCode == HttpStatusCode.OK || responseCode == HttpStatusCode.Created)
{
return ExtractNewID(returnData, "contacts");
}
else
{
return -1;
}
}
public int CreateInvoice(int contactID, DateTime invoiceDate, int terms, string reference, string comments, string currency = "GBP")
{
StringBuilder request = new StringBuilder();
request.Append("{\"invoice\":{");
request.Append("\"dated_on\": \"");
request.Append(invoiceDate.ToString("yyyy-MM-ddTHH:mm:00Z"));
request.Append("\",");
if (!string.IsNullOrEmpty(reference))
{
request.Append("\"reference\": \"");
request.Append(reference);
request.Append("\",");
}
if (!string.IsNullOrEmpty(comments))
{
request.Append("\"comments\": \"");
request.Append(comments);
request.Append("\",");
}
request.Append("\"payment_terms_in_days\": \"");
request.Append(terms);
request.Append("\",");
request.Append("\"contact\": \"");
request.Append(contactID);
request.Append("\",");
if (currency == "EUR")
{
request.Append("\"ec_status\": \"");
request.Append("EC Services");
request.Append("\",");
}
request.Append("\"currency\": \"");
request.Append(currency);
request.Append("\"");
request.Append("}}");
string returnData = string.Empty;
HttpStatusCode responseCode = SendWebRequest(Methods.POST, "invoices", request.ToString(), out returnData);
if (responseCode == HttpStatusCode.OK || responseCode == HttpStatusCode.Created)
{
return ExtractNewID(returnData, "invoices");
}
else
{
return -1;
}
}
public bool ChangeInvoiceStatus(int invoiceID, Status status)
{
string returnData = string.Empty;
HttpStatusCode resp = SendWebRequest(Methods.PUT, "invoices/" + invoiceID.ToString() + "/transitions/mark_as_" + status.ToString().ToLower(), string.Empty, out returnData);
return false;
}
public int CreateInvoiceItem(int invoiceID, string itemType, float price, int quantity, float taxRate, string description)
{
StringBuilder request = new StringBuilder();
request.Append("{\"invoice\":{");
request.Append("\"invoice_items\":[{");
request.Append("\"item_type\": \"");
request.Append(itemType);
request.Append("\",");
request.Append("\"price\": \"");
request.Append(price.ToString("0.00"));
request.Append("\",");
request.Append("\"quantity\": \"");
request.Append(quantity);
request.Append("\",");
request.Append("\"sales_tax_rate\": \"");
request.Append(taxRate.ToString("0.00"));
request.Append("\",");
request.Append("\"description\": \"");
request.Append(description);
request.Append("\"");
request.Append("}]");
request.Append("}");
request.Append("}");
string returnData = string.Empty;
HttpStatusCode responseCode = SendWebRequest(Methods.PUT, "invoices/" + invoiceID.ToString(), request.ToString(), out returnData);
if (responseCode == HttpStatusCode.OK || responseCode == HttpStatusCode.Created)
{
// Invoice items is an update call to an invoice, so we just get a 200 OK with no response body to extract an ID from
return 0;
}
else
{
return -1;
}
}
}
}
To use this in an MVC setup you need to detect in the Index whether the login is necessary or not. If it is, you tell FreeAgent to come back to another ActionResult (I've used Auth) to catch the redirect and store the code.
public ActionResult Index()
{
if (MyNamespace.External.FreeAgent.AuthToken == "")
{
return Redirect("https://api.freeagent.com/v2/approve_app?redirect_uri=" + Request.Url.Scheme + "://" + Request.Url.Authority + "/Invoice/Auth" + "&response_type=code&client_id=1234567890123456789012");
}
else
{
return View();
}
}
public ActionResult Auth()
{
if (Request.Params.Get("code").ToString() != "")
{
ClientZone.External.FreeAgent.AuthToken = Request.Params.Get("code").ToString();
}
return View("Index");
}
Then to use the functions all you need is
MyNameSpace.External.FreeAgent FA = new ClientZone.External.FreeAgent("abcdefghhijklmnopqrstu", "1234567890123456789012", "acme", "http://localhost:52404/Invoice/Auth");
int newID = FA.CreateInvoice(54321, InvoiceDate, 30, "", "", "GBP");
if (newID != -1)
{
FA.CreateInvoiceItem(newID, unit, rate, quantity, number, description);
}
There is some UK/EU specific coding in the CreateInvoice. I'm not sure how custom or standard this is, but I've left it in as it's easier to delete it than recreate it.

Categories