Sitecore Hackathon 2022

I’m very happy to participate in Sitecore Hackathon 2022!. I participated with my teammate Daniel Pastor in the team Send Pizza and Coffee and I’m proud of what we archived. This year there were 3 topics for Sitecore Hackathon ideation.

  • Build and e-commerce Minimum Viable Product to sell community t-shirts
  • Extend the Sitecore Command Line (CLI) Plugin
  • Best addition to the Sitecore MVP Site.

Additionally we were thinking in a problematic that business have to facing during the COVID-19. One of the coolest rewards after becoming a Sitecore MVP has always been the Sitecore Swag we receive. However, due to the pandemic and changes from year to year, the delivery process can be a little confusing. We decided to make a “Sitecore MVP Swag tracker” page with some modules where the user could track the delivery status of their care package.

The value of real-time systems came into focus in 2020 as companies responded to the disruptions due to COVID-19. Nowadays, people want to have information at hand efficiently, this creates confidence in technological users, the information must be in real time, the actual generation want the information right now.

This module will display the status of the delivery in real time from an external push service which could come from a mobile app from the delivery service.

Our proposal was focused on the user experience, they could know where the MVP swag package is in the delivery process. Our technology is based in web-sockets using Pusher as key platform to our solution succeed.

Web-sockets provides a channel to broadcast to the users the information about their package tracking with the following events:

  • On Track: Send the deliver man location in real-time, the idea is that each delivery man have a mobile application that sends the geo location.
  • In Order Completed: This event notify to the website’s user that the order is completed and delivered at the door.

Finally, we provide a Sitecore Form which could be use in case that something wrong happens with the delivery, and the people in charge could get in contact.

You’ll find more information about our project in the following link:

https://github.com/Sitecore-Hackathon/2022-Send-Pizza-and-Coffee

Read more

Create your first JSS module with NextJS – Part 1

In Article I’ll show you how to create a module in Sitecore using JSS and NextJS. For who is not familiar with this topics. I explain a little bit in my previous blogpost.

Prepare your environment

Before create a new component, you need to connect your Sitecore website with JSS front end project. The easy solution is using Docker and create a new project based on sitecore.nextjs.gettingstarted template.

If you don’t want to use docker you need to ensure that you have installed Sitecore Headless Services (formally the JSS Server Components).

Create a Json Rendering

Json Rendering let you create a rendering which connects to your React Module. First, you need to create your Json Rendering for this example, I’ll create a "Blue Box" component.

Template: /sitecore/templates/Foundation/JavaScript Services/Json Rendering

In experience editor, to allow content authors the ability to select the new component you can added in the placeholder settings as a normal component in Sitecore.

In your Front End Solution (NextJS Solution), you can add a new file /src/components/BlueBox.tsx, we can create the component as React Functional Component.

To import assets or styles you can use import statement and create your modular style file like [Module-Name].module.css.

In order to support inline-editing through experience editor you can use the Text component which is part of '@sitecore-jss/sitecore-jss-react' library.

Do not forget to start your Front-End server with jss start.

If you want to ensure that Data Source is not empty you can use the withDataSourceCheck() hook

Finally, you can add the rendering in your JSS Application Route Page and you are ready to go. Stay tuned and I will enter in details using Placeholders in JSS Application.

Read more

Getting Started Sitecore JSS with NextJS

JAMStack is a modern way to build website and apps that delivers better performance. JSS enables Sitecore to be a powerful Headless CMS and Next.js gives you the best developer experience to build a hybrid static & server rendering.

In this article, I’ll talk about how to get started on Sitecore JSS and Next.js. For this experiment, I followed the instructions provided by Sitecore in the following link:

Walkthrough: Setting up a development environment with the Sitecore Containers template for Next.js

Once you follow the steps you’ll be able to log in to Sitecore CM and browse the website.

During the initial setup, I found some issues related to Docker configuration and I found this article really useful:

