Identifying country by IP address - geolocation

Is there a way to figure out the country name just by looking at an IP address? I mean, do countries have specific ranges of IP addresses? For example, Australia can have IP addresses only in the range of 123.45.56.89 - 231.54.65.98 (just an example)

No you can't - IP addresses get reallocated and reassigned from time to time, so the mapping of IP to location will also change over time.
If you want to find out the location that an IP address currently maps to you can either download a geolocation database, such as GeoLite from MaxMind, or use an API like http://ipinfo.io (my own service) which will also give you additional details:
$ curl ipinfo.io/8.8.8.8
{
"ip": "8.8.8.8",
"hostname": "google-public-dns-a.google.com",
"loc": "37.385999999999996,-122.0838",
"org": "AS15169 Google Inc.",
"city": "Mountain View",
"region": "California",
"country": "US",
"phone": 650
}

I think what you're looking for is an IP Geolocation database or service provider. There are many out there and some are free (get what you pay for).
Although I haven't used this service before, it claims to be in real-time: https://www.kickfire.com
Here's another IP geo location API from Abstract API - https://www.abstractapi.com/ip-geolocation-api
But just do a google search on IP geo and you'll get more results than you need.

I know that it is a very old post but for the sake of the users who are landed here and looking for a solution, if you are using Cloudflare as your DNS then you can activate IP geolocation and get the value from the request header,
Here is the code snippet in C# after you enable IP geolocation in Cloudflare through the network tab
var countryCode = HttpContext.Request.Headers.Get("cf-ipcountry"); // in older asp.net versions like webform use HttpContext.Current.Request. ...
var countryName = new RegionInfo(CountryCode)?.EnglishName;
You can simply map it to other programming languages, please take a look at the Cloudflare's documentation here
But if you are really insisting on using a 3rd party solution to have more precise information about the visitors using their IP here is a complete, ready to use implementation using C#:
The 3rd party I have used is https://ipstack.com, you can simply register for a free plan and get an access token to use for 100 API requests each month, I am using the JSON model to retrieve and like to convert all the info the API gives me, here we go:
The DTO:
using System;
using Newtonsoft.Json;
public partial class GeoLocationModel
{
[JsonProperty("ip")]
public string Ip { get; set; }
[JsonProperty("hostname")]
public string Hostname { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("continent_code")]
public string ContinentCode { get; set; }
[JsonProperty("continent_name")]
public string ContinentName { get; set; }
[JsonProperty("country_code")]
public string CountryCode { get; set; }
[JsonProperty("country_name")]
public string CountryName { get; set; }
[JsonProperty("region_code")]
public string RegionCode { get; set; }
[JsonProperty("region_name")]
public string RegionName { get; set; }
[JsonProperty("city")]
public string City { get; set; }
[JsonProperty("zip")]
public long Zip { get; set; }
[JsonProperty("latitude")]
public double Latitude { get; set; }
[JsonProperty("longitude")]
public double Longitude { get; set; }
[JsonProperty("location")]
public Location Location { get; set; }
[JsonProperty("time_zone")]
public TimeZone TimeZone { get; set; }
[JsonProperty("currency")]
public Currency Currency { get; set; }
[JsonProperty("connection")]
public Connection Connection { get; set; }
[JsonProperty("security")]
public Security Security { get; set; }
}
public partial class Connection
{
[JsonProperty("asn")]
public long Asn { get; set; }
[JsonProperty("isp")]
public string Isp { get; set; }
}
public partial class Currency
{
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("plural")]
public string Plural { get; set; }
[JsonProperty("symbol")]
public string Symbol { get; set; }
[JsonProperty("symbol_native")]
public string SymbolNative { get; set; }
}
public partial class Location
{
[JsonProperty("geoname_id")]
public long GeonameId { get; set; }
[JsonProperty("capital")]
public string Capital { get; set; }
[JsonProperty("languages")]
public Language[] Languages { get; set; }
[JsonProperty("country_flag")]
public Uri CountryFlag { get; set; }
[JsonProperty("country_flag_emoji")]
public string CountryFlagEmoji { get; set; }
[JsonProperty("country_flag_emoji_unicode")]
public string CountryFlagEmojiUnicode { get; set; }
[JsonProperty("calling_code")]
public long CallingCode { get; set; }
[JsonProperty("is_eu")]
public bool IsEu { get; set; }
}
public partial class Language
{
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("native")]
public string Native { get; set; }
}
public partial class Security
{
[JsonProperty("is_proxy")]
public bool IsProxy { get; set; }
[JsonProperty("proxy_type")]
public object ProxyType { get; set; }
[JsonProperty("is_crawler")]
public bool IsCrawler { get; set; }
[JsonProperty("crawler_name")]
public object CrawlerName { get; set; }
[JsonProperty("crawler_type")]
public object CrawlerType { get; set; }
[JsonProperty("is_tor")]
public bool IsTor { get; set; }
[JsonProperty("threat_level")]
public string ThreatLevel { get; set; }
[JsonProperty("threat_types")]
public object ThreatTypes { get; set; }
}
public partial class TimeZone
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("current_time")]
public DateTimeOffset CurrentTime { get; set; }
[JsonProperty("gmt_offset")]
public long GmtOffset { get; set; }
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("is_daylight_saving")]
public bool IsDaylightSaving { get; set; }
}
The Helper:
using System.Configuration;
using System.IO;
using System.Net;
using System.Threading.Tasks;
public class GeoLocationHelper
{
public static async Task<GeoLocationModel> GetGeoLocationByIp(string ipAddress)
{
var request = WebRequest.Create(string.Format("http://api.ipstack.com/{0}?access_key={1}", ipAddress, ConfigurationManager.AppSettings["ipStackAccessKey"]));
var response = await request.GetResponseAsync();
using (var stream = new StreamReader(response.GetResponseStream()))
{
var jsonGeoData = await stream.ReadToEndAsync();
return Newtonsoft.Json.JsonConvert.DeserializeObject<GeoLocationModel>(jsonGeoData);
}
}
}

