HttpClient GetAsync with query string
Asked Answered
T

2

7

I am using Google's GeoCoding API. I have two methods where one works and the other doesn't and I can't seem to figure out why:

string address = "1400,Copenhagen,DK";
string GoogleMapsAPIurl = "https://maps.googleapis.com/maps/api/geocode/json?address={0}&key={1}";
string GoogleMapsAPIkey = "MYSECRETAPIKEY";
string requestUri = string.Format(GoogleMapsAPIurl, address.Trim(), GoogleMapsAPIkey);

// Works fine                
using (var client = new HttpClient())
{
    using (HttpResponseMessage response = await client.GetAsync(requestUri))
    {
        var responseContent = response.Content.ReadAsStringAsync().Result;
        response.EnsureSuccessStatusCode();
    }
}

// Doesn't work
using (HttpClient client = new HttpClient())
{
    client.BaseAddress = new Uri("https://maps.googleapis.com/maps/api/", UriKind.Absolute);
    client.DefaultRequestHeaders.Add("key", GoogleMapsAPIkey);

    using (HttpResponseMessage response = await client.GetAsync("geocode/json?address=1400,Copenhagen,DK"))
    {
        var responseContent = response.Content.ReadAsStringAsync().Result;
        response.EnsureSuccessStatusCode();
    }
}

My last method with GetAsync where I am sending a query string doesn't work and I am in doubt why it is so. When I introduce BaseAddress on the client the GetAsync somehow doesn't send to the correct URL.

Toughminded answered 19/7, 2020 at 12:49 Comment(2)
Note: using (HttpClient client = new HttpClient()) - HttpClient is intended to be instantiated once per application, rather than per-use.Excommunication
This is just a demo console app to show that I can't use a BaseAddress as I want to.Toughminded
C
5

The problem is related with key parameter on URL. Change your code like this:

using (HttpClient client = new HttpClient())
{
   client.BaseAddress = new Uri("https://maps.googleapis.com/maps/api/");
   
   using (HttpResponseMessage response = await client.GetAsync("geocode/json?address=1400,Copenhagen,DK&key=" + GoogleMapsAPIkey))
    {
       var responseContent = response.Content.ReadAsStringAsync().Result;
       response.EnsureSuccessStatusCode();
    }
}

As google sheets said:

After you have an API key, your application can append the query parameter key=yourAPIKey to all request URLs. The API key is safe for embedding in URLs; it doesn't need any encoding.

Cobaltic answered 19/7, 2020 at 13:2 Comment(9)
I have tried that: client.BaseAddress = new Uri("maps.googleapis.com/maps/api/geocode/"); and calling GetSync with client.GetAsync("json?address=1400,Copenhagen,DK")) but it returns 403Toughminded
And what if client.GetAsync("json?address=1400,Copenhagen,DK&key=MYSECRETAPIKEY")) ?Cobaltic
I was just about to post that. This seems to work: client.BaseAddress = new Uri("maps.googleapis.com/maps/api/geocode/"); client.GetAsync("json?address=1400,Copenhagen,DK&key=myGoogleAPIkey") so as long as I am not setting the HTTP Header "key" then it does work. The key apparently needs to be sent as a query parameter. I thought that a HTTP header was standard way to send API keys.Toughminded
I Update my answer. Good to ear that!Cobaltic
Strange that Google doesn't have the option to send the API key as a HTTP Header. Wouldn't it be more secure?Toughminded
Yeah I tought the same. But reading this they said "After you have an API key, your application can append the query parameter key=yourAPIKey to all request URLs. The API key is safe for embedding in URLs; it doesn't need any encoding." If google said is secure, must be secure :)Cobaltic
Checked twice. UriKind.Absolute has no effect int this exact case.Excommunication
Ok, so the problem was just the key on url. Let me update the answer.Cobaltic
@OliverNilsen there's no difference on security where API key is located: in URL or in headers. HTTPS is enough to secure it.Excommunication
E
5

I don't suggest adding API key into globals. Maybe you'll need to send some HTTP request outside of the API and the key will be leaked.

Here's the example that works.

using Newtonsoft.Json;
public class Program
{
    private static readonly HttpClient client = new HttpClient();
    private const string GoogleMapsAPIkey = "MYSECRETAPIKEY";

    static async Task Main(string[] args)
    {
        client.BaseAddress = new Uri("https://maps.googleapis.com/maps/api/");

        try
        {
            Dictionary<string, string> query = new Dictionary<string, string>();
            query.Add("address", "1400,Copenhagen,DK");
            dynamic response = await GetAPIResponseAsync<dynamic>("geocode/json", query);
            Console.WriteLine(response.ToString());
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
        Console.ReadKey();
    }

    private static async Task<string> ParamsToStringAsync(Dictionary<string, string> urlParams)
    {
        using (HttpContent content = new FormUrlEncodedContent(urlParams))
            return await content.ReadAsStringAsync();
    }

    private static async Task<T> GetAPIResponseAsync<T>(string path, Dictionary<string, string> urlParams)
    {
        urlParams.Add("key", GoogleMapsAPIkey);
        string query = await ParamsToStringAsync(urlParams);
        using (HttpResponseMessage response = await client.GetAsync(path + "?" + query, HttpCompletionOption.ResponseHeadersRead))
        {
            response.EnsureSuccessStatusCode();
            string responseText = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<T>(responseText);
        }
    }
}
Excommunication answered 19/7, 2020 at 14:7 Comment(4)
Thx for the answer. I have this shorter form working fine now. The whole issue was passing the API key as a HTTP header, which Google's geocoding API doesn't accept. You have to send it as part of the query string. So this works for me: client.BaseAddress = new Uri("https://maps.googleapis.com/maps/api/geocode/"); string requestURI = string.Format("json?address={0}&key={1}", address.Trim(), APIkey);Toughminded
@OliverNilsen You're welcome. Sure, that's shorter but my solution supports any collection of URL parameters not address only. It's scalable. I'm not sure that you'll use only geocode/json from that API. Then, string.Format here produce not proper URL-encoded string, it may fail with some not latin or special characters included. For example, you'll may easily break it with adding & or = to the address value. Just a warning but you may test and compare the resulting urls. Shorter not means better.Excommunication
Hmm you might be right if I end up having & or = in the address. Can't I still use string.Format and just use one of the URI encoding methods and encode the address variable?Toughminded
@OliverNilsen there's no so many bulletproof methods to encode the string into URL format correctly. One of them shown above, other one available with HttpUtility form System.Web namespace. But some developers don't like to include that namespace, I don't know why, thus I've shown the common one above. I have no idea why do you prefer to avoid the ParamsToStringAsync method approach. Btw, I'ts up to you.Excommunication

© 2022 - 2024 — McMap. All rights reserved.