How to Seriailize an c# object into Json, while using httpClient? - c#

I have a little program which should communicate with "Slack". In an older Version I used "Dictionary<string, string>" and then put them into UrlEncodedContent - which worked fine.
Now I am trying to create a Json-object, using Newtonsoft's Nuget-package and (in my opinion) formatting my object the way they say on their website.
Problem is, when I try to make a simple request, my program just runs to one specific line in the code(var response = await _httpClient.SendAsync(request);) and then it just ends. It doesn't throw an exception or display any kind of message, it simply ends on this line. I went through my code step by step while debugging, that's how I know it ends on exactly this line. And I just don't know why!
Now my code:
First, my object...
namespace BPS.Slack
{
public class JsonObject
{
//generally needed parameters
[JsonProperty("ok")]
public bool ok { get; set; }
[JsonProperty("error")]
public string error { get; set; }
[JsonProperty("channel")]
public string channel { get; set; }
[JsonProperty("token")]
private string token = "xoxp-MyToken";
[JsonProperty("as_user")]
public bool as_user = false;
[JsonProperty("username")]
public string username { get;set; }
//--------------------------------
//only needed for textmessages
[JsonProperty("text")]
public string text { get; set; }
//--------------------------------
//for posting messages with data attached
[JsonProperty("initial_comment")]
public string initial_comment { get; set; }
[JsonProperty("file")]
public string file { get; set; }
[JsonProperty("channels")]
public string channels { get; set; }
//--------------------------------
//for getting the latest message from a channel
[JsonProperty("count")]
public string count = "1";
[JsonProperty("unreads")]
public bool unreads = true;
}
}
now the client:
namespace BPS.Slack
{
public class BpsHttpClient
{
private readonly HttpClient _httpClient = new HttpClient { };
public Uri UriMethod { get; set; }
public BpsHttpClient(string webhookUrl)
{
UriMethod = new Uri(webhookUrl);
}
public async Task<HttpResponseMessage> UploadFileAsync(MultipartFormDataContent requestContent)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, UriMethod);
request.Content = requestContent;
var response = await _httpClient.SendAsync(request);
return response;
}
}
}
and the main
namespace TestArea
{
class MainArea
{
public static void Main( string[] args)
{
try
{
Task.WhenAll(SendMessage());
}
catch(Exception ass)
{
Console.WriteLine(ass);
Console.ReadKey();
}
}
private static async Task SendMessage()
{
var client = new BpsHttpClient("https://slack.com/api/im.history");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
var Json = JsonConvert.SerializeObject(JO);
var StringJson = new StringContent(Json, Encoding.UTF8);
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(StringJson);
var Response = await client.UploadFileAsync(content);
string AnswerContent = await Response.Content.ReadAsStringAsync();
Console.WriteLine(AnswerContent);
Console.ReadKey();
}
}
}
I had the same problem in my older version, BUT only as I wanted to DEserialize an answer I got from Slack. It had to do with my object I tried do deserialize the answer into. But this time I can not figure out what's wrong. But, as I said, I do not have any experience with using serialized objects as Json-property to send requests... anyone has an idea what is wrong with my code?
EDIT: This problem is kinda solved. But there is a follow up problem.
Okay, I found out that the reason for the abprubt termination was the
Task.WhenAll(SendMessage());
it should be
Task.WaitAll(SendMessage()); Why??? Somebody said I should use WhenAll, but obviously it doesn't work properly in this case...
Now I get a response from Slack, but now a different problem has arisen. 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 method that calls the upper mehtod:
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 bascially I am passing "FormUrlEncodedContent" instead of "MultipartFormDataContent" in this, and then I get the response I want and can work wiht it. BUT this i 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 are serializing your object to Json and then adding it to a Multipart body, that's quite strange. Unless you're uploading binary data (eg Files), there is no need to use MultipartFormDataContent.
You are can directly post your JsonObject serialized as JSON:
public async Task<HttpResponseMessage> PostJsonAsync(StringContent content)
{
var response = await client.PostAsync(url, content);
return response;
}
var client = new BpsHttpClient("https://slack.com/api/im.history");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
var Json = JsonConvert.SerializeObject(JO);
var StringJson = new StringContent(Json, Encoding.UTF8);
var Response = await client.PostJsonAsync(content);
Also this is should be POST on the UploadFileAsync function.
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, UriMethod);

so I figured out that in the Main() the problem was this:
Task.WhenAll(SendMessage());
I should instead use:
Task.WaitAll(SendMessage());
Anyone who has more knowledge on this, please elaborate why!

Related