IP addresses are quite commonly used for geo-targeting i.e. customizing the content of a website by the visitor's location / country but they are not permanently associated with a country and often get re-assigned.
To accomplish what you want, you need to keep an up to date lookup to map an IP address to a country either with a database or a geolocation API. Here's an example :
> https://ipapi.co/8.8.8.8/country
US
> https://ipapi.co/8.8.8.8/country_name
United States
Or you can use the full API to get complete location for IP address e.g.
https://ipapi.co/8.8.8.8/json
{
"ip": "8.8.8.8",
"city": "Mountain View",
"region": "California",
"region_code": "CA",
"country": "US",
"country_name": "United States",
"continent_code": "NA",
"postal": "94035",
"latitude": 37.386,
"longitude": -122.0838,
"timezone": "America/Los_Angeles",
"utc_offset": "-0800",
"country_calling_code": "+1",
"currency": "USD",
"languages": "en-US,es-US,haw,fr",
"asn": "AS15169",
"org": "Google Inc."
}

You could use ipdata.co to perform the lookup
This answer uses a 'test' API Key that is very limited and only meant for testing a few calls. Signup for your own Free API Key and get up to 1500 requests daily for development.
curl https://api.ipdata.co/23.221.76.66?api-key=test
Ipdata has 10 endpoints globally each able to handle >10,000 requests per second!
Gives
{
"ip": "23.221.76.66",
"city": "Cambridge",
"region": "Massachusetts",
"region_code": "MA",
"country_name": "United States",
"country_code": "US",
"continent_name": "North America",
"continent_code": "NA",
"latitude": 42.3626,
"longitude": -71.0843,
"asn": "AS20940",
"organisation": "Akamai International B.V.",
"postal": "02142",
"calling_code": "1",
"flag": "https://ipdata.co/flags/us.png",
"emoji_flag": "\ud83c\uddfa\ud83c\uddf8",
"emoji_unicode": "U+1F1FA U+1F1F8",
"is_eu": false,
"languages": [
{
"name": "English",
"native": "English"
}
],
"currency": {
"name": "US Dollar",
"code": "USD",
"symbol": "$",
"native": "$",
"plural": "US dollars"
},
"time_zone": {
"name": "America/New_York",
"abbr": "EDT",
"offset": "-0400",
"is_dst": true,
"current_time": "2018-04-19T06:32:30.690963-04:00"
},
"threat": {
"is_tor": false,
"is_proxy": false,
"is_anonymous": false,
"is_known_attacker": false,
"is_known_abuser": false,
"is_threat": false,
"is_bogon": false
}
}⏎

