How do I stop Refit encoding my querystring? - c#

I'm using an API that's in beta and it's essentially a querystring for a Mongodb db as a parameter. Refit is encoding my url, which included plenty of curly braces, and their server doesn't like it and it essentially ignores all of my query attributes.
I've tried passing the entire query as a string, which doesn't work but also doesn't chuck an error. I've found a commit to Refit which directly addresses this issue here.
That says to use the header [QueryUriFormat(UriFormat.Unescaped)], which sounds great, but I'm clearly not using the library the QueryUriFormat attribute is in and I can't find it in a google search.
In the interface:
[QueryUriFormat(UriFormat.Unescaped)]
[Headers("Authorization: Basic")]
[Get("/stuff/etc")]
Task<myModel> GetStuff(string q);
The string:
"filter={name:'THENAME',timestamp:{$gt:1571238110000},TOTAL_MB: {$gt:0},thingyId:{$eq:2500}}";
The call:
var result = client.GetUsageSince(stringB).Result;
I tried the link on the first comment (below)
var HttpClient = new HttpClient(new UriQueryUnescapingHandler()) { BaseAddress = new Uri(url) };
HttpClient.DefaultRequestHeaders.Add("Accept", "application/json");
HttpClient.DefaultRequestHeaders.Add("Content", "application/json");
var byteArray = Encoding.UTF8.GetBytes($"{username}:{password}");
HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
Stuff = RestService.For<IStuff>(HttpClient, refitSettings);
After doing this it looks like the absolute Uri is now correct, but the basic auth header is not working on my customer HttpClient.

Related

Why do I keep getting "Missing/Malformed URL Parameters" when consuming my API in C# (RestSharp) with VS 2022?

I'm trying to do a POST request in C#, using RestSharp, in Visual Studio 2022.
I tested the API using Postman, which was successful. The API requires a 64-bit encoded Basic Auth (which I have), and a unique API key (which I also have). I then looked at the C# Restsharp code provided by Postman, and tried to copy paste it into my VS project, but most of the libraries were deprecated. I then ended up modifying the code so it didn't give me any errors, and the code itself runs, but getting a semantic error: the request returns "Missing or Malformed URL parameters". In the body parameter, I send one parameter in the form
var body = #"{""fieldNameHere"": intHere}";
My full code (redacted):
var options = new RestClientOptions("URLHERE")
{
Timeout = -1
};
var client = new RestClient(options);
var request = new RestRequest
{
Method = Method.Post
};
request.AddHeader("API_KEY", "keyHere");
request.AddHeader("Authorization", "authHere");
request.AddHeader("Content-Type", "application/json");
var body = #"{""fieldNameHere"": intHere}";
request.AddParameter("application/json; charset=utf-8", body, ParameterType.RequestBody);
request.RequestFormat = DataFormat.Json;
RestResponse response = await client.ExecuteAsync(request);
So I tried using a JObject for the parameter, got the same error, this is my code:
JObject jObjectBody = new JObject();
jObjectBody.Add("FieldName", intHere);
request.AddParameter("application/json", jObjectBody, ParameterType.RequestBody);
var clientValue = await client.ExecuteAsync(request);
I also tried using HttpWebRequest, but got an auth error so not sure what was going on there, didn't get that anywhere else. Would prefer to use RestClient anyway. This is the other way I tried to do the body parameter:
string postData = "{\"FieldNameHere\":" + intHere + "}";
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
I haven't found anything that works yet. If someone can guide me towards a solution I'd be mad grateful, thanks. It definitely seems to be the body giving me the issue.
Ah! For some reason, phrasing my body like this worked:
var body = new { FieldNameHere = intHere }
request.AddJsonBody(body);
I have no idea why this worked, but it did!
A list of things that DID NOT work:
JObject technique
switching header placement
encoding the auth myself
how the RR instantiation is set up
with and without explicitly saying application/json (also including the charset and excluding)
using HttpWebRequest instead of RestSharp

How to replicate Postman POST request in C#