Why do two (almost) similar methods(using c#) of uploading a file to Slack cause different outcomes?

so this question baffels me. I'll post quite abit of code to explain this one. First, I have and "old" version of code(c#), which I used to post messages and files to Slack. And this code works fine for me! The method of interest is the following:
public class PostMessage
{
private string _token = "xoxp-MyToken";
public string token { get { return _token; } }
public string channel { get; set; }
public string text { get; set; }
public MultipartFormDataContent UploadFile()
{
var requestContent = new MultipartFormDataContent();
var fileContent = new StreamContent(GetFile.ReadFile());
requestContent.Add(new StringContent(token), "token");
requestContent.Add(new StringContent(channel), "channels");
requestContent.Add(fileContent, "file", Path.GetFileName(GetFile.path));
return requestContent;
}
public static class GetFile
{
public static string path = #"C:\Users\f.held\Desktop\Held-Docs\Download.jpg";
public static FileStream ReadFile()
{
FileInfo fileInfo = new FileInfo(path);
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
return fs;
}
}
Here is my client:
public class SlackClient
{
public Uri _method { get; set; }
private readonly HttpClient _httpClient = new HttpClient {};
public SlackClient(Uri webhookUrl)
{
_method = webhookUrl;
}
public async Task<HttpResponseMessage> UploadFileAsync(MultipartFormDataContent requestContent)
{
var response = await _httpClient.PostAsync(_method, requestContent);
return response;
}
}
And I call all of this in this Main:
public static void Main(string[] args)
{
Task.WaitAll(TalkToSlackAsync());
private static async Task TalkToSlackAsync()
{
var webhookUrl = new Uri("https://slack.com/api/files.upload");
var slackClient = new SlackClient(webhookUrl);
PostMessage PM = new PostMessage();
PM.channel = "DCW21NBHD";
var cont = PM.UploadFile();
var response = await slackClient.UploadFileAsync(cont);
string content = await response.Content.ReadAsStringAsync();
}
}
So far, so good! But now it gets interesting. I build a similar version, in which I use Newtonsoft's Json NuGet-package
Now, first the code:
the client:
public async Task<HttpResponseMessage> SendFileAsync(MultipartFormDataContent requestContent)
{
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "xoxp-MyToken");
var response = await _httpClient.PostAsync(UriMethod, requestContent);
return response;
}
the same Filestram-method for reading the file:
public class Message
{
public class GetFile // Just pass a path here as parameter!
{
public static string path = #"C:\Users\f.held\Desktop\Held-Docs\Download.jpg";
public static FileStream ReadFile()
{
FileInfo fileInfo = new FileInfo(path);
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
return fs;
}
}
the Json-class which I serialize:
public class JsonObject
{
[JsonProperty("file")]
public string file { get; set; }
[JsonProperty("channels")]
public string channels { get; set; }
}
And the Main:
class MainArea
{
public static void Main( string[] args)
{
try
{
Task.WaitAll(SendMessage());
}
catch(Exception dudd)
{
Console.WriteLine(dudd);
Console.ReadKey();
}
}
private static async Task SendMessage()
{
var client = new BpsHttpClient("https://slack.com/api/files.upload");
JsonObject JO = new JsonObject();
JO.channels = "DCW21NBHD";
var Json = JsonConvert.SerializeObject(JO, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
var StringJson = new StringContent(Json, Encoding.UTF8, "multipart/form-data");
var DeSon = JsonConvert.DeserializeObject(Json);
Console.WriteLine(DeSon);
Console.ReadKey();
var requestContent = new MultipartFormDataContent();
var fileContent = new StreamContent(Message.GetFile.ReadFile());
requestContent.Add(fileContent, "file", Path.GetFileName(Message.GetFile.path));
requestContent.Add(StringJson);
var ResponseFile = await client.SendFileAsync(requestContent);
Console.WriteLine(ResponseFile);
Console.ReadKey();
}
}
So, both SEEM to work. But the latter of these methods does NOT post the file to the declared channel - it merely uploads it to Slack. Which would be fine, because I could then work with the 'public_url' to publicise it on any channel. BUT - BIG BUT - with the first method, it immediately loads it to my channel! And it tells me so in the response I get from Slack. The responses are in both exactly the same - except for the timestamps and file_id etc. obviously. But the ending is different!
Here is the ending of the response from the old version:
"shares":{"private":{"DCW21NBHD":[{"reply_users":[],"reply_users_count":0,"reply_count":0,"ts":"1544025773.001700"}]}},"channels":[],"groups":[],"ims":["DCW21NBHD"]}}
and here is the answer from the new version:
"shares":{},"channels":[],"groups":[],"ims":[]}}
Okay now, why on god's green earth does one method do that and the other one does not? :D
Thanks to anybody who has some insight and knowledge on this specific "issue" and is willing to share!
As stated in the documentation for files.upload:
Present arguments as parameters in application/x-www-form-urlencoded
querystring or POST body. This method does not currently accept
application/json.
So the reason this does not work is that you are trying to provide the API parameters like channels as JSON, when this method does not support JSON. The result is that those properties are ignore, which is why the image is uploaded, but not shared in the designated channel.
To fix it simply provide your parameters as application/x-www-form-urlencoded querystring as you did in your 1st example.
Note that in general only a subset of the Slack API methods support using JSON for providing the parameters as listed here. If you want to use JSON, please double-check if the API method supports it, or stick with x-www-form-urlencoded (which is the standard for POST) to be on the safe side.

