Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Amazon Product API - are there any valid, up-to-date examples in C#?

I must have run through 10 different sample C# projects that are out of date, and no longer work.

All I'm after is a way to get a book's information back based on an ISBN or title.

The Amazon Web Service site is an absolutely miserable morass.

Update:

I've managed to use this as a starting point, and have successfully completed a basic request. I'll continue to look for other resources that are up to date that might go into some other detailed scenarios.

like image 483
asfsadf Avatar asked Nov 10 '10 23:11

asfsadf


3 Answers

A while back I had wrote a client library for communicating with Amazon's ECS (A2S) services. Seems that Amazon hates their developers and keeps changing their service name and framework. Well, my client is now completely broken because of Recent API Changes.

Your best bet would be to look at their samples here.

Good luck with this. Their API is huge and outdated from the experience I had, but that was two years ago. I get the impression that they really cater to developers for their enterprise services, not the free A2S.

like image 50
Daniel Avatar answered Nov 06 '22 23:11

Daniel


The SprightlySoft AWS Component for .NET allows you to interact will all Amazon service including the Product Advertising API. Here is some sample code for looking up an ISBN. Get the component for free at http://sprightlysoft.com/.

//Product Advertising API, ItemLookup: http://docs.amazonwebservices.com/AWSECommerceService/2010-10-01/DG/ItemLookup.html

SprightlySoftAWS.REST MyREST = new SprightlySoftAWS.REST();

String RequestURL;
RequestURL = "https://ecs.amazonaws.com/onca/xml?Service=AWSECommerceService&Operation=ItemLookup&Version=2010-10-01";
RequestURL += "&AWSAccessKeyId=" + System.Uri.EscapeDataString(AWSAccessKeyId) + "&SignatureVersion=2&SignatureMethod=HmacSHA256&Timestamp=" + Uri.EscapeDataString(DateTime.UtcNow.ToString("yyyy-MM-dd\\THH:mm:ss.fff\\Z"));
RequestURL += "&ItemId=9781904233657";
RequestURL += "&IdType=ISBN";
RequestURL += "&SearchIndex=Books";

String RequestMethod;
RequestMethod = "GET";

String SignatureValue;
SignatureValue = MyREST.GetSignatureVersion2Value(RequestURL, RequestMethod, "", AWSSecretAccessKey);

RequestURL += "&Signature=" + System.Uri.EscapeDataString(SignatureValue);

Boolean RetBool;
RetBool = MyREST.MakeRequest(RequestURL, RequestMethod, null);

System.Diagnostics.Debug.Print("");
System.Diagnostics.Debug.Print(MyREST.LogData);
System.Diagnostics.Debug.Print("");

String ResponseMessage = "";

if (RetBool == true)
{
    System.Xml.XmlDocument MyXmlDocument;
    System.Xml.XmlNamespaceManager MyXmlNamespaceManager;
    System.Xml.XmlNode MyXmlNode;
    System.Xml.XmlNodeList MyXmlNodeList;

    MyXmlDocument = new System.Xml.XmlDocument();
    MyXmlDocument.LoadXml(MyREST.ResponseString);

    MyXmlNamespaceManager = new System.Xml.XmlNamespaceManager(MyXmlDocument.NameTable);
    MyXmlNamespaceManager.AddNamespace("amz", "http://webservices.amazon.com/AWSECommerceService/2010-10-01");

    MyXmlNodeList = MyXmlDocument.SelectNodes("amz:ItemLookupResponse/amz:Items/amz:Item", MyXmlNamespaceManager);

    if (MyXmlNodeList.Count == 0)
    {
        ResponseMessage = "No items found.";
    }
    else
    {
        foreach (System.Xml.XmlNode ItemXmlNode in MyXmlNodeList)
        {
            MyXmlNode = ItemXmlNode.SelectSingleNode("amz:ItemAttributes/amz:Title", MyXmlNamespaceManager);
            ResponseMessage += "Title = " + MyXmlNode.InnerText;

            ResponseMessage += Environment.NewLine;
        }
    }

    MessageBox.Show(ResponseMessage);
}
else
{
    ResponseMessage = MyREST.ResponseStringFormatted;

    MessageBox.Show(ResponseMessage);
}
like image 22
Anton Avatar answered Nov 06 '22 23:11

Anton


I've been struggling with this also (and I'm also at the learning stage).

What I found worked best was to use the REST api, and then to parse the results with Linq to XML into a custom results object I could work with. Excuse the long code paste (not sure on the protocol with that on this board) but figured you'd want it all. Much of this came from one of Amazon's own samples, but there site is not organized so I'm struggling to find it again.

// custom class for result item
public class AmazonBookSearchResult
{
    public string ASIN;
    public string Author;
    public string Title;
    public Uri DetailPageURL;
    public Uri AllCustomerReviews;
}

