This project is about the implementation of a simple and extensible IP geofencing engine. It discusses how geofencing rule are declared, and how to configure, run and extend the geofencing engine.
Overview
Geofencing is the use of virtual geographic perimeters to determine the behavior of an application. Provided a specific coordinate (latitude and longitude), a geofencing engine can determine to which virtual geographic area or areas the location belongs, and consequentially take action.
The IP geofencing engine in this project runs based on specific IP addresses. The engine would first translate the IP address into a geographic location, using the free CSV database provided by IP2Location
CSV database (included in the project).
The engine then determines which of the configured geographic areas contain the location, and for each match fires the correspondent configured action. Geographic areas can be configured in the engine by importing a file in GeoJSON format. Sample GeoJSON
files are included in this project for testing purposes, and are also freely available for the public on the OpenDataSoft website.
Use Case Scenarios
This engine is particularly useful in cases when the IP address is available, but not the coordinate.
These are the typical scenarios in which web applications can use the engine with the IP address of the http requests:
- Blocking requests from black-listed geographic areas
- Enabling/disabling features for specific regions
- Localizing content
- Finding the availability of products or service nearby the IP location
Anatomy of a Geofencing Rule
A rule is constructed from three elements: name
, predicate
and action
.
-
rule name: short description for the rule
-
predicate: a boolean function that determines if the rule should be applied. The function will receive as input the list of geographic areas that contain the IP address location (normally only one area if the areas do not overlap), plus the IP address and its location info:
predicate: (areas, ip, location) =>
{ return areas.Any(A => A.Name == "New York"); }
- action: a routine that will be executed only if the predicate is evaluated as
true
. Same inputs as the predicate:
action: (areas, ip, location) =>
{ Console.WriteLine($"The IP Address: {ip} is in New York State!"); })
In addition to regular rules, default rules can also be specified. Default rules are executed if and only if the IP address location is not contained in any of the geographic areas available. Unlike regular rules, default rules only need the action to be constructed.
Configuring the Geofencing Engine
var engine = new IPGeoFencingEngineBuilder()
.AddIP2LocationFromCSVFile(@"\\geofencing\data\IP2LOCATION-LITE-DB11.CSV")
.AddGeographicAreasFromGeoJSONFile(@"\\geofencing\data\demo.geojson")
.AddRule("New York",
predicate: (areas, ip, location) =>
{ return areas.Any(A => A.Name == "New York"); },
action: (areas, ip, location) =>
{ Console.WriteLine($"The IP Address: {ip} is in New York State!"); })
.AddRule("Montana",
predicate: (areas, ip, location) =>
{ return areas.Any(A => A.Name == "Montana"); },
action: (areas, ip, location) =>
{ Console.WriteLine($"The IP Address: {ip} is in Montana!"); })
.AddRule("Billings",
predicate: (areas, ip, location) =>
{ return areas.Any(A => A.Name == "Billings"); },
action: (areas, ip, location) =>
{ Console.WriteLine($"The IP Address: {ip} is in Billings, MT"); })
.AddRule("Montana but not Billings",
predicate: (areas, ip, location) =>
{ return areas.Any(A => A.Name == "Montana") &&
!areas.Any(A => A.Name == "Billings"); },
action: (areas, ip, location) =>
{ Console.WriteLine($"The IP Address:
{ip} is in Montana but not in Billings!"); })
.AddRule("New York or Montana",
predicate: (areas, ip, location) =>
{ return areas.Any(A => A.Name == "Montana") ||
areas.Any(A => A.Name == "New York"); },
action: (areas, ip, location) =>
{ Console.WriteLine($"The IP Address:
{ip} is in New York State or Montana!"); })
.AddDefaultAction((ip, loc) =>
Console.WriteLine($"The IP Address: {ip} is outside all the areas provided"))
.Build();
Running the Engine
engine.Run("98.127.147.57");
engine.Run("172.254.112.210");
engine.Run("157.240.3.35");
Running the Demo
The solution comes with a simple demo console app that demonstrates the engine configuration and execution. Before you run the demo, make sure to edit the appsettings.json file included in the project and set the correct full path to the data folder in your file system.
Example:
{
"DataFolder": "D:\\IPGeoFencing\\Data"
}
Extending the Engine
The core services used by the engine that can be easily extended to enrich its functionality:
IIP2LocationProvider
This interface provides the engine with the translation service to obtain the grographic location from the IP address. The project provides out-of-the-box a simple implementation where the Ip2Location
CSV data is loaded entirely in memory and the IP translation occurs via Linq queries. More efficient implementations can be added that utilize databases queries/indexes to look up the locations, or leverage the IP2Location
API for a lightweight solution, etc.
public interface IIP2LocationProvider
{
LocationModel? GetLocationFromIP(IPAddress ipAddress);
LocationModel? GetLocationFromIP(long ipAddress);
}
IGeographicAreasProvider
This interface is used by the engine to determine which geographic areas contain a given coordinate point. The default implementation in the project is loading in memory the full collection of geographic areas (in the shape of polygons or circles) that need to be evaluated. Like the IIP2LocationProvider
, more efficient implementations can be added to leverage databases, particularly those supporting geospatial queries (e.g., MongoDB
). Moreover, the project currently only supports the GeoJSON
file format to import geographic areas, however there are other popular formats that can be integrated: Shapefile (GIS), KML (Google Earth), etc.
public interface IGeographicAreasProvider
{
IEnumerable<GeographicAreaModel> GetAreasContaining(GeoCoordinate point);
}
Plugging in Your Own Service Implementations
The engine builder already allows developers to plug in their own service implementations:
IIP2LocationProvider myIP2LocationProvider =
new myOwnIP2LocationProviderImplementation();
IGeographicAreasProvider myGeographicAreasProvider =
new myOwnGeographicAreasProviderImplementation();
var engine = new IPGeoFencingEngineBuilder()
.AddIP2LocationProvider(myIP2LocationProvider)
.AddGeographicAreasProvider(myGeographicAreasProvider)
.AddRule( ...