Yes, countries have specific IP address ranges as you mentioned.
For example, Australia is between 16777216 - 16777471. China is between 16777472 - 16778239. But one country may have multiple ranges. For example, Australia also has this range between 16778240 - 16779263
(These are numerical conversions of IP addresses. It depends whether you use IPv4 or IPv6)
More information about these ranges can be seen here: https://lite.ip2location.com/ip-address-ranges-by-country
We get the ip addresses of our website visitors and sometimes want to make relevant campaign for a specific country. We were using bulk conversion tools but later on decided to define the rules in an Excel file and convert it in the tool. And we have built this Excel template: https://www.someka.net/excel-template/ip-to-country-converter/
Now we use this for our own needs and also sell it. I don't want it to be a sales pitch but for those who are looking for an easy solution can benefit from this.

Amazon's CloudFront content delivery network can now be configured to pass this information through as a header. Given Amazon's size (they're big and stable, not going anywhere) and this is configuration over code (no third-party API to learn or code to maintain), all around believe this to be the best option.
If you do not use AWS CloudFront, I'd look into seeing if your CDN has a similar header option that can be turned on. Usually the large providers are quick to push for feature parity. And if you are not using a CDN, you could put CloudFront in front of your infrastructure and simply set the origin to resolve to whatever you are currently using.
Additionally, it also makes sense to resolve this at the CDN level. Your CDN is already having to figure out geo location to route the user to the nearest content node, might as well pass this information along and not figure it out twice through a third party API (this becomes chokepoint for your app, waiting for a geo location lookup to resolve). No need to do this work twice (and the second time, arguably less resilient [e.g., 3rd party geo lookup]).
https://aws.amazon.com/blogs/aws/enhanced-cloudfront-customization/
Geo-Targeting – CloudFront will detect the user’s country of origin
and pass along the county code to you in the CloudFront-Viewer-Country
header. You can use this information to customize your responses
without having to use URLs that are specific to each country.

It's not that easy.
IP adresses are not assigned to countries as such, but to companies and organizations.
But maybe this can help you out: http://www.maxmind.com/app/geolitecountry

You can try using https://ip-api.io - geo location api that returns country among other IP information.
For example with Node.js
const request = require('request-promise')
request('http://ip-api.io/api/json/1.2.3.4')
.then(response => console.log(JSON.parse(response)))
.catch(err => console.log(err))

May be these two links can help you Associate IP addresses with countries
http://en.wikipedia.org/wiki/Regional_Internet_Registry

I agree with above answers, the best way to get country from ip address is Maxmind.
If you want to write code in java, you might want to use i.e. geoip-api-1.2.10.jar and geoIP dat files (GeoIPCity.dat), which can be found via google.
Following code may be useful for you to get almost all information related to location, I am also using the same code.
public static String getGeoDetailsUsingMaxmind(String ipAddress, String desiredValue)
{
Location getLocation;
String returnString = "";
try
{
String geoIPCity_datFile = System.getenv("AUTOMATION_HOME").concat("/tpt/GeoIP/GeoIPCity.dat");
LookupService isp = new LookupService(geoIPCity_datFile);
getLocation = isp.getLocation(ipAddress);
isp.close();
//Getting all location details
if(desiredValue.equalsIgnoreCase("latitude") || desiredValue.equalsIgnoreCase("lat"))
{
returnString = String.valueOf(getLocation.latitude);
}
else if(desiredValue.equalsIgnoreCase("longitude") || desiredValue.equalsIgnoreCase("lon"))
{
returnString = String.valueOf(getLocation.longitude);
}
else if(desiredValue.equalsIgnoreCase("countrycode") || desiredValue.equalsIgnoreCase("country"))
{
returnString = getLocation.countryCode;
}
else if(desiredValue.equalsIgnoreCase("countryname"))
{
returnString = getLocation.countryName;
}
else if(desiredValue.equalsIgnoreCase("region"))
{
returnString = getLocation.region;
}
else if(desiredValue.equalsIgnoreCase("metro"))
{
returnString = String.valueOf(getLocation.metro_code);
}
else if(desiredValue.equalsIgnoreCase("city"))
{
returnString = getLocation.city;
}
else if(desiredValue.equalsIgnoreCase("zip") || desiredValue.equalsIgnoreCase("postalcode"))
{
returnString = getLocation.postalCode;
}
else
{
returnString = "";
System.out.println("There is no value found for parameter: "+desiredValue);
}
System.out.println("Value of: "+desiredValue + " is: "+returnString + " for ip address: "+ipAddress);
}
catch (Exception e)
{
System.out.println("Exception occured while getting details from max mind. " + e);
}
finally
{
return returnString;
}
}

Yes, you can download the IP address ranges by country from https://lite.ip2location.com/ip-address-ranges-by-country
You can see that each country has multiple ranges and changes frequently.

Here is my solution in Python 3.x to return geo-location info given a dataframe containing IP Address(s); efficient parallelized application of function on vectorized pd.series/dataframe is the way to go.
Will contrast performance of two popular libraries to return location.
TLDR: use geolite2 method.
1. geolite2 package from geolite2 library
Input
# !pip install maxminddb-geolite2
import time
from geolite2 import geolite2
geo = geolite2.reader()
df_1 = train_data.loc[:50,['IP_Address']]
def IP_info_1(ip):
try:
x = geo.get(ip)
except ValueError: #Faulty IP value
return np.nan
try:
return x['country']['names']['en'] if x is not None else np.nan
except KeyError: #Faulty Key value
return np.nan
s_time = time.time()
# map IP --> country
#apply(fn) applies fn. on all pd.series elements
df_1['country'] = df_1.loc[:,'IP_Address'].apply(IP_info_1)
print(df_1.head(), '\n')
print('Time:',str(time.time()-s_time)+'s \n')
print(type(geo.get('48.151.136.76')))
Output
IP_Address country
0 48.151.136.76 United States
1 94.9.145.169 United Kingdom
2 58.94.157.121 Japan
3 193.187.41.186 Austria
4 125.96.20.172 China
Time: 0.09906983375549316s
<class 'dict'>
2. DbIpCity package from ip2geotools library
Input
# !pip install ip2geotools
import time
s_time = time.time()
from ip2geotools.databases.noncommercial import DbIpCity
df_2 = train_data.loc[:50,['IP_Address']]
def IP_info_2(ip):
try:
return DbIpCity.get(ip, api_key = 'free').country
except:
return np.nan
df_2['country'] = df_2.loc[:, 'IP_Address'].apply(IP_info_2)
print(df_2.head())
print('Time:',str(time.time()-s_time)+'s')
print(type(DbIpCity.get('48.151.136.76',api_key = 'free')))
Output
IP_Address country
0 48.151.136.76 US
1 94.9.145.169 GB
2 58.94.157.121 JP
3 193.187.41.186 AT
4 125.96.20.172 CN
Time: 80.53318452835083s
<class 'ip2geotools.models.IpLocation'>
A reason why the huge time difference could be due to the Data structure of the output, i.e direct subsetting from dictionaries seems way more efficient than indexing from the specicialized ip2geotools.models.IpLocation object.
Also, the output of the 1st method is dictionary containing geo-location data, subset respecitively to obtain needed info:
x = geolite2.reader().get('48.151.136.76')
print(x)
>>>
{'city': {'geoname_id': 5101798, 'names': {'de': 'Newark', 'en': 'Newark', 'es': 'Newark', 'fr': 'Newark', 'ja': 'ニューアーク', 'pt-BR': 'Newark', 'ru': 'Ньюарк'}},
'continent': {'code': 'NA', 'geoname_id': 6255149, 'names': {'de': 'Nordamerika', 'en': 'North America', 'es': 'Norteamérica', 'fr': 'Amérique du Nord', 'ja': '北アメリカ', 'pt-BR': 'América do Norte', 'ru': 'Северная Америка', 'zh-CN': '北美洲'}},
'country': {'geoname_id': 6252001, 'iso_code': 'US', 'names': {'de': 'USA', 'en': 'United States', 'es': 'Estados Unidos', 'fr': 'États-Unis', 'ja': 'アメリカ合衆国', 'pt-BR': 'Estados Unidos', 'ru': 'США', 'zh-CN': '美国'}},
'location': {'accuracy_radius': 1000, 'latitude': 40.7355, 'longitude': -74.1741, 'metro_code': 501, 'time_zone': 'America/New_York'},
'postal': {'code': '07102'},
'registered_country': {'geoname_id': 6252001, 'iso_code': 'US', 'names': {'de': 'USA', 'en': 'United States', 'es': 'Estados Unidos', 'fr': 'États-Unis', 'ja': 'アメリカ合衆国', 'pt-BR': 'Estados Unidos', 'ru': 'США', 'zh-CN': '美国'}},
'subdivisions': [{'geoname_id': 5101760, 'iso_code': 'NJ', 'names': {'en': 'New Jersey', 'es': 'Nueva Jersey', 'fr': 'New Jersey', 'ja': 'ニュージャージー州', 'pt-BR': 'Nova Jérsia', 'ru': 'Нью-Джерси', 'zh-CN': '新泽西州'}}]}

Related

Endpoint Not found MVC

I'm working with a very weird endpoint,
Now the data I post to my endpoint is the scorecardId and the DashboardConfig and the rest of my data is populated via the backend, which is the UserId and the DateCreated, Now I need to do a get request for the specific user and I did something like this:
#region Public Methods
[HttpGet]
[Route("GetbyUserID")]
[ValidateModel]
public IHttpActionResult GetbyUserID(Guid UserID)
{
UserID = this.GetUserId();
var config = _prefentialDashboardConfigService.GetByUserID(UserID);
return Ok(config);
}
My model:
public Guid ScorecardId { get; set; }
public Guid UserId { get; set; }
public DateTime DateCreated { get; set; }
public string DashboardConfig { get; set; }
My CRUD:
public PrefentialDashboardConfig GetByUserID(System.Guid UserId, params string[] includes)
{
return Repository.SingleOrDefault<PrefentialDashboardConfig>(config => config.UserId == UserId, includes);
}
}
My ICRUD:
T SingleOrDefault<T>(Expression<Func<T, bool>> where, params string[] includes) where T : class;
And in my front end I just called the get request but it gives me a 404 resource not found error. I call my end point like this in knockout:
var test = PreferentialProcurementDashboardApi.GetbyUserID();
//For testing purposes
console.log("You got it right!" + JSON.stringify(test));
What would be the best way to get my data to the frontend console by the UserId which is taken from my backend?
only guessing, you are probably not passing the UserId when you are doing the http get request.
var test = PreferentialProcurementDashboardApi.GetbyUserID();
probably should be
var test = PreferentialProcurementDashboardApi.GetbyUserID(5);
check the network tab in the browser dev tools to make sure that there are parameters are being passed to the backend

