Skip to content

Commit 431a9f8

Browse files
committed
Remove HERE legacy credential flow
Root cause: the branch briefly restored the retired HERE app_id/app_code path even though this major-version line is meant to standardize on the current API-key-based Geocoding and Search API. Remove the legacy HERE constructor, parsing models, sample/test config, and docs so the public surface matches the intended breaking-change direction.
1 parent 7ec05ce commit 431a9f8

8 files changed

Lines changed: 21 additions & 521 deletions

File tree

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# Generic C# Geocoding API [![CI](https://github.com/exceptionless/Geocoding.net/actions/workflows/build.yml/badge.svg)](https://github.com/exceptionless/Geocoding.net/actions/workflows/build.yml) [![CodeQL](https://github.com/exceptionless/Geocoding.net/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/exceptionless/Geocoding.net/actions/workflows/codeql-analysis.yml)
22

3-
Includes a model and interface for communicating with current geocoding providers while preserving selected legacy compatibility surfaces.
3+
Includes a model and interface for communicating with current geocoding providers.
44

55
| Provider | Package | Status | Auth | Notes |
66
| --- | --- | --- | --- | --- |
77
| Google Maps | `Geocoding.Google` | Supported | API key or signed client credentials | `BusinessKey` supports signed Google Maps client-based requests when your deployment requires them. |
88
| Azure Maps | `Geocoding.Microsoft` | Supported | Azure Maps subscription key | Primary Microsoft-backed geocoder. |
99
| Bing Maps | `Geocoding.Microsoft` | Deprecated compatibility | Bing Maps enterprise key | `BingMapsGeocoder` remains available for existing consumers and is marked obsolete for new development. |
10-
| HERE Geocoding and Search | `Geocoding.Here` | Supported | HERE API key or legacy app_id/app_code | Uses the current HERE Geocoding and Search API when an API key is configured and retains the legacy credential flow for compatibility. |
10+
| HERE Geocoding and Search | `Geocoding.Here` | Supported | HERE API key | Uses the current HERE Geocoding and Search API. |
1111
| MapQuest | `Geocoding.MapQuest` | Supported | API key | Commercial API only. OpenStreetMap mode is no longer supported. |
1212
| Yahoo PlaceFinder/BOSS | `Geocoding.Yahoo` | Deprecated | OAuth consumer key + secret | Legacy package retained for compatibility, but the service remains deprecated and unverified. |
1313

@@ -76,7 +76,9 @@ Bing Maps requires an existing Bing Maps enterprise key. The provider is depreca
7676

