Skip to content

C# / .NET Quickstart

Server-side: tile proxy, geocoding. .NET 8+.

Project setup

bash
dotnet add package System.Net.Http.Json

Geocoding client

csharp
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;

public record GeocodeItem(
    double Lat,
    double Lng,
    string Label,
    [property: JsonPropertyName("confidence")] double Confidence,
    string Type
);

public sealed class MapsForDevsClient
{
    private const string Base = "https://api.mapsfordevs.com";
    private readonly HttpClient _http;

    public MapsForDevsClient(string apiKey, HttpClient? http = null)
    {
        _http = http ?? new HttpClient();
        _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
        _http.Timeout = TimeSpan.FromSeconds(10);
    }

    public async Task<IReadOnlyList<GeocodeItem>> GeocodeAsync(string query, string? country = null, int limit = 5)
    {
        var url = $"{Base}/geocode/forward?q={Uri.EscapeDataString(query)}&limit={limit}";
        if (country is not null) url += $"&country={country}";

        var resp = await _http.GetFromJsonAsync<GeocodeResponse>(url)
                   ?? throw new Exception("empty response");
        if (!resp.Ok) throw new Exception(resp.Error ?? "mfd error");
        return resp.Items ?? Array.Empty<GeocodeItem>();
    }

    private record GeocodeResponse(
        bool Ok,
        IReadOnlyList<GeocodeItem>? Items,
        string? Error
    );
}

Tile proxy (ASP.NET Core minimal API)

csharp
using System.Security.Cryptography;
using System.Text;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("mfd", c =>
{
    c.BaseAddress = new Uri("https://tiles.mapsfordevs.com");
});

var app = builder.Build();

app.MapGet("/tiles/{z:int}/{x:int}/{y:int}.pbf",
    async (int z, int x, int y, IHttpClientFactory factory) =>
{
    var key    = Environment.GetEnvironmentVariable("MFD_SRV_KEY")!;
    var secret = Environment.GetEnvironmentVariable("MFD_SRV_SECRET")!;
    var path   = $"/tiles/{z}/{x}/{y}.pbf";
    var ts     = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();

    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var sig = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes($"GET\n{path}\n{ts}"))).ToLowerInvariant();

    var http = factory.CreateClient("mfd");
    var req = new HttpRequestMessage(HttpMethod.Get, path);
    req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", key);
    req.Headers.Add("X-MFD-Timestamp", ts);
    req.Headers.Add("X-MFD-Signature", sig);

    var upstream = await http.SendAsync(req);
    var bytes    = await upstream.Content.ReadAsByteArrayAsync();
    return Results.File(bytes, "application/x-protobuf");
});

app.Run();