MongoDB join using Linq

CommentCollection
{
"_id":"5b63f0f23846b70011330889",
"CommentType":"task",
"EntityReferenceId":"6082ef25-6f9a-4874-a832-f72e0f693409",
"CommentLink":null,
"EntityName":"task2",
"participants":[
ObjectId("52ffc4a5d85242602e000000"),
ObjectId("52ffc4a5d85242602e000001")
],
"Threads":[
{
"_id":"69bcef71-3695-4340-bdec-4a6e4c58c490",
"CommentType":"task",
"UserId":ObjectId("52ffc4a5d85242602e000000"),
"CommentByUserType":"Admin",
"EntityReferenceId":"6082ef25-6f9a-4874-a832-f72e0f693409",
"Content":"fdffd",
"ProjectName":null,
"PostedDate":"2018-08-03T13:03:05.939Z",
"Active":true,
"Attachment":[
]
}
another Collection is
userCollection
{
"Id":ObjectId("52ffc4a5d85242602e000000"),
"Name":"Pms Admin",
"Email":"pms#xtrastaff.com",
"Type":"Admin",
"UserId":"6082ef25-6f9a-4874-a832-f72e0f693409",
"UserImage":"6082ef25-6f9a-4874-a832-f72e0f693409"
}
In the CommentCollection there is an array of "participants" which is storing the id's of users (from usercollection).
My requirement is join these two collections for getting user details in my asp.net core project(Linq).Participants contains list of id's
In Mongo shell you would use $lookup which can be used on arrays like in this example and your query could look like this:
db.Comment.aggregate([
{
$lookup: {
from: "user",
localField: "participants",
foreignField: "Id",
as: "participants"
}
}
])
Which simply replaces participants with array of objects from second collection:
{
"_id" : "5b63f0f23846b70011330889",
"CommentType" : "task",
"EntityReferenceId" : "6082ef25-6f9a-4874-a832-f72e0f693409",
"CommentLink" : null,
"EntityName" : "task2",
"participants" : [
{
"_id" : ObjectId("5b6e875b9d52833fbe9879c2"),
"Id" : ObjectId("52ffc4a5d85242602e000000"),
"Name" : "Pms Admin",
"Email" : "pms#xtrastaff.com",
"Type" : "Admin",
"UserId" : "6082ef25-6f9a-4874-a832-f72e0f693409",
"UserImage" : "6082ef25-6f9a-4874-a832-f72e0f693409"
}
],
"Threads" : //...
}
In C# you can express that using Lookup syntax. First option allows you to get a list of BsonDocument type which simply skips type checking:
var collection = db.GetCollection<Comment>("Comment");
List<BsonDocument> result = collection.Aggregate()
.Lookup("user", "participants", "Id", "participants")
.ToList();
The reason why you can't use regular LINQ join here is that actually you're compaing an array with a scalar value (that's what should be in equals part of join). However if you need strongly typed result instead of BsonDocuments you can use different version of Lookup method which takes types and expressions instead of strings. So you need another class for $lookup result which might be solved using inheritance:
public class Comment
{
public string _id { get; set; }
public string CommentType { get; set; }
public string EntityReferenceId { get; set; }
public string CommentLink { get; set; }
public string EntityName { get; set; }
public ObjectId[] participants { get; set; }
public Thread[] Threads { get; set; }
}
public class CommentWithUsers : Comment
{
public User[] Users { get; set; }
}
Then you can get a list of CommentWithUser:
var comments = mydb.GetCollection<Comment>("Comment");
var users = mydb.GetCollection<User>("user");
List<CommentWithUser> result = comments.Aggregate()
.Lookup<Comment, User, CommentWithUsers>(
users,
x => x.participants,
x => x.Id,
x => x.Users).ToList();

Odata v4 - $expand then flattern result

Objective: To expand an object, and project a nested property, onto the root selection, alongside with other props.
Having the following relationship:
public class Product {
public string Barcode { get; set; }
public double Price { get; set; }
public Category Category { get; set; }
}
public class Category {
public string Name { get; set; }
}
I would like to make a projection which will result in this:
{
"#odata.context": "http://localhost/odata/$metadata#Product",
"value": [
{
"Price": 500,
"Name": "Meat Products" // this is category name, ideally would be to rename it to CategoryName
}
]
}
Where as currently I get this:
{
"#odata.context": "http://localhost/odata/$metadata#Product",
"value": [
{
"Price": 500,
"Category": {
"Name": "Meat Products"
}
}
]
}
The query used is the following:
/odata/Product?$expand=Category($select=Name)&$select=Price
I would expect to write a projection like this:
/odata/Product?$expand=Category&$select=Price,Category/Name as CategoryName
or
/odata/Product?$expand=Category&$select=Price,Category($select=Name as CategoryName)
or
/odata/Product?$expand=Category&$select=Price,Category($select=Name)
Is that achievable ? Thank you!
P.S. OData V4.
This is not achievable with odata v4 query semantic. As you can see, the response body contains a line:
"#odata.context": "http://localhost/odata/$metadata#Product"
This is to indicate the whole response payload represents an instance of 'Product' type. Suppose 'CategoryName' property does not exist on that type, it is not possible to instruct service to dynamically add one via the 'AS' clause. And keyword 'AS' also does not exist in standard OData query spec.
However, it is indeed valid to return an additional property beyond the metadata, see Reference.
Clients MUST be prepared to receive additional properties in an entity
or complex type instance that are not advertised in metadata, even for
types not marked as open.
So in this case, the service could just return an additional 'virtual' property 'CategoryName' in the response. (If you're the service owner you can update response logic and do the change.) This could be a service behavior, rather than a reaction to certain client query.

JsonConverter: Serialize c# object to json object

When using Json.NET to serialize a MVC view model to json, I have a generic object property on my view model (public object Result { get; set;}) that is getting serialized to key-value pairs instead of an actual json object. Is there a converter I can use to force it to be serialized properly?
This is what is currently being output by Json.NET:
"result": [
{
"key": "AssetId",
"value": "b8d8fb71-2553-485b-91bf-14c6c563d78b"
},
{
"key": "SearchResultType",
"value": "Assets"
},
{
"key": "Name",
"value": "abstract-q-c-1920-1920-8"
}
]
Instead, I would want it to output this:
"result": {
"AssetId": "b8d8fb71-2553-485b-91bf-14c6c563d78b",
"SearchResultType": "Assets",
"Name": "abstract-q-c-1920-1920-8"
}
EDIT:
To answer the question of how that property is getting populated, it is via a RavenDB index:
public class SiteSearchIndexTask : AbstractMultiMapIndexCreationTask<SiteSearchResult>
{
public class Result
{
public object[] Content { get; set; }
}
public override string IndexName
{
get
{
return "SiteSearch/All";
}
}
public SiteSearchIndexTask()
{
AddMap<Asset>(items => from item in items
where item.IsDeleted == false
select new
{
Id = item.Id.ToString(),
ObjectId = item.Id,
ResultType = SearchResultType.Assets,
Title = item.Name.Boost(3),
Tags = item.Tags.Select(x => x.Name).Boost(2),
Result = (object)item,
Query = string.Join(" ", item.Description)
});
AddMap<User>(items => from item in items
where item.IsDeleted == false
select new
{
Id = item.Id,
ObjectId = item.UserId,
ResultType = SearchResultType.Users,
Title = item.Username.Boost(3),
Tags = (BoostedValue) null,
Result = (object)item,
Query = string.Join(" ", item.FullName, item.Email)
});
Store(x => x.ObjectId, FieldStorage.Yes);
Store(x => x.ResultType, FieldStorage.Yes);
Store(x => x.Title, FieldStorage.Yes);
Store(x => x.Tags, FieldStorage.Yes);
Store(x => x.Result, FieldStorage.Yes);
Store(x => x.Query, FieldStorage.Yes);
}
}
Edit 2
Here are the Asset and User models (truncated for brevity, since they're just a bunch of auto properties)
public class Asset : IHasId
{
public string Id { get; set; }
public Guid AssetId
{
get
{
Guid assetId;
Guid.TryParse((Id ?? string.Empty).Replace("assets/", ""), out assetId);
return assetId;
}
set { Id = "assets/" + value; }
}
public string Name { get; set; }
public string Description { get; set; }
}
public class User : IHasId
{
public User()
{
Status = UserStatus.Active;
}
public string Id { get; set; }
public int UserId
{
get
{
int userId;
int.TryParse((Id ?? string.Empty).Replace("users/", ""), out userId);
return userId;
}
set { Id = "users/" + value; }
}
public string Username { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public UserStatus Status { get; set; }
}
Edit 3
As it turns out, when I step through the code, that object is actually of the type Raven.Abstractions.Linq.DynamicJsonObject, which contains an array of key-value pairs. So I guess this question might be more related to Raven than Json.NET. Unless of course there is a converter to go from key-value pair to json object.
It would help to see how you are querying exactly, but you should take a look at the example on querying unlike documents with a multimap index on the RavenDB site.
When I look at your index, it seems like you are trying to do way too much in terms of field storage and encapsulation of your results. It's sometimes difficult to grasp, but the mapping done in the index is not to define how the results are returned, but rather how the index is built. Unless you are doing a Reduce or TransformResults step, you are still returning the original document.
So encapsulating the document as Result = (object)item is overkill. So is having a ResultType enum. If you need to know the type of document that matched, simply use .GetType() on the object and you will quickly see if it is a User or an Asset.
Here is how I would define your search index. Note there's some differences because there were properties you showed in your index that aren't in the models you provided. (I'm assuming there is a separate entity on the backend than the models on the front end, but adjust as needed.)
public class SearchIndex : AbstractMultiMapIndexCreationTask<SearchIndex.Result>
{
public class Result
{
public object[] Content { get; set; }
public string ResultType { get; set; }
}
public SearchIndex()
{
AddMap<Asset>(items => from item in items
select new Result
{
Content = new object[] {item.Name, item.Description},
ResultType = MetadataFor(item)["Raven-Entity-Name"].ToString()
});
AddMap<User>(items => from item in items
select new Result
{
Content = new object[] { item.Username, item.FirstName, item.LastName, item.Email },
ResultType = MetadataFor(item)["Raven-Entity-Name"].ToString()
});
Index(x => x.Content, FieldIndexing.Analyzed);
}
}
And then I would query it like so:
var results = session.Advanced
.LuceneQuery<object, SearchIndex>()
.Where("ResultType:" + resultTypeName)
.AndAlso()
.Search("Content", searchTerm);
You can then simply examine your results and find that while cast as object, you can indeed do result.GetType() and see how that object is constructed. If desired, you could also put a common interface on your entities to cast to instead of object, such as the IAmSearchable shown in the Raven example.
When you finally pass your result back through MVC, it should be serialized properly, since it will be coming from the real object and not the raven DynamicJsonObject.

Entity validation with different scenario

On the assumption that I have Entity with couple of fields. Some fields are required at some specific state but others only on further/other state.
public class Entity
{
//Required always
public SomeReference {}
//Required in specific situation/scenario
public OtherReference {}
}
How to achieve that scenario with some known validation framework or how to do it by my self?
For help:
Udi Dahan has some thoughts on this.
http://www.udidahan.com/2007/04/30/generic-validation/
I have a solution that I am using at the moment. I use Fluent validation and am still getting used to it. I can give you an example of a simple scenario I have. maybe it helps. I have a user class, with a address Object property. At some point, I want to only validate the User details(name, email, password, etc) and at another state I want to validate the user address(first line, postcode, etc).
Classes look like this:
public class User {
public virtual string Name { get; set; }
public virtual string Email { get; set; }
public virtual string Password { get; set; }
public virtual Address Address { get; set; }
}
public class Address {
public virtual string Address1 { get; set; }
public virtual string PostCode { get; set; }
}
I then have two (simplfied) validators, one for an address and one for a user:
public AddressValidator() {
RuleFor(address => address.Address1)
.NotNull()
.WithMessage("Please enter the first line of your address");
RuleFor(address => address.PostCode)
.NotNull()
.WithMessage("Please enter your postcode")
.Matches(UK_POSTCODE_REGEX)
.WithMessage("Please enter a valid post code!");
}
public UserValidator() {
RuleFor(user => user.FirstName)
.NotNull()
.WithMessage("Please provide a first name")
.Length(3, 50)
.WithMessage("First name too short");
RuleFor(user=> user.Password)
.Length(8, 50)
.WithMessage("Password is too short");
}
I then create a Model Validator, so for example, say we have a form where the user enters an address, we create a AddressModelValidator, and can re-use the validators we have written:
public AddressModelValidator() {
RuleFor(user => user.id)
.NotNull()
.WithMessage("An error has occured, please go back and try again");
RuleFor(user => user.Address).SetValidator(new AddressValidator());
}
So, with some thought, you can really create some nice models, and reduce your validation code duplication!
My preferernce is to localize common validation functions such as email and date validations into a ValidationService class that I can pass my object into. For the rest though I tend to put the validation into the class itself. If I am using LINQ to SQL then I can create a Validate() method on my object which LINQ to SQL will call prior to saving the object to the db like this:
public void Validate()
{
if(!IsValid)
throw new ValidationException("Rule violations prevent saving");
}
public bool IsValid
{
get { return GetRuleViolations().Count() == 0;}
}
public IEnumerable<RuleViolation> GetRuleViolations()
{
if(this.TermID == 0)
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(1), "agreeWithTerms");
if(ValidationService.ValidateDate(this.Birthdate.ToString()))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(2), "birthDate");
if (!(Username.Length >= ConfigurationService.GetMinimumUsernameLength()) ||
!(Username.Length <= ConfigurationService.GetMaximumUsernameLength()))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(5), "username");
if(ValidationService.ValidateUsernameComplexity(Username))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(6), "username");
if (AccountID == 0 && ObjectFactory.GetInstance<IAccountRepository>().UsernameExists(this.Username))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(7), "username");
if (!ValidationService.ValidateEmail(Email))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(8), "email");
if (AccountID == 0 && ObjectFactory.GetInstance<IAccountRepository>().EmailExists(this.Email))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(9), "email");
yield break;
}
Read here for a full understanding of this: http://nerddinnerbook.s3.amazonaws.com/Part3.htm

Resources