The aim of the program below is to get a list of Reports built in our database and find out how many of these reports use the field NameFirst within them.
I'm able to make an API call and, at GetReports, get a list of the ReportIDs.
However, I'm unable to move forward with calling the list I created at GetReports in the next method, GetNameFirst. I was wondering if someone could please help me out with this.
For the script below, I get a red underline for the variable values. This is understandable because I didn't know where and how to tell my code to bind the list output for GetReports to the variable values in GetNameFirst.
Also, if I could get some help in finding out which reports have the field NameFirst in them once I accomplish calling the list from the first method to the second, I'd appreciate that also. I'm currently heading in the direction of using a foreach, but I'm unsure if that's the best path to take.
Main Program
namespace NameFirstSearch
{
class Program
{
static void Main(string[] args)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
const string username = "Username";
const string password = "Password";
const string baseUrl = "https://example.com/rest/services/";
const string queryString = "query?q=Select * From Report Where LastRanDate is not null";
const string queryNameFirst = "getreport/";
var client = new HttpClient();
client.BaseAddress = new Uri(baseUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var auth = Convert.ToBase64String(Encoding.Default.GetBytes(username + ":" + password));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);
GetReports(client, queryString).Wait();
GetNameFirst(client, queryNameFirst).Wait();
Console.ReadLine();
}
static async Task<List<Properties>> GetReports(HttpClient client, string queryString)
{
List<Properties> result = new List<Properties>();
var response = await client.GetAsync(queryString);
// Check for a successfull result
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<List<Properties>>(json);
Console.WriteLine(result.Count());
}
else
{
// Error code returned
Console.WriteLine("No records found on first method.");
}
return result;
}
static async Task GetNameFirst(HttpClient client, string queryNameFirst)
{
string reportType = "JSON";
foreach (var item in values)
{
var output = await client.GetAsync(queryNameFirst + item.ReportID + reportType);
if (output.IsSuccessStatusCode)
{
var allText = await output.Content.ReadAsStringAsync();
var fields = JsonConvert.DeserializeObject<List<NameFirst>>(allText);
}
else
{
// Error code returned
Console.WriteLine("No records found on second method.");
}
}
}
}
Class for report list
class Properties
{
public int ReportID { get; set; }
}
Class for reports' NameFirst property
class NameFirst
{
public string FirstName { get; set; }
}
I thought this was a partial code, but since you've cleared things out.
you'll need to change your code a bit
this :
GetReports(client, queryString).Wait();
do it like this :
var reportsList = GetReports(client, queryString).Result;
now, you'll need to pass the reportsList to the second method GetNameFirst which would be adjusted to this :
static async Task GetNameFirst(HttpClient client, string queryNameFirst, List<Properties> results)
{
string reportType = "JSON";
foreach (var item in results)
{
var output = await client.GetAsync(queryNameFirst + item.ReportID + reportType);
if (output.IsSuccessStatusCode)
{
var allText = await output.Content.ReadAsStringAsync();
var fields = JsonConvert.DeserializeObject<List<NameFirst>>(allText);
}
else
{
// Error code returned
Console.WriteLine("No records found on second method.");
}
}
}
with this adjustment, you'll need to adjust the call as well :
GetNameFirst(client, queryNameFirst, reportsList).Wait();
Related
I have successfully created a rest API to connected to a Microsoft SQL database. All get, post, pull & delete functions work successfully in the API. I then created a UI using the Blazor server app. The UI communicates with the API to retrieve & send data. All info in the database have been retrieved successfully but I encounter this error "Error: System.Text.Json.JsonException: 'N' is an invalid start of a value. Path: $ | LineNumber: 0 | " when trying to add new data (task) to the database. When I reload the page after the error has occurred, the data is added successfully even though it fails in the initial process.
Below is the Create click...
#code{
public class TasksClass
{
public int TaskID { get; set; }
public string TaskTitle { get; set; }
public string TaskDetails { get; set; }
public string DateAdded { get; set; }
}
private IEnumerable<TasksClass> tasks = Array.Empty<TasksClass>();
private string modalTitle;
private int TaskID;
private string TaskTitle;
private string TaskDetails;
private DateTime DateAdded;
// Create click
private async Task createClick()
{
try
{
var tskObj = new TasksClass()
{
TaskTitle = TaskTitle,
TaskDetails = TaskDetails,
DateAdded = DateAdded.ToString("yyyy-MM-dd")
};
var request = new HttpRequestMessage(HttpMethod.Post,
config["API_URL"] + "Tasks/AddTasks");
request.Content = new StringContent(JsonSerializer.Serialize(tskObj), null, "application/json-patch+json");
var client = ClientFactory.CreateClient();
var response = await client.SendAsync(request);
using var responseStream = await response.Content.ReadAsStreamAsync();
string res = await JsonSerializer.DeserializeAsync<string>(responseStream);
await JS.InvokeVoidAsync("alert", res);
await refreshList();
}
catch(HttpRequestException)
{
throw new BadHttpRequestException("no");
}
}
I put a breakpoint in the createClick & did a walk through & realized the error occurs here
string res = await JsonSerializer.DeserializeAsync(responseStream);
await JS.InvokeVoidAsync("alert", res);
I can't seem to figure out exactly what the problem is.
That error is being thrown because the response body starts with an "N", but the JsonDeserializer is expecting it to start as a JSON object (e.g. with a curly brace). My assumption is that Tasks/AddTasks is just returning a string, so it's not in JSON format. Quick solution is to just not deserialize the response, because all you want is the raw string response anyway.
To do so, just replace these lines:
using var responseStream = await response.Content.ReadAsStreamAsync()
string res = await JsonSerializer.DeserializeAsync<string>(responseStream);
with this:
string res = await response.Content.ReadAsStringAsync();
Below is a script making an API call which retrieves some ReportIDs (from a database table), passes them through a method which will run them individually, and finally see if each contains the field, Name_First.
What I need help with is creating a count that will tell me how many reports contain the field Name_First, how many don't, and what's the percentage out of all reports having the field, Name_First.
Currently, the script queries two ReportIDs (12300,12301) for testing. One report has the field Name_First in it and the other one doesn't. From the foreach loop I've written, I'm aiming to count every time there is a null and every time there isn't, get a total, and just divide the not null count by the sum of both. However, when I run this script, the console at
Console.WriteLine(total);
Console.WriteLine(total_noFirstName);
Console.WriteLine(total_FirstName);
returns values of 0, 0, and 0. I think I'm having a scope issue here, but I'm not really sure. If I could receive some help solving this, I'd very much appreciate it.
Thank you!
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
namespace NameFirstSearch
{
class Program
{
static void Main(string[] args)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
const string username = "Username";
const string password = "Password";
const string baseUrl = "https://test.com/rest/services/";
const string queryString = "query?q=Select * From Report Where ReportID in (12300,12301)";
const string queryNameFirst = "getreport/";
var client = new HttpClient();
client.BaseAddress = new Uri(baseUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var auth = Convert.ToBase64String(Encoding.Default.GetBytes(username + ":" + password));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);
//GetReports(client, queryString).Wait();
var reportsList = GetReports(client, queryString).Result;
GetNameFirst(client, queryNameFirst, reportsList).Wait();
Console.ReadLine();
}
static async Task<List<Properties>> GetReports(HttpClient client, string queryString)
{
List<Properties> result = new List<Properties>();
var response = await client.GetAsync(queryString);
// Check for a successfull result
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<List<Properties>>(json);
}
else
{
// Error code returned
Console.WriteLine("No records found on first method.");
}
return result;
}
static async Task GetNameFirst(HttpClient client, string queryNameFirst, List<Properties> results)
{
string reportType = ".json";
foreach (var item in results)
{
var output = await client.GetAsync(queryNameFirst + item.ReportID + reportType);
if (output.IsSuccessStatusCode)
{
var allText = await output.Content.ReadAsStringAsync();
var fields = JsonConvert.DeserializeObject<List<FirstName>>(allText);
var test = JsonConvert.SerializeObject(fields);
Console.WriteLine(test);
int total_FirstName = 0;
int total_noFirstName = 0;
int total = total_FirstName + total_noFirstName;
foreach (var split in fields)
{
if (split.Name_First != null)
{
total_FirstName++;
}
else if (split.Name_First == null)
{
total_noFirstName++;
}
}
Console.WriteLine(total);
Console.WriteLine(total_noFirstName);
Console.WriteLine(total_FirstName);
}
else
{
// Error code returned
Console.WriteLine("No records found on second method.");
}
}
}
}
}
Class Properties
int ReportID {get; set;}
Class FirstName
string Name_First {get; set;}
Results at Console.WriteLine(test);
[{"Name_First":"Mario"}]
[{"Name_First":null}]
Results for Console.WriteLine(allText);
[{"Name_First":"Mario","Entry_ID":"72313"}]
[{"Name_Last":"Rincon Recio","Entry_ID":"72313"}]
If you want to keep a count of things in a loop, you need to declare the counter variables outside of the loop, otherwise they get reinitialised. Using your data from the question, the relevant loop should look like this:
List<string> allTexts = new List<string>
{ #"[{""Name_First"":""Mario"",""Entry_ID"":""72313""}]",
#"[{""Name_Last"":""Rincon Recio"",""Entry_ID"":""72313""}]" };
int total_FirstName = 0;
int total_noFirstName = 0;
foreach(var allText in allTexts)
{
var fields = JsonConvert.DeserializeObject<List<FirstName>>(allText);
var test = JsonConvert.SerializeObject(fields);
Console.WriteLine(test);
foreach (var split in fields)
{
if (split.Name_First != null)
{
total_FirstName++;
}
else
{
total_noFirstName++;
}
}
}
int total = total_FirstName + total_noFirstName;
Console.WriteLine(total);
Console.WriteLine(total_noFirstName);
Console.WriteLine(total_FirstName);
Output:
[{"Name_First":"Mario"}]
[{"Name_First":null}]
2
1
1
Hello I am learning WebApi and got this problem. Hours of search didn't yield any solution.
I am trying to call an api passing two parameters just for testing purposes. The one on which I am getting 404 error is GetBalance(param1, param2). I have another function exposed by the Api called GetOffice(param1) with one parameter which returns 200. The 404 I am getting is for the two parameter function.
public void GetBalance(string accountNumber,int officeId)
{
using (var client = new WebClient())
{
client.Headers.Add("Content-Type:application/json");
client.Headers.Add("Accept:application/json");
client.Headers.Add("API_KEY","1234CHECK");
var result = client.DownloadString("http://localhost/api/Accounts/GetBalance/" + accountNumber + officeId ); //URI
Console.WriteLine(Environment.NewLine + result);
}
}
static void Main(string[] args)
{
ConsumeApiSync objSync = new ConsumeApiSync();
objSync.GetBalance("01-13-00000595", 1);
}
Route
RouteTable.Routes.MapHttpRoute("OfficeApi", "api/{controller}/{action}/{accountNumber}/{officeId}");
I get 404 not found error. What must be wrong? Help Appreciated. Thanks
use a view model on your Web Api controller that contains both properties. So instead of:
public HttpresponseMessage GetBalance(string accountNumber,int officeId)
{
...
}
use:
public HttpresponseMessage Post(ViewModelName model)
{
...
}
var uri = string.Concat("http://localhost/api/Accounts/GetBalance",model);
Seems like your request URI is not correct.
var uri = string.Concat("http://localhost/api/Accounts/GetBalance/", accountNumber, "/", officeId);
Try the following code.
public void GetBalance(string accountNumber,int officeId)
{
using (var client = new WebClient())
{
client.Headers.Add("Content-Type:application/json");
client.Headers.Add("Accept:application/json");
client.Headers.Add("API_KEY","1234CHECK");
var uri = string.Concat("http://localhost/api/Accounts/GetBalance/", accountNumber, "/", officeId);
var result = client.DownloadString(uri); //URI
Console.WriteLine(Environment.NewLine + result);
}
}
static void Main(string[] args)
{
ConsumeApiSync objSync = new ConsumeApiSync();
objSync.GetBalance("01-13-00000595", 1);
}
Solved!!! - See last edit.
In my MVC app I make calls out to a Web API service with HMAC Authentication Filterign. My Get (GetMultipleItemsRequest) works, but my Post does not. If I turn off HMAC authentication filtering all of them work. I'm not sure why the POSTS do not work, but the GETs do.
I make the GET call from my code like this (this one works):
var productsClient = new RestClient<Role>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
var getManyResult = productsClient.GetMultipleItemsRequest("api/Role").Result;
I make the POST call from my code like this (this one only works when I turn off HMAC):
private RestClient<Profile> profileClient = new RestClient<Profile>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
[HttpPost]
public ActionResult ProfileImport(IEnumerable<HttpPostedFileBase> files)
{
//...
var postResult = profileClient.PostRequest("api/Profile", newProfile).Result;
}
My RestClient builds like this:
public class RestClient<T> where T : class
{
//...
private void SetupClient(HttpClient client, string methodName, string apiUrl, T content = null)
{
const string secretTokenName = "SecretToken";
client.BaseAddress = new Uri(_baseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (_hmacSecret)
{
client.DefaultRequestHeaders.Date = DateTime.UtcNow;
var datePart = client.DefaultRequestHeaders.Date.Value.UtcDateTime.ToString(CultureInfo.InvariantCulture);
var fullUri = _baseAddress + apiUrl;
var contentMD5 = "";
if (content != null)
{
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json); // <--- Javascript serialized version is hashed
}
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
var hmac = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
client.DefaultRequestHeaders.Add(secretTokenName, hmac);
}
else if (!string.IsNullOrWhiteSpace(_sharedSecretName))
{
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
client.DefaultRequestHeaders.Add(secretTokenName, sharedSecretValue);
}
}
public async Task<T[]> GetMultipleItemsRequest(string apiUrl)
{
T[] result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "GET", apiUrl);
var response = await client.GetAsync(apiUrl).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T[]>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
public async Task<T> PostRequest(string apiUrl, T postObject)
{
T result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "POST", apiUrl, postObject);
var response = await client.PostAsync(apiUrl, postObject, new JsonMediaTypeFormatter()).ConfigureAwait(false); //<--- not javascript formatted
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
//...
}
My Web API Controller is defined like this:
[SecretAuthenticationFilter(SharedSecretName = "xxxxxxxxxxxxxxx", HmacSecret = true)]
public class ProfileController : ApiController
{
[HttpPost]
[ResponseType(typeof(Profile))]
public IHttpActionResult PostProfile(Profile Profile)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
GuidValue = Guid.NewGuid();
Resource res = new Resource();
res.ResourceId = GuidValue;
var data23 = Resourceservices.Insert(res);
Profile.ProfileId = data23.ResourceId;
_profileservices.Insert(Profile);
return CreatedAtRoute("DefaultApi", new { id = Profile.ProfileId }, Profile);
}
}
Here is some of what SecretAuthenticationFilter does:
//now try to read the content as string
string content = actionContext.Request.Content.ReadAsStringAsync().Result;
var contentMD5 = content == "" ? "" : Hashing.GetHashMD5OfString(content); //<-- Hashing the non-JavaScriptSerialized
var datePart = "";
var requestDate = DateTime.Now.AddDays(-2);
if (actionContext.Request.Headers.Date != null)
{
requestDate = actionContext.Request.Headers.Date.Value.UtcDateTime;
datePart = requestDate.ToString(CultureInfo.InvariantCulture);
}
var methodName = actionContext.Request.Method.Method;
var fullUri = actionContext.Request.RequestUri.ToString();
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var expectedValue = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
// Are the hmacs the same, and have we received it within +/- 5 mins (sending and
// receiving servers may not have exactly the same time)
if (messageSecretValue == expectedValue
&& requestDate > DateTime.UtcNow.AddMinutes(-5)
&& requestDate < DateTime.UtcNow.AddMinutes(5))
goodRequest = true;
Any idea why HMAC doesn't work for the POST?
EDIT:
When SecretAuthenticationFilter tries to compare the HMAC sent, with what it thinks the HMAC should be they don't match. The reason is the MD5Hash of the content doesn't match the MD5Hash of the received content. The RestClient hashes the content using a JavaScriptSerializer.Serialized version of the content, but then the PostRequest passes the object as JsonMediaTypeFormatted.
These two types don't get formatted the same. For instance, the JavaScriptSerializer give's us dates like this:
\"EnteredDate\":\"\/Date(1434642998639)\/\"
The passed content has dates like this:
\"EnteredDate\":\"2015-06-18T11:56:38.6390407-04:00\"
I guess I need the hash to use the same data that's passed, so the Filter on the other end can confirm it correctly. Thoughts?
EDIT:
Found the answer, I needed to change the SetupClient code from using this line:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
To using this:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
Now the sent content (formatted via JSON) will match the hashed content.
I was not the person who wrote this code originally. :)
Found the answer, I needed to change the SetupClient code from using this line:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
To using this:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
Now the content used for the hash will be formatted as JSON and will match the sent content (which is also formatted via JSON).
I have a unique issue, I want to get the name of an application from it's AppID while I convert an XML file into objects. This is the code I'm using at present:
if (xdoc.Element("Application") != null)
{
var data = from query in xdoc.Descendants("AppID")
select new APP
{
AppID = query.Value,
AppName = GetName(query.Value).ToString(),
};
itemGridView.DataContext = data;
}
This is the code I'm using to convert the GUID into an app name using Microsoft's Store API. I can confirm that it does return the app name. I'm just unsure how I can get this to display.
private async Task<string> GetName(string guid)
{
try
{
string link = "https://services.apps.microsoft.com/browse/6.2.9200-1/615/en-NZ_en-NZ/c/NZ/cp/10005001/Apps/{0}";
string url = string.Format(link, guid);
var httpClient = new HttpClient();
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
var response = await httpClient.SendAsync(httpRequestMessage);
var xmlString = await response.Content.ReadAsStringAsync();
XmlDocument NameXML = new XmlDocument();
NameXML = await XmlDocument.LoadFromUriAsync(new Uri(url));
string sAppName = NameXML.GetElementsByTagName("T")[0].ChildNodes[0].NodeValue.ToString();
return sAppName;
}
catch(Exception)
{
return guid;
}
}
I think my problem is with the async / await tasks. I've just been exposed to it now... how would I load up the App Name alongside the AppID when I parse the xml file?
The output that's being displayed when I run the app is "System.Threading.Tasks.Task[System.String]" (The objects load and the links and everything works fine, its just that the above is displayed instead of the app name).
I've been debugging using breakpoints, it appears that the GetName method only seems to be triggered later on, I'm not sure however.
Try to change this line :
AppName = GetName(query.Value).ToString(),
To this :
AppName = await GetName(query.Value),
GetName will return Task<string> instead of string when not awaited. And the method where above code resides required to be async because of using await inside that method :
private async void SomeMethod()
{
....
if (xdoc.Element("Application") != null)
{
var data = from query in xdoc.Descendants("AppID")
select new APP
{
AppID = query.Value,
AppName = await GetName(query.Value),
};
itemGridView.DataContext = data;
}
....
}
UPDATE :
As you already noticed, LINQ has very limited support for async/await currently. So to workaround this limitation, we can use normal for loop to avoid calling async function inside LINQ :
private async void SomeMethod()
{
....
if (xdoc.Element("Application") != null)
{
var query = from query in xdoc.Descendants("AppID")
select query.Value;
var data = new List<App>();
foreach (var q in query)
{
data.Add(new App{ AppId = q, AppName = await GetName(q) });
}
itemGridView.DataContext = data;
}
....
}