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
- Use
IHttpClientFactoryvia DI to avoid socket exhaustion - Cache responses to reduce latency and external API calls
- Middleware makes location data available app-wide
- Records provide clean, immutable data models
- Always handle timeouts and failures gracefully