7777
MapQuest requires a [developer API key](https://developer.mapquest.com/user/me/apps).
7878

79-
HERE supports a [HERE API key](https://www.here.com/docs/category/identity-and-access-management) for the current Geocoding and Search API. Existing consumers can also continue using the legacy `app_id`/`app_code` constructor for compatibility.
79+
HERE supports a [HERE API key](https://www.here.com/docs/category/identity-and-access-management) for the current Geocoding and Search API.
80+
81+
The current major-version line no longer supports HERE `app_id`/`app_code` credentials. Migrate existing HERE integrations to API keys before upgrading.
8082

8183
Yahoo still uses the legacy OAuth consumer key and consumer secret flow, but onboarding remains unverified and the package is deprecated.
8284

@@ -93,7 +95,7 @@ Alternatively, if you are on Windows, you can open the solution in [Visual Studi
9395

9496
### Service Tests
9597

96-
You will need credentials for each respective service to run the service tests. Make a `settings-override.json` as a copy of `settings.json` in the test project and put in your provider credentials there. For HERE, that can be either `Providers:Here:ApiKey` or the legacy `Providers:Here:AppId` plus `Providers:Here:AppCode` pair. Then you should be able to run the tests.
98+
You will need credentials for each respective service to run the service tests. Make a `settings-override.json` as a copy of `settings.json` in the test project and put in your provider credentials there. Then you should be able to run the tests.
9799

98100
Most provider-backed integration tests skip with a message indicating which setting is required when credentials are missing. The Yahoo suite now follows the same credential gating, but the provider remains deprecated and unverified.
99101

@@ -105,4 +107,4 @@ The sample app in `samples/Example.Web` is an ASP.NET Core 10 minimal API that c
105107
dotnet run --project samples/Example.Web/Example.Web.csproj
106108
```
107109

108-
Configure a provider in `samples/Example.Web/appsettings.json` or via environment variables such as `Providers__Azure__ApiKey`, `Providers__Bing__ApiKey`, `Providers__Google__ApiKey`, `Providers__Here__ApiKey`, `Providers__Here__AppId`, `Providers__Here__AppCode`, or `Providers__MapQuest__ApiKey`. Once the app is running, use `samples/Example.Web/sample.http` to call `/providers`, `/geocode`, and `/reverse`.
110+
Configure a provider in `samples/Example.Web/appsettings.json` or via environment variables such as `Providers__Azure__ApiKey`, `Providers__Bing__ApiKey`, `Providers__Google__ApiKey`, `Providers__Here__ApiKey`, or `Providers__MapQuest__ApiKey`. Once the app is running, use `samples/Example.Web/sample.http` to call `/providers`, `/geocode`, and `/reverse`.

samples/Example.Web/Program.cs

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,7 @@ static string[] GetConfiguredProviders(ProviderOptions options)
117117

118118
configuredProviders.Add("google");
119119

120-
if (!String.IsNullOrWhiteSpace(options.Here.ApiKey)
121-
|| (!String.IsNullOrWhiteSpace(options.Here.AppId) && !String.IsNullOrWhiteSpace(options.Here.AppCode)))
120+
if (!String.IsNullOrWhiteSpace(options.Here.ApiKey))
122121
configuredProviders.Add("here");
123122

124123
if (!String.IsNullOrWhiteSpace(options.MapQuest.ApiKey))
@@ -163,23 +162,16 @@ static bool TryCreateGeocoder(string provider, ProviderOptions options, out IGeo
163162
return true;
164163

165164
case "here":
166-
if (!String.IsNullOrWhiteSpace(options.Here.ApiKey))
167-
{
168-
geocoder = new HereGeocoder(options.Here.ApiKey);
169-
error = null;
170-
return true;
171-
}
172-
173-
if (!String.IsNullOrWhiteSpace(options.Here.AppId) && !String.IsNullOrWhiteSpace(options.Here.AppCode))
165+
geocoder = default!;
166+
if (String.IsNullOrWhiteSpace(options.Here.ApiKey))
174167
{
175-
geocoder = new HereGeocoder(options.Here.AppId, options.Here.AppCode);
176-
error = null;
177-
return true;
168+
error = "Configure Providers:Here:ApiKey before using the HERE provider.";
169+
return false;
178170
}
179171

180-
geocoder = default!;
181-
error = "Configure Providers:Here:ApiKey, or provide both Providers:Here:AppId and Providers:Here:AppCode, before using the HERE provider.";
182-
return false;
172+
geocoder = new HereGeocoder(options.Here.ApiKey);
173+
error = null;
174+
return true;
183175

184176
case "mapquest":
185177
if (String.IsNullOrWhiteSpace(options.MapQuest.ApiKey))
@@ -266,8 +258,6 @@ internal sealed class GoogleProviderOptions
266258
internal sealed class HereProviderOptions
267259
{
268260
public String ApiKey { get; init; } = String.Empty;
269-
public String AppId { get; init; } = String.Empty;
270-
public String AppCode { get; init; } = String.Empty;
271261
}
272262

273263
internal sealed class MapQuestProviderOptions

samples/Example.Web/appsettings.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
"ApiKey": ""
1111
},
1212
"Here": {
13-
"AppCode": "",
14-
"AppId": "",
1513
"ApiKey": ""
1614
},
1715
"MapQuest": {

src/Geocoding.Here/HereGeocoder.cs

Lines changed: 4 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
using System.Globalization;
22
using System.Net;
33
using System.Net.Http;
4-
using System.Runtime.Serialization.Json;
54
using System.Text;
65
using System.Text.Json;
76
using System.Text.Json.Serialization;
8-
using HereJson = Geocoding.Here.Json;
97

108
namespace Geocoding.Here;
119

@@ -19,13 +17,8 @@ public class HereGeocoder : IGeocoder
1917
{
2018
private const string BaseAddress = "https://geocode.search.hereapi.com/v1/geocode";
2119
private const string ReverseBaseAddress = "https://revgeocode.search.hereapi.com/v1/revgeocode";
22-
private const string LegacyBaseAddress = "https://geocoder.api.here.com/6.2/geocode.json?app_id={0}&app_code={1}&{2}";
23-
private const string LegacyReverseBaseAddress = "https://reverse.geocoder.api.here.com/6.2/reversegeocode.json?app_id={0}&app_code={1}&mode=retrieveAddresses&{2}";
2420

25-
private readonly string? _apiKey;
26-
private readonly string? _legacyAppId;
27-
private readonly string? _legacyAppCode;
28-
private bool UsesLegacyCredentials => !String.IsNullOrWhiteSpace(_legacyAppId) && !String.IsNullOrWhiteSpace(_legacyAppCode);
21+
private readonly string _apiKey;
2922

3023
/// <summary>
3124
/// Gets or sets the proxy used for HERE requests.
@@ -56,28 +49,8 @@ public HereGeocoder(string apiKey)
5649
_apiKey = apiKey;
5750
}
5851

59-
/// <summary>
60-
/// Initializes a new instance of the <see cref="HereGeocoder"/> class using the deprecated app_id/app_code signature.
61-
/// </summary>
62-
/// <param name="appId">The deprecated HERE application identifier.</param>
63-
/// <param name="appCode">The deprecated HERE application code.</param>
64-
public HereGeocoder(string appId, string appCode)
65-
{
66-
if (String.IsNullOrWhiteSpace(appId))
67-
throw new ArgumentException("appId can not be null or empty.", nameof(appId));
68-
69-
if (String.IsNullOrWhiteSpace(appCode))
70-
throw new ArgumentException("appCode can not be null or empty.", nameof(appCode));
71-
72-
_legacyAppId = appId;
73-
_legacyAppCode = appCode;
74-
}
75-
7652
private Uri GetQueryUrl(string address)
7753
{
78-
if (UsesLegacyCredentials)
79-
return GetLegacyQueryUrl(("searchtext", address));
80-
8154
var parameters = CreateBaseParameters();
8255
parameters.Add(new KeyValuePair<string, string>("q", address));
8356
AppendGlobalParameters(parameters, includeAtBias: true);
@@ -86,30 +59,17 @@ private Uri GetQueryUrl(string address)
8659

8760
private Uri GetQueryUrl(string street, string city, string state, string postalCode, string country)
8861
{
89-
if (new[] { street, city, state, postalCode, country }.All(part => String.IsNullOrWhiteSpace(part)))
90-
throw new ArgumentException("At least one address component is required.");
91-
92-
if (UsesLegacyCredentials)
93-
{
94-
return GetLegacyQueryUrl(
95-
("street", street),
96-
("city", city),
97-
("state", state),
98-
("postalcode", postalCode),
99-
("country", country));
100-
}
101-
10262
var query = String.Join(", ", new[] { street, city, state, postalCode, country }
10363
.Where(part => !String.IsNullOrWhiteSpace(part)));
10464

65+
if (String.IsNullOrWhiteSpace(query))
66+
throw new ArgumentException("At least one address component is required.");
67+
10568
return GetQueryUrl(query);
10669
}
10770

10871
private Uri GetQueryUrl(double latitude, double longitude)
10972
{
110-
if (UsesLegacyCredentials)
111-
return GetLegacyReverseQueryUrl(latitude, longitude);
112-
11373
var parameters = CreateBaseParameters();
11474
parameters.Add(new KeyValuePair<string, string>("at", String.Format(CultureInfo.InvariantCulture, "{0},{1}", latitude, longitude)));
11575
AppendGlobalParameters(parameters, includeAtBias: false);
@@ -129,67 +89,6 @@ private List<KeyValuePair<string, string>> CreateBaseParameters()
12989
return parameters;
13090
}
13191

132-
private Uri GetLegacyQueryUrl(params (string Key, string Value)[] values)
133-
{
134-
var parameters = CreateLegacyBaseParameters(includeProxBias: true);
135-
foreach (var (key, value) in values)
136-
{
137-
if (!String.IsNullOrWhiteSpace(value))
138-
parameters.Add(new KeyValuePair<string, string>(key, value));
139-
}
140-
141-
return BuildLegacyUri(LegacyBaseAddress, parameters);
142-
}
143-
144-
private Uri GetLegacyReverseQueryUrl(double latitude, double longitude)
145-
{
146-
var parameters = CreateLegacyBaseParameters(includeProxBias: false);
147-
parameters.Add(new KeyValuePair<string, string>("prox", FormatLegacyCoordinate(latitude, longitude)));
148-
return BuildLegacyUri(LegacyReverseBaseAddress, parameters);
149-
}
150-
151-
private List<KeyValuePair<string, string>> CreateLegacyBaseParameters(bool includeProxBias)
152-
{
153-
var parameters = new List<KeyValuePair<string, string>>();
154-
155-
if (includeProxBias && UserLocation is not null)
156-
parameters.Add(new KeyValuePair<string, string>("prox", FormatLegacyCoordinate(UserLocation.Latitude, UserLocation.Longitude)));
157-
158-
if (UserMapView is not null)
159-
{
160-
parameters.Add(new KeyValuePair<string, string>(
161-
"mapview",
162-
FormatLegacyBounds(UserMapView)));
163-
}
164-
165-
if (MaxResults is > 0)
166-
parameters.Add(new KeyValuePair<string, string>("maxresults", MaxResults.Value.ToString(CultureInfo.InvariantCulture)));
167-
168-
return parameters;
169-
}
170-
171-
private Uri BuildLegacyUri(string baseAddress, IEnumerable<KeyValuePair<string, string>> parameters)
172-
{
173-
var url = String.Format(CultureInfo.InvariantCulture, baseAddress, UrlEncode(_legacyAppId!), UrlEncode(_legacyAppCode!), BuildQueryString(parameters));
174-
return new Uri(url);
175-
}
176-
177-
private static string FormatLegacyCoordinate(double latitude, double longitude)
178-
{
179-
return String.Format(CultureInfo.InvariantCulture, "{0},{1}", latitude, longitude);
180-
}
181-
182-
private static string FormatLegacyBounds(Bounds bounds)
183-
{
184-
return String.Format(
185-
CultureInfo.InvariantCulture,
186-
"{0},{1},{2},{3}",
187-
bounds.SouthWest.Latitude,
188-
bounds.SouthWest.Longitude,
189-
bounds.NorthEast.Latitude,
190-
bounds.NorthEast.Longitude);
191-
}
192-
19392
private void AppendGlobalParameters(ICollection<KeyValuePair<string, string>> parameters, bool includeAtBias)
19493
{
19594
if (includeAtBias && UserLocation is not null)
@@ -242,12 +141,6 @@ private string BuildQueryString(IEnumerable<KeyValuePair<string, string>> parame
242141
try
243142
{
244143
var url = GetQueryUrl(address);
245-
if (UsesLegacyCredentials)
246-
{
247-
var legacyResponse = await GetLegacyResponse(url, cancellationToken).ConfigureAwait(false);
248-
return ParseLegacyResponse(legacyResponse);
249-
}
250-
251144
var response = await GetResponse(url, cancellationToken).ConfigureAwait(false);
252145
return ParseResponse(response);
253146
}
@@ -263,12 +156,6 @@ private string BuildQueryString(IEnumerable<KeyValuePair<string, string>> parame
263156
try
264157
{
265158
var url = GetQueryUrl(street, city, state, postalCode, country);
266-
if (UsesLegacyCredentials)
267-
{
268-
var legacyResponse = await GetLegacyResponse(url, cancellationToken).ConfigureAwait(false);
269-
return ParseLegacyResponse(legacyResponse);
270-
}
271-
272159
var response = await GetResponse(url, cancellationToken).ConfigureAwait(false);
273160
return ParseResponse(response);
274161
}
@@ -293,12 +180,6 @@ private string BuildQueryString(IEnumerable<KeyValuePair<string, string>> parame
293180
try
294181
{
295182
var url = GetQueryUrl(latitude, longitude);
296-
if (UsesLegacyCredentials)
297-
{
298-
var legacyResponse = await GetLegacyResponse(url, cancellationToken).ConfigureAwait(false);
299-
return ParseLegacyResponse(legacyResponse);
300-
}
301-
302183
var response = await GetResponse(url, cancellationToken).ConfigureAwait(false);
303184
return ParseResponse(response);
304185
}
@@ -380,27 +261,6 @@ private async Task<HereResponse> GetResponse(Uri queryUrl, CancellationToken can
380261
return JsonSerializer.Deserialize<HereResponse>(json, Extensions.JsonOptions) ?? new HereResponse();
381262
}
382263

383-
private async Task<HereJson.Response> GetLegacyResponse(Uri queryUrl, CancellationToken cancellationToken)
384-
{
385-
using var client = BuildClient();
386-
using var request = CreateRequest(queryUrl);
387-
using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
388-
using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
389-
390-
var serializer = new DataContractJsonSerializer(typeof(HereJson.ServerResponse));
391-
var payload = (HereJson.ServerResponse?)serializer.ReadObject(stream);
392-
if (payload is null)
393-
return new HereJson.Response { View = Array.Empty<HereJson.View>() };
394-
395-
if (!String.IsNullOrWhiteSpace(payload.ErrorType))
396-
{
397-
var errorType = payload.ErrorType!;
398-
throw new HereGeocodingException(payload.Details ?? errorType, errorType, payload.ErrorSubtype ?? errorType);
399-
}
400-
401-
return payload.Response ?? new HereJson.Response { View = Array.Empty<HereJson.View>() };
402-
}
403-
404264
private static HereLocationType MapLocationType(string? resultType)
405265
{
406266
switch (resultType?.Trim().ToLowerInvariant())
@@ -425,42 +285,6 @@ private static HereLocationType MapLocationType(string? resultType)
425285
}
426286
}
427287

428-
private IEnumerable<HereAddress> ParseLegacyResponse(HereJson.Response response)
429-
{
430-
if (response.View is null)
431-
yield break;
432-
433-
foreach (var view in response.View)
434-
{
435-
if (view?.Result is null)
436-
continue;
437-
438-
foreach (var result in view.Result)
439-
{
440-
if (result?.Location is null)
441-
continue;
442-
443-
var location = result.Location;
444-
if (location.DisplayPosition is null)
445-
continue;
446-
447-
if (!Enum.TryParse(location.LocationType, true, out HereLocationType locationType))
448-
locationType = HereLocationType.Unknown;
449-
450-
yield return new HereAddress(
451-
location.Address?.Label ?? location.Name ?? String.Empty,
452-
new Location(location.DisplayPosition.Latitude, location.DisplayPosition.Longitude),
453-
location.Address?.Street,
454-
location.Address?.HouseNumber,
455-
location.Address?.City,
456-
location.Address?.State,
457-
location.Address?.PostalCode,
458-
location.Address?.Country,
459-
locationType);
460-
}
461-
}
462-
}
463-
464288
private string UrlEncode(string toEncode)
465289
{
466290
if (String.IsNullOrEmpty(toEncode))

0 commit comments

Comments
 (0)