Página 3 de 3

Custom Field for Sitecore Multi-list with Search

In this article, I will focus on a customization for Multi-list with Search. This scenario is not common. But sometimes you will need to customize a field in Sitecore. I will show you the steps that I followed to customize the Multi-List with Search.

Step 1: Prepare your component

I created a List View Component which contains multiple items.

Figure 1: List View Data source

This item will contains 2 fields:

  • Title – Single-Line Text
  • Description – Multi-Line Text
Figure 2: List Item

As we know, The Multi-List with Search by default shows the Item Name / Display Name, but as per client requirements we need to display and search using another field or even another item relation field.

Step 2: Create custom control

For this operation you can duplicate existing Multi-List with Search and name it “Custom Multilist with Search” or name that make sense to you.

Then, scroll down to Control Field and name something like “contentSpecificExtension:CustomMultilistWithSearch”

Step 3: Create custom Search.ashx Service and page load control class

Mutli-List with Search as you know it use Solr search to fetch the options. So in that case we will override search functionality to display the name as we required. Also we need to change the page load rendering to display the desired name.

Page Load Control Class

To create the class that will control the UI, we need to create a class named CustomMultilistWithSearch.cs

  public class CustomMultilistWithSearch : BucketList
    {

        protected override string ScriptParameters => string.Format("'{0}'", (object)string.Join("', '", (object)this.ID, (object)this.ClientID, (object)this.PageNumber, (object)"/sitecore/shell/Applications/Buckets/Services/CustomSearchSearch.ashx", (object)this.Filter, (object)SearchHelper.GetDatabaseUrlParameter("&"), (object)this.TypeHereToSearch, (object)this.Of, (object)this.EnableSetNewStartLocation));
       
        public override string OutputString(Item item)
        {
            Item bucketItemOrParent = item.GetParentBucketItemOrParent();
            //string str = bucketItemOrParent != null ? " - " + bucketItemOrParent.DisplayName : string.Empty;
            return item.Fields["Title"]?.Value;
          
        }
    }

In this script we are going to override ScriptParameters and noticed that is pointing to a CustomSearch.ashx file. And OuputString here we can add the code to display the desired name.

Then Website\sitecore\shell\Applications\Buckets\Services create a file which specified the class Handler that we are going to use to override the Search Behavior.

<%@ WebHandler="" Language="C#" CodeBehind="Search.ashx.cs" Class="Website.Areas.DemoSite.Infraestructure.CustomControls.Services.Buckets.Search" %>

