F# cannot post to web api? - c#

I have the following code to post to a web api.
[<CLIMutable>]
type MyModel = { FileName:string; Period:DateTime; DownloadedTime:DateTimeOffset; Url:string; }
let PostDownload (filepath, date, url) =
async {
try
use client = new HttpClient()
let content = { FileName = filepath; Period = date; DownloadedTime = DateTimeOffset.Now; Url = url }
let! response = Async.AwaitTask(client.PostAsJsonAsync("http://localhost:5000/api/DownloadedFiles", content))
with
| ex -> logger.Error(ex, "Exception: " + ex.Message)
} |> Async.Start
The service has the following code and the debugger shows all the fields of downloadedFile are the default values (nulls or the minimum values for value types).
[HttpPost]
public void Post([FromBody]DownloadedFile downloadedFile)
{
try
{
_context.DownloadedFile.Add(downloadedFile);
_context.SaveChanges();
}
catch (Exception ex) { ...... }
}
The fiddler shows the F# code (or PostAsJsonAsync cannot handle F# mutable record type?) added # at the end of field name?
{"FileName#":"test","Period#":"2100-01-01T00:00:00","DownloadedTime#":"2016-08-18T15:50:37.5004391-04:00","Url#":"test"}

I don't know from where you get HttpClient.PostAsJsonAsync, because it's not on the version of HttpClient I'm currently looking at. Nevertheless, I typically use this extension, which works for me:
type HttpClient with
member this.PostAsJsonAsync (requestUri : string, value : obj) =
let json = string value
let content = new StringContent (json)
content.Headers.ContentType <-
Headers.MediaTypeHeaderValue "application/json"
this.PostAsync (requestUri, content)

I just ran into this issue and lost some hours solving it.
It seems the serializer that is used in HttpClient.PostAsJsonAsync does not handle fsharp types well. The # symbol issue is also not easily googleable.
Using the following code however seems to work:
task {
let content = JObject.FromObject({ Id = "foo" }).ToString()
let! response = client.PostAsync("url", new StringContent(content, Encoding.UTF8, "application/json"))
return "Hello world"
}

Related

Single Line string is failing during deserialization using Jsonsoft

I have the following line of code
public T PostData<T>(string url, object content)
{
var result = default(T);
var response = string.Empty;
// other lines of code setting up client in between .. ignoring them for readability
response = client.UploadString(new Uri(url, UriKind.Relative), contentString);
result = JsonConvert.DeserializeObject<T>(response, settings);
}
result line is where the code is failing.
The above post content is called from
public string GetCustomer(CustomerReq request)
{
var url = string.Format(_apiUrl + "/GetCustomer");
var result = _webApiRouter.Route<string>(url, Enums.HttpMethodType.Post, request);
return result;
}
The above code is where we pass the type as string.
It works fine for all objects until I get a as string and respone is one word ex: "test"
Int , byte , double seem to work fine .
So now I want it as below. What is the best way to handle this code in a if condition
if (response = string i.e one word )
result =(T)Convert.ChangeType(response, typeof(T));
DeserializeObject() can handle plain strings as long as they are quoted:
var test = JsonConvert.DeserializeObject<string>("\"test\"");
Working example: https://dotnetfiddle.net/35qWGB
In case you cannot change the endpoint you can simply create a second overload of PostData() that doesn't do any deserialization:
public string PostData(string url, object content)
{
// other lines of code setting up client in between .. ignoring them for readability
return client.UploadString(new Uri(url, UriKind.Relative), contentString);
}
You could also do something weird like:
public T PostData<T>(string url, object content)
{
// other lines of code setting up client in between .. ignoring them for readability
var response = client.UploadString(new Uri(url, UriKind.Relative), contentString);
return typeof(T) == typeof(string)
? (T)(object)response
: JsonConvert.DeserializeObject<T>(response, settings);
}

Why do I get "invalid_form_data" error when trying to post to Slack-API?

