0
\$\begingroup\$

i am writting a code to get all variants from shopify. as shopify has cursor -based pagination, i want to hit the api until i reach last page. i have write below code. please suggest if i am improve this or refactor this code in better way:

public static List<productVariantShopify.ProductVariant> getallProductVariant(long productId, string cursor)
{
List<productVariantShopify.ProductVariant> variantlst = new List<productVariantShopify.ProductVariant>();
if (string.IsNullOrEmpty(cursor))
{
 //means first time hit is coming
 variantlst = VariantListttttt(productId, cursor, variantlst);
}
return variantlst;
}
 
private static List<ProductVariant> VariantListttttt(long productId, string cursor, List<ProductVariant> variantlst)
{
 var result = GetProductVariant(productId, cursor);
 Type myType = result.GetType();
 IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
 var pageInfo = new object();
 foreach (PropertyInfo prop in props)
 {
 if (prop != null && result != null)
 {
 var propValue = prop.GetValue(result, null);
 if (prop.Name == "variantlst")
 {
 if (propValue != null)
 {
 var resultLst = (List<ProductVariant>)propValue;
 variantlst.AddRange(resultLst);
 }
 }
 else if (prop.Name == "pageInfo")
 {
 pageInfo = propValue;
 }
 }
 }
 PropertyInfo[] propertyInfos = pageInfo.GetType().GetProperties();
 if (propertyInfos.Length > 0)
 {
 var hasNextPage = propertyInfos.Where(x => x.Name.Equals("hasNextPage")).FirstOrDefault();
 if (hasNextPage != null)
 {
 if (Convert.ToBoolean(hasNextPage.GetValue(pageInfo, null)))
 {
 var cursorProp = pageInfo.GetType().GetProperties().Where(x => x.Name.Equals("endCursor")).FirstOrDefault();
 if (cursorProp != null)
 {
 var cursorValue = Convert.ToString(cursorProp.GetValue(pageInfo, null));
 if (cursorValue != null)
 {
 cursor = cursorValue;
 }
 variantlst = VariantListttttt(productId, cursor, variantlst);
 }
 }
 }
 }
 pageInfo = null;
 return variantlst;
}
public static object GetProductVariant(long productId, string cursor)
{
 var httpClient = new HttpClient
 {
 BaseAddress = new Uri("https://myshopify.com/admin/api/2023-07/graphql.json")
 };
 httpClient.DefaultRequestHeaders.Add("X-Shopify-Access-Token", "xxxxx");
 object variables = new object();
 if (string.IsNullOrEmpty(cursor))
 {
 variables = new
 {
 numProducts = 10,
 query = "product_id:" + productId
 };
 }
 else
 {
 variables = new
 {
 numProducts = 10,
 query = "product_id:" + productId,
 cursor
 };
 }
 var queryObject = new
 {
 query = @"query ($numProducts: Int!,$query: String ,$cursor: String){
 productVariants(first: $numProducts,query: $query, after: $cursor) {
 edges {
 cursor
 node {
 title
 id
 selectedOptions{
 name
 value
 }
 metafields(keys: [
 ""key0"", 
 ""key1"", ""key2"", 
 ""key3"",""key4""
 ]
 first:5
 ){
 nodes{
 namespace
 key
 value
 id
 }
 }
 }
 }
 pageInfo {
 hasNextPage
 endCursor
 }
 }
 }
 ",
 variables
 };
 var request = new HttpRequestMessage
 {
 Method = HttpMethod.Post,
 Content = new StringContent(JsonConvert.SerializeObject(queryObject), Encoding.UTF8, "application/json")
 };
 List<productVariantShopify.ProductVariant> variantlst = new List<productVariantShopify.ProductVariant>();
 try
 {
 using (var response = httpClient.SendAsync(request).GetAwaiter().GetResult())
 {
 response.EnsureSuccessStatusCode();
 var responseString = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
 var result = JsonConvert.DeserializeObject<UpdatedProductVariantModel.Root>(responseString);
 //string debugQueryObject = queryObject.query;
 //string debugVariableObject = JsonConvert.SerializeObject(queryObject.variables).Replace("\\r", "").Replace("\\n", "");
 if (result != null && result.data != null && result.data.productVariants != null && result.data.productVariants.edges != null)
 {
 fillProdVariantData(variantlst, result);
 return new
 {
 variantlst,
 result.data.productVariants.pageInfo
 };
 }
 else
 {
 if (responseString.Contains("error") && responseString.Contains("Throttled"))
 {
 return GetProductVariant(productId, string.Empty);
 }
 }
 }
 }
 catch (Exception ex)
 {
 if (ex.InnerException != null && ex.InnerException.Message == "")
 {
 if (ex.InnerException != null && ex.InnerException.Message.Trim() == "The request was aborted: Could not create SSL/TLS secure channel.")
 {
 return GetProductVariant(productId, string.Empty);
 }
 }
 }
 return null;
}
private static List<ProductVariant> fillProdVariantData(List<ProductVariant> variantlst, UpdatedProductVariantModel.Root? result)
{
 if (result != null)
 {
 var variants = result.data.productVariants.edges;
 if (variants != null)
 {
 if (variants.Count > 0)
 {
 foreach (var edge in variants)
 {
 var node = edge.node;
 string color = string.Empty;
 var colorOption1 = node.selectedOptions.Where(x => x.name == "Color").ToList();
 if (colorOption1 != null && colorOption1.Count > 0)
 {
 var colorOption1Value = colorOption1.FirstOrDefault();
 if(colorOption1Value != null)
 {
 color = colorOption1Value.value;
 }
 
 }
 string size = string.Empty;
 var sizeOption2 = node.selectedOptions.Where(x => x.name == "Size").ToList();
 if (sizeOption2 != null && sizeOption2.Count > 0)
 {
 var sizeOption2Value = sizeOption2.FirstOrDefault();
 if (sizeOption2Value != null)
 {
 size = sizeOption2Value.value;
 }
 }
 if (color == "" && size == "")
 continue;
 var metaFieldID = string.Empty;
 var metaFieldKey = string.Empty;
 if (node.metafields.nodes != null)
 {
 List<MetaField> myMetafields = new List<MetaField>();
 foreach (var node2 in node.metafields.nodes)
 {
 var field = node2;
 myMetafields.Add(new MetaField { Id = Convert.ToInt64(field.id.Replace("gid://shopify/Metafield/", "")), Key = field.key, Value = field.value });
 };
 ProductVariant productVariant = new ProductVariant()
 {
 Id = Convert.ToInt64(node.id.Replace("gid://shopify/ProductVariant/", "")),
 Option1 = color,
 Option2 = size,
 AdminGraphQLAPIId = node.id,
 Metafields = myMetafields
 };
 variantlst.Add(productVariant);
 }
 else
 {
 ProductVariant productVariant = new ProductVariant()
 {
 Id = Convert.ToInt64(node.id.Replace("gid://shopify/ProductVariant/", "")),
 Option1 = color,
 Option2 = size,
 AdminGraphQLAPIId = node.id
 };
 variantlst.Add(productVariant);
 }
 }
 }
 }
 }
 return variantlst;
}
asked Nov 20, 2023 at 22:56
\$\endgroup\$
7
  • \$\begingroup\$ have you thought about using ShopifySharp library? \$\endgroup\$ Commented Nov 21, 2023 at 14:45
  • \$\begingroup\$ As a very first step I would suggest to use consistent naming style across your code and try to use meaningful names. \$\endgroup\$ Commented Nov 21, 2023 at 20:35
  • \$\begingroup\$ @iSR5, yes, i checked the library, but has number of things missing and is not able to fulfil my needs. \$\endgroup\$ Commented Nov 22, 2023 at 18:38
  • \$\begingroup\$ @PeterCsala, that makes sense, but i am actually referring for logic improvements. \$\endgroup\$ Commented Nov 22, 2023 at 18:38
  • 2
    \$\begingroup\$ The current question title, which states your concerns about the code, is too general to be useful here. Please edit to the site standard, which is for the title to simply state the task accomplished by the code. Please see How to get the best value out of Code Review: Asking Questions for guidance on writing good question titles. \$\endgroup\$ Commented Mar 3, 2024 at 10:40

1 Answer 1

1
\$\begingroup\$

There is an unusual lack of types in this code - it looks like it was originally written in Python or JavaScript and ported to C#. All of the places that you use object and Reflection would be easier to read and safer to run if you were using types and normal property accessors (getter/setter).

Similarly, detecting exception cases based on the Message strings is not best practice. You should be able to catch based on Exception type, or if there isn't one specific enough, you should be able to check a property like e.g. ErrorCode. Message strings are generally not guaranteed to be stable and can change based on the environment because of localization.

There are chunks of near-identical code to read color and size that could be pulled out into a separate function.

Naming is inconsistent. I assume VariantListttttt is not serious, but it doesn't match getallProductVariant either in casing or style. These should probably be GetVariantList and GetAllProductVariants, though it's hard to tell from those names what the difference is - can you think of better names? node2 could be childNode or similar, I dislike adding a number to make a unique name, it doesn't tell you how it's different. Similarly, using "my" as in myMetafields is a pet peeve of mine - what does that name have over nodefields?

Why does getallProductVariant return an empty list if cursor is not null?

answered Mar 2, 2024 at 1:54
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.