I'm fairly new to .NET's HTTPClient class, hence kindly excuse if I sounded noob. I'm tryin to replicate Postman's POST request in C# .Net and written following code. However I'm not getting any response but StatusCode: 404. Could someone assist understanding where I'm going wrong?
Also I'd like to understand, how do set Body in following code.
var httpClient = new HttpClient
{
BaseAddress = new Uri("https://testURL.com"),
Timeout = TimeSpan.FromMinutes(10)
};
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("audio/wav"));
httpClient.DefaultRequestHeaders.Add("Authorization", "Basic ldjfdljfdlfjdsjfdsl");
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("model", "Test"),
});
var result = httpClient.PostAsync("api/v1/recognize", content).Result;
Here is what I'm doing in Postman and it works:
"Params" in Postman refers to query parameters which are appended to the URL. You'll see that the URL in Postman contains the parameters you added in the "Params" tab:
However, it seems those are just dummy values you've entered so perhaps you don't need them? In any case, the way you add query parameters to the request for HttpClient is a little different as it needs to be added to the URL.
After that you also need to add the audio file as content to your request. At the moment you're setting the "Accept" header to "audio/wav" but you probably want to set the "Content-Type" header instead (or are you expecting a WAV file to be returned in the response too?).
As far as I can see this is what you're missing:
using (var httpClient = new HttpClient())
{
httpClient.Timeout = TimeSpan.FromMinutes(10);
// Set request headers
httpClient.DefaultRequestHeaders.Add("Authorization", "Basic ldjfdljfdlfjdsjfdsl");
// Set query parameters
var uriBuilder = new UriBuilder("https://testURL.com/api/v1/recognize");
uriBuilder.Query = "model=Test";
// Build request body
// Read bytes from the file being uploaded
var fileBytes = File.ReadAllBytes(wavFilePath);
// Create request content with metadata/headers to tell the
// backend which type of data (media type) is being uploaded
var byteArrayContent = new ByteArrayContent(fileBytes);
byteArrayContent.Headers.ContentType = MediaTypeHeaderValue.Parse("audio/wav");
// Wrap/encode the content as "multipart/form-data"
// See example of how the output/request looks here:
// https://dotnetfiddle.net/qDMwFh
var requestContent = new MultipartFormDataContent
{
{byteArrayContent, "audio", "filename.wav"}
};
var response = await httpClient.PostAsync(uriBuilder.Uri, requestContent);
}
I haven't tested this of course against your application, but it should be something along the lines of this. It might be that the backend doesn't expect "multipart/form-data" and just needs the "audio/wav". I can't see the output headers in your Postman screenshots, but if so, you can use byteArrayContent directly instead of wrapping it in MultipartFormDataContent.
Note: Don't use httpClient.PostAsync(...).Result. If you want to use the asynchronous method, you should await it. Depending on your code, using Result might give you problems if you're not careful. And remember to dispose the HttpClient after use (easiest solution is to use a using statement). If you plan on reusing the HttpClient for more requests, you can avoid disposing it until you're done.

Rest calls in c#

I am struggling with Rest call. Here is my code and it is working for basic authentication.
public async Task RunAsync(string name, string value)
{
using (var handler = new HttpClientHandler { UseDefaultCredentials = true })
using (var client = new HttpClient(handler))
{
var byteArray = Encoding.ASCII.GetBytes("username:password");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
var urlRestGet = HomeController.url;
client.BaseAddress = new Uri(urlRestGet + "?name=" + name + "&value=" + value + "");
client.DefaultRequestHeaders.Accept.Clear();
**1. if(HomeController.contentType.ToLower()=="xml"){
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
}**
else if (HomeController.contentType.ToLower() == "json")
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
HttpResponseMessage response = await client.GetAsync(urlRestGet + "?name=" + name + "&value=" + value + "");
if (response.IsSuccessStatusCode)
{
//Get the response
loginJsonString = await response.Content.ReadAsStringAsync();
//Converting to xml
using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(loginJsonString)))
{
var output = new XmlDictionaryReaderQuotas();
xmlResult = XDocument.Load(JsonReaderWriterFactory.CreateJsonReader(stream, output)).ToString();
}
}
}
}
1) If the content type is application/xml am I correct to use line 1 part in the code.
2) How can I make this code more generic. (when the authentication type is different eg: tokenized or cookiebased how can I change this.)
There are a couple of things about your code I do not understand.
What is HomeController.contentType all about? The name "HomeController" suggests you're writing an ASP.NET MVC Controller (serverside). Though you seem to be writing something intended to be used as a HTTP client. But I could be mistaken or mislead here.
You are reading a Json response, then loading it as a Xml document?
I'll try to answer anyway.
1) If the content type is application/xml am I correct to use line 1 part in the code.
The Accept header sent by the client (you) tells the server that you accept the given content type. If you send Accept application/xml you tell the server you prefer if the response is Xml.
Your code seem to assume the response's content type is always Json.
You could include both application/xml and application/json as Accept headers in your request. The server should honor that request and pick the first supported content type for it's response.
When processing the response you should check the actual content type and handle the response content appropriately.
Remember that Accept only tells the server that you prefer those content types. The server may decide not to honor your whishes and can return any content type it desires.
2) How can I make this code more generic. (when the authentication type is different eg: tokenized or cookiebased how can I change this.)
If you mean tokenized as in a query parameter you should probably handle your query parameters as a collection rather than a hardcoded formatted string.
Check out the NameValueCollection class and this SO question on NameValueCollection to query string.
To handle cookies, you basically need to copy/re-use the cookie collection returned in a response in the next request.
See this SO question on how to inject cookies when you create a new HttpClient.
... but it's much easier to use a library
As you already discovered, making a robust REST/HTTP client is not a easy task.
And as #Mafii and #Fildor already pointed out in comments there are already numerous libraries available. RestSharp (https://restsharp.org) being one very popular.