C# Api controller not receiving json parameter

I am trying to create a basic test web api, and use a standard controller to test call it.
When I run it, by putting
http://localhost:55144/home/testapi
it'll run the catcher function and completely ignore the parameter.
Then, the catcher will happily return a value, which can be seen in the calling code.
I have tried various combinations of putting [FromBody], changing the type of the parameter in TestApiMethod, and seeing if making a list or array makes any difference.
I've noticed a couple of weird things:
- I'm not using the parameter in the code of TestApiMethod, but Visual Studio is not giving me an unused variable warning.
- If I make the type of the parameter testString a string or even an int, the code below will route to the catcher. If I make it some variation of a model or a Jobject, it will not. It gets as far as running
HttpResponseMessage response = await client.PostAsJsonAsync("api/activity", sendData);
then just returns to the web page.
Here's the code:
Models
public class testStringModel
{
public string testString { get; set; }
}
public class apiResponse
{
public string response { get; set; }
}
Home controller calling Api:
public void TestApi()
{
Task myTask = testApiCall();
}
private async Task<string> testApiCall()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:55144");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
testStringModel data = new testStringModel { testString = "cheese" };
string jsonData = JsonConvert.SerializeObject(data);
var sendData = new StringContent(jsonData, Encoding.UTF8, "application/json");
//var sendData = new Dictionary<string, string>
//{
// {"testString", "cheese"}
//};
HttpResponseMessage response = await client.PostAsJsonAsync("api/activity", sendData);
string responseBodyAsText = await response.Content.ReadAsStringAsync();
dynamic stuff = JObject.Parse(responseBodyAsText);
string finalResponse = stuff.response;
return finalResponse;
}
}
The api:
namespace ApplicationActivity
{
public class ActivityController : ApiController
{
[System.Web.Http.HttpPost]
public HttpResponseMessage Catcher()
{
apiResponse apiResponseObject = new apiResponse();
apiResponseObject.response = "You have somehow wound up in the catcher";
string json = JsonConvert.SerializeObject(apiResponseObject);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, "value");
response.Content = new StringContent(json, Encoding.Unicode, "application/json");
response.Headers.CacheControl = new CacheControlHeaderValue()
{
MaxAge = TimeSpan.FromMinutes(20)
};
return response;
}
[System.Web.Http.HttpPost]
public HttpResponseMessage TestApiMethod(string testString)
{
apiResponse apiResponseObject = new apiResponse();
apiResponseObject.response = "OK from test";
string json = JsonConvert.SerializeObject(apiResponseObject);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, "value");
response.Content = new StringContent(json, Encoding.Unicode, "application/json");
response.Headers.CacheControl = new CacheControlHeaderValue()
{
MaxAge = TimeSpan.FromMinutes(20)
};
return response;
}
}
}
Please will you tell me what I'm doing wrong with my code, how to fix it and what is happening when the code doesn't get to the catcher?
Thanks.
It turns out that I was using an older version of visual studio and as a result the whole thing got really confused with whether is was running .net core or not.
Upgrading to the latest and making sure the latest .net core is installed solved most of my troubles

What is wrong with this request (Azure management.azure.com API)

I am trying to get this azure API to work:
https://learn.microsoft.com/en-us/rest/api/machinelearning/webservices/createorupdate
The following code runs a GET request which returns successfully (200 OK and return body), but the Put request to the exact same uri at the bottom fails with a 400 "Bad request".
The code inbetween basically just unwraps the json output of the first request and tried to send the same unchanged data in the input format of the PUT on the same uri.
Could anyone help me see why this fails?
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await httpClient.GetAsync($"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.MachineLearning/webServices/{serviceName}?api-version=2016-05-01-preview");
var json = await response.Content.ReadAsStringAsync();
var f = JsonConvert.DeserializeObject<WebService>(json);
var requestBody = new RequestBody()
{
location = f.Location,
name = f.Name,
tags = f.Tags,
properties = f.Properties
};
var jsoncontent = JsonConvert.SerializeObject(requestBody, Formatting.None);
var content = new StringContent(jsoncontent, Encoding.UTF8, "application/json"); //tried simpler things like "{\"location\":\"West Europe\"}" with no result
var response2 = await httpClient.PutAsync($"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.MachineLearning/webServices/{serviceName}?api-version=2016-05-01-preview", content, new JsonMediaTypeFormatter());
// last line returns a 400 bad request only.
And then I use this custom object;
private class RequestBody
{
public string location { get; set; }
public string name { get; set; }
public WebServiceProperties properties { get; set; }
public IDictionary<string, string> tags { get; set; }
}
It seems it was one of those obvious things.
The ", new JsonMediaTypeFormatter()" snippet at the end ruined it. Removing it fixed the issue and it returns 200 OK without it.

