As the title says, the code is as below. I have tried setting chunkedTransfer=false, Content-Type application/json, WWWForm, building the JSON object manually, and HttpClient. For Content-Type application/json, the API isn't even hit. For the rest, the body is an empty object. I have no idea what the issue is here looking through StackOverflow, YouTube, Unity documentation, and all the other resources.
As of this morning I am using Newtonsoft.Json to serialize the JSON body. I think the biggest issue right now is that when I set webRequest.SetRequestHeader("Content-Type", "application/json"); the API route doesn't even receive the request.
async Task<string> makeRequest()
{
string url = API_BASE + "/users";
Dictionary<string, string> body = new Dictionary<string, string>();
body.Add("username", username);
body.Add("password", password);
body.Add("email", email);
using (UnityWebRequest webRequest = UnityWebRequest.Post(url, JsonConvert.SerializeObject(body)))
{
await webRequest.SendWebRequest();
string result = Encoding.UTF8.GetString(webRequest.downloadHandler.data);
if (webRequest.result != UnityWebRequest.Result.Success)
{
JSONNode error = JSON.Parse(result);
registerAndLoginError.GetComponent<Text>().text = error["error"]["message"];
registerAndLoginError.SetActive(true);
return "";
}
}
BasicLogin();
return "";
}
So I had seen this solution elsewhere, but I continued ignoring it because it's hacky af and seems like something that should be addressed. However, I'm at the point of not caring right now.
Use UnityWebRequest.Put instead of UnityWebRequest.Post
Set webRequest.method = "POST";
Set webRequest.SetRequestHeader("Content-Type", "application/json");
This works, but it feels really bad and doesn't make any sense.
The builtin JsonUtility does NOT support Dictionary! It follows the same serialization rules as the Inspector, see Script Serialization.
=> The JsonUtility.ToJson(body) will just return "" or in best case "{}".
Try to rather create the JSON string "manually" like e.g.
var json = "{\"username\":\""+username+"\", \"password\":\""+password+"\", \"email\":\""+email+"\"}";
or use a different library like e.g. Newtonsoft .NET Json (available as Package via the PackageManager) which supports direct (de)serialization of collections and Dictionary.
Related
I am testing a REST API post, and it works well when I try it on Postman. However, in some scenario (related to the posting XML data) if I post with HttpClient API, I would receive the following error:
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
But the same XML content works fine on Postman with status OK and proper response.
What is the differences between using the C# HttpClient API and the postman testing? How can I configure my API call to match with the behavior on postman?
Here I attached the source code, and the Postman screenshot
public void createLoan()
{
string baseCreateLoanUrl = #"https://serverhost/create?key=";
var strUCDExport = XDocument.Load(#"C:\CreateLoan_testcase.xml");
using (var client = new HttpClient())
{
var content = new StringContent(strUCDExport.ToString(), Encoding.UTF8, Mediatype);
string createLoanApi = string.Concat(baseCreateLoanUrl, APIKey);
try
{
var response = client.PostAsync(createLoanApi, content).Result;
}
catch (Exception ex)
{
MessageBox.Show("Error Happened here...");
throw;
}
if (response.IsSuccessStatusCode)
{
// Access variables from the returned JSON object
string responseString = response.Content.ReadAsStringAsync().Result;
JObject jObj = JObject.Parse(responseString);
if (jObj.SelectToken("failure") == null)
{
// First get the authToken
string LoanID = jObj["loanStatus"]["id"].ToString();
MessageBox.Show("Loan ID: " + LoanID);
}
else
{
string getTokenErrorMsg = string.Empty;
JArray errorOjbs = (JArray) jObj["failure"]["errors"];
foreach (var errorObj in errorOjbs)
{
getTokenErrorMsg += errorObj["message"].ToString() + Environment.NewLine;
}
getTokenErrorMsg.Dump();
}
}
}
Thanks for Nard's comment, after comparing the header, I found the issue my client header has this:
Expect: 100-continue
While postman doesn't has.
Once I removed this by using the ServicePointManager:
ServicePointManager.Expect100Continue = false;
Everything seems fine now. Thanks all the input!
My gut tells me it's something simple. First, we know the API works, so I'm thinking it's down to how you are using the HttpClient.
First things first, try as suggested by this SO answer, creating it as a singleton and drop the using statement altogether since the consensus is that HttpClient doesn't need to be disposed:
private static readonly HttpClient HttpClient = new HttpClient();
I would think it would be either there or an issue with your content encoding line that is causing issues with the API. Is there something you are missing that it doesn't like, I bet there is a difference in the requests in Postman vs here. Maybe try sending it as JSON ala:
var json = JsonConvert.SerializeObject(strUCDExport.ToString());
var content = new StringContent(json, Encoding.UTF8, Mediatype);
Maybe the header from Postman vs yours will show something missing, I think the real answer will be there. Have fiddler running in the background, send it via Postman, check it, then run your code and recheck. Pay close attention to all the attribute tags on the header from Postman, the API works so something is missing. Fiddler will tell you.
I was struggling with this for 2 days when I stumbled over Fiddler which lets you record the traffic to the service. After comparing the calls I saw that I had missed a header in my code.
I've a problem as I need to send some json to a url. When I send all my json and token to the page.
Then there will be no content JSON value into the system.
I have checked up on whether there is some content and it is there, but it sends just do not like json values.
string apiKeyToken = model.reepaytoken; // TOKEN HERE.
string URLLink = APIClassPay.HelperPay.CreateCustomerURL;//URL to send it json to.
WebClient client = new WebClient();
//JSON coming here!
var JSONCustomer = APIClassPay.HelperPay.CreateCustomer(model.Brugernavn, model.Adresse, model.Byen, model.Postnr.ToString(), model.Mobil.ToString(), model.Fornavn, model.Efternavn);
client.Headers.Add("text/json", JSONCustomer);
client.Headers.Set("X-Auth-Token", apiKeyToken);
string reply = client.DownloadString(URLLink);
When I blow my json looks like this.
[HttpPost]
public ActionResult information(BuyMedlemskabViewModel model)
{
DataLinqDB db = new DataLinqDB();
var Pric = db.PriceValues.FirstOrDefault(i => i.id == model.HiddenIdMedlemskab);
if (Pric != null)
{
string _OrderValue = DateTime.Now.Year + Helper.Settings.PlanValue();
Session[HelperTextClass.HelperText.SessionName.OrderId] = _OrderValue;
Session[HelperTextClass.HelperText.SessionName.FakturaId] = model.HiddenIdMedlemskab;
Session[HelperTextClass.HelperText.SessionName.fornavn] = model.Fornavn;
Session[HelperTextClass.HelperText.SessionName.efternavn] = model.Efternavn;
Session[HelperTextClass.HelperText.SessionName.Adresse] = model.Adresse;
Session[HelperTextClass.HelperText.SessionName.Post] = model.Postnr;
Session[HelperTextClass.HelperText.SessionName.Byen] = model.Byen;
Session[HelperTextClass.HelperText.SessionName.Mobil] = model.Mobil;
string apiKeyToken = model.reepaytoken;.
string URLLink = APIClassPay.HelperPay.CreateCustomerURL;//URL to send it json to.
WebClient client = new WebClient();
//JSON coming here!
var JSONCustomer = APIClassPay.HelperPay.CreateCustomer(model.Brugernavn, model.Adresse, model.Byen, model.Postnr.ToString(), model.Mobil.ToString(), model.Fornavn, model.Efternavn);
client.Headers.Add("text/json", JSONCustomer);
client.Headers.Set("X-Auth-Token", apiKeyToken);
string reply = client.DownloadString(URLLink);
}
return RedirectToAction("information");
}
EDIT - Update (ERROR HERE):
ReePay API reference: https://docs.reepay.com/api/
I think there are a few things, you'll have to fix:
First of all you're obviously trying to create a ressource (usually a POST or PUT, speaking in REST-words but you're using WebClient's DownloadString-method which performs a GET. So I think you should probably use a POST or PUT instead but which one to chose exactly depends on the web service you're contacting.
Then you seem to have mistaken the Content-Type-header and tried to pack the payload in there. The payload - your customer JSON - will have to be put into the request's body.
Based on your previous questions I assume the service you're trying to contact is either PayPal or QuickPay. To further help you with this question, it'd be helpful if you could specify which one you use.
If it's QuickPay, please notice that there's an official .NET client which you could use instead of using WebClient on you own.
But anyway for making HTTP requests I'd suggest you to use HttpClient in favor of WebClient. You'd generally do it in a way like this:
using (var httpClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post,
APIClassPay.HelperPay.CreateCustomerURL);
request.Headers.Add("X-Auth-Token", apiKeyToken);
request.Headers.Add("Content-Type", "application/json");
request.Content = new StringContent(JSONCustomer);
var response = await httpClient.SendAsync(request);
}
EDIT:
As you clarified in a comment, the service you're using is Reepay. If you take a look at the documentation of the create customer method, you can see, that the necessary HTTP method is POST. So the code snippet above should generally fit.
Regarding the compilation error you faced, I updated the code-snipped above. There was a mistake in the variable names I chose. Please note, that you dropped the keyword await as I can see from your screenshot. Please re-enter it. If the compiler complains about it, it's very likely that the .NET framework version of your project is less than 4.5 which is necessary to use async/await.
So you should update your project's .NET framework version at best to version 4.6.1 as Microsoft recently announced that support for 4.5 and others is discontinued. Have a look here on how to do that.
We have found that using an attribute on the controller we can have the controllers default json formatter replaced :-
public class WcsControllerConfigAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
var mediaType = ConfigurationManager.AppSettings["WcsMediaType"];
if (mediaType == "application/json")
{
var formatter = controllerSettings.Formatters.OfType<JsonMediaTypeFormatter>().Single();
controllerSettings.Formatters.Remove(formatter);
formatter = new JsonMediaTypeFormatter
{
SerializerSettings =
{
ContractResolver = new NullableValueContractResolver(),
NullValueHandling = NullValueHandling.Include,
DefaultValueHandling = DefaultValueHandling.Populate
}
};
controllerSettings.Formatters.Add(formatter);
}
}
}
But this seems to have the effect of replacing the formatter not only for the result we wish to return but also for the json body of the incoming request. Is there a way to replace only the formatter for the response and not the request?
Edit: OK In response the doctors remark I think I should possibly state what my ultimate goal is because then perhaps you can offer an even better solution than using the formatter.
We are implementing a RESTful server with a partner. Their system (based on websphere) sends us perfectly standard JSON except that everything is enclosed in quotes. The standard formatter/parser does not miss a beat and happily converts all the incoming quoted values to dates, decimals, booleans, etc. We are not looking to change this behaviour.
The responses are also standard JSON but they also want all the values to be quoted. So in our data transfer object everything that is not either a child object or an array is a string and gets serialised correctly most of the time. But a further limitation is that all the properties that are null have to be treated differently to how most systems would treat them. Namely:
Null strings are passed as empty string. "property":"" and not "property":null
Null arrays are passed as empty arrays. "array":[] and not "array":null
Null child objects are passed as an empty object. "object":{} and not "object":null
And we are not allowed to simply omit them either. Not that that would be easy I suspect.
I hope that makes the requirement a little clearer. Our solution to use the formatter was based on another question Json Convert empty string instead of null and this works well for what it's worth but stumbles only because it tries to apply the rules in both directions.
You can write a custom MediaTypeFormatter to achieve what you need.
An official tutorial on how to write custom media-type formatters is available in this link http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters
Doing so will allow you to perform an http request using JSON and return a different media-type than JSON.
Keep in mind that when doing so the client needs to send the desired media-type on the request (in the accept header).
ICredentials credentials = CredentialCache.DefaultCredentials;
NetworkCredential credential = credentials.GetCredential(uri, "Basic");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.Credentials = credential;
request.Method = "GET";
request.Headers.Add("api-version", "1.0");
request.Headers.Add("customer-code", code);
request.Headers.Add("Authorization", AuthUser);
request.ContentType = "application/json"; //The request is sent in JSON
request.Accept = "text/plain"; //You ask the server to return the response in the format you need it
HttpWebResponse response = (HttpWebResponse) request.GetResponse();
Stream receiveStream = response.GetResponseStream();
YES - You can..
You can define 2 different implementation of MediaTypeFormatter. Set the properties CanReadType() & CanWriteType() as per your choice. Then implement WriteToStream() & ReadFromStream() in both formatters. Finally register both of them for your specific route.
using (WebClient wc = new WebClient())
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
string HtmlResult = wc.UploadString(url, "sign=fsadfasdf&charset=utf-8");
}
Server can get value of sign and charset.
But there is a third parameter LIST, which is a list of object (this object is an entity class).
How can I pass this parameter to the server?
I tried to use "sign=fsadfasdf&charset=hhh&list=" + Json(list) as postData (convert List to json string). But the server didn't get value of this list param.
I hate to post 1 line answers with links in them but this has been solved on here before ...
POSTing JsonObject With HttpClient From Web API
HttpClient class is designed to solve exactly this problem, i believe its found in the nuget package "Microsoft.AspNet.WebApi.Client", this should add the namespace "System.Net.Http" to your project.
It's also geared at being completely async which should be nicer to your server!
EDIT:
To post an array / collection you would do something like this ...
var myObject = (dynamic)new JsonObject();
myObject.List = new List<T>();
// add items to your list
httpClient.Post(
"",
new StringContent(
myObject.ToString(),
Encoding.UTF8,
"application/json"));
I need to HTTP POST a complex type to a web service (which I don't controll). I believe the web service was built using an older version of ASP.NET MVC. It model binds payloads formatted as form-url-encoded.
If I fire the following at it, it works perfectly. As you can see, I've manually created a collection of key/value pairs.
var values = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("Username", "some-username"),
new KeyValuePair<string, string>("Password", "some-password"),
new KeyValuePair<string, string>("Product", "some-product")
};
var content = new FormUrlEncodedContent(values);
var response = new HttpClient().PostAsync(url, content).Result;
But I don't want to have to do this, I just want to send complex types if I can.
var content = new ComplexType("some-username", "some-password", "some-product");
var response = new HttpClient().PostAsync(url, content).Result;
I believe there used to be a HttpRequestMessage<T> but that's been dropped in favour of
HttpClient.PostAsJsonAsync<T>(T value) sends “application/json”
HttpClient.PostAsXmlAsync<T>(T value) sends “application/xml”
But I don't want to send Json or XML I want to send form-url-ecncoded without the hassle of converting complex types to collections of key/value pairs.
Essentially I'd also like to know the answer to this question that Jaans poses (His is the second comment to the second answer).
Can anyone advise please.
Flurl [disclosure: I'm the author] provides a method that seems to be exactly what you're looking for:
using Flurl.Http;
var resp = await url.PostUrlEncodedAsync(new {
Username = "some-username",
Password = "some-password",
Product = "some-product",
});
Flurl is small and portable, and uses HttpClient under the hood. It is available via NuGet:
PM> Install-Package Flurl.Http
Since you've almost got a solution that does work, I'd say just go with it. Organize your code inside an extension method so that you can post using that, something like:
public static async Task<HttpResponseMessage> PostAsFormUrlEncodedAsync<T>(
this HttpClient httpClient, T value)
{
// Implementation
}
Your implementation just needs to serialize your object into form-encoded values, which you should be able to do easily with reflection.
Then you can call the code exactly as you would for JSON or XML.
You can do this:
var content = new ComplexType("some-username", "some-password", "some-product");
var response = new HttpClient().PostAsync<ComplexType>(url, content, new FormUrlEncodedMediaTypeFormatter()).Result;