Xamarin Async deadlock - Cannot get data from API - c#

Im trying to connect to an API and get the data from the "User" table so that the credentials can be authenticated. However after executing GetAsync() the app stucks in a deadlock and doesnt do anything. I have tested the API with postman and it works.
public async Task<User> UserCredentialsGet(string name, string password)
{
var user = new User();
HttpClient client = new HttpClient();
string url = "https://xxx.xxx.xxx.xxx:xxxx/api/Users/username=" + name + "/" + "password=" + password ;
Uri uri = new Uri(url);
try
{
var response = await client.GetAsync(uri).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
string content = response.Content.ReadAsStringAsync().Result;
user = JsonConvert.DeserializeObject<User>(content);
}
}
catch (Exception ex)
{
}
return user;
//return await Task.FromResult(user);
}

GetAsync is Idisposable so try to use something like
`using (HttpClient client = new HttpClient())
{
using (HttpResponseMessage response = await client.GetAsync(uri, cancellationToken))
{
var stringResponse = await response.Content.ReadAsStringAsync();
//todo process response
}
}
`
For me it works fine.
Do not for forget await response

Related

Microsoft Graph API Returning 401 Despite Having Required Scope & Account Type

We have an API that utilizes a service account (work account type - not a personal account) to do the following:
Using our Service Account, Gets a Token from our Azure AD with scopes: ["user.read", "User.Read.All", "Files.ReadWrite.All", "ChatMessage.Send", "Chat.Create", "Chat.ReadWrite"]
Use that token to upload a file to our SharePoint. -> This succeeds
Use that token to Get the UserID from email (UserPrincipalNames map in our AD, so this is not a concern) -> This succeeds
Use that token to Create a new chat/Get the existing chat between the user & our service account (using POST /v1.0/chats) -> This returns 401 Unauthorized
Send the message from service account to user (using POST /v1.0/chats/{chatID returned from step 4}/messages)
The users we are attemping to send these messages to are all in our Azure AD.
We are unsure how the previous requests with this token succeed, and the Create/Get Chat fails with a 401. We have confirmed that the token is correctly being set in the authorization header (we are using the exact same process as the previous requests).
Also, we have these API Permissions set in our Azure AD for this application:
If we use jwt.io to examine the token, we do see the scope is set in the token being set in the Authorization header of the request.
Additionally the audience is set to Graph API:
Here is our code:
using Microsoft.Graph;
using Newtonsoft.Json;
using OurAPI.Helpers.IHelpers;
using OurAPI.Models;
using OurAPI.Repositories.IRepositories;
using System.Text;
namespace OurAPI.Repositories
{
public class TeamsRepository : ITeamsRepository
{
private readonly IConfiguration _config;
private readonly ITokenHelper _tokenHelper;
private readonly IHttpClientFactory _httpClientFactory;
public TeamsRepository(IConfiguration config, ITokenHelper tokenHelper, IHttpClientFactory httpClientFactory)
{
_config = config;
_tokenHelper = tokenHelper;
_httpClientFactory = httpClientFactory;
}
public async Task<object> GeneratePDFAndSendToTeams(TeamsPostRequest request)
{
// Authenticate
string token = await _tokenHelper.GetMicrosoftGraphAccessToken();
// Convert from Base64 to Memory Stream
var file = GeneratePDF(request.Base64);
// Upload PDF to SharePoint
var sharepointFile = await UploadFile(file, request, token);
// Get UserID from email
var user = await GetUser(request.User, token);
// Create a new chat with service account or Retrieve Chat if already exists
var chat = await GetChat(user.Id, token);
// Post to chat between service account and requesting user
var chatMessage = await SendToTeams(sharepointFile, chat, token);
return chat;
}
public async Task<object> GetChats(string token)
{
using var request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/users/serviceaccount#domain.com/chats");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var chats = JsonConvert.DeserializeObject(contents);
return chats;
}
public async Task<User> GetUser(string userEmail, string token)
{
using var request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/users/" + userEmail);
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var user = JsonConvert.DeserializeObject<User>(contents);
return user;
}
public async Task<Chat> GetChat(string userID, string token)
{
var chatRequest = new Models.ChatRequest(userID);
chatRequest.ChatType = "oneOnOne";
var chatMember = new ChatMember();
chatMember.Type = "#microsoft.graph.aadUserConversationMember";
chatMember.Roles = new List<string>() { "owner" };
chatMember.User = $"https://graph.microsoft.com/beta/users('{userID}')";
var serviceAccount = new ChatMember();
serviceAccount.Type = "#microsoft.graph.aadUserConversationMember";
serviceAccount.Roles = new List<string>() { "owner" };
serviceAccount.User = "https://graph.microsoft.com/beta/users('{OurServiceAccountID}')";
chatRequest.Members = new List<ChatMember> { chatMember, serviceAccount };
using var request = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/v1.0/chats");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
string content = JsonConvert.SerializeObject(chatRequest);
request.Content = new StringContent(content, Encoding.UTF8, "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var chat = JsonConvert.DeserializeObject<Chat>(contents);
return chat;
}
// POST /chats/{chat-id}/messages
public async Task<ChatMessage> SendToTeams(DriveItem sharePointFile, Chat chat, string token)
{
var chatMessageRequest = new Models.ChatMessageRequest(sharePointFile);
using var request = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/v1.0/chats/" + chat.Id + "/messages");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
request.Content = new StringContent(JsonConvert.SerializeObject(chatMessageRequest), Encoding.UTF8, "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
//response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var chatMessageResponse = JsonConvert.DeserializeObject<ChatMessage>(contents);
return chatMessageResponse;
}
public async Task<DriveItem> UploadFile(MemoryStream file, TeamsPostRequest teamsPostRequest, string token)
{
string siteID = "{ourSiteID}";
string PTparentID = "{ourParentID}"
string custNameForFile = teamsPostRequest.CustomerName;
using var request = new HttpRequestMessage(HttpMethod.Put, _config["Graph:BaseUrl"] + "/sites/" + siteID + "/drive/items/" + PTparentID + ":/" + custNameForFile + teamsPostRequest.PickTicketNo + ".pdf:/content");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
request.Content = new StreamContent(file);
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var driveItem = JsonConvert.DeserializeObject<DriveItem>(contents);
return driveItem;
}
private static MemoryStream GeneratePDF(string base64)
{
byte[] bytes = Convert.FromBase64String(base64);
MemoryStream ms = new(bytes);
return ms;
}
}
}
Here is the request we are making to get the token:
public async Task<string> GetMicrosoftGraphAccessToken()
{
string authority = "https://login.microsoftonline.com/{ourTenantID}/";
string[] scopes = new string[] { "user.read", "User.Read.All", "Files.ReadWrite.All", "ChatMessage.Send", "Chat.Create", "Chat.ReadWrite" };
IPublicClientApplication app = PublicClientApplicationBuilder.Create(_config["AzureAD:ClientId"])
.WithAuthority(authority)
.Build();
var securePassword = new SecureString();
foreach (char c in _config["MicrosoftGraph:Password"])
{
securePassword.AppendChar(c);
}
AuthenticationResult result = await app.AcquireTokenByUsernamePassword(scopes, _config["MicrosoftGraph:Username"], securePassword)
.ExecuteAsync();
return result.AccessToken;
}
We are at a lose for what could be going on here. Any ideas would be greatly appreciated!
It helps to see the details of the token.
Copy the token, paste it at https://jwt.ms and inspect the details.
You’re probably interested in the scopes.
I guess the account you’re using should be one of the participants of the chat, this cannot be used to create chats between other users.
https://learn.microsoft.com/en-us/graph/api/chat-post you seem to be doing the right request.

HttpClient GetAsync with query string

I am using Google's GeoCoding API. I have two methods where one works and the other doesn't and I can't seem to figure out why:
string address = "1400,Copenhagen,DK";
string GoogleMapsAPIurl = "https://maps.googleapis.com/maps/api/geocode/json?address={0}&key={1}";
string GoogleMapsAPIkey = "MYSECRETAPIKEY";
string requestUri = string.Format(GoogleMapsAPIurl, address.Trim(), GoogleMapsAPIkey);
// Works fine
using (var client = new HttpClient())
{
using (HttpResponseMessage response = await client.GetAsync(requestUri))
{
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
}
}
// Doesn't work
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("https://maps.googleapis.com/maps/api/", UriKind.Absolute);
client.DefaultRequestHeaders.Add("key", GoogleMapsAPIkey);
using (HttpResponseMessage response = await client.GetAsync("geocode/json?address=1400,Copenhagen,DK"))
{
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
}
}
My last method with GetAsync where I am sending a query string doesn't work and I am in doubt why it is so. When I introduce BaseAddress on the client the GetAsync somehow doesn't send to the correct URL.
I don't suggest adding API key into globals. Maybe you'll need to send some HTTP request outside of the API and the key will be leaked.
Here's the example that works.
using Newtonsoft.Json;
public class Program
{
private static readonly HttpClient client = new HttpClient();
private const string GoogleMapsAPIkey = "MYSECRETAPIKEY";
static async Task Main(string[] args)
{
client.BaseAddress = new Uri("https://maps.googleapis.com/maps/api/");
try
{
Dictionary<string, string> query = new Dictionary<string, string>();
query.Add("address", "1400,Copenhagen,DK");
dynamic response = await GetAPIResponseAsync<dynamic>("geocode/json", query);
Console.WriteLine(response.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
Console.ReadKey();
}
private static async Task<string> ParamsToStringAsync(Dictionary<string, string> urlParams)
{
using (HttpContent content = new FormUrlEncodedContent(urlParams))
return await content.ReadAsStringAsync();
}
private static async Task<T> GetAPIResponseAsync<T>(string path, Dictionary<string, string> urlParams)
{
urlParams.Add("key", GoogleMapsAPIkey);
string query = await ParamsToStringAsync(urlParams);
using (HttpResponseMessage response = await client.GetAsync(path + "?" + query, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();
string responseText = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(responseText);
}
}
}
Ciao, the problem is related with key parameter on URL. Change your code like this:
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("https://maps.googleapis.com/maps/api/");
using (HttpResponseMessage response = await client.GetAsync("geocode/json?address=1400,Copenhagen,DK&key=" + GoogleMapsAPIkey))
{
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
}
}
As google sheets said:
After you have an API key, your application can append the query parameter key=yourAPIKey to all request URLs. The API key is safe for embedding in URLs; it doesn't need any encoding.

Unable to send string as HTTPContent

I have created an API, which shall have the capability to connect to en external API via POST and with a request body in form of a string.
I am able to connect directly to the API from Postman without trouble.. But it does not work via my own API.
Any ideas?
This is the Pastebin.
private string EncodeExternalApiLink = "https://blabla.dk";
private string EncodeExternalApiLinkPostFilter = "searchstring/blabla/api/search";
[HttpPost("getdata/filtered")]
public async Task<IActionResult> GetDataFromExternalFiltered([FromBody] string filter)
{
var filterString = new StringContent(filter);
EncodeExternalToken token = GetExternalToken().Result;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(EncodeExternalApiLink);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.access_token);
using (var response = await client.PostAsync(EncodeExternalApiLinkPostFilter, filterString))
{
return Json(response);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
return Content(content, "application/json");
}
else
{
return NotFound();
}
}
}
}
Salutations. You might need to add a "/" to the end of your base address EncodeExternalApiLink or to the beginning of EncodeExternalApiLinkPostFilter.