Passed Variables Null

I am building a small Web API for syncing data and pulling down the objects works great, but pushing my objects up doesn't work no matter what I have tried.
Edited to reflect some changes:
Here is my Controller:
[System.Web.Mvc.HttpPost]
public void UpdateTasks([FromBody] string s)
{
Console.WriteLine(s);
}
Here is my Client code:
HttpContent c = new StringContent("1234");
HttpClient client = new HttpClient();
c.Headers.ContentType = new MediaTypeHeaderValue("application/json");
client.BaseAddress = new Uri("http://localhost/QAQC_SyncWebService/Tasks/UpdateTasks/");
var resp = client.PostAsync(client.BaseAddress, c).Result;
I can get a value though if I put it in the URI, but the string content alone doesn't seem to work.
Try
[HttpPut]
public void UpdateTasks([FromBody]string s)
{
Console.WriteLine(s);
}
Please also note:
[FromBody] parameters must be encoded as =value
The final hurdle remaining is that Web API requires you to pass [FromBody] parameters in a particular format. That’s the reason why our value parameter was null in the previous example even after we decorated the method’s parameter with [FromBody].
Instead of the fairly standard key=value encoding that most client- and server-side frameworks expect, Web API’s model binder expects to find the [FromBody] values in the POST body without a key name at all. In other words, instead of key=value, it’s looking for =value.
This part is, by far, the most confusing part of sending primitive types into a Web API POST method. Not too bad once you understand it, but terribly unintuitive and not discoverable.
from http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/
The line you are initializing client.BaseAddress looks a bit off.
HttpContent c = new StringContent("1234");
HttpClient client = new HttpClient();
c.Headers.ContentType = new MediaTypeHeaderValue("application/json");
client.BaseAddress = new Uri("http://localhost/QAQC_SyncWebService/Tasks/UpdateTasks");
var resp = client.PutAsync(client.BaseAddress, c).Result;
The PutAsync method is expecting the full URI, not just a method. Read more here: https://msdn.microsoft.com/en-us/library/hh138168(v=vs.118).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1

How to use RestSharp for non standard url containing symbols like = and ::?

I have to call the following URL using RestSharp.
Some part of the uri are not standard.
How can I get to use them in
The url is the following but without the white space:
http:// mysite.com/api/v2.php?method = information :: method&token=b&target_id=0
I've tried something like this but RestSharp is not calling the URL I was expecting to call.
var client2 = new RestClient("http:// mysite.com/api/v2.php");
var request = new RestRequest("method=information::method", Method.GET);
request.AddParameter("token", authenticationToken);
request.AddParameter("target_id", targetId);
You don't mention what url your code actually calls, but I guess that your method is parsed as a file/path, and not a parameter.
var client = new RestClient("http:// mysite.com/api/");
var request = new RestRequest("v2.php", Method.GET);
request.AddParameter("method", "information::method");
request.AddParameter("token", authenticationToken);
request.AddParameter("target_id", targetId);

Categories