<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">Blog posts by Yen Nguyen</title><link href="http://world.optimizely.com" /><updated>2019-03-26T03:46:39.0000000Z</updated><id>https://world.optimizely.com/blogs/yen-nguyen/</id> <generator uri="http://world.optimizely.com" version="2.0">Optimizely World</generator> <entry><title>Auto Translation V2 to V3 migration</title><link href="https://world.optimizely.com/blogs/yen-nguyen/dates/2019/3/auto-translation-api-v3-was-released/" /><id>&lt;h2&gt;&lt;br /&gt;Api V3 has came out.&lt;/h2&gt;
&lt;p&gt;Microsoft has migrated auto translation service api from V2 to V3 in the beginning of this year. &lt;br /&gt;Version 2 was deprecated on April 30, 2018 and will be discontinued on April 30, 2019 according to Microsoft announcement &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/cognitive-services/translator/migrate-to-v3&quot; title=&quot;V2 to V3 Migration&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;here&lt;/a&gt;&lt;br /&gt;Episerver&#39;s Language Manager add-on&amp;nbsp;was updated in response to that and released in version EPiServer.Labs.LanguageManager 3.3.0. &lt;br /&gt;The upgraded auto translation service&#39;s name is changed to &quot;Cognitive Service Translation&quot; (was &quot;Bing Translator&quot; before) with new api endpoint: &lt;strong&gt;api.cognitive.microsofttranslator.com&lt;/strong&gt;.&lt;br /&gt;Microsoft also introduces service improvements in V3:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is generally available and fully supported.&lt;/li&gt;
&lt;li&gt;Is GDPR compliant as a processor and satisfies all ISO 20001 and 20018 as well as SOC 3 certification requirements.&lt;/li&gt;
&lt;li&gt;Allows you to invoke the neural network translation systems you have customized with Custom Translator (Preview), the new Translator NMT customization feature.&lt;/li&gt;
&lt;li&gt;Does not provide access to custom translation systems created using the Microsoft Translator Hub.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Authentication&lt;/h2&gt;
&lt;p&gt;The V3 api will accept authentication key of V2, so you don&#39;t need to get a new subscription&amp;nbsp;that make it easier for you to migrate to new version without breaking anything.&lt;/p&gt;
&lt;h2&gt;How to update Episerver&#39;s Language Manager package?&lt;/h2&gt;
&lt;p&gt;These updates were released in package EPiServer.Labs.LanguageManager 3.3.0. So, all you need to do is to update this package, then go to Language Manager settings change provider to &quot;Cognitive Service Translation&quot;.&lt;br /&gt;All behaviors should remain the same.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/81aaa3538934467498694335ba22c0d6.aspx&quot; width=&quot;500&quot; height=&quot;298&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;Hope you guys find this info useful. Thanks!&lt;/p&gt;</id><updated>2019-03-26T03:46:39.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Implement a custom geolocation provider with Forms</title><link href="https://world.optimizely.com/blogs/yen-nguyen/dates/2019/1/implement-a-custom-geolocation-provider-/" /><id>&lt;p&gt;I got a support ticket a few days ago in terms of Forms&#39; hidden vistor profiling element. The customer basically wanted to capture user&#39;s zip code by using the element with a external geolocation api. He could get ip, location name however zip code always return empty in submission. So, i digged in the form&#39;s visitor profiling element and its geolocation provider. It turns out Form is using an api named FreeGeo and fetch geolocation info base on user&#39;s ip address and you can replace it by your own api for sure. The thing is, to make it work, we&#39;ll need some extra steps to config custom provider and justify the api result to meet our needs. The following implementaion is built with Episerver 11 Alloy site together with Forms 4.20. This is step by step guide to acomplish it.&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;1. Change configuration using new provider&lt;/strong&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Open Web.config file, add following block of config under&amp;nbsp;&amp;lt;virtualPathProviders&amp;gt; section.&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;    &amp;lt;geolocation defaultProvider=&quot;customGeoProvider&quot;&amp;gt;
      &amp;lt;providers&amp;gt;
        &amp;lt;add name=&quot;customGeoProvider&quot; type=&quot;AlloySample.Internal.GeoData.CustomGeolocationProvider, AlloySample&quot; geoApiUrl=&quot;http://{YOUR_API_URI}/{0}?access_key={YOUR_API_KEY}&quot;/&amp;gt;
      &amp;lt;/providers&amp;gt;
    &amp;lt;/geolocation&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This config will switch geolocation to your custom one. You can modify type and geoApiUrl to fit your needs and notice that {0} will be filled with user IP when making a request to api.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;2. Create custom provider to fetch api result&lt;/strong&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Firstly, we add a new provider class that inherit&amp;nbsp;GeolocationProviderBase class and&amp;nbsp;ICustomGeolocationProvider interface (this is required because Forms will scan all implementation of&amp;nbsp;ICustomGeolocationProvider to process). In this sample, i created a provider class named CustomGeolocaitonProvider&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt; public class CustomGeolocationProvider : GeolocationProviderBase, ICustomGeolocationProvider
    {
        private string _geoApiUrl { get; set; }
        private static readonly ILogger _logger = LogManager.GetLogger(typeof(FreeGeolocationProvider));
        public override IGeolocationResult Lookup(System.Net.IPAddress address)
        {

            return GetGeoData(address.ToString()).Result;
        }
        private async Task&amp;lt;EPiServer.Forms.Internal.GeoData.GeolocationResult&amp;gt; GetGeoData(string clientIp)
        {
            if (string.IsNullOrEmpty(clientIp))
            {
                return null;
            }

            //create request and get reponse in Json from API
            var request = (HttpWebRequest)WebRequest.Create(string.Format(_geoApiUrl, clientIp));
            request.Method = WebRequestMethods.Http.Get;
            request.Accept = &quot;application/json&quot;;

            var response = await request.GetResponseAsync().ConfigureAwait(false);
            string jsonResponse = &quot;&quot;;
            using (var reader = new StreamReader(response.GetResponseStream()))
            {
                jsonResponse = reader.ReadToEnd();
            }

            if (string.IsNullOrEmpty(jsonResponse))
            {
                return null;
            }

            EPiServer.Forms.Internal.GeoData.GeolocationResult result;
            try
            {
                //convert json response to GeolocationResult
                dynamic geoJsonData = jsonResponse.ToObject();

                result = new EPiServer.Forms.Internal.GeoData.GeolocationResult
                {
                    ip = geoJsonData[&quot;ip&quot;],
                    country_code = geoJsonData[&quot;country_code&quot;],
                    country_name = geoJsonData[&quot;country_name&quot;],
                    region_code = geoJsonData[&quot;region_code&quot;],
                    region_name = geoJsonData[&quot;region_name&quot;],
                    city = geoJsonData[&quot;city&quot;],
                    zip_code = geoJsonData[&quot;zip&quot;],
                    time_zone = geoJsonData[&quot;time_zone&quot;]?.id,
                    latitude = geoJsonData[&quot;latitude&quot;],
                    longitude = geoJsonData[&quot;longitude&quot;],
                    CountryCode = geoJsonData[&quot;country_code&quot;],
                    Region = geoJsonData[&quot;region_name&quot;],
                };

            }
            //if jsonResponse is not a valid json string:
            catch
            {
                _logger.Error(&quot;Free Geo Api does not return a valid json string&quot;);
                return null;
            }

            return result;
        }
        public override IEnumerable&amp;lt;string&amp;gt; GetCountryCodes(string continentCode)
        {
            throw new NotImplementedException();
        }
        public override IEnumerable&amp;lt;string&amp;gt; GetRegions(string countryCode)
        {
            throw new NotImplementedException();
        }
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(name, config);

            //get geoApiUrl in Provider Config
            if (string.IsNullOrEmpty(config[&quot;geoApiUrl&quot;]))
            {
                throw new ConfigurationErrorsException(&quot;Invalid configuration. Geo API url is missing&quot;);
            }
            _geoApiUrl = config[&quot;geoApiUrl&quot;];
        }
        public override Capabilities Capabilities =&amp;gt; throw new NotImplementedException();
        public IGeoDataProcessService GeoDataProcessService
        {
            get
            {
                return new CustomGeoDataProcessService();
            }

        }


    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this provider class, we care about two methods: GetGeoData - method will handle geolocation data receiving and GeoDataProcessService - method return data processor. In&amp;nbsp;GetGeoData method, just go ahead and write your code to pull api geo location data. I&#39;m assuming your api is returning json data, the code is nothing but a simple api call using WebRequest.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;            var request = (HttpWebRequest)WebRequest.Create(string.Format(_geoApiUrl, clientIp));
            request.Method = WebRequestMethods.Http.Get;
            request.Accept = &quot;application/json&quot;;

            var response = await request.GetResponseAsync().ConfigureAwait(false);
            string jsonResponse = &quot;&quot;;
            using (var reader = new StreamReader(response.GetResponseStream()))
            {
                jsonResponse = reader.ReadToEnd();
            }

            if (string.IsNullOrEmpty(jsonResponse))
            {
                return null;
            }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After receving json response from api, the remaining task is so simple, you just need to create a new GeoLocationResult instance to store all json data by mapping field by field of json to result model or you can custome result here. The tricky point is that you need to map result property value to the right json object property. For example, my api return json result with &quot;zip&quot;: &quot;XXX&quot; but the other api return&amp;nbsp; &quot;zip_code&quot;: &quot;XXX&quot;.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;          try
            {
                //convert json response to GeolocationResult
                dynamic geoJsonData = jsonResponse.ToObject();

                result = new EPiServer.Forms.Internal.GeoData.GeolocationResult
                {
                    ip = geoJsonData[&quot;ip&quot;],
                    country_code = geoJsonData[&quot;country_code&quot;],
                    country_name = geoJsonData[&quot;country_name&quot;],
                    region_code = geoJsonData[&quot;region_code&quot;],
                    region_name = geoJsonData[&quot;region_name&quot;],
                    city = geoJsonData[&quot;city&quot;],
                    zip_code = geoJsonData[&quot;zip&quot;],
                    time_zone = geoJsonData[&quot;time_zone&quot;]?.id,
                    latitude = geoJsonData[&quot;latitude&quot;],
                    longitude = geoJsonData[&quot;longitude&quot;],
                    CountryCode = geoJsonData[&quot;country_code&quot;],
                    Region = geoJsonData[&quot;region_name&quot;],
                };

            }
            //if jsonResponse is not a valid json string:
            catch
            {
                _logger.Error(&quot;Free Geo Api does not return a valid json string&quot;);
                return null;
            }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After get geo data, we&#39;ll need one more step to process the data. Method&amp;nbsp;GeoDataProcessService return a service named&amp;nbsp;GeoDataProcessService that will be created on the next step.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;3. Add a custom geolocation data process service&amp;nbsp;&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Add a new class, name it whatever you want, inherit&amp;nbsp;IGeoDataProcessService with code as below:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt; public class CustomGeoDataProcessService : IGeoDataProcessService
    {
       
        EPiServer.Forms.Internal.GeoData.GeolocationResult IGeoDataProcessService.ProcessGeoData(IGeolocationResult geoLocationResult)
        {
            var result = geoLocationResult as EPiServer.Forms.Internal.GeoData.GeolocationResult;
            return result;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This class has only one method and it gets IGeolocationResult from&amp;nbsp;provider&#39;s Lookup method and return strong typed result. This step is mandatory because Forms requires a data processor.&lt;/p&gt;
&lt;p&gt;Compile, run and subit forms, im now able to capture geolocation info as exact as api result.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/9e29603508f74447acad69fae9ab0631.aspx&quot; width=&quot;440&quot; height=&quot;177&quot; /&gt;&lt;/p&gt;
&lt;p&gt;*Note: The form&#39;s element will work only if site is online, the local sites won&#39;t be able to capture gelocation data.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;If you have any questions in relation to this post, please comment below. Thanks!&lt;/span&gt;&lt;/p&gt;</id><updated>2019-01-04T05:44:30.0000000Z</updated><summary type="html">Blog post</summary></entry></feed>