In EPiServer it’s quite easy to personalize content. Personalize content means that content is shown based on the current visitor information. For this blog I created an example how you can show content based on the visitor’s weather tempature. In EPiServer this is possible with visitor group criteria’s.
Visitor group result
Below is the visitor group result of the code sample from this blog. It contains two input parameters, the ‘MinTempature’ and ‘MaxTempature’.
Below a personalized content area property. The spring weather visitor group is configured with the input parameter MinTempature 10 and MaxTempature 20. For the summer visitor group MinTempature 20 and MaxTempature 30. For example if a visitor visits the website when the current tempature of his location is 25 degree the Summer content block will be displayed.
Unfortunaly it’s more spring weather in Holland then summer weather.
Below a sequence diagram for this example. On the content block the weather criterion visitor group is configured. That means the WeatherCriterion is called when the content block is on the current visited page. The WeatherCriterion must check the visitor weather tempature. I created two services to accomplish this, IGeoService and the IWeatherService. The IGeoService returns the visitor geo coordinates based on IP address and the IWeatherService returns the tempature based on geo coordinates. Both services uses extern API’s: freegeoip.net and api.openweathermap.org. Both API’s are free to use.
The criterion settings class is used for content editors to set some parameters. In my example editors can set the minimum and maximum tempature. This class must inherit from CriterionModelBase. The properties of the class will be rendered as input fields, it is also possible to render your own fields with Dojo.
public class Weather1CriterionSettings : CriterionModelBase
{
[Required]
public int MinTempature { get; set; }
[Required]
public int MaxTempature { get; set; }
public override ICriterionModel Copy()
{
return ShallowCopy();
}
}
This class checks if the current visitor can view the content. The class must inherit from the abstract class CriterionBase. This class contains the abstract method IsMatch that must be implemented. This method accepts two parameters, an implementation of the IPrincipal interface and the current HttpContext. The method returns a boolean indicating if the content can be displayed for the current visitor.
[VisitorGroupCriterion(
Category = "Weather",
DisplayName = "Current weather",
Description = "Check the user current weather location")]
public class WeatherVisitorCriterion : CriterionBase<Weather1CriterionSettings>
{
private readonly IWeatherService _weatherService = new WeatherService();
private readonly IGeoService _geoService = new GeoService();
/// <summary>
/// Is match method
/// </summary>
/// <param name="principal"></param>
/// <param name="httpContext"></param>
/// <returns></returns>
public override bool IsMatch(IPrincipal principal, HttpContextBase httpContext)
{
var ipAddress = GetUserIPAddress(httpContext);
var geo = _geoService.GetGeoByIp(ipAddress);
if (geo.Latitude > 0 && geo.Longitude > 0)
{
var tempature = _weatherService.GetCurrentTempature(geo.Latitude, geo.Longitude);
return tempature >= Model.MinTempature && tempature <= Model.MaxTempature;
}
return false;
}
/// <summary>
/// Get user IP address
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private string GetUserIPAddress(HttpContextBase context)
{
if (context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"] != null)
{
return context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();
}
else if (context.Request.UserHostAddress.Length != 0)
{
return context.Request.UserHostAddress;
}
return string.Empty;
}
}
The geo service contains one method to return the geo coordinates by IP address. This service calls an extern API to get the data.
public class GeoService : IGeoService
{
private const string _geoApiUrl = "http://freegeoip.net/json/{0}";
private readonly IHttpRequestUtils _httpRequestUtils = new HttpRequestUtils();
/// <summary>
/// Get Geo coordinates by ip address
/// </summary>
/// <param name="ip"></param>
/// <returns></returns>
public GeoCoordinates GetGeoByIp(string ip)
{
var geo = new GeoCoordinates();
var url = new UrlBuilder(string.Format(_geoApiUrl, ip));
var json = _httpRequestUtils.DoHttpRequest(url.ToString());
if (!string.IsNullOrEmpty(json))
{
var jsonObject = JObject.Parse(json);
var latitude = 0.0;
var longitude = 0.0;
if (double.TryParse(jsonObject["latitude"].Value<string>(), out latitude) &&
double.TryParse(jsonObject["longitude"].Value<string>(), out longitude))
{
geo.Latitude = latitude;
geo.Longitude = longitude;
}
}
return geo;
}
}
The weather service returns the current tempature by geo coordinates. This service also uses an extern API for retrieving the data.
public class WeatherService : IWeatherService
{
private const double _kelvin = 273.15;
private const string _weatherApiUrl = "http://api.openweathermap.org/data/2.5/weather";
private readonly IHttpRequestUtils _httpRequestUtils = new HttpRequestUtils();
/// <summary>
/// Get current tempature
/// </summary>
/// <param name="latitude"></param>
/// <param name="longitude"></param>
/// <returns></returns>
public int GetCurrentTempature(double latitude, double longitude)
{
var url = new UrlBuilder(_weatherApiUrl);
url.QueryCollection.Add("lat", latitude.ToString());
url.QueryCollection.Add("lon", longitude.ToString());
var json = _httpRequestUtils.DoHttpRequest(url.ToString());
if (!string.IsNullOrEmpty(json))
{
var jsonObject = JObject.Parse(json);
var tempature = 0.0;
var result = jsonObject["main"].Children<JProperty>().FirstOrDefault(x => x.Name == "temp").Value.Value<string>();
if (double.TryParse(result, out tempature))
{
return Convert.ToInt32(tempature - _kelvin);
}
}
return 0;
}
}
This is just a small example of what you can do with personalization in EPiServer. Since version 7 it is possible to use DOJO widgets for the rendering of input fields. This makes the developer free to create custom visitor groups. You can find the full source on my GitHub account.