Adding new Vary By Caching option in Sitecore

While working with Sitecore HTML cache we stuck with multiple issues and there was only two ways for US either remove caching completely from rendering or customize the same.

So better I thought to go with customize or adding new options for Vary By caching on rendering.

And here is how I started –

Created a Data Template with the checkbox field with name VaryByCustomData

Now go to /sitecore/templates/System/Layout/Sections/Caching item in Sitecore and inherite the newly create template to this data template.

Now if you go to the rendering your newly created Checkbox field will start appearing under Caching section.

Created a class inherited by RenderRenderingProcessor and added the logic there to add/update the cache key on the bases of the CustomData which is updated from another system.

using Sitecore.Mvc.Pipelines.Response.RenderRendering;
using Sitecore.Mvc.Presentation;

namespace Foundation.Caching.Pipelines
{
	public class VaryByCustomData : RenderRenderingProcessor
	{
		public override void Process(RenderRenderingArgs args)
        {
            Assert.ArgumentNotNull(args, nameof(args));
            if (args.Rendered || !args.Cacheable || string.IsNullOrEmpty(args.CacheKey))
            {
                return;
            }

            var doVaryByCustomData = args.Rendering.RenderingItem.InnerItem[new ID(Caching.Fields.VaryByCustomData)].ToBool();
            if (!doVaryByCustomData || !userContext.User.IsAuthenticated)
            {
                return;
            }

            var key = customData;            
            args.CacheKey += $"_#customData:{key}";
        }
	}
}

And now it’s turn to add this pipeline to config –

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<pipelines>
			<mvc.renderRendering>
				<processor patch:instead="processor[@type='Sitecore.Mvc.Pipelines.Response.RenderRendering.GenerateCacheKey, Sitecore.Mvc']"
								type="Foundation.Caching.Pipelines.VaryByCustomData, Foundation.Caching" resolve="true" />
			</mvc.renderRendering>
		</pipelines>
	</sitecore>
</configuration>

And yes it is working now. So now checking this checkbox is clearing cache on updating the data under CustomData.

Happy Coding!!

Posted in Sitecore, Sitecore 9.3, Sitecore Cache, Sitecore Rules Field, Uncategorized | Tagged , , | Leave a comment

Good use of Rule field in Sitecore

So as the heading says I am going to tell here what I have learned about the new use of Rules field. So we have a header and footer navigation control and we got requirement to show some of the link items on the bases of conditions (Which we already implemented custom rules). So there was a simple option handle everything in backend and make it static or we can think something better which can be useful for content author or client to manage in future.

I generally think about the future possibilities also while implementing something here also I did same and another method found create checkboxes fields on link items and manage links easily or create each link as a separate rendering so can apply personalization and on the bases of that show and hide the link item.

But that is also not a good idea as we have 100+ link items and adding 100+ presentations not a good idea. So best I thought to add the Rules field on link item it self and manage all the conditions from there and on the bases of that condition show or hide the links as well as we can change the link text or link url values also on the bases of the conditions.

So now I just need to think about hiding or restricting the link item and I created a action for same. Below is the steps I followed to achieve the same.

– Created Rule field on Link item data template.

– Created action method to hide the item as below

a. Created action class to add the param –

public class DoesNotHaveAccessToItem<T> : RuleAction<T> where T : RuleContext
    {
        public DoesNotHaveAccessToItem() { }

        public override void Apply(T ruleContext)
        {
            if (string.IsNullOrEmpty(Convert.ToString(ruleContext.Parameters["DoesNotHaveAccessToItem"])))
                ruleContext.Parameters.Add("DoesNotHaveAccessToItem", true);
        }
    }

– Created action item in Sitecore under /sitecore/system/Settings/Rules/Definitions/Elements as given below

Sitecore Action Method

Now lets come to the point where we will know how we can use this action and hide the link items as per the conditions we are looking for.

Now we need to write down the code to execute each link with the condition for example –

dataSource.Children<LinkModel>(false)?.Where(w => w.TemplateID.ToString().Equals(LinkModel.Properties.IdString) && !ExecuteLinkRules.ExecuteNavigationRules(w.InnerItem)).ToList();

