<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">Blog posts by Jonas Näslund</title><link href="http://world.optimizely.com" /><updated>2018-04-10T15:17:00.0000000Z</updated><id>https://world.optimizely.com/blogs/Jonas-Naslund/</id> <generator uri="http://world.optimizely.com" version="2.0">Optimizely World</generator> <entry><title>Managing Episerver Find Exceptions when querying the index</title><link href="https://world.optimizely.com/blogs/steven-galton/dates/2018/4/managing-episerver-find-exceptions-when-calling-the-service/" /><id>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;I have been working on the Episerver Find implementation here at &lt;a href=&quot;https://www.redweb.com/&quot;&gt;Redweb&lt;/a&gt;&amp;nbsp;for a client build. I was noticing that there were some errors that would occasionally happen when performing some queries against the index. After having a look around, I came across &lt;a href=&quot;https://www.brianweet.com/2017/03/17/handling-find-serviceexception.html&quot;&gt;this article&lt;/a&gt; that gave me a solution to the issue and thought I would share the implementation into the project.&lt;/p&gt;
&lt;p&gt;Firstly, I put a try catch around any of the Find services when they would be making a query against the index so that any errors would be caught. I can then check to see if the error is a ClientException or a ServiceException. If they are I can log the error with the exception to make sure there isn&amp;rsquo;t an issue with what we have written.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;try
{
    var search = _findClient.Search&amp;lt;SupplierProduct&amp;gt;();

    if (!productCategoryId.IsNullOrWhiteSpace())
    {
        search = search.Filter(x =&amp;gt; x.ParentCategory.Match(categoryId));
    }

    if (!string.IsNullOrWhiteSpace(queryString))
    {
        search = search.For(queryString).InField(x =&amp;gt; x.Name);
    }

    if (!facetsList.IsNullOrEmpty())
    {
        search = search.MatchCategories(facetsList);
    }
    
    var supplierResults = search
        .ApplyBestBets()
        .UsingAutoBoost()
        .Skip((currentPage - 1) * resultsPerPage)
        .Take(resultsPerPage)
        .StaticallyCacheFor(TimeSpan.FromMinutes(1))
        .GetContentResult();

    return supplierResults;
}
catch (Exception ex) when(ex is ClientException || ex is ServiceException)
{
    Services.Episerver.Logger.Log(Level.Error, &quot;Shop Search encountered an Episerver Find Exception&quot;, ex);
    return new EmptyContentResult&amp;lt;SupplierProduct&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make sure that the user has the best experience possible, a new type of content result was created. This means that if an error occurs an EmptyContentResult would be returned rather than an error. The new content result would have no results programmed set within the properties and can have the type passed into it so that it returns the correct class type for the search being performed.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class EmptyContentResult&amp;lt;T&amp;gt; : ContentResult&amp;lt;T&amp;gt; where T : IContentData
{
    public EmptyContentResult() : base(
        Enumerable.Empty&amp;lt;T&amp;gt;(),
        new SearchResults&amp;lt;ContentInLanguageReference&amp;gt;(
            new SearchResult&amp;lt;ContentInLanguageReference&amp;gt;()
            {
                Facets = new FacetResults(),
                Hits = new HitCollection&amp;lt;ContentInLanguageReference&amp;gt;()
                {
                    Hits = Enumerable.Empty&amp;lt;SearchHit&amp;lt;ContentInLanguageReference&amp;gt;&amp;gt;().ToList()
                },
                Shards = new Shards()
            }))
    { }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means that if any errors occur, although an empty page would show to the user, it would not break their view of the site and handle the error in an appropriate manner. This was very quick to implement across all the search methods on the site and also catches any of the errors that could occur out of our control.&lt;/p&gt;
&lt;p&gt;I hope this helps some people out and I have also linked to the &lt;a href=&quot;https://www.brianweet.com/2017/03/17/handling-find-serviceexception.html&quot;&gt;original source&lt;/a&gt; if anyone wants to look at it as well.&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</id><updated>2018-04-10T15:17:00.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Indexing extra Episerver Find properties onto a single variant for Episerver Commerce using Extension Methods</title><link href="https://world.optimizely.com/blogs/steven-galton/dates/2018/4/indexing-extra-episerver-find-properties-onto-a-single-variant-for-episerver-commerce-using-extension-methods/" /><id>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;I have been working on a project that uses Episerver Find heavily on Episerver Commerce Items. Previously I have posted about indexing extra properties on a single variant and got some responses back about using extension methods to index the information. Previously I had some issues with approaching the problem this way, but I have been able to do this now.&lt;/p&gt;
&lt;p&gt;First, I needed to make an Initialization Module with a reference for the Search Client. Next, I created a variable that has the Search Client Conventions. From this, I was able to make it that every Supplier Product in my Commerce catalogue had an extra field that is only needed in the search result. This was the variant URL that we need for the search pages.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;    [InitializableModule]    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class EpiserverFindInitilizationModule : IInitializableModule
    {
        public void Initialize(InitializationEngine context)
        {
            var conventionIndexer = SearchClient.Instance.Conventions;
            conventionIndexer.ForInstancesOf&amp;lt;SupplierProduct&amp;gt;().IncludeField(x =&amp;gt; x.VariantUrl());
         }
     }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After this, I had an Extension Method that would use our resolver to pass in the current Supplier Product and then return the Variant URL for the Search Results.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public static string VariantUrl(this SupplierProduct supplierProduct)
{
    var contentLoader = ServiceLocator.Current.GetInstance&amp;lt;SupplierProductSummaryResolver&amp;gt;();
    var variant = contentLoader.GetViewModel(supplierProduct);
    return variant.Url;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before, I could not figure out how to retrieve this value from the Index and return it in my search results. I got some help and all I needed to do was call the extension method again on the search item.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private List&amp;lt;ShopItemInformationModel&amp;gt; PopulateShopItems(IEnumerable&amp;lt;SupplierProduct&amp;gt; shopResults)
{
   var shopResponseList = new List&amp;lt;ShopItemInformationModel&amp;gt;();

    foreach (var supplierProduct in shopResults)
    {
        var item =  new ShopItemInformationModel
        {
            Title = supplierProduct.Name,
            Code = supplierProduct.Code,
            Url = supplierProduct.VariantUrl()
        };

        shopResponseList.Add(item);
    }

   return shopResponseList;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One thing to note with this method though. This does call the extension method again and recalls the logic. We will be exploring this over the coming weeks and will get an update up over the next couple of weeks.&lt;/p&gt;
&lt;p&gt;Hope this helps someone!&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</id><updated>2018-04-03T09:49:26.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Indexing extra Episerver Find properties onto a single variant for Episerver Commerce </title><link href="https://world.optimizely.com/blogs/steven-galton/dates/2017/12/indexing-extra-episerver-find-properties-onto-a-single-variant-for-episerver-commerce-/" /><id>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;Recently on a client project that I have been working on at &lt;a href=&quot;https://www.redweb.com/&quot;&gt;Redweb&lt;/a&gt;, I have been working on getting Episerver Find working with Episerver Commerce. This was a simple task as you just needed to do some configuration and run the index job in the Commerce manager (&lt;a href=&quot;/link/f559faa8b71d44b985950e35340abd3f.aspx&quot;&gt;Guide found here&lt;/a&gt;).&amp;nbsp;&lt;/p&gt;
&lt;p&gt;The main issue that arose was that the catalogue structure was very complicated, and information was split across difference parents and children. This made it a bit difficult to get all the information needed for a single variant. Rather than doing multiple searches, I found that you could simply index this information into a single variant.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;To get the information to index onto the single variant, I created some properties that are ignored by the CMS. These would be used to store the actual information needed from the different parents of the variant.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code&gt;[Ignore]
public string LocationName =&amp;gt; SearchLocationName();

[Ignore]
public string Title =&amp;gt; SearchTitleText();

[Ignore]
public string Description =&amp;gt; SearchDescriptionText();

[Ignore]
public bool IsWeekendCourse =&amp;gt; CourseRunType.HasFlag(CourseRunType.Weekend);

[Ignore]
public bool IsWeekdayCourse =&amp;gt; CourseRunType.HasFlag(CourseRunType.Weekday);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some more properties were added that would store a reference to the parents of the variant that were also hidden from the CMS. Some methods were created that would then extract the information from these properties and then assign them to the properties created above.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private CourseVenueProduct _parentCourseVenueProduct;
private CourseProduct _parentCourseProduct;
private CourseCategory _parentCourseCategory;
private CourseRunType ? _courseRunType;

[Ignore]
public CourseVenueProduct ParentCourseVenueProduct 
{
    get 
    {
        if (_parentCourseVenueProduct == null) 
        {
            var parentProduct = this.GetParentProducts().FirstOrDefault();
            var loader = ServiceLocator.Current.GetInstance&amp;lt;IContentLoader&amp;gt;();
            _parentCourseVenueProduct = loader.Get&amp;lt;CourseVenueProduct&amp;gt;(parentProduct);
        }
        
        return _parentCourseVenueProduct;
    }
}

[Ignore]
public CourseProduct ParentCourseProduct 
{
    get 
    {
        if (_parentCourseProduct == null) 
        {
            var parentCourseProduct = this.ParentLink;
            var loader = ServiceLocator.Current.GetInstance&amp;lt;IContentLoader&amp;gt;();
            _parentCourseProduct = loader.Get&amp;lt;CourseProduct&amp;gt;(parentCourseProduct);
        }
    
        return _parentCourseProduct;
    }
}

[Ignore]
public CourseCategory ParentCourseCategory 
{
    get 
    {
        if (_parentCourseCategory == null) 
        {
            var parentCourseCategory = ParentCourseProduct.ParentLink;
            var loader = ServiceLocator.Current.GetInstance&amp;lt;ContentLoader&amp;gt;();
            _parentCourseCategory = loader.Get&amp;lt;CourseCategory&amp;gt; (parentCourseCategory);
        }
        
        return _parentCourseCategory;
    }
}

[Ignore]
public CourseRunType CourseRunType 
{
    get 
    {
        if (_courseRunType == null) 
        {
            var workingDaysService = ServiceLocator.Current.GetInstance&amp;lt;IWorkingDays&amp;gt;();
            _courseRunType = workingDaysService.GetCourseRunType(CourseStartDate, CourseEndDate);
        }
    
        return _courseRunType.Value;
    }
}

private string SearchLocationName() 
{
    return ParentCourseVenueProduct.DisplayName;
}

private string SearchTitleText() 
{
    return ParentCourseCategory.DisplayName;
}

private string SearchDescriptionText() 
{
    return ParentCourseProduct.DisplayName;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a new item was created, updated or the indexing job was run, the new properties would be updated and indexed so they could be used to filter against in the search queries. There was also an added benefit as you could also use these properties elsewhere in the solution.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/f8e80dc436e04f6fac958fc8fc17f331.aspx&quot; alt=&quot;Image Capture.PNG&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;[Pasting files is not allowed]&lt;/span&gt;&lt;span&gt;[Pasting files is not allowed]&lt;/span&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</id><updated>2018-01-02T09:50:12.0000000Z</updated><summary type="html">Blog post</summary></entry></feed>