// I call this with the search terms, returns an IEnumerable of results
public class ItemLookup
{
    private const string MY_AWS_ACCESS_KEY_ID = "YOUR KEY";
    private const string MY_AWS_SECRET_KEY = "YOUR KEY";
    private const string DESTINATION = "ecs.amazonaws.com";

    private const string NAMESPACE = "http://webservices.amazon.com/AWSECommerceService/2009-03-31";

    public static IEnumerable<AmazonBookSearchResult> AmazonItemLookup(string category, string browseNode, string keyWords)
    {
        SignedRequestHelper helper = new SignedRequestHelper(MY_AWS_ACCESS_KEY_ID, MY_AWS_SECRET_KEY, DESTINATION);
        String requestUrl;

        IDictionary<string, string> r1 = new Dictionary<string, String>();
        r1["Service"] = "AWSECommerceService";
        r1["Version"] = "2009-03-31";
        r1["Operation"] = "ItemSearch";
        r1["ResponseGroup"] = "ItemAttributes";
        r1["SearchIndex"] = category;
        r1["Keywords"] = keyWords;

        requestUrl = helper.Sign(r1);
        XmlDocument xmlDoc = sendRequest(requestUrl);
        XDocument doc = XDocument.Load(new XmlNodeReader(xmlDoc));

        XNamespace ns = "http://webservices.amazon.com/AWSECommerceService/2009-03-31";
        IEnumerable<AmazonBookSearchResult> result =
            from items in doc.Element(ns + "ItemSearchResponse").Elements(ns + "Items").Elements(ns + "Item")
            select new AmazonBookSearchResult
            {
                ASIN = (string)items.Element(ns + "ASIN")
            };

        int count = doc.Element(ns + "ItemSearchResponse").Elements(ns + "Items").Elements(ns + "Item").Count();

        return result;

    } // end Lookup()

    // this is just taken from the amazon sample
    private static XmlDocument sendRequest(string url)
    {
        try
        {
            WebRequest request = HttpWebRequest.Create(url);
            WebResponse response = request.GetResponse();
            XmlDocument doc = new XmlDocument();
            doc.Load(response.GetResponseStream());

            XmlNodeList errorMessageNodes = doc.GetElementsByTagName("Message", NAMESPACE);
            if (errorMessageNodes != null && errorMessageNodes.Count > 0)
            {
                String message = errorMessageNodes.Item(0).InnerText;
                throw new Exception("Error " + message);
            }

            return doc;
        }
        catch (Exception e)
        {
            System.Console.WriteLine("Caught Exception: " + e.Message);
            System.Console.WriteLine("Stack Trace: " + e.StackTrace);
        }

        return null;
    }// end FetchTitle()
}

class SignedRequestHelper
{
    private string endPoint;
    private string akid;
    private byte[] secret;
    private HMAC signer;

    private const string REQUEST_URI = "/onca/xml";
    private const string REQUEST_METHOD = "GET";

    /*
     * Use this constructor to create the object. The AWS credentials are available on
     * http://aws.amazon.com
     *
     * The destination is the service end-point for your application:
     *  US: ecs.amazonaws.com
     *  JP: ecs.amazonaws.jp
     *  UK: ecs.amazonaws.co.uk
     *  DE: ecs.amazonaws.de
     *  FR: ecs.amazonaws.fr
     *  CA: ecs.amazonaws.ca
     */
    public SignedRequestHelper(string awsAccessKeyId, string awsSecretKey, string destination)
    {
        this.endPoint = destination.ToLower();
        this.akid = awsAccessKeyId;
        this.secret = Encoding.UTF8.GetBytes(awsSecretKey);
        this.signer = new HMACSHA256(this.secret);
    }

    /*
     * Sign a request in the form of a Dictionary of name-value pairs.
     *
     * This method returns a complete URL to use. Modifying the returned URL
     * in any way invalidates the signature and Amazon will reject the requests.
     */
    public string Sign(IDictionary<string, string> request)
    {
        // Use a SortedDictionary to get the parameters in naturual byte order, as
        // required by AWS.
        ParamComparer pc = new ParamComparer();
        SortedDictionary<string, string> sortedMap = new SortedDictionary<string, string>(request, pc);

        // Add the AWSAccessKeyId and Timestamp to the requests.
        sortedMap["AWSAccessKeyId"] = this.akid;
        sortedMap["Timestamp"] = this.GetTimestamp();

        // Get the canonical query string
        string canonicalQS = this.ConstructCanonicalQueryString(sortedMap);

        // Derive the bytes needs to be signed.
        StringBuilder builder = new StringBuilder();
        builder.Append(REQUEST_METHOD)
            .Append("\n")
            .Append(this.endPoint)
            .Append("\n")
            .Append(REQUEST_URI)
            .Append("\n")
            .Append(canonicalQS);

        string stringToSign = builder.ToString();
        byte[] toSign = Encoding.UTF8.GetBytes(stringToSign);

        // Compute the signature and convert to Base64.
        byte[] sigBytes = signer.ComputeHash(toSign);
        string signature = Convert.ToBase64String(sigBytes);

        // now construct the complete URL and return to caller.
        StringBuilder qsBuilder = new StringBuilder();
        qsBuilder.Append("http://")
            .Append(this.endPoint)
            .Append(REQUEST_URI)
            .Append("?")
            .Append(canonicalQS)
            .Append("&Signature=")
            .Append(this.PercentEncodeRfc3986(signature));

        return qsBuilder.ToString();
    }