Sitecore 10 on docker – common issues – Error cotidianam (wordpress.com)

If you have the latest Docker Desktop, please disable Use Docker Compose V2.

In order to start up JSS project you should navigate to src\rendering folder which contains the NextJS project.

You can run the following command and then you are ready to go.

jss start

I found Docker containers as an easy way to set up a Sitecore environment you don’t need to worry about to install Solr, create the certificates, etc.

Next.js is the future of web development, it is the most famous framework to build super fast and SEO-efficient JAMStack websites with the following important benefits for me:

  • Faster time to market – make and build an MVP (Minimun Viable Product) is much faster.
  • Performance – NextJS generates an static website that runs on NodeJS. Improved page load speed which means higher conversion rate.
  • Live Reload – Working with those frameworks, I love that development is fast you don’t need to wait to compile, it is a live-editing experience.
  • Fully omnichannel– you don’t need to worried about to build API’s for website or mobile applications. You can reuse some React components for your mobile application if you are using web-based technologies.

In future blog posts, I could prepare tutorials about Sitecore module development. Stay tuned!

Read more

Sitecore CDP Decision Models

Sitecore CDP is a Customer Data Platform, which means that we can store data related to our customers.

This valuable data is very useful for analysis and allows you to make assertive marketing decisions. Sitecore CDP allows you to create Decision Tables or even Decision Template which are programmable.

Here we can see some components added to the Decision Model Canvas, you can find more info https://doc.sitecore.com/cdp/en/users/sitecore-customer-data-platform/using-decision-model-components-in-sitecore-cdp.html

Basically best practices mentioned to begin from bottom to top that makes easily to read. Based on that I could say in this example we have three sections.

  • Model Inputs: Here we can add Input Data like guest data or connect to a Data System API’s.
  • Transform and Extract Data: We can add programable boxes to add some logic to validate data, extract fields like GetPropensity, or Order Related Data like HasOrders from the Guest Object.
  • Decision Table, Knowledge Source: At the very top we have the Decision Table.

Decision Table Overview

Client groups according revenue. Adapted from: Esteban-Talaya y Mondéjar-Jiménez (2017). Kotler and Armstrong (2018).

Here we can implement some Marketing Strategies for example if we have a “Butterfly client” we can give them an offer. or if we have a client which is a “unfamiliar” we can give them a lower offer or even decide to do not invest on them.

Our decision table has inputs and outputs:

  • Input: Propensity, Has Orders.
  • Output: Offers

This presented Decision table is a basic example how you can make automated decisions, and how powerful is this platform for your business. It helps you to maintain a good relationship with your clients.

Read more

Sitecore CDP Register Events

Sitecore CDP is a great platform that allows you to register Customer Data for analysis and build Decision Models to create Customer Experiences.

As a developer our first steps are to capture data, it could be some integrations like visiting a page. In this article I will show you how to send events to Sitecore CDP.

Step 1. Create your Point of Sale

Click on System Settings > Point of Sale then click on Create

Fill the form:

  • Name your Point Of Sale: Your Point of Sale Name.
  • Market: Information about the market.
  • Brand: You can fill with your brand.
  • Language: Target language
  • Timeout (in minutes) : Once an user is inactive to close the browser there is a timeout setting that is useful to understand when the user abandon the session.

Step 2: Setup Javascript Library in your Website


var _boxeverq = _boxeverq || [];

// Define the Boxever settings 
var _boxever_settings = {
  client_key: '[YOUR_CLIENT_KEY]', 
  target: 'https://api.boxever.com/v[YOUR_API_VERSION]', 
  cookie_domain: '[YOUR_WEBSITE_DOMAIN]', 
  web_flow_target:  "https://[YOUR_WEBFLOW_TARGET].cloudfront.net",
  pointOfSale: "[YOUR_POINT_OF_SALE]"
};

(function() {
  var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true;  
  s.src = '[CDN]/boxever-1.4.1.min.js';
  var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x);
})();


