private static int LocalAccountCacheTimeInHours = 48; //<-- We cache the account locally for 2 days to avoid a Redis or WCF call on a hot account public static AccountManagementService.Account GetAccountObject(string accountNameKey) { AccountManagementService.Account account = null; #region (Plan A) Get Account from Local Cache bool localCacheEmpty = false; string localCacheKey = accountNameKey + ":account"; try { account = (AccountManagementService.Account)HttpRuntime.Cache[localCacheKey]; } catch (Exception e) { var error = e.Message; //TODO: Log: error message for local cache call } #endregion if (account == null) { localCacheEmpty = true; #region (Plan B) Get Account from Redis Cache try { //First we attempt to get the account from the Redis Cache IDatabase cache = CoreServices.RedisConnectionMultiplexers.RedisMultiplexer.GetDatabase(); string hashKey = "accountbyname:" + accountNameKey; string hashField = "model"; try { var redisValue = cache.HashGet(hashKey, hashField); if (redisValue.HasValue) { account = JsonConvert.DeserializeObject <AccountManagementService.Account>(redisValue); } } catch { } } catch (Exception e) { var error = e.Message; } #endregion } if (account == null) { #region (Plan C) Get Account from WCF //If a failure occurs, or the redis cache is empty we get the user from the WCF service var accountManagementServiceClient = new AccountManagementService.AccountManagementServiceClient(); try { accountManagementServiceClient.Open(); account = accountManagementServiceClient.GetAccount(accountNameKey, Common.SharedClientKey); //Close the connection WCFManager.CloseConnection(accountManagementServiceClient); } catch (Exception e) { #region Manage Exception string exceptionMessage = e.Message.ToString(); var currentMethod = System.Reflection.MethodBase.GetCurrentMethod(); string currentMethodString = currentMethod.DeclaringType.FullName + "." + currentMethod.Name; // Abort the connection & manage the exception WCFManager.CloseConnection(accountManagementServiceClient, exceptionMessage, currentMethodString); #endregion } #endregion } if (localCacheEmpty) { #region store Account into local cache //store string in local cache for a few moments: HttpRuntime.Cache.Insert(localCacheKey, account, null, DateTime.Now.AddHours(Common.LocalAccountCacheTimeInHours), TimeSpan.Zero); #endregion } return(account); }
public static Models.Search.SearchResults SearchProducts(AccountManagementService.Account account, string text, string filter = null, string orderBy = "relevance", int skip = 0, int top = 120, bool locationSort = false, string locationSortString = null, bool includeHidden = false) { if (top > 120) { //We do not ever allow more than 120 results per page in search top = 120; } //ProductResults productResults = null; //List<ProductDocumentModel> products = null; //dynamic products = null; DocumentSearchResult azuresearchresult = null; //Get from cache first var accountSearchIndex = Common.SearchIndexCache[account.ProductSearchIndex] as ISearchIndexClient; if (accountSearchIndex == null) { //Not in cache, create: ---------------------------- //Get client for this accounts search index (Moved to partitioning) //var accountSearchIndex = CoreServices.SearchServiceQueryClient.Indexes.GetClient(account.ProductSearchIndex); //Get search partition for this account, create client and connect to index: ======================================================= if (CoreServices.PlatformSettings.SearchParitions == null || CoreServices.PlatformSettings.SearchParitions.ToList().Count == 0) { //No Search Partitions Available in Static List, refresh list from Core Services Common.RefreshPlatformSettings(); } var searchPartition = CoreServices.PlatformSettings.SearchParitions.FirstOrDefault(partition => partition.Name == account.SearchPartition); if (searchPartition == null) { //May be a new partition, refresh platform setting and try again Common.RefreshPlatformSettings(); searchPartition = CoreServices.PlatformSettings.SearchParitions.FirstOrDefault(partition => partition.Name == account.SearchPartition); } var searchServiceClient = new SearchServiceClient(searchPartition.Name, new SearchCredentials(searchPartition.Key)); accountSearchIndex = searchServiceClient.Indexes.GetClient(account.ProductSearchIndex); //Store in cache: --------------------- Common.SearchIndexCache.Set(account.ProductSearchIndex, accountSearchIndex, new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(Common.SearchIndexClientCacheTimeInMinutes) }); } //Prepare the query var searchParameters = new SearchParameters(); if (includeHidden == false) { if (String.IsNullOrEmpty(filter)) { filter = "(visible eq true)"; } else { filter += " and (visible eq true)"; } } //Assign filters searchParameters.Filter = filter; //Add location sorting first (always supercedes others) if applicable if (locationSort) { //include location sorting //if (locationSortType == "point") //{ searchParameters.OrderBy.Add(locationSortString); //} //else if (locationSortType == "bounds") //{ //} } searchParameters.Skip = skip; searchParameters.Top = top; //We only add ordering and result count if we ask for MORE than 1 item back searchParameters.OrderBy = new List <string>(); if (top > 1) { if (orderBy.ToLower() != "relevance") { var orderByArray = orderBy.Split(','); searchParameters.OrderBy.Add(orderBy); } searchParameters.IncludeTotalResultCount = true; } //build strings to search against: //TO DO: // bool listingImagesOnly = true; //bool includesLocationData = false; //<--If true we will need to merge location Metadata and remove Metadata field if (top == 1) { //This uses the Details Property Fields: //$select=categoryNameKey,categoryName,orderId,subcategoryName listingImagesOnly = false; //<--Get all images for details searchParameters.Select = GetSearchFields(account.AccountNameKey, PropertyListType.Details); //<-- InOrder??? } else { //This uses the Listings Property Fields: //$select=categoryNameKey,categoryName,orderId,subcategoryName searchParameters.Select = GetSearchFields(account.AccountNameKey, PropertyListType.Listings); //<-- InOrder??? } //If the query contains dashes and no spaces, wrap it in quotes in order to get GUIDS and SKUS (The indexer replaces "-" with " " when storing into search) try { if (text.Contains("-")) { if (!text.Contains(" ")) { text = "\"" + text + "\""; } } } catch { } //Perform the search //NOTE: We add a wildcard at the end to get better results from text searches azuresearchresult = accountSearchIndex.Documents.Search(text + "*", searchParameters); #region Loop through each search result, transform into SearchResult object and append associated 'Listing' type ImageRecords to each results var searchresults = new Models.Search.SearchResults(); searchresults.Count = azuresearchresult.Count; searchresults.Returned = azuresearchresult.Results.Count(); if ((skip + top) >= azuresearchresult.Count) { searchresults.Remaining = 0; } else { searchresults.Remaining = Convert.ToInt32(azuresearchresult.Count) - (skip + top); } if (skip >= searchresults.Count) { searchresults.Range = null; } else if (searchresults.Returned == 0) { searchresults.Range = null; } else { searchresults.Range = (skip + 1) + "-" + (skip + searchresults.Returned); } searchresults.ContinuationToken = azuresearchresult.ContinuationToken; searchresults.Facets = azuresearchresult.Facets; searchresults.Coverage = azuresearchresult.Coverage; searchresults.Results = new List <Models.Search.Result>(); foreach (Microsoft.Azure.Search.Models.SearchResult azureresult in azuresearchresult.Results) { var result = new Models.Search.Result(); result.Score = azureresult.Score; result.Document = azureresult.Document; //result.Images = new List<dynamic>(); //result.Images = new System.Dynamic.ExpandoObject(); result.Images = Dynamics.Images.BuildDynamicImagesListForJson(account.AccountNameKey, "product", azureresult.Document["id"].ToString(), listingImagesOnly); searchresults.Results.Add(result); } #endregion return(searchresults); }
public JsonNetResult ProductFacets(bool includeHidden = false) { ExecutionType executionType = ExecutionType.local; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); //Get the subdomain (if exists) for the api call string accountNameKey = Common.GetSubDomain(Request.Url); if (String.IsNullOrEmpty(accountNameKey)) { return(new JsonNetResult { Data = "Not found" }); //return Request.CreateResponse(HttpStatusCode.NotFound); } AccountManagementService.Account account = Common.GetAccountObject(accountNameKey); string localCacheKey = accountNameKey + ":product:search:facets:includeHidden:" + includeHidden; List <ProductSearchFacet> facets = null; List <ProductSearchFacetJson> facetsJson = null; if (account == null) { return(new JsonNetResult { Data = "Not found" }); //return Request.CreateResponse(HttpStatusCode.NotFound); } else { #region (Plan A) Get data from local cache try { facetsJson = (List <ProductSearchFacetJson>)HttpRuntime.Cache[localCacheKey]; } catch (Exception e) { var error = e.Message; //TODO: Log: error message for local cache call } #endregion if (facetsJson == null) { #region (Plan B) Get Public json from second layer of Redis Cache IDatabase cache = CoreServices.RedisConnectionMultiplexers.RedisMultiplexer.GetDatabase(); string pathAndQuery = Common.GetApiPathAndQuery(Request.Url); string hashApiKey = accountNameKey + ":apicache"; string hashApiField = pathAndQuery; try { var redisApiValue = cache.HashGet(hashApiKey, hashApiField); if (redisApiValue.HasValue) { facetsJson = JsonConvert.DeserializeObject <List <ProductSearchFacetJson> >(redisApiValue); executionType = ExecutionType.redis_secondary; } } catch { } #endregion if (facetsJson == null) { #region (Plan C) Get data from Redis Cache try { //Attempt to get facets from the Redis Cache string hashKey = accountNameKey + ":search"; string hashField = "facets:products:visible"; if (includeHidden) { hashField = "facets:products"; } try { var redisValue = cache.HashGet(hashKey, hashField); if (redisValue.HasValue) { facets = JsonConvert.DeserializeObject <List <ProductSearchFacet> >(redisValue); executionType = ExecutionType.redis_main; } } catch { } } catch (Exception e) { var error = e.Message; //Log error message for Redis call } #endregion if (facets == null) { #region (Plan D) Get data from WCF var applicationSearchServiceClient = new ApplicationSearchService.ApplicationSearchServiceClient(); try { applicationSearchServiceClient.Open(); facets = applicationSearchServiceClient.GetProductFacets(accountNameKey, includeHidden, Common.SharedClientKey).ToList(); //Close the connection WCFManager.CloseConnection(applicationSearchServiceClient); executionType = ExecutionType.wcf; } catch (Exception e) { #region Manage Exception string exceptionMessage = e.Message.ToString(); var currentMethod = System.Reflection.MethodBase.GetCurrentMethod(); string currentMethodString = currentMethod.DeclaringType.FullName + "." + currentMethod.Name; // Abort the connection & manage the exception WCFManager.CloseConnection(applicationSearchServiceClient, exceptionMessage, currentMethodString); #endregion } #endregion } } #region Transform into json object, & cache locally or locally and redisAPI layer if (facetsJson != null) { //Just cache locally (we got json from the api redis layer) HttpRuntime.Cache.Insert(localCacheKey, facetsJson, null, DateTime.Now.AddMinutes(Common.SearchFacetsCacheTimeInMinutes), TimeSpan.Zero); } else if (facets != null) { //Transform categories into JSON and cache BOTH locally AND into redis facetsJson = Transforms.Json.SearchTransforms.ProductFacets(facets); HttpRuntime.Cache.Insert(localCacheKey, facetsJson, null, DateTime.Now.AddMinutes(Common.SearchFacetsCacheTimeInMinutes), TimeSpan.Zero); try { cache.HashSet(hashApiKey, hashApiField, JsonConvert.SerializeObject(facetsJson), When.Always, CommandFlags.FireAndForget); } catch { } } #endregion } } //Create results object ProductSearchFacetsJson facetsJsonResult = new ProductSearchFacetsJson(); facetsJsonResult.facets = facetsJson; //Add execution data stopWatch.Stop(); facetsJsonResult.executionType = executionType.ToString(); facetsJsonResult.executionTime = stopWatch.Elapsed.TotalMilliseconds + "ms"; JsonNetResult jsonNetResult = new JsonNetResult(); jsonNetResult.Formatting = Newtonsoft.Json.Formatting.Indented; jsonNetResult.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local; //<-- Convert UTC times to LocalTime jsonNetResult.Data = facetsJsonResult; return(jsonNetResult); }