If you are using Episerver Social on your site, it's possible to "personalize" search results for a logged in user.
Read my blog here
| namespace EPiServer.SocialAlloy.Web.Business.FindHelpers | |
| { | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Linq.Expressions; | |
| using System.Reflection; | |
| using EPiServer.Core; | |
| using EPiServer.Find; | |
| using EPiServer.Find.Api.Facets; | |
| using EPiServer.Find.Api.Querying.Queries; | |
| /// <summary> | |
| /// Class FindExtensions. | |
| /// </summary> | |
| public static class FindExtensions | |
| { | |
| /// <summary> | |
| /// Adds category boosts to the search. | |
| /// </summary> | |
| /// <typeparam name="T">The type to query for.</typeparam> | |
| /// <param name="query">The query.</param> | |
| /// <param name="favoriteCategories">The categories to boost, with the boost factor.</param> | |
| /// <returns>The <see cref="IQueriedSearch{T}"/> with added BoostMatching.</returns> | |
| /// <remarks><para>The BoostMatching method must be called before any method not related to the search query (such as Filter, Take, and Skip).</para> | |
| /// <para>This is enforced by the fact that the For method in the above sample returns a IQueriedSearch object.</para> | |
| /// </remarks> | |
| public static IQueriedSearch<T> AddCategoryBoosts<T>( | |
| this IQueriedSearch<T> query, | |
| Dictionary<int, int> favoriteCategories) | |
| where T : ICategorizable | |
| { | |
| return favoriteCategories.Aggregate( | |
| query, | |
| (current, favoriteCategory) => current.BoostMatching(x => x.Category.In(new[] { favoriteCategory.Key }), favoriteCategory.Value)); | |
| } | |
| /// <summary> | |
| /// Adds content type boosts to the search. | |
| /// </summary> | |
| /// <typeparam name="T">The type to query for.</typeparam> | |
| /// <param name="query">The query.</param> | |
| /// <param name="favoriteUserContent">The content types to boost, with the boost factor.</param> | |
| /// <returns>The <see cref="IQueriedSearch{T}"/> with added BoostMatching.</returns> | |
| /// <remarks><para>The BoostMatching method must be called before any method not related to the search query (such as Filter, Take, and Skip).</para> | |
| /// <para>This is enforced by the fact that the For method in the above sample returns a IQueriedSearch object.</para> | |
| /// </remarks> | |
| public static IQueriedSearch<T> AddContentTypeBoosts<T>( | |
| this IQueriedSearch<T> query, | |
| Dictionary<int, int> favoriteUserContent) | |
| where T : IContent | |
| { | |
| return favoriteUserContent.Aggregate( | |
| query, | |
| (current, favoriteContent) => current.BoostMatching(x => x.ContentTypeID.Match(favoriteContent.Key), favoriteContent.Value)); | |
| } | |
| } | |
| } |
| namespace EPiServer.SocialAlloy.Web.Models.ViewModels | |
| { | |
| using System; | |
| using System.Web; | |
| using EPiServer; | |
| using EPiServer.Find.Cms; | |
| using EPiServer.Find.Framework.Statistics; | |
| using EPiServer.ServiceLocation; | |
| using EPiServer.SocialAlloy.Web.Models.Pages; | |
| public class FindSearchContentModel : PageViewModel<FindSearchPage> | |
| { | |
| public FindSearchContentModel(FindSearchPage currentPage) | |
| : base(currentPage) | |
| { | |
| } | |
| /// <summary> | |
| /// Gets or sets the content result. | |
| /// </summary> | |
| /// <value>The content result.</value> | |
| public IContentResult<StandardPage> ContentResult { get; set; } | |
| /// <summary> | |
| /// Public proxy path mainly used for constructing url's in javascript | |
| /// </summary> | |
| public string PublicProxyPath { get; set; } | |
| /// <summary> | |
| /// Flag to indicate if both Find serviceUrl and defaultIndex are configured | |
| /// </summary> | |
| public bool IsConfigured { get; set; } | |
| /// <summary> | |
| /// Constructs a url for a section group | |
| /// </summary> | |
| /// <param name="groupName">Name of group</param> | |
| /// <returns>Url</returns> | |
| public string GetSectionGroupUrl(string groupName) | |
| { | |
| return UriSupport.AddQueryString(this.RemoveQueryStringByKey(HttpContext.Current.Request.Url.AbsoluteUri,"p"), "t", HttpContext.Current.Server.UrlEncode(groupName)); | |
| } | |
| /// <summary> | |
| /// Removes specified query string from url | |
| /// </summary> | |
| /// <param name="url">Url from which to remove query string</param> | |
| /// <param name="key">Key of query string to remove</param> | |
| /// <returns>New url that excludes the specified query string</returns> | |
| private string RemoveQueryStringByKey(string url, string key) | |
| { | |
| var uri = new Uri(url); | |
| var newQueryString = HttpUtility.ParseQueryString(uri.Query); | |
| newQueryString.Remove(key); | |
| string pagePathWithoutQueryString = uri.GetLeftPart(UriPartial.Path); | |
| return newQueryString.Count > 0 | |
| ? String.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString) | |
| : pagePathWithoutQueryString; | |
| } | |
| /// <summary> | |
| /// Number of matching hits | |
| /// </summary> | |
| public int NumberOfHits | |
| { | |
| get { return this.ContentResult.TotalMatching; } | |
| } | |
| /// <summary> | |
| /// Current active section filter | |
| /// </summary> | |
| public string SectionFilter | |
| { | |
| get { return HttpContext.Current.Request.QueryString["t"] ?? string.Empty; } | |
| } | |
| /// <summary> | |
| /// Retrieve the paging page from the query string parameter "p". | |
| /// If no parameter exists, default to the first page. | |
| /// </summary> | |
| public int PagingPage | |
| { | |
| get | |
| { | |
| int pagingPage; | |
| if (!int.TryParse(HttpContext.Current.Request.QueryString["p"], out pagingPage)) | |
| { | |
| pagingPage = 1; | |
| } | |
| return pagingPage; | |
| } | |
| } | |
| /// <summary> | |
| /// Retrieve the paging section from the query string parameter "ps". | |
| /// If no parameter exists, default to the first paging section. | |
| /// </summary> | |
| public int PagingSection | |
| { | |
| get | |
| { | |
| return 1 + (this.PagingPage - 1) / this.PagingSectionSize; | |
| } | |
| } | |
| /// <summary> | |
| /// Calculate the number of pages required to list results | |
| /// </summary> | |
| public int TotalPagingPages | |
| { | |
| get | |
| { | |
| if (CurrentPage.PageSize > 0) | |
| { | |
| return 1 + (this.ContentResult.TotalMatching - 1)/CurrentPage.PageSize; | |
| } | |
| return 0; | |
| } | |
| } | |
| public int PagingSectionSize | |
| { | |
| get { return 10; } | |
| } | |
| /// <summary> | |
| /// Calculate the number of paging sections required to list page links | |
| /// </summary> | |
| public int TotalPagingSections | |
| { | |
| get | |
| { | |
| return 1 + (this.TotalPagingPages - 1) / this.PagingSectionSize; | |
| } | |
| } | |
| /// <summary> | |
| /// Number of first page in current paging section | |
| /// </summary> | |
| public int PagingSectionFirstPage | |
| { | |
| get { return 1 + (this.PagingSection - 1) * this.PagingSectionSize; } | |
| } | |
| /// <summary> | |
| /// Number of last page in current paging section | |
| /// </summary> | |
| public int PagingSectionLastPage | |
| { | |
| get { return Math.Min((this.PagingSection * this.PagingSectionSize), this.TotalPagingPages); } | |
| } | |
| /// <summary> | |
| /// Create URL for a specified paging page. | |
| /// </summary> | |
| /// <param name="pageNumber">Number of page for which to get a url</param> | |
| /// <returns>Url for specified page</returns> | |
| public string GetPagingUrl(int pageNumber) | |
| { | |
| return UriSupport.AddQueryString(HttpContext.Current.Request.RawUrl, "p", pageNumber.ToString()); | |
| } | |
| /// <summary> | |
| /// Create URL for the next paging section. | |
| /// </summary> | |
| /// <returns>Url for next paging section</returns> | |
| public string GetNextPagingSectionUrl() | |
| { | |
| return UriSupport.AddQueryString(HttpContext.Current.Request.RawUrl, "p", ((this.PagingSection * this.PagingSectionSize) + 1).ToString()); | |
| } | |
| /// <summary> | |
| /// Create URL for the previous paging section. | |
| /// </summary> | |
| /// <returns>Url for previous paging section</returns> | |
| public string GetPreviousPagingSectionUrl() | |
| { | |
| return UriSupport.AddQueryString(HttpContext.Current.Request.RawUrl, "p", ((this.PagingSection - 1) * this.PagingSectionSize).ToString()); | |
| } | |
| /// <summary> | |
| /// User query to search | |
| /// </summary> | |
| public string Query | |
| { | |
| get { return (HttpContext.Current.Request.QueryString["q"] ?? string.Empty).Trim(); } | |
| } | |
| /// <summary> | |
| /// Search tags like language and site | |
| /// </summary> | |
| public string Tags | |
| { | |
| get { return string.Join(",", ServiceLocator.Current.GetInstance<IStatisticTagsHelper>().GetTags()); } | |
| } | |
| /// <summary> | |
| /// Length of excerpt | |
| /// </summary> | |
| public int ExcerptLength | |
| { | |
| get { return CurrentPage.ExcerptLength; } | |
| } | |
| /// <summary> | |
| /// Height of hit images | |
| /// </summary> | |
| public int HitImagesHeight | |
| { | |
| get { return CurrentPage.HitImagesHeight; } | |
| } | |
| /// <summary> | |
| /// Flag retrieved from editor settings to determine if it should | |
| /// use AND as the operator for multiple search terms | |
| /// </summary> | |
| public bool UseAndForMultipleSearchTerms | |
| { | |
| get { return CurrentPage.UseAndForMultipleSearchTerms; } | |
| } | |
| } | |
| } |
| namespace EPiServer.SocialAlloy.Web.Controllers | |
| { | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Net; | |
| using System.Web.Mvc; | |
| using EPiServer.Core; | |
| using EPiServer.Find; | |
| using EPiServer.Find.Api.Facets; | |
| using EPiServer.Find.Api.Querying.Filters; | |
| using EPiServer.Find.Api.Querying.Queries; | |
| using EPiServer.Find.Cms; | |
| using EPiServer.Find.Framework.Statistics; | |
| using EPiServer.Find.Helpers.Text; | |
| using EPiServer.Find.UI; | |
| using EPiServer.Framework.Web.Resources; | |
| using EPiServer.Globalization; | |
| using EPiServer.SocialAlloy.Web.Business.FindHelpers; | |
| using EPiServer.SocialAlloy.Web.Models.Pages; | |
| using EPiServer.SocialAlloy.Web.Models.ViewModels; | |
| using EPiServer.SocialAlloy.Web.Social.Repositories; | |
| using EPiServer.Web; | |
| using Configuration = EPiServer.Find.Configuration; | |
| public class FindSearchPageController : PageControllerBase<FindSearchPage> | |
| { | |
| private readonly IClient searchClient; | |
| private readonly IFindUIConfiguration findUIConfiguration; | |
| private readonly IRequiredClientResourceList requiredClientResourceList; | |
| private readonly ISocialRatingRepository socialRatingRepository; | |
| private readonly IUserRepository userRepository; | |
| public FindSearchPageController(IClient searchClient, IFindUIConfiguration findUIConfiguration, IRequiredClientResourceList requiredClientResourceList, ISocialRatingRepository socialRatingRepository, IUserRepository userRepository) | |
| { | |
| this.searchClient = searchClient; | |
| this.findUIConfiguration = findUIConfiguration; | |
| this.requiredClientResourceList = requiredClientResourceList; | |
| this.socialRatingRepository = socialRatingRepository; | |
| this.userRepository = userRepository; | |
| } | |
| [ValidateInput(false)] | |
| public ViewResult Index(FindSearchPage currentPage, string q) | |
| { | |
| FindSearchContentModel model = new FindSearchContentModel(currentPage) | |
| { | |
| PublicProxyPath = this.findUIConfiguration.AbsolutePublicProxyPath() | |
| }; | |
| // detect if serviceUrl and/or defaultIndex is configured. | |
| model.IsConfigured = this.SearchIndexIsConfigured(EPiServer.Find.Configuration.GetConfiguration()); | |
| if (model.IsConfigured && !string.IsNullOrWhiteSpace(model.Query)) | |
| { | |
| ITypeSearch<StandardPage> queryFor = this.BuildBoostedQuery(model); | |
| model.ContentResult = queryFor.GetContentResult(); | |
| } | |
| this.RequireClientResources(); | |
| return View(model); | |
| } | |
| private ITypeSearch<StandardPage> BuildBoostedQuery(FindSearchContentModel model) | |
| { | |
| string userId = this.userRepository.GetUserId(this.User); | |
| Dictionary<int, int> cats = this.socialRatingRepository.GetFavoriteCategoriesForUser(userId); | |
| Dictionary<int, int> types = this.socialRatingRepository.GetFavoriteContentTypesForUser(userId); | |
| ITypeSearch<StandardPage> query = | |
| this.searchClient.Search<StandardPage>( | |
| this.searchClient.Settings.Languages.GetSupportedLanguage(ContentLanguage.PreferredCulture) | |
| ?? Language.None).For(model.Query) | |
| // Boost the users favorite categories | |
| .AddCategoryBoosts(cats) | |
| // Boost the users favorite content types | |
| .AddContentTypeBoosts(types) | |
| // Fetch the specific paging page. | |
| .Skip((model.PagingPage - 1) * model.CurrentPage.PageSize).Take(model.CurrentPage.PageSize) | |
| // Allow editors (from the Find/Optimizations view) to push specific hits to the top | |
| // for certain search phrases. | |
| .ApplyBestBets(); | |
| // obey DNT | |
| string doNotTrackHeader = System.Web.HttpContext.Current.Request.Headers.Get("DNT"); | |
| // Should not track when value equals 1 | |
| if (doNotTrackHeader == null || doNotTrackHeader.Equals("0")) | |
| { | |
| query = query.Track(); | |
| } | |
| return query; | |
| } | |
| /// <summary> | |
| /// Checks if service url and index are configured | |
| /// </summary> | |
| /// <param name="configuration">Find configuration</param> | |
| /// <returns>True if configured, false otherwise</returns> | |
| private bool SearchIndexIsConfigured(Configuration configuration) | |
| { | |
| return !configuration.ServiceUrl.IsNullOrEmpty() | |
| && !configuration.ServiceUrl.Contains("YOUR_URI") | |
| && !configuration.DefaultIndex.IsNullOrEmpty() | |
| && !configuration.DefaultIndex.Equals("YOUR_INDEX"); | |
| } | |
| /// <summary> | |
| /// Requires the client resources used in the view. | |
| /// </summary> | |
| private void RequireClientResources() | |
| { | |
| // jQuery.UI is used in autocomplete example. | |
| // Add jQuery.UI files to existing client resource bundles or load it from CDN or use any other alternative library. | |
| // We use local resources for demo purposes without Internet connection. | |
| this.requiredClientResourceList.RequireStyle(VirtualPathUtilityEx.ToAbsolute("~/Static/css/jquery-ui.css")); | |
| this.requiredClientResourceList.RequireScript(VirtualPathUtilityEx.ToAbsolute("~/Static/js/jquery-ui.js")).AtFooter(); | |
| } | |
| } | |
| } |