Add this script into your website and this will inject Sitecore CDP JavaScript Library.

Step 3: Add Event in your website page.

// Place an anonymous function in the Boxever queue
_boxeverq.push(function() {
  var viewEvent = {
    browser_id: Boxever.getID(),
    channel: "WEB",
    type: "VIEW",
    language: "EN",
    currency: "USD",
    page: "/", // Your page path
    pos: "[YOUR_POINT_OF_SALE]"
  };
   //Add UTM params
    viewEvent = Boxever.addUTMParams(viewEvent);
  // Invoke event create
  // (<event msg>, <callback function>, <format>)
  Boxever.eventCreate(viewEvent, function(data) {}, "json");
});

Step 4: Visit your website

Finally if you visit your website and then go to Sitecore CDP Platform you will see a new event registered for your target user.

Here you can find more information about how to customize an event:

https://doc.sitecore.com/cdp/en/developers/sitecore-customer-data-platform–data-model-2-1/send-a-custom-event-to-sitecore-cdp.html

I hope this article will be helpful to start your Sitecore CDP implementations.

Read more

Sitecore Experience Editor Tips! – Part 2

This article will explain how you can improve the Content Author’s experience. Actually, content authors need to change content or settings (like checkbox, drop list) on data source which probably is not accessible by the Experience Editor´s UI. What content authors usually do in those cases, is open Content Editor and browse the data source and edit it, but at this point content author exits the experience editor.

In order to improve their lives, it´s recommended to use a Custom Experience Button.

You can create an Experience Button from the content editor using the following steps:

  1. Select Core Database
Switch to Core DB
  1. Go to path /sitecore/content/Applications/WebEdit/Custom Experience Buttons

3. You can create a folder with your site name that contains the experience buttons for your module.

  1. Create an item like [Your-Module-Name] with /sitecore/templates/System/WebEdit/Field Editor Button Template.
  • Header: You can fill with “Edit”
  • Icon: Choose an icon, edit.png fits.
  • Fields: Pipe-separated list of field names to be edited by the Field Edtior
  • Tooltip: you can fill with “Edit”.

5. Add your Experience Button in your desired Rendering.

6. Now, in Experience Editor you can see a new Button in the options. If you clicked you will find a form to fill all fields that you declared in the Experience Button.

This will increse the content authors experence using Experience Editor, especially in the fields that are not in-line editing.

Read more

Sitecore Experience Editor Tips! – Part 1

I want to talk about a common problem that you could face when you develop for Experience Editor.

Imagine that you build your module and it requires Datasource attached to the rendering. If you didn´t add any validation in the view just for the experience editor. Content authors probably experience some errors or couldn’t see the module like the following example:

This is a simple controller which generate a model based on RenderingContext.Current.Rendering.Item.

  public class HeadlineAndDescriptionController : Controller
    {
        private readonly IViewPathResolver _viewPathResolver;
        private readonly IModelMapper _modelMapper;

        public HeadlineAndDescriptionController(IViewPathResolver viewPathResolver, IModelMapper modelMapper)
        {
            this._viewPathResolver = viewPathResolver;
            this._modelMapper = modelMapper;
        }

        public ActionResult Index()
        {
            var model = _modelMapper.MapItemToNew<HeadlineAndDescription>(RenderingContext.Current.Rendering.Item);
            return View(this._viewPathResolver.ResolveViewPath(RenderingContext.Current.Rendering.RenderingItem), model);
        }
    }

We are going to add the Rendering on a Sample Page, noticed that the Data Source is empty.

Then if we open the Experience Editor, it generates a bad experience for the content author because we coudn´t find anything on the view in order to associate the desired data source.

Solution:

In your controller you can add [RequireDatasource] filter.

  [RequireDatasource]
        public ActionResult Index()
        {
            var model = _modelMapper.MapItemToNew<HeadlineAndDescription>(RenderingContext.Current.Rendering.Item);
            return View(this._viewPathResolver.ResolveViewPath(RenderingContext.Current.Rendering.RenderingItem), model);
        }

