Kamero

IP Geolocation in C# .NET: Get Location from IP Address

C# and .NET power a huge portion of enterprise web applications. Adding IP geolocation lets you personalize content, detect fraud, and comply with regional regulations. This guide covers everything from basic HTTP calls to production-ready ASP.NET Core middleware.

Basic HttpClient Request

using System.Net.Http.Json;

var client = new HttpClient();
var geo = await client.GetFromJsonAsync<GeoResponse>(
    "https://geo.kamero.ai/api/geo"
);

Console.WriteLine($"{geo.City}, {geo.Country} ({geo.Timezone})");

public record GeoResponse(
    string Ip, string City, string Country,
    string CountryRegion, string Continent,
    string Latitude, string Longitude,
    string Timezone, string PostalCode, string Region
);

The GetFromJsonAsync method handles both the HTTP request and JSON deserialization in one call. Using a record type keeps the model clean and immutable.

Create a Geolocation Service

public interface IGeoLocationService
{
    Task<GeoResponse?> GetLocationAsync(
        CancellationToken ct = default);
}

public class GeoLocationService : IGeoLocationService
{
    private readonly HttpClient _http;

    public GeoLocationService(HttpClient http)
    {
        _http = http;
        _http.BaseAddress = new Uri("https://geo.kamero.ai");
        _http.Timeout = TimeSpan.FromSeconds(5);
    }

    public async Task<GeoResponse?> GetLocationAsync(
        CancellationToken ct = default)
    {
        try
        {
            return await _http.GetFromJsonAsync<GeoResponse>(
                "/api/geo", ct);
        }
        catch (HttpRequestException ex)
        {
            Console.Error.WriteLine(
                $"Geo lookup failed: {ex.Message}");
            return null;
        }
    }
}

Register with Dependency Injection

// Program.cs
builder.Services.AddHttpClient<IGeoLocationService,
    GeoLocationService>();

// In a controller or Razor Page:
public class HomeController : Controller
{
    private readonly IGeoLocationService _geo;

    public HomeController(IGeoLocationService geo)
        => _geo = geo;

    public async Task<IActionResult> Index()
    {
        var location = await _geo.GetLocationAsync();
        ViewBag.City = location?.City ?? "Unknown";
        return View();
    }
}

Using AddHttpClient gives you automatic HttpClient lifecycle management through IHttpClientFactory, avoiding socket exhaustion issues.

Add In-Memory Caching

public class CachedGeoService : IGeoLocationService
{
    private readonly IGeoLocationService _inner;
    private readonly IMemoryCache _cache;

    public CachedGeoService(
        IGeoLocationService inner, IMemoryCache cache)
    {
        _inner = inner;
        _cache = cache;
    }

    public async Task<GeoResponse?> GetLocationAsync(
        CancellationToken ct = default)
    {
        return await _cache.GetOrCreateAsync(
            "geo_location",
            async entry =>
            {
                entry.AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromMinutes(10);
                return await _inner.GetLocationAsync(ct);
            });
    }
}

// Register in DI:
builder.Services.AddMemoryCache();
builder.Services.AddHttpClient<GeoLocationService>();
builder.Services.Decorate<IGeoLocationService,
    CachedGeoService>();

ASP.NET Core Middleware

public class GeoLocationMiddleware
{
    private readonly RequestDelegate _next;

    public GeoLocationMiddleware(RequestDelegate next)
        => _next = next;

    public async Task InvokeAsync(
        HttpContext context, IGeoLocationService geo)
    {
        var location = await geo.GetLocationAsync();
        if (location != null)
        {
            context.Items["GeoCountry"] = location.Country;
            context.Items["GeoCity"] = location.City;
            context.Items["GeoTimezone"] = location.Timezone;
        }
        await _next(context);
    }
}

// In Program.cs:
app.UseMiddleware<GeoLocationMiddleware>();

This middleware runs on every request and stores location data in HttpContext.Items, making it available to all downstream handlers.

Minimal API Example

app.MapGet("/my-location", async (IGeoLocationService geo) =>
{
    var location = await geo.GetLocationAsync();
    return location is not null
        ? Results.Ok(location)
        : Results.Problem("Could not determine location");
});

Geo-Based Access Control

public class GeoBlockAttribute : ActionFilterAttribute
{
    private readonly string[] _blockedCountries;

    public GeoBlockAttribute(params string[] countries)
        => _blockedCountries = countries;

    public override async Task OnActionExecutionAsync(
        ActionExecutingContext context,
        ActionExecutionDelegate next)
    {
        var geo = context.HttpContext
            .RequestServices
            .GetRequiredService<IGeoLocationService>();

        var location = await geo.GetLocationAsync();

        if (location != null &&
            _blockedCountries.Contains(location.Country))
        {
            context.Result = new StatusCodeResult(403);
            return;
        }

        await next();
    }
}

// Usage:
[GeoBlock("CN", "RU")]
public IActionResult SensitiveData() => View();

Key Takeaways

Try the API

Get started with a free geolocation API — no API key needed.

View Documentation →