Finally create a class Website.Areas.DemoSite.Infraestructure.CustomControls.Services.Buckets.Search.cs which contains the Search Logic

 public class Search : IHttpHandler, IRequiresSessionState
    {
        #region Private Variables

        private string _pageNumber = "1";

        private int _itemsPerPage;

        private readonly List<SearchStringModel> _searchQuery = new List<SearchStringModel>();

        private bool _runFacet;

        private Stopwatch _stopwatch;

        private bool isJSONP;

        private string callback;

        #endregion

        private ICollection<SitecoreUISearchResultItem> FullSearch(List<SearchStringModel> currentSearchString, out int hitCount, int pageNumber, int pageSize, string sortField, string sortDirection)
        {
            var results = new List<SitecoreUISearchResultItem>();
            
            // YOur Search Logic Here
           
            return results
                .Skip((pageNumber - 1) * pageSize)
                .Take(pageSize)
                .ToList();
        }

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "application/json";
            context.Response.ContentEncoding = Encoding.UTF8;
            isJSONP = false;
            this._itemsPerPage = 20; // Constants.SettingsItem.Fields[Constants.ItemsPerPageText].Value.IsNumeric() ? int.Parse(Constants.SettingsItem.Fields[Constants.ItemsPerPageText].Value) : 20;
            this.ExtractSearchQuery(context.Request.Form);
            this._stopwatch = new Stopwatch();
            this._stopwatch.Start();
            this.StoreUserContextSearches();
            int hitCount;
            var debugMode = SearchHelper.GetDebug(this._searchQuery).IsNotEmpty();


            this._itemsPerPage = SearchHelper.GetPageSize(this._searchQuery).IsNumeric() ? int.Parse(SearchHelper.GetPageSize(this._searchQuery)) : 20;
            var items = FullSearch(this._searchQuery, out hitCount, pageNumber: int.Parse(this._pageNumber), pageSize: this._itemsPerPage, sortField: SearchHelper.GetSort(this._searchQuery), sortDirection: SearchHelper.GetOrderDirection(this._searchQuery));
            var itemsCount = hitCount;
            var pageNumbers = ((itemsCount % this._itemsPerPage) == 0) ? (itemsCount / this._itemsPerPage) : ((itemsCount / this._itemsPerPage) + 1);
            var currentPage = int.Parse(this._pageNumber);
            var startItemIdx = (currentPage - 1) * this._itemsPerPage;
            if (startItemIdx >= itemsCount)
            {
                currentPage = 1;
            }

            if (items != null)
            {

                foreach (var sitecoreItem in items)
                {
                    var innerItem = sitecoreItem.GetItem();
                    if (innerItem != null)
                    {
                        sitecoreItem.TemplateName = innerItem.TemplateName;
                        var parentBucketItemOrParent = innerItem.GetParentBucketItemOrParent();
                        if (parentBucketItemOrParent != null)
                        {
                            sitecoreItem.Bucket = parentBucketItemOrParent.Name;
                        }
                        else
                        {
                            sitecoreItem.Bucket = "sitecore";
                        }
                        sitecoreItem.CreatedDate = innerItem.Statistics.Created;
                        //sitecoreItem.Language = innerItem.Language.CultureInfo.TwoLetterISOLanguageName;
                        sitecoreItem.Path = innerItem.Paths.IsMediaItem
                                                ? innerItem.Paths.MediaPath
                                                : innerItem.Paths.FullPath;
                        sitecoreItem.CreatedBy = innerItem.Statistics.CreatedBy != string.Empty ? innerItem.Statistics.CreatedBy : innerItem.Statistics.UpdatedBy;


                        if (sitecoreItem.Content.IsNullOrEmpty())
                        {
                            sitecoreItem.Content = string.Empty;
                        }

                        if (sitecoreItem.ImagePath.IsNullOrEmpty())
                        {
                            sitecoreItem.ImagePath = "/temp/IconCache/" + sitecoreItem.GetItem().Template.Icon;
                        }
                    }
                }
            }


            if (!this._runFacet)
            {
                context.Response.Write(callback + "(" + JsonConvert.SerializeObject(new FullSearch
                {
                    PageNumbers = pageNumbers,
                    items = items,
                    launchType = GetEditorLaunchType(),
                    SearchTime = this.SearchTime,
                    SearchCount = itemsCount.ToString(),
                    CurrentPage = currentPage,
                    Location = Context.ContentDatabase.GetItem(this.LocationFilter) != null ? Context.ContentDatabase.GetItem(this.LocationFilter).Name : "Unknown"
                }) + ")");

                this._stopwatch.Stop();

            }


        }



        private static string GetEditorLaunchType()
        {
            return "contenteditor:launchtab";
        }

        private void StoreUserContextSearches()
        {
            var searchQuery = this._searchQuery.Where(query => !string.IsNullOrEmpty(query.Value)).Aggregate(string.Empty, (current, query) => current + query.Type + ":" + query.Value + ";");

            if (ClientContext.GetValue("Searches") == null)
            {
                ClientContext.SetValue("Searches", string.Empty);
            }

            if (!ClientContext.GetValue("Searches").ToString().Contains("|" + searchQuery + "|"))
            {
                ClientContext.SetValue("Searches", ClientContext.GetValue("Searches") + "|" + searchQuery + "|");
            }
        }

        private void ExtractSearchQuery(NameValueCollection searchQuery)
        {
            var fromField = false;
            if (searchQuery.Keys[0] == "fromBucketListField")
            {
                fromField = true;
            }

            this._runFacet = false;
            for (var i = 0; i < searchQuery.Keys.Count; i++)
            {
                if (searchQuery.Keys[i] == "callback")
                {
                    isJSONP = true;
                    callback = searchQuery[searchQuery.Keys[i]];
                    continue;
                }
                if (searchQuery.Keys[i] != "pageNumber")
                {
                    if (searchQuery.Keys[i] == "fromBucketListField" || searchQuery.Keys[i] == "filterText")
                    {
                        this._searchQuery.Add(new SearchStringModel
                        {
                            Type = "text",
                            Value = searchQuery[i]
                        });
                    }
                    else
                    {
                        if (fromField)
                        {
                            this.BuildSearchStringModelFromFieldQuery(searchQuery, i);
                        }
                        else
                        {
                            this.BuildSearchStringModelFromQuery(searchQuery, i);
                        }
                    }
                }
                else if (searchQuery.Keys[i] == "pageSize")
                {
                    this.ExtractPageSizeFromQuery(searchQuery, i);
                }
                else
                {
                    this.ExtractPageNumberFromQuery(searchQuery, i);
                }

                if (!fromField)
                {
                    i++;
                }
            }
        }

        private void ExtractPageSizeFromQuery(NameValueCollection searchQuery, int i)
        {
            var pageSize = searchQuery[searchQuery.Keys[i]];
            if (pageSize == "0" || pageSize == string.Empty)
            {
                this._itemsPerPage = 20;
            }
            else
            {
                this._itemsPerPage = int.Parse(pageSize);
            }
        }

        private void ExtractPageNumberFromQuery(NameValueCollection searchQuery, int i)
        {
            this._pageNumber = searchQuery[searchQuery.Keys[i]];
            if (this._pageNumber == "0")
            {
                this._pageNumber = "1";
            }
        }

        private void BuildSearchStringModelFromFieldQuery(NameValueCollection searchQuery, int i)
        {
            if (searchQuery.Keys[i] == "pageNumber")
            {
                this.ExtractPageNumberFromQuery(searchQuery, i);
            }
            else
            {
                this._searchQuery.Add(new SearchStringModel
                {
                    Type = searchQuery.Keys[i],
                    Value = searchQuery[searchQuery.Keys[i]]
                });
            }
        }

        private void BuildSearchStringModelFromQuery(NameValueCollection searchQuery, int i)
        {
            // if (i % 2 == 0)
            // {
            if (searchQuery[searchQuery.Keys[i]] != "Query")
            {
                if (searchQuery.Keys[i] != "_")
                {
                    string type = searchQuery[searchQuery.Keys[i]];
                    string value = searchQuery[searchQuery.Keys[i + 1]];

                    // It is better to NOT do a '*' match then to simply not add the clause!
                    if (!(type == "text" && value == "*"))
                    {
                        this._searchQuery.Add(new SearchStringModel
                        {
                            Type = type,
                            Value = value
                        });
                    }
                }
            }
            //}
        }


        private string getSiteIdFromName(string name)
        {
            SiteContext siteContext = SiteContextFactory.GetSiteContext(SiteManager.GetSite(name).Name);
            var db = Context.ContentDatabase ?? Context.Database;
            return db.GetItem(siteContext.StartPath).ID.ToString();
        }

        protected string Lang
        {
            get { return WebUtil.GetQueryString("la"); }
        }

        protected string LocationFilter
        {
            get { return string.IsNullOrEmpty(WebUtil.GetQueryString("id")) ? WebUtil.ExtractUrlParm("id", HttpContext.Current.Request.UrlReferrer.AbsoluteUri) : WebUtil.ExtractUrlParm("id", HttpContext.Current.Request.UrlReferrer.AbsoluteUri); }
        }

        protected string Db
        {
            get { return WebUtil.GetQueryString("db"); }
        }

        protected string SearchTime
        {
            get { return this._stopwatch.Elapsed.ToString(); }
        }

        protected Database ContentDatabase
        {
            get { return Factory.GetDatabase(this.Db) ?? Context.ContentDatabase; }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

I hope you could find useful this blog post.

Read more

Check programmatically if rendering exists in Sitecore

Sometimes you will need to check programmatically if rendering is already added to a Presentation Details in Sitecore. In my case Content Authors doesn’t want to add a modal in renderings each time that they added another component to the view. Also, you can add multiple times that Widget rendering on this page, but the modal should be added once.

To provide a solution I added a helper I will show you the code that I used.


  public static class RenderingHelper
    {
        public static bool HasRendering(string guid)
        {

            var refs = Sitecore.Context.Item.Visualization.GetRenderings(Context.Device, false);
            if (!refs.Any())
                return false;

            var renderingReferences = refs.Where(r => r.RenderingID.ToString().Equals(guid, StringComparison.InvariantCultureIgnoreCase)).ToList();

            if (renderingReferences.Any())
                return true;

            return false;

        }
    }

Then, when you implement the validation the view you can add something like this


@if (!RenderingHelper.HasRendering("{--YOUR RENDERING ID HERE--}"))
{
    @Html.Sitecore().Rendering("{--YOUR RENDERING ID HERE--}", new
{
    DataSource = "{--YOUR DATASOURCE ITEM ID HERE--}"
})
}


I hope this will be useful for you. Happy Coding.

Read more

Sitecore Geo IP Services (SC 9.3)

Many businesses require to implements GEO IP Services. Business Rules differ between locations. As Developers, Sitecore makes our lives easier by providing a Service that we can use to obtain the location of the website’s visitor.

The IP Geolocation provides information like country, state, city, and every visitor’s registered company name as well. Based on geodata, we can build modules that interact with our visitors and provide accurate information depending on where they are.

Installation and Configuration

Here you will find the official link to activate and set up the Sitecore IP Geolocation: Set up Sitecore IP Geolocation

In your Siecore Solution ensure you add the following Nuget Packages:

  • Sitecore.Kernel
  • Sitecore.Analytics

Solution

Sometimes when you work with lower environments or your local environment Geo IP is not available. I developed the following simple class to get the visitor’s country and city. We can add the next file to setup a city and country code when Sitecore IP Geolocation is not available for your local. I used Sitecore 9.3.


<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/" xmlns:env="http://www.sitecore.net/xmlconfig/env/">
  <sitecore>
    <settings env:require="Local">
      <setting name="Dev.IPGeolocation.IsDevMode" value="true"/>
      <setting name="Dev.IPGeolocation.City" value="Boston"/>
      <setting name="Dev.IPGeolocation.CountryCode" value="US"/>
    </settings>
  </sitecore>
</configuration>

The next class contains a method GetUserGeolocation to get user location information.

public class IPGeolocationService
    {
         /// <summary>
        /// Get User IP form Request Context.
        /// </summary>
        /// <returns>(string) IP Address</returns>
        private static string GetUserIP()
        {
            var ip = (HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"] != null
                      && HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"] != "")
                ? HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]
                : HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
            if (ip.Contains(","))
                ip = ip.Split(',').First().Trim();

            return ip.Trim();
        }

        /// <summary>
        ///   Fetch country from Sitecore IP Geolocation Service check 
        ///   setup at https://doc.sitecore.com/developers/93/sitecore-experience-manager/en/set-up-sitecore-ip-geolocation.html
        /// </summary>
        /// <returns></returns>
        public static GeoData GetUserGeolocation()
        {
            GeoData geoData;
            bool IsDevEnviroment = Settings.GetBoolSetting("Dev.IPGeolocation.IsDevMode", false);
            if (!IsDevEnviroment)
            {
                var millisecondsTimeout = 2000;
                
                try
                {
                    var options = new GeoIpOptions(IPAddress.Parse(GetUserIP()), millisecondsTimeout);
                    var result = GeoIpManager.GetGeoIpData(options);
                    if (result.ResolveState == GeoIpResolveState.Resolved &&
                        result.WhoIsInformation != null)
                    {
                        geoData = new GeoData()
                        {
                            City = result.WhoIsInformation.City,
                            CountryCode = result.WhoIsInformation.Country
                        };
                    }
                    else
                    {
                        throw new Exception("GeoIP whoIsInformation is null");
                    }
                }
                catch (Exception e)
                {
                    Log.Error("Error Getting IPAddress",e, typeof(IPGeolocationService));
                    if (Tracker.Current != null && Tracker.Current.Interaction != null && Tracker.Current.Interaction.GeoData != null)
                    {
                        geoData = new GeoData()
                        {
                            City = Tracker.Current.Interaction?.GeoData?.City,
                            CountryCode = Tracker.Current.Interaction?.GeoData?.Country
                        };

                    }
                    else
                    {
                        Log.Error("IPGeolocation: There is an issue with IP Geolocation Service", typeof(IPGeolocationService));
                        geoData = new GeoData()
                        {
                            City = null,
                            CountryCode = Settings.GetSetting("LocationEngine.IPGeolocation.CountryCode")
                        };
                    }
                }

            }
            else
            {
                Log.Info("IPGeolocation: Dev Mode is enabled", typeof(IPGeolocationService));
                geoData = new GeoData()
                {
                    City = Settings.GetSetting("Dev.IPGeolocation.City"),
                    CountryCode = Settings.GetSetting("Dev.IPGeolocation.CountryCode")
                };
            }
            Log.Info($"IPGeolocation: Country Code is {geoData.CountryCode}", typeof(IPGeolocationService));
            return geoData;
        }
    }

Finally, GeoData is a model class that contains the location information.


 public class GeoData
    {
        public string CountryCode { get; set; }
        public string City { get; set; }
    }

I hope this information will be usefully when you work with Sitecore Geo IP Services.

Read more

How to integrate API’s with Sitecore using Swagger

In this blog post I will talk about how to integrate APIs with Sitecore using Swagger.

What is Swagger?

Swagger is an open-source tool that uses Open API Specification, the initiative was started in 2015 by Linux Foundation. In my opinion, this tool saves a lot of time, it will support your development lifecycle starting with Design, Building, and Documentation.

Design Phase:

Swagger Editor will help you to design your API.

In the following example, we can appreciate that /store/inventory is a GET http method which is part of "store" section. The definition is clear and easy to understand. You can read what is the possible responses as 200 code that responds an object (Dictionary<string,int>).

Once you design the definition you are ready to go to the next phase.

Build Phase:

Swagger provides Swagger CodeGen to generate any Client or Server code base, this provides the interfaces where you can implement to add the API’s business logic.

Swagger editor also provide you a tab in the interface to generate this client / server code. This blog post will show you how to implement Swagger Client API with an existing Sitecore MVC Solution. In order to generate the client you will need to select from Generate client tab -> csharp.

It will generate a C# Project. That contains the following folder structure:

  • API: Contains all Entity Interfaces, in our previous example, we had an operation GetInventory which is part of the store group. Swagger will generate a class per group, Example: StoreApi
  • Client: The folder contains all classes to build the client. Basically, you will find classes like ApiCall, Configuration, Exceptions.
  • Model: This is the model folder that contains all entities used in your APIs, like Pet, ApiResponse, Catalog, Product, etc.

Move API, Client and Model folder to your Sitecore Solution Project.

Prepare your solution

Install the following NuGet packages on your solution:

  • RestSharp
  • Newtonsoft.Json
  • System.ComponentModel
  • System.ComponentModel.Annotations
  • Sitecore.Kernel

Configure your solution

After installing all packages that are required and moved the files into the solution edit Client\Configuration.cs class in order to setup the Base URL.

  /// <summary>
        /// Initializes a new instance of the <see cref="Configuration" /> class
        /// </summary>
        public Configuration()
        {
            UserAgent = "Swagger-Codegen/1.0.0/csharp";
            BasePath = Sitecore.Configuration.Settings.GetSetting("PetStoreConnector.BaseUrl");
            DefaultHeader = new ConcurrentDictionary<string, string>();
            ApiKey = new ConcurrentDictionary<string, string>();
            ApiKeyPrefix = new ConcurrentDictionary<string, string>();

            // Setting Timeout has side effects (forces ApiClient creation).
            Timeout = 100000;
        }

Change the constructor class to include Sitecore.Configuration.Settings.GetSetting("PetStoreConnector.BaseUrl") and create a config file on Sitecore like PetStoreConnector.config

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/" xmlns:env="http://www.sitecore.net/xmlconfig/env/">
    <sitecore>
        <settings>
            <setting name="PetStoreConnector.BaseUrl" value="YOUR_BASE_PATH"/>
        </settings>
    </sitecore>
</configuration>

In your Controller you can add some code to call the API´s endpoints, here there are an example:

  public class InventoryController : Controller
    {
        public ActionResult Index()
        {
            IStoreApi api = new StoreApi();
            var inventory = api.GetInventory();
            return Json(inventory, JsonRequestBehavior.AllowGet);
        }


    }

Finally build your solution and deploy your changes and you are ready to consume your API on Sitecore using Swagger.

This approach could reduce your development time, you can easily integrate your logic with another system building a robust C# client solution.

I had the opportunity to talk in Sitecore User Group Ecuador Session 15 in the following YouTube link you can watch the session (in Spanish).

Read more

How to install Solr?

This guide is about how to install apache Solr in Windows 10.

Requirements

  1. Download Apache Solr
  2. Download NSSM
  3. Install Java JDK

Steps

  1. Create a folder named Solr in your C:/ root.
  2. Extract the zip folder in your Solr folder.
  1. In C:\Solr\solr-8.4.0\server\etc add the following Powershell script and save it as solrssl.ps1:
param(
	[string]$KeystoreFile = 'solr-ssl.keystore.jks',
	[string]$KeystorePassword = 'secret',
	[string]$SolrDomain = 'localhost',
	[switch]$Clobber
)

$ErrorActionPreference = 'Stop'

### PARAM VALIDATION
if($KeystorePassword -ne 'secret') {
	Write-Error 'The keystore password must be "secret", because Solr apparently ignores the parameter'
}

if((Test-Path $KeystoreFile)) {
	if($Clobber) {
		Write-Host "Removing $KeystoreFile..."
		Remove-Item $KeystoreFile
	} else {
		$KeystorePath = Resolve-Path $KeystoreFile
		Write-Error "Keystore file $KeystorePath already existed. To regenerate it, pass -Clobber."
	}
}

$P12Path = [IO.Path]::ChangeExtension($KeystoreFile, 'p12')
if((Test-Path $P12Path)) {
	if($Clobber) {
		Write-Host "Removing $P12Path..."
		Remove-Item $P12Path
	} else {
		$P12Path = Resolve-Path $P12Path
		Write-Error "Keystore file $P12Path already existed. To regenerate it, pass -Clobber."
	}
}

try {
	$keytool = (Get-Command 'C:\Program Files\Java\jdk1.8.0_261\bin\keytool.exe').Source
} catch {
	$keytool = Read-Host "keytool.exe not on path. Enter path to keytool (found in JRE bin folder)"

	if([string]::IsNullOrEmpty($keytool) -or -not (Test-Path $keytool)) {
		Write-Error "Keytool path was invalid."
	}
}

### DOING STUFF

Write-Host ''
Write-Host 'Generating JKS keystore...'
& $keytool -genkeypair -alias solr-ssl -keyalg RSA -keysize 2048 -keypass $KeystorePassword -storepass $KeystorePassword -validity 9999 -keystore $KeystoreFile -ext SAN=DNS:$SolrDomain,IP:127.0.0.1 -dname "CN=$SolrDomain, OU=Enginnering, O=Verndale, L=Quito, ST=Pichincha, C=EC"

Write-Host ''
Write-Host 'Generating .p12 to import to Windows...'
& $keytool -importkeystore -srckeystore $KeystoreFile -destkeystore $P12Path -srcstoretype jks -deststoretype pkcs12 -srcstorepass $KeystorePassword -deststorepass $KeystorePassword

Write-Host ''
Write-Host 'Trusting generated SSL certificate...'
$secureStringKeystorePassword = ConvertTo-SecureString -String $KeystorePassword -Force -AsPlainText
$root = Import-PfxCertificate -FilePath $P12Path -Password $secureStringKeystorePassword -CertStoreLocation Cert:\LocalMachine\Root
Write-Host 'SSL certificate is now locally trusted. (added as root CA)'

Write-Host ''
Write-Host '########## NEXT STEPS ##########' -ForegroundColor Green
Write-Host ''
Write-Host '1. Copy your keystore to $SOLR_HOME\server\etc (MUST be here)' -ForegroundColor Green

if(-not $KeystoreFile.EndsWith('solr-ssl.keystore.jks')) {
	Write-Warning 'Your keystore file is not named "solr-ssl.keystore.jks"'
	Write-Warning 'Solr requires this exact name, so make sure to rename it before use.'
}

$KeystorePath = Resolve-Path $KeystoreFile
Write-Host ''
Write-Host '2. Add the following lines to your solr.in.cmd:' -ForegroundColor Green
Write-Host ''
Write-Host "set SOLR_SSL_KEY_STORE=etc/solr-ssl.keystore.jks" -ForegroundColor Yellow
Write-Host "set SOLR_SSL_KEY_STORE_PASSWORD=$KeystorePassword" -ForegroundColor Yellow
Write-Host "set SOLR_SSL_TRUST_STORE=etc/solr-ssl.keystore.jks" -ForegroundColor Yellow
Write-Host "set SOLR_SSL_TRUST_STORE_PASSWORD=$KeystorePassword" -ForegroundColor Yellow
Write-Host ''
Write-Host 'Done!'

You need to replace C:\Program Files\Java\jdk1.8.0_261\bin\keytool.exe path that match your installed Java JDK.

  1. Then you need to run the following powershell script that is located at C:\Solr\solr-8.4.0\server\etc
.\solrssl.ps1
  1. When execution is done, you will see solr-ssl.keystore.jks and solr-ssl.keystore.p12 files generated.

  2. Edit C:\Solr\solr-8.4.0\bin\solr.in.cmd file and uncomment the following lines:

set SOLR_SSL_KEY_STORE=etc/solr-ssl.keystore.jks
set SOLR_SSL_KEY_STORE_PASSWORD=secret
set SOLR_SSL_TRUST_STORE=etc/solr-ssl.keystore.jks
set SOLR_SSL_TRUST_STORE_PASSWORD=secret
set SOLR_SSL_KEY_STORE_TYPE=JKS
set SOLR_SSL_TRUST_STORE_TYPE=JKS

  1. Extract NSSM zip file into C:\nssm-2.24

8. Add NSSM_HOME in your Enviroment variables:

  1. Finally, run the following command
nssm install

10. Fill the form with the following information:

11. Open your browser to check Solr installed at the following url: https://localhost:8983/solr/#/

And that’s all you have Solr successfully installed in your development machine.

Read more
Verndale Hero

My experience at Verndale

I started at Verndale in December 2019. In my job interview, I mentioned that I didn’t have much experience in C# .NET MVC, because I worked in many projects in PHP with Laravel which is an MVC Framework, it’s not the same but… dude concepts are concepts. To avoid making it long, I talked about my amazing experiences in my previous company Umpacto Soluciones and all international competitions that I participated in during my university career. Including competitions in Bogotá, Shanghai, Abu Dhabi, and many others in my natal city Quito.

In a few words, I considered that I love design patterns and programming concepts. In my personal opinion, this is why I see that programming is like an art, you could express all your ideas through your designs and code. Another important thing to mention is that I love challenges.

Verndale is a company that creates customer experiences, so I’m very happy to be part of this company. In my onboarding process, I learn many things about Verndale, and my first steps at Verndale was learning a new Platform called Sitecore.

Sitecore is an Experience Digital Platform (XDP), which includes powerful features like Personalization, Marketing Automatization, A/B Testing.

I got my Sitecore Developer Certification in my first month at the company and I love it. I love the way to do modules, the simplicity. The modules that I develop in Sitecore are semantic and clean.

Another important thing to mention is that I learned about Sitecore Content Hub, this is a Digital Asset Management (DAM), Content Marketing Platform (CMP), Marketing Resource Management (MRM). In my personal opinion I see this out of this world, Can you image manage all your digital assets in one platform?, and all images are automatically tagged by Azure Cognitive Services, and also integrated with Photoshop, Sitecore, One Drive, and many other services. So I recommended this platform for all companies that need to handle huge digital assets.

Finally, I could say that I’m happy in what I do. I’m going to post more about my experiences and troubleshooting in those technologies. Happy coding.

Read more