SearchService

SearchService is found in Poq.Shopify.Services namespace. SearchService service uses only Shopify Storefront API to obtain data and can be used for feedless implementation of BFC. It requires two dependencies to be used in your backend. All required dependencies should be registered using IoC:

services.AddScoped<IShopifySearchClient, ShopifySearchClient>();
services.AddScoped<ISearchContractMapper, SearchContractMapper>();
services.AddScoped<ISearchService, SearchService>();

Explanation of services

  • IShopifySearchClient is available as part of Poq Shopify nuget package. No further action is required than properly registering the service.

  • ISearchService with implementation SearchService

    Search Service searches for products by using Shopify Storefront Api. Service functionality and products mapping can be modified by providing custom implementation of ISearchContractMapper. SearchService has generic overloads that allow customization of Graphql queries and contract.

  • ISearchContractMapper with implementation SearchContractMapper is responsible of mapping product listings, filters and pagination. It is used to map filters to shopify contract.

Here in an example of overriding default handling in ISearchContractMapper.

services.AddScoped<ISearchContractMapper, MySearchContractMapper>();
public class MySearchContractMapper : ISearchContractMapper
{
public IList<SearchResponseFilter> MapCollectionFilters(IList<ShopifyProductFilter>? filters)
{
var result = new List<SearchResponseFilter>();
if (filters == null)
{
return result;
}
foreach (var filter in filters)
{
if (filter.Id.StartsWith("filter.v.option."))
{
result.Add(new SearchResponseFilter()
{
Id = filter.Id.Replace("filter.v.option.", ""),
Label = filter.Label,
DisplayType = SearchResponseFilterDisplayType.List,
List = new SearchResponseFilterList(filter.Values.Select(v => new SearchResponseFilterValue(v.Id.Replace($"{filter.Id}.", ""), v.Label, v.Count)))
});
}
}
return result;
}
public IList<SearchResponseFilter> CreateResponseFilters(IList<SearchResponseProductListing> productListings)
{
double minPrice = productListings.Any() ? productListings.Min(x => x.PriceRange.Now.Min) : 0;
double maxPrice = productListings.Any() ? productListings.Max(x => x.PriceRange.Now.Max) : 0;
var result = new List<SearchResponseFilter>
{
new SearchResponseFilter()
{
Id = "available",
Label = "Available",
DisplayType = SearchResponseFilterDisplayType.Toggle,
Toggle = new SearchResponseFilterToggle("true", "false")
},
new SearchResponseFilter()
{
Id = "price",
Label = "Price",
DisplayType = SearchResponseFilterDisplayType.Price,
Price = new SearchResponseFilterSlider((int)minPrice, (int)maxPrice)
}
};
return result;
}
public SearchResponsePagination CreateResponsePagination(int count, IQueryCollection? query, string? cursor)
{
return new SearchResponsePagination(count, "/search?cursor=cursorvalue", "/search?=cursorvalue");
}
public IList<SearchResponseProductListing> MapProductListings(IList<ProductListingEdge>? products)
{
var result = new List<SearchResponseProductListing>();
if (products == null || products.Count == 0)
{
return result;
}
foreach (var product in products)
{
var price = new SearchResponseListingPrice(
new SearchResponseListingPriceRange(
(double)(product.ProductListing.PriceRange.MinVariantPrice.Amount ?? 0),
(double)(product.ProductListing.PriceRange.MaxVariantPrice.Amount ?? 0)
));
var variantGroups = CreateVariantGroups(product);
result.Add(new SearchResponseProductListing(
product.ProductListing.Id,
product.ProductListing.Id,
product.ProductListing.Title,
price,
variantGroups
)
{
Forms = CreateListingForms(product)
});
}
return result;
}
public IList<KeyValuePair<string, string>> CreateOptionFiltersFromQueryString(IQueryCollection query)
{
var result = new List<KeyValuePair<string, string>>();
foreach (var item in query)
{
if (item.Key == "size")
{
var values = new List<string>();
foreach (var val in item.Value)
{
values.AddRange(val.Split(",", StringSplitOptions.RemoveEmptyEntries));
}
foreach (var value in values)
{
result.Add(new KeyValuePair<string, string>("size", value));
}
}
if (item.Key == "colour" || item.Key == "color")
{
var values = new List<string>();
foreach (var val in item.Value)
{
values.AddRange(val.Split(",", StringSplitOptions.RemoveEmptyEntries));
}
foreach (var value in values)
{
result.Add(new KeyValuePair<string, string>("color", value));
}
}
}
return result;
}
...
}

Methods

  • SearchAsync searches in Shopify storefront API for matches by price, availability and query. Compared to Poq Platform search this method cannot filter on variant options like colour and size when there is no filtering by category applied!

  • SearchByBarcodeAsync searches in Shopify storefront API for matches by barcode. Searching by barcode is not officially supported by shopify, but when barcode is used in unique product variants in Shopify inventory it gives reliable results.