public AffiliateProductDto Get() { // Input is passed in the query string. Interface might change eventually. // For now lets just get a keyword from query string and use that. // Do check the keyword though because our control will pass it otherwise what's the point. // Do input validation first because it is less expensive if (!Request.Query.ContainsKey(QUERY_STRING_PARAMETER_KEYWORD)) { return(null); } var keyword = Request.Query[QUERY_STRING_PARAMETER_KEYWORD][0]; if (string.IsNullOrEmpty(keyword) || string.IsNullOrWhiteSpace(keyword)) { return(null); } string category = null; if (Request.Query.ContainsKey(QUERY_STRING_PARAMETER_CATEGORY)) { category = Request.Query[QUERY_STRING_PARAMETER_CATEGORY][0]; } // Check if caller gave us security header. // This might throw exception if there was a header but invalid. But if someone is just messing with us we will return null. // Null in this case because we will also check that in react. var product = new AffiliateProductDto(); if (!ParseAntiForgeryHeader(_antiforgery, product, HttpContext)) { return(product); } // Form the parameters object. This will get smarter then just keyword eventually. var parameters = new ProductSearchParameters() { Keyword = keyword, Category = category }; // Call the service product = _productService.FindProduct(parameters); // this may be null but we are not going to do anything here no pint really. Just let the client side deal with results. return(product); }
/// <summary> /// Add product to cache. /// </summary> /// <param name="productKey"></param> /// <param name="product"></param> public void CacheProduct(string productKey, AffiliateProductDto product) { // This is not intended to be called from the outside and is public only for tests and transparency. // This is supposed to be called only from this class therefor we can throw an expection. if (string.IsNullOrEmpty(productKey) || string.IsNullOrWhiteSpace(productKey)) { throw new Exception("AmazonProductService CacheProduct function is called with empty productKey."); } if (_cacheManager == null) { throw new Exception("AmazonProductService CacheProduct function is called with empty cache manager."); } // Re-Check cache in case product is already added while we were waiting for amazon. var data = _cacheManager.Get(productKey); // return if found if (data != null) { return; } // Add to cache _cacheManager.Add(productKey, product, DEFAULT_PRODUCT_EXPIRATION_SECONDS); }
/// <summary> /// Parses amazon product details into a generic dto object from the xml node retuned by amazon rest call. /// </summary> /// <param name="productXmlNode"></param> /// <returns></returns> /// <remarks> /// Check the myxml.xml file for xml example. /// I specifically decided not to parse into an object. Too much trouble with serialization/deserialization. /// Underneath it is probably parsing each node anyway when converting to object. /// </remarks> public AffiliateProductDto ParseProduct(XmlNode productXmlNode, XmlNamespaceManager nsmgr, string namespacePrefix) { // This starts in Item node // check input if (productXmlNode == null) { return(null); } var product = new AffiliateProductDto() { Merchant = "Amazon" }; product.DetailPageURL = GetChildNodeValue(productXmlNode, "DetailPageURL"); product.MerchantProductId = GetChildNodeValue(productXmlNode, "ASIN"); var attributesNode = GetChildNode(productXmlNode, "ItemAttributes"); product.Title = GetChildNodeValue(attributesNode, "Title"); // Parse price var listPriceNode = GetChildNode(attributesNode, "ListPrice"); // Parse list price node if there. Search for ItemAttributes/ListPrice/FormattedPrice if (listPriceNode != null) { product.FormattedPrice = GetChildNodeValue(listPriceNode, "FormattedPrice"); } else { // Search for OfferSummary/LowestNewPrice/FormattedPrice var offerSummaryNode = GetChildNode(productXmlNode, "OfferSummary"); if (offerSummaryNode != null) { var lowestNewPriceNode = GetChildNode(offerSummaryNode, "LowestNewPrice"); if (lowestNewPriceNode != null) { product.FormattedPrice = GetChildNodeValue(lowestNewPriceNode, "FormattedPrice"); } } } // Parse description // Search for OfferSummary/LowestNewPrice/FormattedPrice var editorialReviewsNode = GetChildNode(productXmlNode, "EditorialReviews"); if (editorialReviewsNode != null) { foreach (XmlNode editorialReviewNode in editorialReviewsNode.ChildNodes) { if (editorialReviewNode.Name == "EditorialReview") { var source = GetChildNodeValue(editorialReviewNode, "Source"); var content = GetChildNodeValue(editorialReviewNode, "Content"); if (!product.Descriptions.ContainsKey(source)) { product.Descriptions.Add(source, content); } } } } // Let's get all images because we need to pick some decent size. // I will let the function to add of not add image var images = new List <ProductImage>(); // see if medium image is good enough ParseAndAddImage(images, productXmlNode, "MediumImage"); // see if large image is good enough ParseAndAddImage(images, productXmlNode, "LargeImage"); // see if small image is good enough ParseAndAddImage(images, productXmlNode, "SmallImage"); if (images.Count > 0) { product.MiddleImageURL = images[0].Url; product.MiddleImageWidth = images[0].Width; product.MiddleImageHeight = images[0].Height; } return(product); }