MVC Api Controller Serielized parameters

I am doing an MVC 5 Application, and I am calling a API controller method that is in another Solution.
I am using HttpClient(). and I am calling PostAsJsonAsync with some parameters, an instance of a class.
It looks like this.
string apiUrl = "localhost:8080/api/";
ContactWF contactWF = new contactWF();
contactWF.contact_id=0;
contactWF.UserOrigin_id=20006
contactWF.ProcessState_id=2;
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(apiUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync(apiUrl + "Contact/Method", contactWF);
if (response.IsSuccessStatusCode)
{
return response.Content.ReadAsAsync<int>().Result;
}
}
My API controller method is like this.
[ActionName("Method")]
[HttpGet]
public int Method([FromBody] ContactWF userwf)
{
return 10;
}
It Works fine...
My problem is when I try Serialized the parameter class instance
I replace line
HttpResponseMessage response = await client.PostAsJsonAsync(apiUrl + "Contact/Method", contactWF);
with this one
string jsonData = JsonConvert.SerializeObject(contactWF);
HttpResponseMessage response = client.PostAsJsonAsync("api/Contact/Method", jsonData).Result;
I've got an Error:405...
It looks like the Json string it is not recognize as a Parameter.
My Json string looks like this.
"{\"Contact_id\":0,\"Description\":null,\"ProcessState_id\":2,\"Type_id\":0,\"Object_id\":0,\"Parent_id\":null}"
that is ContactWD class converter to json.
What´s wrong?
Method PostAsJsonAsync serialize parameter object himself, so it serialized your json string again.
If you need serialize object himself for some reason, then use method HttpClient.PostAsync
string jsonData = JsonConvert.SerializeObject(contactWF);
var stringContent = new StringContent(jsonData, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync("api/Filler/CountMensajeByUser", stringContent);
Change verb to HttpPost in your api controller
[ActionName("Method")]
[HttpPost]
public int Method([FromBody] ContactWF userwf)
{
return 10;
}
Update
You don't need to serialize object in PostAsJsonAsync
HttpResponseMessage response = client.PostAsJsonAsync("api/Contact/Method", contactWF).Result;
Take a look at sample code from microsoft
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/testing
internal class NewIdeaDto
{
public NewIdeaDto(string name, string description, int sessionId)
{
Name = name;
Description = description;
SessionId = sessionId;
}
public string Name { get; set; }
public string Description { get; set; }
public int SessionId { get; set; }
}
//Arrange
var newIdea = new NewIdeaDto("Name", "", 1);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);

Encryption/Decryption through DelegatingChannel (MessageHandler)

I am trying to use DelegatingChannel (MessageHandler) to decrypt the incoming message and encrypt the outgoing message. Code looks like this, but with some placeholder i couldn't figure out how to achieve.
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var inEncryptedMessage = request.Content.ReadAsOrDefault<EncryptedMessage>();
Message inMessage = inEncryptedMessage.Decrypt();
var newContent = new StringContent(inMessage.Data, Encoding.UTF8, "text/json");
newContent.Headers.ContentType = request.Content.Headers.ContentType;
request.Content = newContent;
return base.SendAsync(request, cancellationToken).ContinueWith(
task =>
{
HttpResponseMessage response = task.Result;
// need to serialize the data in response.Content as json
var outMessage = new Message {
Data = ... // but don't know how
};
var outEncryptedMessage = outMessage.Encrypt();
response.Content = new ObjectContent(outEncryptedMessage);
return response;
});
}
public class Message
{
public string Data { get; set; }
}
public class EncryptedMessage
{
public byte[] Key { get; set; }
public byte[] Message { get; set; }
}
I want to pass JSON string in the request to the Operation, and therein deserialise the JSON into object. But failed... But object was created, but all the properties are null.
So which stoped me to think about response aspect. But i still find the difficulties in read object from response.Content.
Please give me some suggestion if you have the answer.
Thanks a lot,
M
If I'm not wrong you could try to implement a DecryptOperationHandler : HttpOperationHandler<HttpRequestMessage>, <HttpRequestMessage>
This should decrypt your incoming message before passing it to the resource itself
Encryption could be done using a EncryptOperationHandler : HttpOperationHandler<HttpResponseMessage>, <HttpResponseMessage> which gets executed after your resource method has been executed

Categories