Curl Command to c# code

I have following CURL command
curl.exe -k "relevance=value" --user admin:pass https://10.221.22.13:34323/api/query
corresponding to this, I have written following c# code
private async void RunClient(string _address, string username, string password)
{
HttpClient client = new HttpClient();
string authString = username + ":" + password;
try
{
var byteArray = Encoding.ASCII.GetBytes(authString);
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
string valueString = "value";
client.DefaultRequestHeaders.Add("relevance", valueString);
// Send a request asynchronously and continue when complete
HttpResponseMessage response = await client.GetAsync(_address);
// Check that response was successful or throw exception
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
}
catch(Exception ex)
{
}
}
The CURL command is working fine in cmd but the c# code throws Bad Request (400) Error.
Where I am wrong ?
Thnx in Advance.
Turns out it was a Post request and "relevance=value" should be passed in content body instead of header, here is the modified code :
private async void RunClient(string _address, string username, string password)
{
// Create an HttpClient instance
HttpClient client = new HttpClient();
string authString = username + ":" + password;
try
{
//add authentication to header
var byteArray = Encoding.ASCII.GetBytes(authString);
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
string valueString = "value";
var con = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("relevance", valueString)
});
// Send a request asynchronously and continue when complete
HttpResponseMessage response = await client.PostAsync(_address, con);
// Check that response was successful or throw exception
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
}
}

How can I use the access token to get a list of projects from a website in c#?

I am trying to create a C# console application to download project details from a website which supports REST OAuth 2.0. How do I make a request/response call to the website using the Access Token?
Here is my code:
public string token = "4bjskfa2-b37d-6244-8413-3358b18c91b6";
public async Task GetProjectsAsync()
{
try
{
HttpClient client = new HttpClient();
var projects = "https://app.rakenapp.com/api/v2/projects?" + token;
client.CancelPendingRequests();
HttpResponseMessage output = await client.GetAsync(projects);
if (output.IsSuccessStatusCode)
{
string response = await output.Content.ReadAsStringAsync();
project proj = JsonConvert.DeserializeObject<project>(response);
if (proj != null)
{
Console.WriteLine(proj.name); // You will get the projects here.
}
}
}
catch (Exception ex)
{
//catching the exception
}
}
you need to add a header to your request:
string url = "https://app.rakenapp.com/api/v2/projects";
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authorizationToken);
HttpResponseMessage response = await httpClient.GetAsync(url);
var contents = await response.Content.ReadAsStringAsync();
var model = JsonConvert.DeserializeObject<project>.contents);
return model;
}

Categories

Resources