Fixing the Google Geocoding API v2 in Ektron

What exactly is Geocoding Service?

Geocoding is the process of converting addresses (like "Ameex Technologies Corp,1701 E Woodfield Rd, Suite 211Schaumburg, IL 60173 ") into geographic coordinates (like latitude 42.042375 and longitude -88.038012), which can be used to place markers or positions on a map. Reverse geocoding is the process of converting geographic coordinates into a human-readable address

Geocoder Service - It's application in Ektron

Geocoder service was primarily used in Ektron for getting the MetaData latitude and longitude coordinates from the address value. Geocoder functionality was used for updating address values for content and for users as well. The actual purpose behind this was to categorize search results based on 'locations'. Whenever a content or catalog entry was made by the user, the address value is converted into the corresponding latitude and longitude coordinates and saved to the metadata.

The issue at hand

Version 2 ("v2") of the Google Geocoding API was officially deprecated on 8th March 2010. The v2 Geocoding API has ceased to work since 9th September 2013. In order use the Geocoder service now, Google has released new version of the Geocoding API, called the Version 3 (v3). The v3 Geocoder uses a new endpoint URL with the output specified format like JSON/XML. The v2 Geocoding API returns a nested, hierarchical response, whereas v3 returns a flat response instead which makes it much easier for parsing the response data. Google now encourages all existing v2 API code to be migrated to v3.

Custom strategy to resolve this issue

Since Google has now stopped providing support for this deprecated v2 API, the impact is being felt by Ektron users who've made use of this API previously, irrespective of CMS version; the Geocoder feature will not be function properly until it is been migrated to v3. I'm assuming that Ektron will resolve this in its upcoming release. This issue can be resolved for now by implementing the custom strategy that I've elaborated below. The following approach will update the metadata values for content in the OnAfterpublishContent strategy method.

Steps to be followed to resolve this issue

Step 1: Create a custom strategy class for content in App code.

Step 2: Implement the OnAfterPublishContent method. The code below should give you an idea of how this needs to be done.

public class CustomContentStrategy : Ektron.Cms.Extensibility.ContentStrategy
{
   public CustomContentStrategy()
   {
      //
      // TODO: Add constructor logic here
      //
   }
   public override void OnAfterPublishContent(Ektron.Cms.ContentData contentData, Ektron.Cms.Extensibility.CmsEventArgs eventArgs)
   {
      string longitude = string.Empty;
      string latitude = string.Empty;
      int exitCount = 0;
      ContentManager contentManager=new ContentManager();
      ContentData cntData = contentManager.GetItem(contentData.Id, true);
      List metaData = cntData.MetaData.ToList();
      string address = metaData.Find(x => x.Name.ToLower() == "mapaddress").Text;
      Ameex.GeoCoder.GeoCoderService.GetResults(address,out longitude,out latitude);
      foreach (ContentMetaData item in metaData)
      {
         if (item.Name.ToLower() == "maplatitude")
         {
            item.Text = latitude;
            exitCount++;
         }
         if(item.Name.ToLower() == "maplongitude")
         {
            item.Text= longitude;
            exitCount++;
         }
      }
      if (longitude != "" && latitude != "")
      {
        new ContentAPI().UpdateContentMetaData(cntData.Id, metaData);
      }
   }
}

Step 3: Add the following Geocoder service class in App code.

   namespace Ameex.GeoCoder
   {
   public class GeoCoderService
   {
      protected static string googleGeoEndPoint = "http://maps.googleapis.com/maps/api/geocode/json?address={0}&sensor=false";
      public static void GetResults(string address, out string longitude, out string latitude)
      {
         string response = string.Empty;
         longitude = string.Empty;
         latitude = string.Empty;
         string endPoint = string.Format(googleGeoEndPoint, address);
         try
         {
            WebRequest webRequest = WebRequest.Create(endPoint) as HttpWebRequest;
            WebResponse webResponse = webRequest.GetResponse() as HttpWebResponse;
            if (webResponse != null)
            {
               using (Stream stream = webResponse.GetResponseStream())
               {
                  using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                  {
                     response = reader.ReadToEnd();
                     GetLatitudeLongitude(response, out longitude, out latitude);
                  }
               }
            }
         }
         catch (Exception ex)
         {
             Ektron.Cms.EkException.LogException(ex, EventLogEntryType.Error);
         }
      }
      private static void GetLatitudeLongitude(string response, out string longitude, out string latitude)
      {
         longitude = string.Empty;
         latitude = string.Empty;
         string status = string.Empty;
         if (response.Length > 0)
         {
            JObject responseObject = JObject.Parse(response);
            status = responseObject.SelectToken("status").ToString();
            if (status == "OK")
            {
               JObject location = (JObject)responseObject.SelectToken("results")[0]["geometry"];
               JToken geometry = location.SelectToken("location");
               latitude = geometry.SelectToken("lat").ToString();
               longitude = geometry.SelectToken("lng").ToString();
            }
            else
            {
               Ektron.Cms.EkException.WriteToEventLog(" Google Status: " + status.ToString(), EventLogEntryType.Information);
            }
         }
      }
   }
}

Step 4: Comment out an existing Geocoder service and add the new custom strategy class type in objectfactory.config as follows.

<add name="Content">
   <strategies>
      <!--<add name="GoogleGeoCoder" type="Cms.Extensions.GoogleGeoCoder.ContentStrategy, Cms.Extensions.GoogleGeoCoder"/>-->
      <add name="CustomContentGeoCoder" type="CustomContentStrategy"/>
   </strategies>
</add>
 

Step 5: Now we can test by adding the content and assigning the value to map address metadata and publishing the content. The latitude and longitude values should get updated in the metadata properly.

Step 6: For users, steps 1 to 5 needs to be followed. In addition to this, a separate custom user strategy class needs to be added as shown below.

public class CustomUserStrategy : Ektron.Cms.CmsUserStategy
{
   public override void OnAfterAddUser(Ektron.Cms.UserData userData, Ektron.Cms.Extensibility.CmsEventArgs eventArgs)
   {
      this.UpdateLatitudeLogitude(userData);
   }
   public override void OnAfterUpdateUser(Ektron.Cms.UserData userData, Ektron.Cms.Extensibility.CmsEventArgs eventArgs)
   {
      this.UpdateLatitudeLogitude(userData);
   }
   private void UpdateLatitudeLogitude(Ektron.Cms.UserData userData)
   {
      string longitude = string.Empty;
      string latitude = string.Empty;
      string address = userData.Address;
      Ameex.GeoCoder.GeoCoderService.GetResults(address, out longitude, out latitude);
      if (longitude != "" && latitude != "")
      {
         new Ektron.Cms.UserAPI().UpdateMapCoordinate(userData.Id, double.Parse(latitude), double.Parse(longitude));
      }
   }
}

Step 7: Update the custom user strategy in objectfactory.config.

<add name="User">
   <strategies>
      <!--<add name="GoogleGeoCoder"type="Cms.Extensions.GoogleGeoCoder.UserStrategy, Cms.Extensions.GoogleGeoCoder"/>-->
      <add name="CustomUserGeocoder" type="CustomUserStrategy"/>
   </strategies>
</add>
 

Step 8: Now add or update the user with the address value, the corresponding latitude and longitude of an address will be updated to the user.

Advantages of using this custom strategy:

  • This code can be implemented into any Ektron version.
  • The DLL upgrade is not needed.
  • Since the code is implemented in strategy class, it does not break any existing functionality. Also, there is no data loss, if any exception is thrown by the custom code.