What I'm trying to do:
I'm trying to practise making HTTP calls (...if that is what it's called) from a simple ASP.NET MVC web application. To do this, I am attempting to get weather details from OpenWeatherMap. You can do this by:
Add the following parameter to the GET request: APPID=APIKEY
Example: api.openweathermap.org/data/2.5/forecast/city?id=524901&APPID=1111111111
My understanding, from my learning:
The controller is the one to make the above HTTP call.
My question:
How do I actually make that HTTP GET request, in ASP.NET MVC?
Use System.Net.Http.HttpClient.
You can do some basic reading from a website using something like the following:
using (var client = new HttpClient())
{
var uri = new Uri("http://www.google.com/");
var response = await client.GetAsync(uri);
string textResult = await response.Content.ReadAsStringAsync();
}
You may want to make sure to test response.IsSuccessStatusCode (checks for an HTTP 200 result) to make sure the result is what you expect before you parse it.
Related
I have the following set up:
JS client -> Web Api -> Web Api
I need to send the auth cookie all the way down. My problem is sending it from one web api to another. Because of integration with an older system, that uses FormsAuthentication, I have to pass on the auth cookie.
For performance reasons I share a list of HttpClients (one for each web api) in the following dictionary:
private static ConcurrentDictionary<ApiIdentifier, HttpClient> _clients = new ConcurrentDictionary<ApiIdentifier, HttpClient>();
So given an identifier I can grab the corresponding HttpClient.
The following works, but I'm pretty sure this is bad code:
HttpClient client = _clients[identifier];
var callerRequest = HttpContext.Current.Items["MS_HttpRequestMessage"] as HttpRequestMessage;
string authCookieValue = GetAuthCookieValue(callerRequest);
if (authCookieValue != null)
{
client.DefaultRequestHeaders.Remove("Cookie");
client.DefaultRequestHeaders.Add("Cookie", ".ASPXAUTH=" + authCookieValue);
}
HttpResponseMessage response = await client.PutAsJsonAsync(methodName, dataToSend);
// Handle response...
Whats wrong about this is that 1) it seems wrong to manipulate DefaultRequestHeaders in a request and 2) potentially two simultanious requests may mess up the cookies, as the HttpClient is shared.
I've been searching for a while without finding a solution, as most having a matching problem instantiates the HttpClient for every request, hence being able to set the required headers, which I'm trying to avoid.
At one point I had get requests working using a HttpResponseMessage. Perhaps that can be of inspiration to a solution.
So my question is: is there a way to set cookies for a single request using a HttpClient, that will be safe from other clients using the same instance?
Instead of calling PutAsJsonAsync() you can use HttpRequestMessage and SendAsync():
Uri requestUri = ...;
HttpMethod method = HttpMethod.Get /*Put, Post, Delete, etc.*/;
var request = new HttpRequestMessage(method, requestUri);
request.Headers.TryAddWithoutValidation("Cookie", ".ASPXAUTH=" + authCookieValue);
request.Content = new StringContent(jsonDataToSend, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
UPDATE:
To make sure that your HTTP client does not store any cookies from a response you need to do this:
var httpClient = new HttpClient(new HttpClientHandler() { UseCookies = false; });
Otherwise you might get unexpected behavior by using one client and sharing other cookies.
Is it somehow possible to make a Web api that calls another web api?
I am using the code below to access a web api from my web api, but it never return from the call. If I use the code from a console app, it is working fine.
public void DoStuff(){
RunAsync().Wait();
}
public static async Task RunAsync(){
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:53452/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP GET
HttpResponseMessage response = await client.GetAsync("umbraco/api/Member/Get?username=test");
if (response.IsSuccessStatusCode)
{
string user = await response.Content.ReadAsAsync<string>();
}
}
I also went through the same problem, after much research I discovered that the await operator does not stop the work if the HttpClient returns error 500. To work around the problem I used Task.Wait().
var response = client.GetAsync ("umbraco/api/Member/Get?username=test");
response.Wait ();
I hope this helps others.
Yes you can make a call to a remote web api within the action method of a web api controller.
Lets eliminate the obvious first.
If you set a breakpoint at the start of this action method it is getting hit right? If not then the issue lies in the routing not the action method.
If you set a breakpoint at the if statement does it get hit or is the client.GetAsync() call never returning?
If you haven't done already you may wish to use a tool like fiddler (http://www.telerik.com/fiddler) to compare the request & response from a working use of the api and this broken one. I know you said it is identical to a working implementation but I have found fiddler invaluable to verify exactly what is being sent "on the wire".
In the console, I follow up a call the site I'm on is is making and I can see the address (some.site.com/gettoken), message header and something that FF calls Message Body. It's in the latter that I can see the credentials that I've entered on the site that are being sent.
So, I've got the URL and the message body. Then, I've tried to implement the behavior using C# for my Azure service layer like so.
String url = #"https://some.site.com/gettoken";
String credentials = "username=super&password=secret";
using (WebClient client = new WebClient())
{
String output = client.UploadString(url, credentials);
result = output;
}
However, I get error 400 - bad result. What did I miss?
I've googled for some stuff but the only remotely relevant hits are talking about the upload methods, which I've used. Am I barking up the wrong tree entirely or just missing something tiny? Some people seem to get it to work but they're not tokenizing around. And I'm not certain enough to determine whether it's of relevance or not.
So, as a summary of what has been discussed in the comments: you can use the more modern HttpClient instead.
Note that this is the System.Net.Http.HttpClient and not Windows.Web.Http.HttpClient.
An example implementation could look like this:
public async Task<string> SendCredentials()
{
string url = #"https://some.site.com/gettoken";
string credentials = "username=super&password=secret";
using(var client = new HttpClient())
{
var response = await client.PostAsync(url, new StringContent(credentials));
return await response.Content.ReadAsStringAsync();
}
}
You might also be interested in System.Net.Http.FormUrlEncodedContent which allows you to pass in the parameters and their values so you don't have to construct the credentials value yourself.
More information on async/await.
I have been banging my head against the wall for the past 1 week now but without any success. Actually I'm writing a C# code(a web api controller action) to call another web api to make a post request with some json data payload in the request body. Syntax-wise there is nothing wrong with the code. But when I directly call the service(web api service) from web browser I get an Html form that has a multiline text box in it, rollback property (as radio button for true and false value for this property), drop down box with 2 options such as html and json (to get response in either format) and a button(for sending request to the server and making edits in the database). Now when I manually put json data inside text box and click the button on that html form edits are done successfully in the database but when programmatically(from my C# code) I send the same json data payload and make a post request edits are never done successfully rather I get an html response body through Fiddler that says status code success 200 but unable to complete operation,some parameters couldn't be recognized.
Here is my code
private static async Task<HttpResponseMessage> GeometryUpdateAsync(Feature updatedFeature, FeatureType featureType, int? objectid = null)
{
var jsonObject = new JObject();
dynamic esriId = jsonObject;
if (objectid == null)
{
objectid = updatedFeature.OBJECTID;
}
esriId.OBJECTID = objectid;
var mergedJsonString = JsonConvert.SerializeObject(new
{
geometry = JObject.Parse(updatedFeature.Geometry.ToString()),
attributes = JObject.Parse(esriId.ToString())
});
mergedJsonString = String.Format("[{0}]", mergedJsonString);
HttpResponseMessage response = null;
using (var client = new HttpClient())
{
//string arguments = "rollbackOnFailure=true&f=pjson&features=";
client.BaseAddress = new Uri("somebaseaddress");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Timeout = TimeSpan.FromSeconds(500.00);
//response = await client.PostAsJsonAsync("someuri", arguments + mergedJsonString);
response = await client.PostAsync("someuri", mergedJsonString, new System.Net.Http.Formatting.JsonMediaTypeFormatter());
if (response.IsSuccessStatusCode)
{
var v = response.Content.ReadAsStringAsync().Result;
}
}
}
When I look at the request body (through fiddler while making a post request through Html form) request body looks like
features=%5B%7B%22geometry%22%3A%7B%22paths%22%3A%5B%5B%5B-91.3888577181506%2C39.703158271352621%5D%91.381838690201192%2C39.690323806398723%5D%2C%5B-91.383241723424632%2C39.689645139311914%5D%2C%5B-91.3849700567206%2C39.6888078408094%5D%2C%5B-91.3861256828518%2C39.688248198995353%5D%5D%5D%7D%2C%22attributes%22%3A%7B%22OBJECTID%22%3A21%7D%5D&gdbVersion=&rollbackOnFailure=true&f=pjson
and the request body for the post request made programmatically looks likes
"[{\"geometry\":{\"paths\":[[[-91.3888577181506,39.703158271352621],[-91.381838690201192,39.690323806398723],[-91.383241723424632,39.689645139311914],[-91.3849700567206,39.6888078408094],[-91.3861256828518,39.688248198995353]]]},\"attributes\":{\"OBJECTID\":21}}]"
Even I tried appending this
string arguments = "rollbackOnFailure=true&f=pjson&features=";
in my commented out code above (where I'm using PostAsJsonAsync) to make the request body look like as if it's coming from Html form. But no success, even I'm not sure whether the JSonFormatter takes this arguments string in to account or just leaves it while serializing/deserializing during the run time. And the post request body that I get after appending "arguments" string to Json string looks like this
"rollbackOnFailure=true&f=pjson&features=[{\"geometry\":{\"paths\":[[[-91.3877577181506,39.703158271352621],[-91.36047320856953,39.702616420911333],[-91.383241723424632,39.689645139311914],[-91.3849700567206,39.6888078408094],[-91.3861256828518,39.688248198995353]]]},\"attributes\":{\"OBJECTID\":21}}]"
But still no success, Now I'm totally running out of ideas as to how to call web api service from my C# code so that web api thinks it's coming from that Html form and end up successfully doing edits in the database programmatically. All suggestions and ideas will be highly appreciated.
The trick lies somewhere else, I was using HttpClient to simulate browser post request and get result in c#. But in this particular scenario HttpClient is of no use. I changed to HttpWebRequest after seeing a code at How to make a post call to a Web Api Action? from utlimate_programmer_BR and it did the trick, again HttpClient was a bad choice by me to get this particular thing done.
I'm trying to fix an issue with a legacy asp.net WebForms application written about 6 years ago. I'll try to explain and hope that someone can see a fix.
Our application supports what we call an "API". Really it's just a couple of pages intended to be displayed within the pages of our customers' websites. We also provice a sample application that shows how to use it.
The sample app has some form fields for the caller to provide values. When submitted, we create a string of HTML containing a elelement with the tag that
submit()'s the form on postgback.
Example:
...
form1.submit();
This method works successfully as long as the end user puts our domain in thier trusted sites. Otherwise the user gets permission denied errors. However, recently, we've had a couple customers refuse to add us to trusted sites and
want the issue fixed another way.
One approach I've tries is to use HttpClient.PostAsync() to do what the form/script above does but from the server side.
public HttpResponseMessage Post(string address, MediaTypeWithQualityHeaderValue acceptType, List<KeyValuePair<string, string>> data)
{
using (HttpClient client = new HttpClient())
{
//client.BaseAddress = new Uri(address);
client.DefaultRequestHeaders.Accept.Add(acceptType);
HttpContent content = new FormUrlEncodedContent(data);
HttpResponseMessage response = client.PostAsync(address, content).Result;
return response.EnsureSuccessStatusCode();
}
}
It's called like this:
string hash = GenerateHashValue();
List<KeyValuePair<string, string>> data = this.BuildPostData(hash);
MediaTypeWithQualityHeaderValue acceptType = new MediaTypeWithQualityHeaderValue("text/html");
HttpResponseMessage msg = Post(this.GetPostTargetUrl(), acceptType, data);
var task = msg.Content.ReadAsStringAsync();
task.Wait();
string result = task.Result;
Response.Write(result);
Response.End();
The response HTML is successfully written into the but it's essentally "disconnected" - nothing works after that. Normally, using the / method about uses can navigate in the iframe to other pages on our
site.Looking at it in fiddler, I see that some of the posts and redirects that I see when using the current / method is happeing and I get 404s for many of the javascript files coming from our site.
Can anyone suggest another approach in which I would not have XSS errors but a "connected" iframe?
Thanks,
Dan