    /*
     * Sign a request in the form of a query string.
     *
     * This method returns a complete URL to use. Modifying the returned URL
     * in any way invalidates the signature and Amazon will reject the requests.
     */
    public string Sign(string queryString)
    {
        IDictionary<string, string> request = this.CreateDictionary(queryString);
        return this.Sign(request);
    }

    /*
     * Current time in IS0 8601 format as required by Amazon
     */
    private string GetTimestamp()
    {
        DateTime currentTime = DateTime.UtcNow;
        string timestamp = currentTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
        return timestamp;
    }

    /*
     * Percent-encode (URL Encode) according to RFC 3986 as required by Amazon.
     *
     * This is necessary because .NET's HttpUtility.UrlEncode does not encode
     * according to the above standard. Also, .NET returns lower-case encoding
     * by default and Amazon requires upper-case encoding.
     */
    private string PercentEncodeRfc3986(string str)
    {
        str = HttpUtility.UrlEncode(str, System.Text.Encoding.UTF8);
        str = str.Replace("'", "%27").Replace("(", "%28").Replace(")", "%29").Replace("*", "%2A").Replace("!", "%21").Replace("%7e", "~").Replace("+", "%20");

        StringBuilder sbuilder = new StringBuilder(str);
        for (int i = 0; i < sbuilder.Length; i++)
        {
            if (sbuilder[i] == '%')
            {
                if (Char.IsLetter(sbuilder[i + 1]) || Char.IsLetter(sbuilder[i + 2]))
                {
                    sbuilder[i + 1] = Char.ToUpper(sbuilder[i + 1]);
                    sbuilder[i + 2] = Char.ToUpper(sbuilder[i + 2]);
                }
            }
        }
        return sbuilder.ToString();
    }

    /*
     * Convert a query string to corresponding dictionary of name-value pairs.
     */
    private IDictionary<string, string> CreateDictionary(string queryString)
    {
        Dictionary<string, string> map = new Dictionary<string, string>();

        string[] requestParams = queryString.Split('&');

        for (int i = 0; i < requestParams.Length; i++)
        {
            if (requestParams[i].Length < 1)
            {
                continue;
            }

            char[] sep = { '=' };
            string[] param = requestParams[i].Split(sep, 2);
            for (int j = 0; j < param.Length; j++)
            {
                param[j] = HttpUtility.UrlDecode(param[j], System.Text.Encoding.UTF8);
            }
            switch (param.Length)
            {
                case 1:
                    {
                        if (requestParams[i].Length >= 1)
                        {
                            if (requestParams[i].ToCharArray()[0] == '=')
                            {
                                map[""] = param[0];
                            }
                            else
                            {
                                map[param[0]] = "";
                            }
                        }
                        break;
                    }
                case 2:
                    {
                        if (!string.IsNullOrEmpty(param[0]))
                        {
                            map[param[0]] = param[1];
                        }
                    }
                    break;
            }
        }

        return map;
    }

    /*
     * Consttuct the canonical query string from the sorted parameter map.
     */
    private string ConstructCanonicalQueryString(SortedDictionary<string, string> sortedParamMap)
    {
        StringBuilder builder = new StringBuilder();

        if (sortedParamMap.Count == 0)
        {
            builder.Append("");
            return builder.ToString();
        }

        foreach (KeyValuePair<string, string> kvp in sortedParamMap)
        {
            builder.Append(this.PercentEncodeRfc3986(kvp.Key));
            builder.Append("=");
            builder.Append(this.PercentEncodeRfc3986(kvp.Value));
            builder.Append("&");
        }
        string canonicalString = builder.ToString();
        canonicalString = canonicalString.Substring(0, canonicalString.Length - 1);
        return canonicalString;
    }
}

/*
 * To help the SortedDictionary order the name-value pairs in the correct way.
 */
class ParamComparer : IComparer<string>
{
    public int Compare(string p1, string p2)
    {
        return string.CompareOrdinal(p1, p2);
    }
}
like image 25
lostinsea Avatar answered Nov 06 '22 23:11

lostinsea