When I use this method:
public async Task<HttpResponseMessage> UploadFileAsync(MultipartFormDataContent requestContent)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, UriMethod);
request.Content = requestContent;
var response = await _httpClient.SendAsync(request);
return response;
}
I allways get the answer:
{"ok":false,"error":"invalid_form_data"}
so I tried to explicitly tell it the 'mediaType', I tried "application/json" and others, but with all of them I get the same error. Here is the full Main-method that calls the upper method:
namespace TestArea
{
class MainArea
{
public static void Main( string[] args)
{
try
{
Task.WaitAll(SendMessage());
}
catch(Exception ex)
{
Console.WriteLine(ex);
Console.ReadKey();
}
}
private static async Task SendMessage()
{
var client = new BpsHttpClient("https://slack.com/api/chat.postMessage");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
JO.text = "This is so much fun :D !";
var Json = JsonConvert.SerializeObject(JO, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
var StringJson = new StringContent(Json, Encoding.UTF8, "application/json");
var requestContent = new MultipartFormDataContent();
requestContent.Add(StringJson);
var Response = await client.UploadFileAsync(requestContent);
string AnswerContent = await Response.Content.ReadAsStringAsync();
}
When I use this method:
public async Task<HttpResponseMessage> SendMessageAsync(FormUrlEncodedContent content)
{
var response = await _httpClient.PostAsync(UriMethod, content);
return response;
}
so basically I am passing "FormUrlEncodedContent" instead of "MultipartFormDataContent" in this, and then I get the response I want and can work wiht it. BUT this is of little use to me since I have to use "MultipartFormDataContent" to be able to send files with my requests.
Anyone have an idea what is failing here? Why does it not like the one content-type but the other one? I'd be gratefull for tipps and ideas!
You get the error "invalid_form_data", because the API method chat.postMessage does not support requests with multipart/form-data.
As you can see from the documentation under "Accepted content types" this method only accepts: application/x-www-form-urlencoded, application/json
Note that you can not upload files to chat.postMessage.
If you want to upload files, please use the API method files.upload, which also supports multipart/form-data.
See also my answer here for how to upload files with a comment.

Sent complex object with api

There are many topic but nothing is helpful to me.
I will show you what have I done, and you can tell me what is wrong.
In api I want to desirialize complex object that is send from client:
[HttpPost]
public async Task Post([FromBody]string item)
{
WorkItem data = JsonConvert.DeserializeObject<WorkItem>(item);
//...
}
How I post complex object:
public async Task AddWorkItem()
{
using (var client = HttpClientFactory.GetClient())
{
var item = new WorkItem()
{
Description = DateTime.Now.ToString("h:mm:ss tt"),
Id = 11
};
var inner1 = new ItemUsage()
{
Id = 45,
UsedFor = "something"
};
Collection<ItemUsage> Usage = new Collection<ItemUsage>();
Usage.Add(inner1);
item.Usage = Usage;
string output = JsonConvert.SerializeObject(item);
var response = await client.PostAsync("api/WorkItem", new StringContent(output));
if (!response.IsSuccessStatusCode)
{
throw new Exception((int)response.StatusCode + "-" + response.StatusCode.ToString());
}
}
I get error : 415-UnsupportedMediaType .
Maybe my aproach is not best?
I need some clean way to post any complex object and my idea is to do this with Json string.
What do you think?
you are missing one of the key features of WebAPI called model binding or parameter binding. Before the Controller's Action is invoked the incoming data is bound to the Actions parameters. In other words the WebAPI pipeline will handle most deserialization for you. In your example your parameter type is a string, but what you are sending to the server cannot be converted into a string. Supposing the rest of your plumbing is fine, changing the parameter type to WorkItem should do the trick.
public async Task Post([FromBody]WorkItem item)
If you must accept a string parameter then you must ensure that the body of the request can be converted to a string or write a custom model binder. See here for more info on raw request body handling
Look like issue is with the datetime serialization. Please see the below answer for help
JsonConvert.SerializeObject vs JsonSerializer.Serialize
Ok it is now clear to me. I will add whole code so that someone else can use it.
[HttpPost]
public async Task Post([FromBody]WorkItem item)
{
//No need to deserialize, you can use item here
}
Call api:
public async Task AddWorkItem()
{
using (var client = HttpClientFactory.GetClient())
{
//Complex object example::
///////////////////
var item = new WorkItem()
{
Description = DateTime.Now.ToString("h:mm:ss tt"),
Id = 11
};
var inner1 = new ItemUsage()
{
Id = 45,
UsedFor = "something"
};
Collection<ItemUsage> Usage = new Collection<ItemUsage>();
Usage.Add(inner1);
item.Usage = Usage;
////////////////////////////
var json = JsonConvert.SerializeObject(item);
var content = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
var response = await client.PostAsync("api/WorkItem", content);
if (!response.IsSuccessStatusCode)
{
throw new Exception((int)response.StatusCode + "-" + response.StatusCode.ToString());
}
}
}
Create client
public static class HttpClientFactory
{
public static HttpClient GetClient()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:1431");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
}

Consuming json input from url ,deserialize into c# & retrieve values from web API

I have been stuck in trying figure out the syntax for a particular scenario.
Scenario: When I give a JSON string as argument in the URL, I want the url to consume an API and retrieve the details from that API, as per the given input.
My project needs deserialization into c# so, I used JSON.NET for the same.
Say: input is - Profile-id : 123456789
The output should consume details pertaining to that Pid and display.
The i/p given in url:
https://www.docscores.com/widget/api/org-profile/demo-health/npi/123456789
The expected o/p:
json string
What i have been doing is :
string url = "https://www.docscores.com/widget/api/org-profile/demo-health/npi/?profile-id=ShowProfile";
string data = GET(url);
dynamic jsonDe = JsonConvert.DeserializeObject(data);
var phyRatings = Convert.ToString(jsonDe.profile.averageRating);
Console.WriteLine(phyRatings);
public string ShowProfile(string pid)
{
}
public static string GET(string url)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream);
string data = reader.ReadToEnd();
reader.Close();
stream.Close();
return data;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return null;
}
So, when I pass profile-id as 123456789 in the url, I want the syntax to extract other info with this Profile-id
I AM totally confused with the syntax in C#. How can I pass the argument and write inside the ShowProfile function? I searched everywhere but not able to find the correct syntax.
Can someone please tell me if this is the right way to do it?
EDIT: Sounds like you have two questions here. First is how to pass your Profile-Id in the URL, and the second is how to deserialize the JSON result into a C# object. But let me know if I'm misunderstanding.
For passing 123456789 as your profile ID, you just need to concatenate it into the URL string. So you might have
public string ShowProfile(string pid)
{
ProfileInfo info = GET(pid);
// Do what you want with the info here.
}
public static ProfileInfo GET(int profileId)
{
try
// Note this ends in "=" now.
string basePath = "/widget/api/org-profile/demo-health/npi/?profile-id=";
string path = basePath + profileId.ToString();
//...
ProfileInfo would be your custom class to match the JSON structure.
Then to deserialize the result, in your GET() method, you might instead try calling the service using HttpClient from the Microsoft.AspNet.WebApi.Client NuGet package, and then read that directly into a C# object whose structure maps to the JSON response you get (see example below). Your GET() method could then return that object, and then it'd be trivial for the ShowProfile() method to read the properties you want from that C# object.
public static ProfileInfo GET(int profileId)
{
try
{
// Note this ends in "=" now.
string basePath = "/widget/api/org-profile/demo-health/npi/?profile-id=";
string path = basePath + profileId.ToString();
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://www.docscores.com");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
ProfileInfo info = await response.Content.ReadAsAsync<ProfileInfo>();
return info;
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return null;
}
More code and info at MSDN: Calling a Web API From a .NET Client in ASP.NET Web API 2 (C#)

Using HttpResponseMessage to view errors

I can use the following code which works fine to log in using my Web API. However, whensomething goes wrong and an error is returned, I don't know how to get at the contect of the HttpResponseMessage. If I just use the ReadAsStringAsync() method, I get the error in the string, but what type is it? If I know the type I can get the object.
HttpResponseMessage response = await client.PostAsJsonAsync("api/Login", loginObject);
if (response.IsSuccessStatusCode)
{
var _logonResponse = await response.Content.ReadAsAsync<TokenResponseModel>();
}
else
{
// an error has occured, but what is the type to read?
var test = await response.Content.ReadAsStringAsync();
}
On the server it is returning;
BadRequest(ModelState).
Thanks for any help.
EDIT: I have since resolved the issue like this;
var value = await response.Content.ReadAsStringAsync();
var obj = new { message = "", ModelState = new Dictionary<string, string[]>() };
var x = JsonConvert.DeserializeAnonymousType(value, obj);
The result returned back is an JSON object with a "Message" and a "ModelState" properties.
The "ModelState" state value is an object, whose properties are arrays of strings. The property list of "ModelState" varies from time to time depending on which property is invalid.
Hence, to get a strong-type returned response, why don't you manipulate the ModelState yourself on the server side, and then pass the object to the BadRequest() method
Here is just grabbing raw json in text of error message...
if (!response.IsSuccessStatusCode)
{
dynamic responseForInvalidStatusCode = response.Content.ReadAsAsync<dynamic>();
Newtonsoft.Json.Linq.JContainer msg = responseForInvalidStatusCode.Result;
result = msg.ToString();
}
Try IOStreamReader. This is vb.net, but that's not too hard to convert:
IOStreamReader = New IO.StreamReader(Response.GetResponseStream)
RespStr = IOStreamReader.ReadToEnd
Or
Dim HttpReq As Net.HttpWebRequest = Nothing
Dim HttpStatus As Net.HttpStatusCode = Nothing
HttpResp = CType(HttpReq.GetResponse(), Net.HttpWebResponse)
'verify the response
HttpStatus = HttpResp.StatusCode
try following :
try
{
HttpResponseMessage response = await client.PostAsJsonAsync("api/Login", loginObject);
response.EnsureSuccessStatusCode();
var _logonResponse = await response.Content.ReadAsAsync<TokenResponseModel>();
return _logonResponse;
}
catch (Exception ex)
{
throw ex;
}

Categories

Resources