This is a simple filter but makes difference for Content Authors:

 public class RequireDatasource : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (RenderingContext.CurrentOrNull != null &&
                (string.IsNullOrEmpty(RenderingContext.Current.Rendering.DataSource) || RenderingContext.Current.Rendering.Item == null))
            {
                // Check Experience Editor
                if (Context.PageMode.IsExperienceEditorEditing)
                {
                    filterContext.Result = new ContentResult() { Content = @"<p class=""rmc-select-datasource"">[Module: " + RenderingContext.Current.Rendering.RenderingItem.Name + " ("+ filterContext.ActionDescriptor.ActionName+ "): No Datasource Found, Please select Datasource Item]</p>", ContentType = "text/html" };

                }
                else
                {
                    filterContext.Result = new EmptyResult();
                }
            }
        }
    }

Finally, if you observe a module with message : “Module: {ModuleName} (Method) : No Datasource found, Please select Datasource item”. And content authors know that they need to add a Data source.

I hope this will be helpful to improve the experience for your Content Authors while they are editing.

Read more

Sitecore Shared Sessions

In this article, I will talk about a Sitecore Shared Sessions configuration. Many times when you deploy your changes to production environment. You will notice that the application pool was recycled. To avoid this problem, you can configure Redis to store the user’s sessions.

This configuration is pretty simple you will need to follow this walkthrough.

Once you enabled shared sessions using Redis you will notice when you deploy your new releases, the users that are navigating to your website don’t required to enter their credentials again.

But following this approach, as developer you will need to take care about the models that you are building for use as session variables.

[Serializable]
    public class Location
    {
        [RawValueOnly]
        public ID ID { get; set; }
        [RawValueOnly]
        public string CountryCode { get; set; }
        [RawValueOnly]
        public string CountryName { get; set; }

        [RawValueOnly]
        public string City { get; set; }

    }

In the example, Imagine that you want to store a location variable into your session. You would need to create a class but the important thing here is do not forget to include [Serializable].

If you do not serialize your model, once you want to retrieve your session variable website will crash.

I hope this information will be useful for you.

Read more

Eloqua / Sitecore Integration

Eloqua is a tool maintained by Oracle that is used for Marketing Automatization. Eloqua helps B2B markets and organizations manage marketing campaigns and sales lead generation. Those organizations have huge websites that contains many contact forms. As, we talk enormous websites Sitecore is a great DXP (Digital Experience Platform) to build those kind of websites.

So, this article is going to talk about how to integrate Sitecore Forms with Eloqua Integration in order to facilitate the developers life. I created a project in Github, which contains the code that integrate Eloqua with Sitecore Forms.

Installation:

https://github.com/RobertoArmas/Sitecore-Forms-Eloqua/releases/tag/v10.1.1.0809202101

  1. Download Feature.EloquaForms-10.1.1.0809202101.zip
  2. Install Sitecore Package

Usage:

  1. Create a Sitecore Form

2. Drag and Drop the form controls that you need it for your form.

When you dragged a control like Single-line text you will noticed that this control contains a section called Eloqua to turn on the Prefill feature if your requirements needed.

The important thing here is named the form same as Eloqua Form and the Single-line text’s control name match as Eloqua Form.

3. Add submit button

After drag and drop Submit Button, Add an action and select Submit To Eloqua.

4. Add Eloqua Tracking Scripts

You can add Eloqua Tracking scripts into your form by drag and drop Eloqua Tracking Scripts control. Then you can fill the Eloqua Form Name and the Eloqua Site Id

And that’s it, now you can place your form in any page. and this will submit the form to Eloqua.

 

In conclusion this is a basic tool that allows you to integrate your Sitecore Forms to Eloqua Forms without coding.

Read more

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