In the ExecuteLinkRules static class we are written code to execute the conditions with the action method –

 public static bool ExecuteNavigationRules(LinkRulesModel navItem)
        {
            if (navItem.LinkRules != null)
            {
                var rules = RuleFactory.GetRules<RuleContext>(navItem.LinkRules).Rules;
                if (rules == null || !rules.Any())
                    return false;

                var ruleContext = new RuleContext()
                {
                    Item = navItem.InnerItem
                };

                foreach (var rule in rules.Where(r => r.Condition != null))
                {
                    if (rule.Evaluate(ruleContext))
                    {
                        rule.Execute(ruleContext);
                        if (ruleContext.Parameters["DoesNotHaveAccessToItem"] == null)
                        {
                            return false;
                        }

                        bool.TryParse(ruleContext.Parameters["DoesNotHaveAccessToItem"].ToString(), out var hideNavItem);
                        if (hideNavItem)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

In Sitecore we need to add the codition as given below –

Added link rules on Sitecore item.

Now when we will execute the code to get links our code will execute the Rule field code and as per action method value we will hide the item or exclude the item from the list.

I hope this will give you some idea if want to build something similar as well as at-least this will help to achieve in any scenario of Sitecore.

Let me know in comments what do you think about this post.

Posted in Sitecore, Sitecore 9.3, Sitecore Rules Field | Tagged , | Leave a comment

Update Rendering Parameter using Sitecore Powershell

I know it’s very basic requirement when someone ask us to update rendering parameter field value on the items which is already created.

So in my case I was working on something and just after content creation done we found a bug and resolving that bug needed a rendering parameter field. So I created rendering parameter field but now the complex part was to update the value of that field in all the items which is already created by content authors.

And yes here we are not talking about updating 10-20 items the number is big more then 10,000 items within different – 2 nodes.

So I thought to write a powershell code as these days I started loving beauty of Sitecore powershell and yes loving power of Powershell.

And yes I thought to write code more generic not for this specific requirement so that it can be useful for future updates as well as other folks.

It will give you more flexibility as I added as much as possible fields in same.

Here is the script I wrote –

<#
    .SYNOPSIS
        Update Rendering Parameter. 
	
#>

$database = "master"
$root = Get-Item -Path (@{$true="$($database):\content"; $false="$($database):\content"}[(Test-Path -Path "$($database):\content")])
$renderingRoot = Get-Item -Path (@{$true="$($database):\layout\Renderings"; $false="$($database):\layout\Renderings"}[(Test-Path -Path "$($database):\layout\Renderings")])

$radioOptions = [ordered]@{
    "<strong>Use Final Layout</strong>" = 1
}

$props = @{
    Parameters = @(
        @{Name="root"; Title="Choose the root"; Tooltip="Root item"; Mandatory = $true; },
		@{Name="renderingRoot"; Title="Choose the Rendering root"; Tooltip="Only items from this root will be returned."; source=$renderingRoot; Mandatory = $true; },
		 @{ Name = "renderingParameterFieldName"; Value=""; Title="Rendering Parameter Field Name"; Tooltip="Rendering Parameter Field Name"; Mandatory = $true;},
		  @{ Name = "renderingParameterFieldValue"; Value=""; Title="Rendering Parameter Field Value"; Tooltip="Rendering Parameter Field Value";  Mandatory = $true; },
		    @{
            Name = "radioSelector"
            Title = "Layout Type Select"
            Editor = "radio"
            Options = $radioOptions
            Tooltip = "Select an option if you want to update on Final Layout"
        }
    )
    Title = "Update Rendering Parameter"
    Description = "Update Rendering Parameter"
    Width = 550
    Height = 300
    ShowHints = $true
}

$defaultLayout = Get-LayoutDevice "Default"


$result = Read-Variable @props

if($result -eq "cancel") {
    exit
}

$list = [System.Collections.ArrayList]@()

# Toggle for whether to update Shared or Final Layout
if($radioSelector -eq "1") {
$useFinalLayout =  $True
}
else {
$useFinalLayout =  $False
} 

#Filter Repository Items from the Content list
filter Where-ContentItems {
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [Sitecore.Data.Items.Item]$item
    )
    
    if ($item) {
	try{
      		# Get renderings in this item that have renderings in the placeholder we want to update 
            $renderings =  Get-Rendering -Item $item -FinalLayout:$useFinalLayout

            foreach ($rendering in $renderings)
            {
			   if($rendering.ItemId -eq $renderingRoot.ID) 
				{
					$parameters = [ordered]@{$renderingParameterFieldName=$renderingParameterFieldValue}
					$rendering | Set-RenderingParameter -Parameter $parameters | Set-Rendering -Item $item -FinalLayout:$useFinalLayout
	       
					Write-Host "$($item.FullPath) - Item Id $($item.ID) - Rendering $($rendering.UniqueID)  $DatasourceItemPath - $($item.Language)"
				}
            }
	}
	catch [System.Net.WebException],[System.IO.IOException]
	{
		$item
	}
  }
}


$items = Get-ChildItem -Path $root.ProviderPath -Language * -Recurse | Where-ContentItems


Close-Window

I know there are multiple more ways to achieve the same but this is what I did so sharing. Hopefully it can save some of your time.

If anyone find anything wrong or want to make it more better please put your thoughts in comment and we can update the same.

Posted in Sitecore, sitecore powershell | Tagged , , , , | Leave a comment

Powershell Script To update placeholder key on specific rendering

I am working on a project where we already finished with content update on millions of items. And now we got request to move one of the generic component to specific location.

So I though to add a dynamic placeholder in that area and now the task is update the placeholder key of that component on all the items which is already created.

So what I though to go with Powershell script which I think easiest way to change but yes it has risk also it can break all items if we are making a wrong change.

My condition was making the change on PROD specially and here is the risk as content was already populated I need to be more careful and need to be more close to investigate that I am making the change on correct item.

Below is the script I implemented for same and it worked … and saved lot more manual work.

<#
    .SYNOPSIS
        Update placeholder name for Component.

Replace your values where I did enclose inside ## .. ##		
	
#>

$database = "master"
$root = Get-Item -Path (@{$true="$($database):##Path Of item Item where you want to Start##"; $false="$($database):\content"][(Test-Path -Path "$($database):##Path Of item Item where you want to Start##")])

$props = @{
    Parameters = @(
        @{Name="root"; Title="Choose the root"; Tooltip="Only items from this root will be returned."; }
    )
    Title = "Update Placeholder key for Component"
    Description = "Update Placeholder key for Component"
    Width = 550
    Height = 300
    ShowHints = $true
}

$defaultLayout = Get-LayoutDevice "Default"
# Toggle for whether to update Shared or Final Layout
$useFinalLayout = $True

$result = Read-Variable @props

if($result -eq "cancel") {
    exit
}

$list = [System.Collections.ArrayList]@()


#Filter Repository Items from the Content list
filter Where-ContentItems {
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [Sitecore.Data.Items.Item]$item
    )
    
    if ($item) {
	try{
        $itemTemplate = [Sitecore.Data.Managers.TemplateManager]::GetTemplate($item)
		if($itemTemplate.ID -eq "##TemplateID Of Item##") 
		{			
			# Get renderings in this item that have renderings in the placeholder we want to update 
            $renderings =  Get-Rendering -Item $item -FinalLayout

            foreach ($rendering in $renderings)
            {
			   
			    if($rendering.ItemId -eq '##Rendering ID##' -And $rendering.Datasource -ne $null  -And $rendering.Datasource -ne "") 
				{
				    $DatasourceItemPath = ""
				     #Write-Host "Item Path " $rendering.Datasource
				    if($rendering.Datasource -like '*/sitecore/content/*') {
				         $DatasourceItemPath = $rendering.Datasource
				    }
				    else {
                       $DatasourceItemV = Get-Item -Path "master:" -ID  $rendering.Datasource -Language $item.Language.Name
                       if($DatasourceItemV -ne $null) {
                           $DatasourceItemPath = $DatasourceItemV.FullPath
                       }
				    }
                
                if($DatasourceItemPath -like "*##Datasource Path (Specific to me so can narrow down the filter)##*")
                {
                    $rendering.Placeholder = "##Dynamic Placeholder Key##" 
                    Set-Rendering -Item $item -Instance $rendering -FinalLayout
                    Write-Host "$($item.FullPath) - Item Id $($item.ID) - Rendering $($rendering.UniqueID)  $DatasourceItemPath - $($item.Language)"
                 }
				}
            }
					
		}
	}
	catch [System.Net.WebException],[System.IO.IOException]
	{
		$item
	}
        
    }
}


$items = Get-ChildItem -Path $root.ProviderPath -Language * -Recurse | Where-ContentItems


Close-Window

I know there are lot more ways to do this but just sharing so that it can at-least save some time for others.

If you have more better code please share in comment.

Posted in Sitecore, Uncategorized | Tagged , , | Leave a comment

How To Use Sitecore Rules field

In my recent project I asked to implement an interesting functionality where we want to handle security and other site specific stuff by Sitecore Rules field.

There are lot more ways to handle the same things I asked to do but I found this was an interesting way to handle this thing. Here in my case we are providing a set up like in some cases content editor want to restrict items under specific node and that restriction is only in en-US language not others. Also restriction not for under items specific node but for specific type of data templates also.

So it become a combination of lot more different-2 rules. And here we came up with this idea to create a setting item with Sitecore Rules field and execute the rule field in Pipeline processor.

So that we can execute the rule before loading the page. So what we did in Sitecore is we created a Setting item with field Rules type and then we created a Pipeline processor to execute the field value of this field.

Pipeline we did hit is –

<mvc.getPageItem>

And patch after Sitecore.Mvc.Pipelines.Response.GetPageItem.GetFromOldContext, Sitecore.Mvc processor.

Below is the code we wrote in class file –


public override void Process(GetPageItemArgs args)
        {
            Assert.ArgumentNotNull(args, nameof(args));

            if ((Context.Item == null) || (Context.Database == null) || Context.PageMode.IsExperienceEditor || Context.PageMode.IsPreview || Context.Database?.Name == "core")
                return;

            try
            {
                var contextItem = Context.Item;
                var RuleSettingsItem = //Get Setting Item from your Sitecore which you created with Rules field
                if (RuleSettingsItem != null && RuleSettingsItem .Rules.HasValue)
                {

                    var rules = RuleFactory.GetRules(RuleSettingsItem.Rules).Rules;

                    if (rules == null || rules.Count() == 0)
                        return;

                    var ruleContext = new PageRuleContext(RuleSettingsItem);
                    ruleContext.Item = Context.Item;
                    foreach (var rule in rules)
                    {
                        if (rule.Condition != null)
                        {
                            var ruleSet = new Rule(rule.Condition, rule.Actions);
                            var stack = new RuleStack();
                            rule.Condition.Evaluate(ruleContext, stack);
                            if (ruleContext.IsAborted)
                            {
                                continue;
                            }
                            if ((stack.Count != 0) && ((bool)stack.Pop()))
                            {
                                rule.Execute(ruleContext);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Error($"RuleSettingsItem Exception: {ex.Message}", ex, this);
            }
        }

Here is what I did but I am sure there are lot more ways to handle the similar scenario.

Please post your suggestions in comments below. Also it always great to hear if there is something I am missing here or I did wrong or If I can make it more better.

Posted in Sitecore, Sitecore Rules Field, Uncategorized | Tagged , , , , | 1 Comment

Refused to load the image ‘URL’ because it violates the following Content Security Policy directive: “img-src ‘self’ data:”. Sitecore

While working with Sitecore 9.3, I found that all the images with External site URL stopped working which was working in older version 9.2.

I started finding the issue with site URL as well as thought something wrong with URL but after sometime when I saw browser consol I found there was some errors which was pointing the Image src.

Refused to load the image ‘URL’ because it violates the following Content Security Policy directive: “img-src ‘self’ data:”.

And then I started finding this error on internet and there I came to know that it’s because of Content Security Policy. And then I started finding the this content type in config files of Sitecore.  After searching for a sometime I found similar settings in web.config file as given below –

 
 <location path="sitecore">
        <system.webServer>
            <httpProtocol>
                <customHeaders>
                    <remove name="X-Content-Type-Options" />
                    <remove name="X-XSS-Protection" />
                    <remove name="Content-Security-Policy" />
                    <add name="X-XSS-Protection" value="1; mode=block" />
                    <add name="X-Content-Type-Options" value="nosniff " />
                    <add name="Content-Security-Policy" value="default-src 'self' 'unsafe-inline' 'unsafe-eval' https://apps.sitecore.net; img-src 'self' data:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' 'unsafe-inline' https://fonts.gstatic.com; upgrade-insecure-requests; block-all-mixed-content;" />
                </customHeaders>
            </httpProtocol>
        </system.webServer>
    </location>
 

The new Content-Security-Policy HTTP response header helps you reduce XSS risks on modern browsers by declaring, which dynamic resources are allowed to load.

So in our case we need to add our external site url in the Content-Security-Policy settings and that’s how it will start working. See Below example with added site URL.

 
 <location path="sitecore">
        <system.webServer>
            <httpProtocol>
                <customHeaders>
                    <remove name="X-Content-Type-Options" />
                    <remove name="X-XSS-Protection" />
                    <remove name="Content-Security-Policy" />
                    <add name="X-XSS-Protection" value="1; mode=block" />
                    <add name="X-Content-Type-Options" value="nosniff " />
                    <add name="Content-Security-Policy" value="default-src 'self' 'unsafe-inline' 'unsafe-eval' https://apps.sitecore.net; img-src 'self' data:https://www.test.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' 'unsafe-inline' https://fonts.gstatic.com; upgrade-insecure-requests; block-all-mixed-content;" />
                </customHeaders>
            </httpProtocol>
        </system.webServer>
    </location>
 

Here I added my https://www.test.com URL in data: setting.

And yes I am able to see all the Images which is coming from https://www.test.com domain.

Hope this can save some of your research time. 🙂

If anyone have any better idea to make it working please share in comment anything which is coming below is appreciated 🙂

Posted in Content Security Policy, Sitecore, Sitecore 9.3, Uncategorized | Tagged , , | 4 Comments

Sitecore Solr – Tips if you are working with Sitecore Solr

As I mentioned in my last post Sitecore Solr – Highlight Search Keyword to manage or handle the sorting and pagination directly from Solr instead of calculating it after getting results.

If you haven’t read my last post you can read here It will give you some idea how we can modify Solr api call.

In this post will share my recent learning about manage pagination, sorting and other functionality directly from Solr API instead of adding it to after getting results –

While working with Search Highlight I found that to highlight keyword we need to run foreach loop for all the items and then we can make it highlight the keyword there I thought if we can do it just the results which is part of current pagination it would be perfect. So I started looking into it and from here I got an idea to add the sorting in Solr API call.

For this you need to add the refrence of below dll’s

  • SolrNet.dll
  • Sitecore.ContentSearch.SolrProvider.dll
  • Sitecore.ContentSearch.SolrNetExtension.dll

And some of the info you can find in my last post for same.

Now create a object for QueryOptions there we can define multiple properties value to calculate the results as per our need.

So as I did for Result Highlight sharing same code here but with additional features.

<pre>

string searchField = "body_t";
            var queryOptions = new QueryOptions {
                Highlight = new HighlightingParameters {
                    Fields = new[] { searchField },
                    BeforeTerm = "<em style="color:red;">",
                    AfterTerm = "</em>",
                    Fragsize = 100,
                    MergeContiguous = true,
                    Snippets = 5
                },
                OrderBy = new[] { new SolrNet.SortOrder("publicationdate_tdt", sorOrder) },
                Rows = Settings.GetIntSetting("Search.Pagination.ResultPerPage", 20),
                Start = Settings.GetIntSetting("Search.Pagination.ResultPerPage", 20) * (pageNumber - 1),
                ExtraParams = new List
    {
        new KeyValuePair("hl.method", "unified")
    }

            };

</pre>

So the first property Highlight I already explained in my previous post. Here we will discuss about other properties  –

  1. Pagination  – For pagination we need to add the values to Rows and Start property as given in above code.
    1. Rows – Number of results we need to show on Page.
    2. Start – Calculation where to start getting result (For example if we are on page 2 and showing 10 results on page then value here will be 11)
  2. OrderBy – You can also specify order by result if you are using any sorting options for your result as in y case I am having Sort dropdown where user can select Oldest to Newest and Newest to Oldest.
    For this we need to create object of Solr.Net.Order as given below –
SolrNet.Order sorOrder = SolrNet.Order.DESC;
            if (!string.IsNullOrEmpty(sortBy)) {
                if (sortBy == "otn") {
                    sorOrder = SolrNet.Order.ASC;
                }
                if (sortBy == "nto") {
                    sorOrder = SolrNet.Order.DESC;
                }
            }

So in the above code I am passing the sortOrder to QueryOptions.

In my code I am sorting result on the bases of Publication Date so defined it for Publication dates only.

3. ExtraParams – Here we can define Collection of key value pairs if we want to define more options to Solr API call or want to get the selected result list.

As I have provided just for example  you can get complete list of Parameters with details here.

Now to get the calculated resuts we are passing in QueryOptions we need to make it as below –

 var solrQuery = new SolrQuery(((IHasNativeQuery) query).Query.ToString());
                var results = context.Query(solrQuery, queryOptions);

With this code I don’t need to query results for pagination it was only returning me results as per my pagination component needs.

Sorting  – Firstly I added sort by to after querying the results but somehow with the same code it was not working for me and then I tried this options and I found it great to implement. I didn’t check this performance wise but I think it’s faster then what we do with basic pagination code.

If you want to check how it is adding to Solr query then after executing the code you can go to Search.log.xxx.xxx under App_Date folder and there you can see what is the final query call.

 

There are many more options to do the same and I would like to see those in comments below 🙂

 

Posted in Sitecore, Sitecore Solr, Uncategorized | Tagged , , , | Leave a comment

Sitecore Solr – Highlight Search Keyword in Search Summary

This is not new topic which I am sharing here but with all the articles earlier I found something different. Also it will be add-on learning over old ones.

So as title suggest I am going to share about Highlighting keyword for search result summary with some useful tips/details.

First of all we will discuss how we can do word highlight in search summary with our search filters.

First of all you need to add the references of below dll’s –

  • SolrNet.dll
  • Sitecore.ContentSearch.SolrProvider.dll
  • Sitecore.ContentSearch.SolrNetExtension.dll

And add the refrences to class file as below –

using Sitecore.ContentSearch.SolrProvider.SolrNetIntegration;
using SolrNet;
using SolrNet.Commands.Parameters;
using Sitecore.ContentSearch.Linq.Utilities;
using Sitecore.ContentSearch.Utilities;

 

Note – We don’t need to make any updates in Solr side for this.

Now let’s do the updates so firstly I implemented all my code for filters by adding predicate builders and in between of that added the code for highlight.

First we need to add the the object for query options and update the properties as below –

string searchField = "body_t";
            var queryOptions = new QueryOptions {
                Highlight = new HighlightingParameters {
                    Fields = new[] { searchField },
                    BeforeTerm = "&amp;amp;lt;em style='color:red'&amp;amp;gt;",
                    AfterTerm = "&amp;amp;lt;/em&amp;amp;gt;",
                    Fragsize = 100,
                    MergeContiguous = true,
                    Snippets=5,
                },
            };

searchField is the field which we want to use for search as I am using body_t (Body) field of Sitecore/Solr to search the keyword.

I am using only 1 field but you can add n number of fields as Fields property is an array. So we add multiple field if we want.

Before Term And After Term – So whatever HTML element you want to add before and after the searched keyword define here.

Fragsize –Specifies the approximate size, in characters, of fragments to consider for highlighting. The default is 100. Using 0 indicates that no fragmenting should be considered and the whole field value should be used.

MergeContignous – Instructs Solr to collapse contiguous fragments into a single fragment. A value of true indicates contiguous fragments will be collapsed into single fragment. The default value, false, is also the backward-compatible setting.

Snippets – Specifies maximum number of highlighted snippets to generate per field. It is possible for any number of snippets from zero to this value to be generated.

More attributes you can find on Solr site.

Now let me come to the point how it will work with your existing filter code so after creating the Query Options you need to merge it with Solr API as below –

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Linq.Parsing;
using Sitecore.ContentSearch.SearchTypes;
using Sitecore.ContentSearch.SolrProvider.SolrNetIntegration;
using SolrNet;
using SolrNet.Commands.Parameters;
using Sitecore.ContentSearch.Linq.Utilities;
using Sitecore.ContentSearch.Utilities;

namespace Xcentium.Feature.Search.Services {
    [Service(typeof(ISearchService))]
    public class SearchService : ISearchService {
        private readonly IRenderingRepository _renderingRepository;
        private readonly IContentRepository _contentRepository;

        public SearchService(IRenderingRepository renderingRepository, IContentRepository contentRepository) {
            _renderingRepository = renderingRepository;
            _contentRepository = contentRepository;
        }

        public SearchResultViewModel GetSearchResults(string selectedCategory, string selectedContentTypes, string[] keywords,  string searchKeyword,  int pageNumber) {
            SearchResultViewModel model = new SearchResultViewModel();
            var index = Sitecore.ContentSearch.ContentSearchManager.GetIndex(SearchUtilities.GetSearchIndexName());
            Expression predicate = PredicateBuilder.True();
            predicate = predicate.And(item =&gt; item.Templates.Contains(DocumentMap.TemplateId));
            predicate = predicate.And(item =&gt; !item.Name.Equals(HelixConstants.StandardValues));
            predicate = predicate.And(item =&gt; !item.Name.Equals(HelixConstants.NameToken));
            if (!string.IsNullOrEmpty(searchKeyword)) {
                    string[] querySplit = searchKeyword.Split(' ');
                    Expression&lt;Func&gt; contentPredicate = PredicateBuilder.True();
                    var ignoreKeywordList = Settings.GetSetting("Search.IgnoreKeywords.FromSearch");
                    foreach (string query in querySplit) {
                        if (ignoreKeywordList != null &amp;&amp; !ignoreKeywordList.Contains(query)) {
                            contentPredicate = contentPredicate.Or(item =&gt; item.Body.Contains(query));
                        }
                    }
                    predicate = predicate.And(contentPredicate);
                }
            }

            if (!string.IsNullOrEmpty(selectedCategory)) {
predicate = predicate.And(item =&gt; item.DocumentCategoryId.Equals(selectedCategory));
}
            if (!string.IsNullOrEmpty(selectedContentTypes)) {
var selectedContentType = new ID(selectedContentTypes);
predicate = predicate.And(item =&gt; item[Foundation.Core.Constants.IndexFieldName.DocumentTypeIndexFieldName] == IdHelper.NormalizeGuid(selectedContentType));
}

            if (keywords != null &amp;&amp; keywords.Any()) {
foreach (var key in keywords) {
predicate = predicate.And(item =&gt; item.KeywordComputedIds.Contains(key));
}
}
            string searchField = "body_t";
            var queryOptions = new QueryOptions {
                Highlight = new HighlightingParameters {
                   Fields = new[] { searchField },
BeforeTerm = "<em style="color:red;">",
AfterTerm = "</em>",
Fragsize = 100,
MergeContiguous = true,
Snippets=5,
                }
            };
            using (var context = index.CreateSearchContext()) {
                  IQueryable query = context.GetQueryable().Where(predicate);
                 List highlightResults = new List();

                 var solrQuery = new SolrQuery(((IHasNativeQuery) query).Query.ToString());
var results = context.Query(solrQuery, queryOptions);

                foreach (var result in results) {
                    var highlights = results.Highlights[result.Fields["_uniqueid"].ToString()];
                    if (highlights.Any()) { foreach (var highlightResult in highlights) {
                            var highValue = string.Join(", ", highlightResult.Value);
                            highlightResults.Add(new DocumentSearchResultItem { Name = result.Name, Body = (!string.IsNullOrEmpty(highValue) ? highValue : result.Body), DocumentTitle=result.DocumentTitle, PublicationDate=result.PublicationDate, PageURL = result.PageURL, DocumentCategory = result.DocumentCategory, DocumentTypeTitle = result.DocumentTypeTitle }); } }
                }

                model.SearchResults = highlightResults;
                return model;
            }
        }
    }
}

 

So this is the complete code which I have implemented search with filters as well as Highlight search keyword.

In the given above code we are getting highlighter value and adding it to list of object with required property values –

foreach (var result in results) {
                    var highlights = results.Highlights[result.Fields["_uniqueid"].ToString()];
                    if (highlights.Any()) { foreach (var highlightResult in highlights) {
                            var highValue = string.Join(", ", highlightResult.Value);
                            highlightResults.Add(new DocumentSearchResultItem { Name = result.Name, Body = (!string.IsNullOrEmpty(highValue) ? highValue : result.Body), DocumentTitle=result.DocumentTitle, PublicationDate=result.PublicationDate, PageURL = result.PageURL, DocumentCategory = result.DocumentCategory, DocumentTypeTitle = result.DocumentTypeTitle }); } }
                }

Article by CHRIS PERKS was very helpful to implement the same.

In the above code after creating the query we need to add it to Solr API url.

 var solrQuery = new SolrQuery(((IHasNativeQuery) query).Query.ToString());

This is the code which is executing our query and making search solr API with all the factes/filters and other options we have added to predicate builder or directly to our query.

Hope this can help you to achieve if you are looking to similar functionality with Sitecore Solr.

In my next post I am going to share how we can use/control the pagination from Solr API and how we can add the query string parameters to Solr API call for example hl.score.b, hl.bs.variant etc.

 

Posted in Sitecore, Sitecore Solr, Solr, Uncategorized | Tagged , , , | 1 Comment

Field Validation on Specific Template Types

Recently I asked to add validation on specific field.  That is out of the box provided by Sitecore you need to go to field item and then select the validation items in the Validation Rule Section. But here is the point I am using same field in many different template types.

So I created a separate data template for specific field type (For example Title) and inherited this field template into many data templates and I was asked to add validation on the field but on some data templates not all of them.

So if I am adding validation rules to field then it will get added to all the data template where it inherited. And this is we don’t need to do. So here I started thinking that how can implement this as per data template specific.

I finally decided to create custom validation rule this was the only way I found to handle the same.

  1. Created a Folder with name of my site under /sitecore/system/Settings/Validation Rules/Field Rules location. I did it to make my rule separate.
  2. Created Validation Rule item by /sitecore/templates/System/Validation/Validation Rule template. (For example I am creating Required field validation).
  3.  Add the basic field value like Title, Description etc.
  4. In this step we need to add value in Type and Parameter but those are depend on the class which we need to create for this.

Below is the class which created for this –

/// <summary>
    /// Class CustomRequiredFieldValidator.
    /// Implements the 
    /// </summary>
    /// 
    [Serializable]
    public class CustomRequiredFieldValidator : StandardValidator
    {
        /// <summary>
        /// Initializes a new instance of the  class.
        /// </summary>
        public CustomRequiredFieldValidator()
        {
        }

        /// <summary>
        /// Initializes a new instance of the  class.
        /// </summary>
        /// The Serialization info.
        /// The context.
        public CustomRequiredFieldValidator(SerializationInfo info, StreamingContext context) : base(info, context)
        {
        }

        /// <summary>
        /// Gets the name.
        /// </summary>
        /// The validator name.
        public override string Name
        {
            get
            {
                return "Required";
            }
        }

        /// <summary>
        /// When overridden in a derived class, this method contains the code to determine whether the value in the input control is valid.
        /// </summary>
        /// The result of the evaluation.
        protected override ValidatorResult Evaluate()
        {
            var templateID = MainUtil.GetStringList(true, this.Parameters["templateids"].Split('|'));
            if (templateID.Contains(this.GetItem().TemplateID.ToString()))
            {
                if (!string.IsNullOrEmpty(this.ControlValidationValue))
                {
                    return ValidatorResult.Valid;
                }

                this.Text = this.GetText("Field \"{0}\" must contain a value.", this.GetFieldDisplayName());
                return this.GetFailedResult(ValidatorResult.CriticalError);
            }

            return ValidatorResult.Valid;
        }

        /// <summary>
        /// Gets the max validator result.
        /// </summary>
        /// The max validator result.
        /// This is used when saving and the validator uses a thread. If the Max Validator Result
        /// is Error or below, the validator does not have to be evaluated before saving.
        /// If the Max Validator Result is CriticalError or FatalError, the validator must have
        /// been evaluated before saving.
        protected override ValidatorResult GetMaxValidatorResult()
        {
            return this.GetFailedResult(ValidatorResult.Error);
        }

 

So in this class we are just checking the condition for required value.

Now lets switch to steps above specially moving back to Step 4 our last step –

So we need to add assembly reference in type field as below –

dm1

And in Parameter field we need to provide pipe separated template id’s where we need to apply the validation.

So here the template id’s are the one where we wan’t to specially add the validations.

 

Now the final steps we need to add this custom validation to the field item.

Navigate to your field item and add the validation in Validation rules section as below – I added it in all four fields but it’s on you what is your requirement – Quick Action Bar, Validate Button, Validator Bar and Workflow. –

dm_2.png

 

This was the interesting implementation I did there are may be many more ways but I found this easy for me.

Stay connected for my next post for adding custom validation for max length 700/300 char again template specific.

 

 

Posted in Sitecore, Sitecore Validation Rules, Uncategorized | Tagged , | 1 Comment

Add specific value to any Link of Website in Sitecore

In my recent project I came across with a requirement where client need specific values or actually contactid of current visitor to added into specific links so that whenever user is clicking the link that will redirect user with contact id in query string and they can store that value for custom reporting.

Actual implementation need is client want to collect actual contact who clicked on the links and complete the transaction or functionality which they want user to do. So they just wanted to track the contacts/users it can be either logged in user or anonymous user.

And for this content editor can add to any link field General Link Field, General Link with Search, Richtext etc…

Also content editor should have functionality to add the link anywhere and it should work.

So I thought to add a token for content editor so that they can add the token to link they want to add contact id and from the backend we can replace the token with actual value.

Now to track this down and update the token value with the actual contact id I didn’t found any direct way to implement it so I started looking over the internet and found that I can update RenderField pipeline of Sitecore. There actually I can get the field value before rendering or serving it to page and I can make the change there.

I created a class file to make this change as below –


public class GetLinkFieldValueExtend
    {
        public void Process(RenderFieldArgs args)
        {
            Assert.ArgumentNotNull(args, nameof(args));
            if ((args.FieldTypeKey != "general link with search" && args.FieldTypeKey != "general link") || args.FieldTypeKey != "rich text" || string.IsNullOrWhiteSpace(args.Result.FirstPart))
            {
                return;
            }

            args.Result.FirstPart = UpdateToken(args.Result.FirstPart);
            args.Result.LastPart = args.Result.LastPart;
        }

        private string UpdateToken(string fieldValue)
        {
            if (fieldValue.Contains("$$contactid$$"))
            {
                fieldValue = GetTokenUpdateLink(fieldValue);
            }
            return fieldValue;
        }

        public static string GetTokenUpdateLink(string url)
        {

            try
            {
                var contactId = "contact id"; // write logic to get the contact id;
                return url.Replace(ConfigSettings.ContactIdTokenName, contactId);
            }
            catch (Exception ex)
            {
                Log.Error("Issue with update token value for link" + ex.Message, true);
            }
            return url;
        }

        /// <summary>
        /// Checks if the field should not be handled by the processor.
        /// </summary>
        /// <param name="args">The arguments.</param>
        /// <returns>true if the field should not be handled by the processor; otherwise false</returns>
        protected virtual bool SkipProcessor(RenderFieldArgs args)
        {
            if (args == null)
            {
                return true;
            }

            string fieldTypeKey = args.FieldTypeKey;
            if (fieldTypeKey != "link")
            {
                return fieldTypeKey != "general link";
            }

            return false;
        }

    }

After that I added config for this to execute this code to just after the RenderField processor.


<?xml version="1.0"?>

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:env="http://www.sitecore.net/xmlconfig/env/">
  <sitecore>
    <pipelines>
      <renderField>
        <processor type="Xcentium.Foundation.SitecoreExtensions.Pipelines.RenderField.GetLinkFieldValueExtend, Xcentium.Foundation.SitecoreExtensions" patch:after="processor[@type='Sitecore.Buckets.Pipelines.RenderField.GetLinkFieldValue, Sitecore.Buckets']">
        </processor>
      </renderField>
    </pipelines>
  </sitecore>
</configuration>

 

This is not for only link field you can use it with any field in Sitecore and make it more specific if you need.

 

Hope it can save some of your time 🙂

 

Posted in Sitecore, Uncategorized | Tagged | Leave a comment