I have sucessfully generated classes from Swagger Definition (Openapi 3.0.3) using nSwag Studio, but I have no idea how to properly use this as a client.
Manually RestSharp code works fine, but I'd like to use autogenerated code to consume webservice methods and can't do it properly
This is working fine:
string clientId = "dev";
string clientSecret = #"pass";
var client = new RestClient("http://192.168.1.10/xyz/api/oauth/token");
var request = new RestRequest(Method.POST);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddHeader("grant_type", "client_credentials");
var credentials = string.Format("{0}:{1}", clientId, clientSecret);
var headerValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(credentials));
request.AddHeader("Authorization", $"Basic {headerValue}");
request.AddParameter($"application/x-www-form-urlencoded", $"grant_type=client_credentials&client_id={clientId}&client_secret{clientSecret}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request, Method.POST);
My try with autogenerated code. My apoligizes if its wrong, but can't find any example.
There's just few examples but looks like client is generated in a different way (client initialization with base url - but still no idea about basic authorization in this context)
Googling returns mostly "How to create server side" or add swagger to asp/mvc/webapi project.
string URL = #"http://192.168.1.10/xyz/api";
string clientId = "dev";
string clientSecret = #"pass";
IO.Swagger.Client.ApiClient client = new IO.Swagger.Client.ApiClient(URL);
IO.Swagger.Client.Configuration.DefaultApiClient = client; //<<this throws error
IO.Swagger.Client.Configuration.Username = clientId;
IO.Swagger.Client.Configuration.Password = clientSecret;
client.AddDefaultHeader("Authorization", "bearer TOKEN");
IO.Swagger.Api.AuthApi authApi = new IO.Swagger.Api.AuthApi(client);
Error in ApiClient class
public ApiClient(String basePath="/xyz/api")
{
BasePath = basePath;
RestClient = new RestClient(BasePath); //System.UriFormatException: 'Invalid URI: The format of the URI could not be determined.'
}
I have tried many string forms of url (class doesn't accept Uri explictly)
Configuration
using System;
using System.Reflection;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace IO.Swagger.Client
{
public class Configuration
{
public const string Version = "1.0.0";
public static ApiClient DefaultApiClient = new ApiClient();
public static String Username { get; set; }
public static String Password { get; set; }
public static Dictionary<String, String> ApiKey = new Dictionary<String, String>();
public static Dictionary<String, String> ApiKeyPrefix = new Dictionary<String, String>();
private static string _tempFolderPath = Path.GetTempPath();
public static String TempFolderPath
{
get { return _tempFolderPath; }
set
{
if (String.IsNullOrEmpty(value))
{
_tempFolderPath = value;
return;
}
// create the directory if it does not exist
if (!Directory.Exists(value))
Directory.CreateDirectory(value);
// check if the path contains directory separator at the end
if (value[value.Length - 1] == Path.DirectorySeparatorChar)
_tempFolderPath = value;
else
_tempFolderPath = value + Path.DirectorySeparatorChar;
}
}
private const string ISO8601_DATETIME_FORMAT = "o";
private static string _dateTimeFormat = ISO8601_DATETIME_FORMAT;
public static String DateTimeFormat
{
get
{
return _dateTimeFormat;
}
set
{
if (string.IsNullOrEmpty(value))
{
// Never allow a blank or null string, go back to the default
_dateTimeFormat = ISO8601_DATETIME_FORMAT;
return;
}
// Caution, no validation when you choose date time format other than ISO 8601
// Take a look at the above links
_dateTimeFormat = value;
}
}
public static String ToDebugReport()
{
String report = "C# SDK (IO.Swagger) Debug Report:\n";
report += " OS: " + Environment.OSVersion + "\n";
report += " .NET Framework Version: " + Assembly
.GetExecutingAssembly()
.GetReferencedAssemblies()
.Where(x => x.Name == "System.Core").First().Version.ToString() + "\n";
report += " Version of the API: 2.0.1\n";
report += " SDK Package Version: 1.0.0\n";
return report;
}
}
}
Client
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using System.IO;
using System.Web;
using System.Linq;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using RestSharp;
using RestSharp.Extensions;
namespace IO.Swagger.Client
{
public class ApiClient
{
private readonly Dictionary<String, String> _defaultHeaderMap = new Dictionary<String, String>();
public ApiClient(String basePath="/xyz/api")
{
BasePath = basePath;
RestClient = new RestClient(BasePath);
}
public string BasePath { get; set; }
public RestClient RestClient { get; set; }
public Dictionary<String, String> DefaultHeader
{
get { return _defaultHeaderMap; }
}
public Object CallApi(String path, RestSharp.Method method, Dictionary<String, String> queryParams, String postBody,
Dictionary<String, String> headerParams, Dictionary<String, String> formParams,
Dictionary<String, FileParameter> fileParams, String[] authSettings)
{
var request = new RestRequest(path, method);
UpdateParamsForAuth(queryParams, headerParams, authSettings);
// add default header, if any
foreach(var defaultHeader in _defaultHeaderMap)
request.AddHeader(defaultHeader.Key, defaultHeader.Value);
// add header parameter, if any
foreach(var param in headerParams)
request.AddHeader(param.Key, param.Value);
// add query parameter, if any
foreach(var param in queryParams)
request.AddParameter(param.Key, param.Value, ParameterType.GetOrPost);
// add form parameter, if any
foreach(var param in formParams)
request.AddParameter(param.Key, param.Value, ParameterType.GetOrPost);
// add file parameter, if any
foreach(var param in fileParams)
request.AddFile(param.Value.Name, param.Value.Writer, param.Value.FileName, param.Value.ContentType);
if (postBody != null) // http body (model) parameter
request.AddParameter("application/json", postBody, ParameterType.RequestBody);
return (Object)RestClient.Execute(request);
}
public void AddDefaultHeader(string key, string value)
{
_defaultHeaderMap.Add(key, value);
}
public string EscapeString(string str)
{
return RestSharp.Contrib.HttpUtility.UrlEncode(str);
}
public FileParameter ParameterToFile(string name, Stream stream)
{
if (stream is FileStream)
return FileParameter.Create(name, stream.ReadAsBytes(), Path.GetFileName(((FileStream)stream).Name));
else
return FileParameter.Create(name, stream.ReadAsBytes(), "no_file_name_provided");
}
public string ParameterToString(object obj)
{
if (obj is DateTime)
// Return a formatted date string - Can be customized with Configuration.DateTimeFormat
// Defaults to an ISO 8601, using the known as a Round-trip date/time pattern ("o")
// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Anchor_8
// For example: 2009-06-15T13:45:30.0000000
return ((DateTime)obj).ToString (Configuration.DateTimeFormat);
else if (obj is List<string>)
return String.Join(",", (obj as List<string>).ToArray());
else
return Convert.ToString (obj);
}
public object Deserialize(string content, Type type, IList<Parameter> headers=null)
{
if (type == typeof(Object)) // return an object
{
return content;
}
if (type == typeof(Stream))
{
var filePath = String.IsNullOrEmpty(Configuration.TempFolderPath)
? Path.GetTempPath()
: Configuration.TempFolderPath;
var fileName = filePath + Guid.NewGuid();
if (headers != null)
{
var regex = new Regex(#"Content-Disposition:.*filename=['""]?([^'""\s]+)['""]?$");
var match = regex.Match(headers.ToString());
if (match.Success)
fileName = filePath + match.Value.Replace("\"", "").Replace("'", "");
}
File.WriteAllText(fileName, content);
return new FileStream(fileName, FileMode.Open);
}
if (type.Name.StartsWith("System.Nullable`1[[System.DateTime")) // return a datetime object
{
return DateTime.Parse(content, null, System.Globalization.DateTimeStyles.RoundtripKind);
}
if (type == typeof(String) || type.Name.StartsWith("System.Nullable")) // return primitive type
{
return ConvertType(content, type);
}
// at this point, it must be a model (json)
try
{
return JsonConvert.DeserializeObject(content, type);
}
catch (IOException e)
{
throw new ApiException(500, e.Message);
}
}
public string Serialize(object obj)
{
try
{
return obj != null ? JsonConvert.SerializeObject(obj) : null;
}
catch (Exception e)
{
throw new ApiException(500, e.Message);
}
}
public string GetApiKeyWithPrefix (string apiKeyIdentifier)
{
var apiKeyValue = "";
Configuration.ApiKey.TryGetValue (apiKeyIdentifier, out apiKeyValue);
var apiKeyPrefix = "";
if (Configuration.ApiKeyPrefix.TryGetValue (apiKeyIdentifier, out apiKeyPrefix))
return apiKeyPrefix + " " + apiKeyValue;
else
return apiKeyValue;
}
public void UpdateParamsForAuth(Dictionary<String, String> queryParams, Dictionary<String, String> headerParams, string[] authSettings)
{
if (authSettings == null || authSettings.Length == 0)
return;
foreach (string auth in authSettings)
{
// determine which one to use
switch(auth)
{
case "BasicAuth":
headerParams["Authorization"] = "Basic " + Base64Encode(Configuration.Username + ":" + Configuration.Password);
break;
case "BearerAuth":
break;
default:
//TODO show warning about security definition not found
break;
}
}
}
public static string Base64Encode(string text)
{
var textByte = System.Text.Encoding.UTF8.GetBytes(text);
return System.Convert.ToBase64String(textByte);
}
public static Object ConvertType(Object fromObject, Type toObject) {
return Convert.ChangeType(fromObject, toObject);
}
}
}
AuthApi
using System;
using System.Collections.Generic;
using RestSharp;
using IO.Swagger.Client;
using IO.Swagger.Model;
namespace IO.Swagger.Api
{
public interface IAuthApi
{
InlineResponse200 OauthTokenPost (string grantType);
}
public class AuthApi : IAuthApi
{
public AuthApi(ApiClient apiClient = null)
{
if (apiClient == null) // use the default one in Configuration
this.ApiClient = Configuration.DefaultApiClient;
else
this.ApiClient = apiClient;
}
public AuthApi(String basePath)
{
this.ApiClient = new ApiClient(basePath);
}
public void SetBasePath(String basePath)
{
this.ApiClient.BasePath = basePath;
}
public String GetBasePath(String basePath)
{
return this.ApiClient.BasePath;
}
public ApiClient ApiClient {get; set;}
public InlineResponse200 OauthTokenPost (string grantType)
{
var path = "/oauth/token";
path = path.Replace("{format}", "json");
var queryParams = new Dictionary<String, String>();
var headerParams = new Dictionary<String, String>();
var formParams = new Dictionary<String, String>();
var fileParams = new Dictionary<String, FileParameter>();
String postBody = null;
if (grantType != null) formParams.Add("grant_type", ApiClient.ParameterToString(grantType)); // form parameter
// authentication setting, if any
String[] authSettings = new String[] { "BasicAuth" };
// make the HTTP request
IRestResponse response = (IRestResponse) ApiClient.CallApi(path, Method.POST, queryParams, postBody, headerParams, formParams, fileParams, authSettings);
if (((int)response.StatusCode) >= 400)
throw new ApiException ((int)response.StatusCode, "Error calling OauthTokenPost: " + response.Content, response.Content);
else if (((int)response.StatusCode) == 0)
throw new ApiException ((int)response.StatusCode, "Error calling OauthTokenPost: " + response.ErrorMessage, response.ErrorMessage);
return (InlineResponse200) ApiClient.Deserialize(response.Content, typeof(InlineResponse200), response.Headers);
}
}
}
I am under impression that your main problem is Uri formating. I would suggest finding the exact problem by doing
string URL = #"http://192.168.1.10/xyz/api";
Uri baseAddress = new Uri(URL);
string clientId = "dev";
string clientSecret = #"pass";
IO.Swagger.Client.ApiClient client = new IO.Swagger.Client.ApiClient(baseAddress.ToString());
IO.Swagger.Client.Configuration.DefaultApiClient = client; //<<this throws error
IO.Swagger.Client.Configuration.Username = clientId;
IO.Swagger.Client.Configuration.Password = clientSecret;
client.AddDefaultHeader("Authorization", "bearer TOKEN");
IO.Swagger.Api.AuthApi authApi = new IO.Swagger.Api.AuthApi(client);
Related
I am uploading files to a Gdrive follow this instructions. In the File objtect I just set the name, like this:
{
"name": "myObjectName"
}
The files are uploading without problem. Now I need to generate a shared link for each upload file, Do you know who is the request that I have to do?
Thanks,
With #Jacques-Guzel HeronĀ“s help, I completed the upload file process. I created a wrapper for Google Drive API v3. That is my code if any need to do something like that: (I am using C#):
public interface IGDriveApiV3Wrapper
{
string UploadFile(string filePath, string gDriveUploadDestinationFolderId = null);
bool SetFilePermissions(string fileId, GDriveFileRole gDriveRole, GDriveFileType gDriveType);
GDriveFile GetFileInfo(string fileId);
}
public class GDriveApiV3NativeWrapper : IGDriveApiV3Wrapper
{
private const string GDriveFilesApiResumablePath = "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable";
private const string GDriveTokenApiPath = "https://oauth2.googleapis.com/token";
private static readonly HttpClient GDriveClient = new HttpClient { Timeout = Timeout.InfiniteTimeSpan };
private readonly List<KeyValuePair<string, string>> _getTokenRequestContent;
private static GDriveTokenInfo _gDriveTokenInfo;
private static readonly object UpdateGDriveTokenInfoLocker = new object();
public GDriveApiV3NativeWrapper(string gDriveApiClientId, string gDriveApiClientSecret, string gDriveApiRefreshToken)
{
_getTokenRequestContent = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", gDriveApiClientId),
new KeyValuePair<string, string>("client_secret", gDriveApiClientSecret),
new KeyValuePair<string, string>("refresh_token", gDriveApiRefreshToken),
new KeyValuePair<string, string>("grant_type", "refresh_token")
};
}
public string UploadFile(string filePath, string gDriveUploadDestinationFolderId = null)
{
if (string.IsNullOrEmpty(filePath))
throw new ArgumentException("Value cannot be null or empty.", nameof(filePath));
FileInfo fileInfo;
try
{
fileInfo = new FileInfo(filePath);
}
catch (Exception ex)
{
throw new ArgumentException("File not valid.", nameof(filePath), ex);
}
if (!fileInfo.Exists)
throw new ArgumentException("File not exists.", nameof(filePath));
using (var initiateResumableUploadSessionRequest = new HttpRequestMessage(HttpMethod.Post, GDriveFilesApiResumablePath))
{
UpdateGDriveTokenInfo();
initiateResumableUploadSessionRequest.Headers.Authorization = new AuthenticationHeaderValue(_gDriveTokenInfo.TokenType, _gDriveTokenInfo.AccessToken);
var jsonContent = new JObject(
new JProperty("name", fileInfo.Name));
if (!string.IsNullOrEmpty(gDriveUploadDestinationFolderId))
{
jsonContent.Add(new JProperty("parents", new JArray { gDriveUploadDestinationFolderId }));
}
initiateResumableUploadSessionRequest.Content = new StringContent(jsonContent.ToString(), Encoding.UTF8, "application/json");
var initiateResumableUploadSessionResponse = GDriveClient.SendAsync(initiateResumableUploadSessionRequest).Result;
if (initiateResumableUploadSessionResponse.StatusCode != HttpStatusCode.OK)
throw new ExternalException(initiateResumableUploadSessionResponse.ToString());
using (var uploadFileRequest = new HttpRequestMessage(HttpMethod.Put, initiateResumableUploadSessionResponse.Headers.Location))
{
uploadFileRequest.Content = new ByteArrayContent(File.ReadAllBytes(filePath));
HttpResponseMessage uploadFileResponse;
uploadFileResponse = GDriveClient.SendAsync(uploadFileRequest).Result;
if (uploadFileResponse.StatusCode != HttpStatusCode.OK && uploadFileResponse.StatusCode != HttpStatusCode.Created)
throw new ExternalException(uploadFileResponse.ReasonPhrase);
var uploadFileResponseBody = uploadFileResponse.Content.ReadAsStringAsync().Result;
JObject uploadFileResponseJson = JObject.Parse(uploadFileResponseBody);
return uploadFileResponseJson["id"].ToString();
}
}
}
public bool SetFilePermissions(string fileId, GDriveFileRole gDriveFileRole, GDriveFileType gDriveFileType)
{
if (string.IsNullOrEmpty(fileId))
throw new ArgumentException("Value cannot be null or empty.", nameof(fileId));
using (var setFilePermissionsRequest = new HttpRequestMessage(HttpMethod.Post, $"https://www.googleapis.com/drive/v3/files/{fileId}/permissions"))
{
UpdateGDriveTokenInfo();
setFilePermissionsRequest.Headers.Authorization = new AuthenticationHeaderValue(_gDriveTokenInfo.TokenType, _gDriveTokenInfo.AccessToken);
var jsonContent2 = new JObject(
new JProperty("role", gDriveFileRole.ToString().ToLower()),
new JProperty("type", gDriveFileType.ToString().ToLower()));
setFilePermissionsRequest.Content = new StringContent(jsonContent2.ToString(), Encoding.UTF8, "application/json");
HttpResponseMessage setFilePermissionsResponse = GDriveClient.SendAsync(setFilePermissionsRequest).Result;
if (setFilePermissionsResponse.StatusCode != HttpStatusCode.OK)
throw new ExternalException(setFilePermissionsResponse.ToString());
}
return true;
}
public GDriveFile GetFileInfo(string fileId)
{
using (var getFileInfoRequest = new HttpRequestMessage(HttpMethod.Get, $"https://www.googleapis.com/drive/v3/files/{fileId}?fields=name,webViewLink"))
{
UpdateGDriveTokenInfo();
getFileInfoRequest.Headers.Authorization = new AuthenticationHeaderValue(_gDriveTokenInfo.TokenType, _gDriveTokenInfo.AccessToken);
HttpResponseMessage getFileInfoResponse = GDriveClient.SendAsync(getFileInfoRequest).Result;
if (getFileInfoResponse.StatusCode != HttpStatusCode.OK)
throw new ExternalException(getFileInfoResponse.ToString());
var getFileInfoResponseBody = getFileInfoResponse.Content.ReadAsStringAsync().Result;
JObject getFileInfoResponseJson = JObject.Parse(getFileInfoResponseBody);
return new GDriveFile
{
Id = fileId,
Name = getFileInfoResponseJson["name"].ToString(),
WebViewLink = getFileInfoResponseJson["webViewLink"].ToString()
};
}
}
private void UpdateGDriveTokenInfo()
{
lock (UpdateGDriveTokenInfoLocker)
{
if (_gDriveTokenInfo != null && !_gDriveTokenInfo.IsExpired())
{
return;
}
using (var refreshTokenRequest = new HttpRequestMessage(HttpMethod.Post, GDriveTokenApiPath))
{
refreshTokenRequest.Content = new FormUrlEncodedContent(_getTokenRequestContent);
var getTokenRequestResponse = GDriveClient.SendAsync(refreshTokenRequest).Result;
var jsonResponse = JObject.Parse(getTokenRequestResponse.Content.ReadAsStringAsync().Result);
_gDriveTokenInfo = new GDriveTokenInfo((string)jsonResponse["access_token"], (int)jsonResponse["expires_in"], (string)jsonResponse["token_type"]);
}
}
}
public class GDriveFile
{
public string Id { get; set; }
public string Name { get; set; }
public string WebViewLink { get; set; }
}
public enum GDriveFileRole
{
Owner,
Organizer,
FileOrganizer,
Writer,
Commenter,
Reader
}
public enum GDriveFileType
{
User,
Group,
Domain,
Anyone
}
public class Program
{
private static IGDriveApiV3Wrapper _gDriveApiV3Wrapper;
private readonly string _gDriveUploadDestinationFolderId;
public Program(IGDriveApiV3Wrapper gDriveApiV3Wrapper, string gDriveUploadDestinationFolderId = null)
{
_gDriveApiV3Wrapper = gDriveApiV3Wrapper;
_gDriveUploadDestinationFolderId = gDriveUploadDestinationFolderId;
}
public string Upload(string filePath)
{
string fileId = _gDriveApiV3Wrapper.UploadFile(filePath, _gDriveUploadDestinationFolderId);
_gDriveApiV3Wrapper.SetFilePermissions(fileId, GDriveFileRole.Reader, GDriveFileType.Anyone);
GDriveFile gDriveFile = _gDriveApiV3Wrapper.GetFileInfo(fileId);
return gDriveFile.WebViewLink;
}
}
I'll assume that you are using Drive API v3. To set up permissions after uploading the file you have to use the method create. You can check the sharing documentation to know more about the different ways of creation of permissions and its different levels of access. After sharing the file, you can retrieve the link with the property webViewLink of the file. If you still have any doubts, please ask me for further clarification.
The Class GDrivenTokenInfo i don`t see its code
I am trying to implement a generic caller that uses OpenWeatherMap's different weather API's, but I got stuck in regards to how I would put in the right identifier for the link.
.../weather?q=... returns JSON data for the current weather;
.../forecast?q=... returns JSON data for a five day forecast.
I am looking for the textbook way to maybe retrieve the API type of each class through accessing GetAPIType(), cast that to an int and put it in the index, so that I would be able to use identifiers[index]. Or perhaps there is an easier way to do it.
Checking for the typeof(T) also crossed my mind, and I would assign the index depending on the if(typeof(T).Equals(typeof(...))) construct, but that seems very messy and if OpenWeatherMap had 100 API's in theory, I would need 100 different if constructs. With this in mind, wouldn't creating those checks beat the purpose of Client being generic?
A third solution I thought of would be passing APIType type as a parameter for the Client constructor,
e.g. var client = new Client<CurrentWeatherDTO>(APIType.CurrentWeather, location, apiKey),
but given the fact that Client is generic and I already provide a type when I instantiate it, it would seem awfully redundant.
Client.cs
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Reflection;
namespace Rainy.OpenWeatherMapAPI
{
public class Client<T>
{
private readonly string location;
private readonly string apiKey;
private readonly string requestUri;
private readonly string[] identifiers = { "weather", "forecast" };
private readonly int index;
public Client(string location, string apiKey)
{
// Get the type of API used in order to get the right identifier for the link.
// ??? Maybe use Reflection, somehow.
this.location = location;
this.apiKey = apiKey;
requestUri = $"api.openweathermap.org/data/2.5/{}?q={location}&appid={apiKey}";
}
public async Task<T> GetWeather(CancellationToken cancellationToken)
{
using (var client = new HttpClient())
using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
var stream = await response.Content.ReadAsStreamAsync();
if (response.IsSuccessStatusCode)
return DeserializeJsonFromStream<T>(stream);
var content = await StreamToStringAsync(stream);
throw new APIException
{
StatusCode = (int)response.StatusCode,
Content = content
};
}
}
private U DeserializeJsonFromStream<U>(Stream stream)
{
if (stream == null || stream.CanRead == false)
return default(U);
using (var sr = new StreamReader(stream))
using (var jtr = new JsonTextReader(sr))
{
var js = new JsonSerializer();
var searchResult = js.Deserialize<U>(jtr);
return searchResult;
}
}
private async Task<string> StreamToStringAsync(Stream stream)
{
string content = null;
if (stream != null)
using (var sr = new StreamReader(stream))
content = await sr.ReadToEndAsync();
return content;
}
}
}
APIType.cs
namespace Rainy.OpenWeatherMapAPI
{
public enum APIType
{
CurrentWeather = 0,
FiveDayForecast = 1
}
}
IWeather.cs
namespace Rainy.OpenWeatherMapAPI
{
public interface IWeather
{
APIType GetAPIType();
}
}
CurrentWeatherDTO.cs
namespace Rainy.OpenWeatherMapAPI.CurrentWeatherData
{
class CurrentWeatherDTO : IWeather
{
public APIType GetAPIType()
{
return APIType.CurrentWeather;
}
}
}
FiveDayForecastDTO.cs
namespace Rainy.OpenWeatherMapAPI.WeatherForecastData
{
class FiveDayForecastDTO : IWeather
{
public APIType GetAPIType()
{
return APIType.FiveDayForecast;
}
}
}
I would not use an enum to drive the index of an array.
I would directly return the string in a static way.
This solution can also work with the index of the array if you want.
Here is the code and the dotnetfiddle:
using System;
public class Program
{
public static void Main()
{
var client1 = new Client<CurrentWeatherDTO>(null);
Console.WriteLine("Client CurrentWeather type: " + client1.Type);
var client2 = new Client<FiveDayForecastDTO>(null);
Console.WriteLine("Client FiveDay type: " + client2.Type);
}
public class Client<T> where T : IWeather, new()
{
public string Type { get; set; }
public Client(string apiKey)
{
var dto = (IWeather)new T();
this.Type = dto.GetAPIType();
}
}
public static class APIType
{
public static string CurrentWeather = "weather";
public static string FiveDayForecast = "forecast";
}
public interface IWeather
{
string GetAPIType();
}
class CurrentWeatherDTO : IWeather
{
public string GetAPIType()
{
return APIType.CurrentWeather;
}
}
class FiveDayForecastDTO : IWeather
{
public string GetAPIType()
{
return APIType.FiveDayForecast;
}
}
}
I would probably use a solution like this, but maybe a bit more error handling.
There's a couple of references for how to use HttpClient.
I don't really understand the part in the requestUri with {}, maybe that's part of your problem, I changed it to {???} in my sample code.
class Client
{
// Problems using HttpClient and look into using IHttpClientFactory...
// http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html
// https://www.hanselman.com/blog/HttpClientFactoryForTypedHttpClientInstancesInASPNETCore21.aspx
static HttpClient _httpClient = new HttpClient();
readonly string WeatherUri = $"api.openweathermap.org/data/2.5/{???}?q={0}&appid={1}";
public async Task<T> GetWeather<T>(string location, CancellationToken cancellationToken)
{
var apiKey = ApiKeyAttribute.GetApiKey<T>();
if (apiKey == null) throw new Exception("ApiKeyAttirbute missing");
var requestUri = string.Format(WeatherUri, location, apiKey);
return await GetItem<T>(requestUri, cancellationToken);
}
public async Task<T> GetItem<T>(string requestUri, CancellationToken cancellationToken)
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
var response = await _httpClient.SendAsync(httpRequestMessage, cancellationToken);
if (!response.IsSuccessStatusCode) throw new Exception("Error requesting data");
if (response.Content == null) return default(T);
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(content);
}
}
[ApiKeyAttribute("weather")]
class CurrentWeatherDTO { /* add appropriat properties */ }
[ApiKeyAttribute("forecast")]
class FiveDayForecastDTO { /* add appropriat properties */ }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
class ApiKeyAttribute : Attribute
{
public string Name { get; private set; }
public ApiKeyAttribute(string name)
{
Name = name;
}
public static string GetApiKey<T>()
{
var attribute = typeof(T).GetCustomAttribute<ApiKeyAttribute>();
return attribute?.Name;
}
}
So I'm stuck at this point. I am trying to communicate with JanRain's "auth_info" service. In fact, to start, I'm just trying to get the "error" message/object/'.jsthingy' that you get when you surf to it directly in the browser:
https://rpxnow.com/api/v2/auth_info
but I want to get that back with a WCF call.
According to Fiddler, the content type of the information at that url is text/javascript. However, from what I can tell, WCF doesn't give me that option when calling it through WCF. I get two options: WebMessageFormat.Json, or WebMessageFormat.Xml.
I get the following error in Visual Studio:
InvalidOperationException was unhandled by User Code -
The incoming message has an unexpected message format 'Raw'. The expected message formats for the operation are 'Xml', 'Json'. This can be because a WebContentTypeMapper has not been configured on the binding. See the documentation of WebContentTypeMapper for more details.
WTF? So can WCF even do this? (I suspect a more manual solution ahead)
JanRain's online code examples are a little bit lacking in the C# examples.
Their documentation link on auth_info is here https://rpxnow.com/docs#auth_info
The address of their auth_info service is here:
https://rpxnow.com/api/v2/auth_info
[TestMethod]
public void CallJanRain()
{
var x = new JanRainProxy("https://rpxnow.com/api/v2");
x.GetAuthInfo("", ""); //the params are apiKey, and token. not passing either at the moment as I want to be able to know how to at least handle the error first. After all, the *browser* at least got it..
}
[ServiceContract]
public interface IJanRainContract
{
[OperationContract(Name="auth_info")]
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Xml)]
JanRainAuthInfo GetAuthInfo(string apiKey, string token);
}
[DataContract]
public class JanRainAuthInfo
{
[DataMember(Name="identifier")]
public string Identifier { get; set; }
}
public class JanRainProxy: ClientBase<IJanRainContract>
{
public JanRainProxy(string url, WebHttpSecurityMode securityMode = WebHttpSecurityMode.Transport)
: base(ConstructEndpoint(url, securityMode))
{
}
//JanRainContract
public JanRainAuthInfo GetAuthInfo(string apiKey, string token)
{
return base.Channel.GetAuthInfo(apiKey, token);
}
// This method constructs a WebHttpBinding endpoint with all the appropriate
// settings for talking to our services.
private static ServiceEndpoint ConstructEndpoint(string serviceUri, WebHttpSecurityMode securityMode)
{
var contract = ContractDescription.GetContract(typeof(IJanRainContract));
var binding = new WebHttpBinding(securityMode);
//{
// MaxBufferPoolSize = 524288000,
// MaxReceivedMessageSize = 65536000
//};
var address = new EndpointAddress(serviceUri);
var endpoint = new ServiceEndpoint(
contract,
binding,
address);
var webHttpBehavior = new WebHttpBehavior()
{
DefaultBodyStyle = WebMessageBodyStyle.Wrapped,
DefaultOutgoingRequestFormat = WebMessageFormat.Json,
DefaultOutgoingResponseFormat = WebMessageFormat.Json,
AutomaticFormatSelectionEnabled = true,
FaultExceptionEnabled = true
};
endpoint.Behaviors.Add(webHttpBehavior);
return endpoint;
}
}
I'm figuring that perhaps I should leave the contenttype at json and tweak the behavior, or binding.
ok.. it looks like I needed to add a custom contentTypeMapper on my binding
after declaring my binding I added the following:
WebContentTypeMapper customMapper = new JsonContentTypeMapper();
binding.ContentTypeMapper = customMapper;
here's the custom Mapper:
public class JsonContentTypeMapper : WebContentTypeMapper
{
public override WebContentFormat
GetMessageFormatForContentType(string contentType)
{
if (contentType == "text/javascript")
{
return WebContentFormat.Raw;
}
else
{
return WebContentFormat.Json;
}
}
}
Kevin,
Here is a C# example on how to process the auth_info response that you might find helpful:
//C# Helper Class for Janrain Engage
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.XPath;
public class Rpx
{
private string apiKey;
private string baseUrl;
public Rpx(string apiKey, string baseUrl) {
while (baseUrl.EndsWith("/"))
baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);
this.apiKey = apiKey;
this.baseUrl = baseUrl;
}
public string getApiKey() { return apiKey; }
public string getBaseUrl() { return baseUrl; }
public XmlElement AuthInfo(string token) {
Dictionary<string,string> query = new Dictionary<string,string>();
query.Add("token", token);
return ApiCall("auth_info", query);
}
public List<string> Mappings(string primaryKey) {
Dictionary<string,string> query = new Dictionary<string,string>();
query.Add("primaryKey", primaryKey);
XmlElement rsp = ApiCall("mappings", query);
XmlElement oids = (XmlElement)rsp.FirstChild;
List<string> result = new List<string>();
for (int i = 0; i < oids.ChildNodes.Count; i++) {
result.Add(oids.ChildNodes[i].InnerText);
}
return result;
}
public Dictionary<string,ArrayList> AllMappings() {
Dictionary<string,string> query = new Dictionary<string,string>();
XmlElement rsp = ApiCall("all_mappings", query);
Dictionary<string,ArrayList> result = new Dictionary<string,ArrayList>();
XPathNavigator nav = rsp.CreateNavigator();
XPathNodeIterator mappings = (XPathNodeIterator) nav.Evaluate("/rsp/mappings/mapping");
foreach (XPathNavigator m in mappings) {
string remote_key = GetContents("./primaryKey/text()", m);
XPathNodeIterator ident_nodes = (XPathNodeIterator) m.Evaluate("./identifiers/identifier");
ArrayList identifiers = new ArrayList();
foreach (XPathNavigator i in ident_nodes) {
identifiers.Add(i.ToString());
}
result.Add(remote_key, identifiers);
}
return result;
}
private string GetContents(string xpath_expr, XPathNavigator nav) {
XPathNodeIterator rk_nodes = (XPathNodeIterator) nav.Evaluate(xpath_expr);
while (rk_nodes.MoveNext()) {
return rk_nodes.Current.ToString();
}
return null;
}
public void Map(string identifier, string primaryKey) {
Dictionary<string,string> query = new Dictionary<string,string>();
query.Add("identifier", identifier);
query.Add("primaryKey", primaryKey);
ApiCall("map", query);
}
public void Unmap(string identifier, string primaryKey) {
Dictionary<string,string> query = new Dictionary<string,string>();
query.Add("identifier", identifier);
query.Add("primaryKey", primaryKey);
ApiCall("unmap", query);
}
private XmlElement ApiCall(string methodName, Dictionary<string,string> partialQuery) {
Dictionary<string,string> query = new Dictionary<string,string>(partialQuery);
query.Add("format", "xml");
query.Add("apiKey", apiKey);
StringBuilder sb = new StringBuilder();
foreach (KeyValuePair<string, string> e in query) {
if (sb.Length > 0) {
sb.Append('&');
}
sb.Append(System.Web.HttpUtility.UrlEncode(e.Key, Encoding.UTF8));
sb.Append('=');
sb.Append(HttpUtility.UrlEncode(e.Value, Encoding.UTF8));
}
string data = sb.ToString();
Uri url = new Uri(baseUrl + "/api/v2/" + methodName);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
// Write the request
StreamWriter stOut = new StreamWriter(request.GetRequestStream(),
Encoding.ASCII);
stOut.Write(data);
stOut.Close();
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream dataStream = response.GetResponseStream ();
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = false;
doc.Load(dataStream);
XmlElement resp = doc.DocumentElement;
if (resp == null || !resp.GetAttribute("stat").Equals("ok")) {
throw new Exception("Unexpected API error");
}
return resp;
}
public static void Main(string[] args) {
Rpx r = new Rpx(args[0], args[1]);
if (args[2].Equals("mappings")) {
Console.WriteLine("Mappings for " + args[3] + ":");
foreach(string s in r.Mappings(args[3])) {
Console.WriteLine(s);
}
}
if (args[2].Equals("all_mappings")) {
Console.WriteLine("All mappings:");
foreach (KeyValuePair<string, ArrayList> pair in r.AllMappings()) {
Console.WriteLine(pair.Key + ":");
foreach (string identifier in pair.Value) {
Console.WriteLine(" " + identifier);
}
}
}
if (args[2].Equals("map")) {
Console.WriteLine(args[3] + " mapped to " + args[4]);
r.Map(args[3], args[4]);
}
if (args[2].Equals("unmap")) {
Console.WriteLine(args[3] + " unmapped from " + args[4]);
r.Unmap(args[3], args[4]);
}
}
}
Code source: https://github.com/janrain/Janrain-Sample-Code/blob/master/c-sharp/csharp-helper-class.cs
Has anyone had success using DotNetOpenAuth to access Yelp's v2 api using DotNetOpenAuth?
After digging through the examples and the source, this is what I came up with:
public class YelpConnector
{
private static readonly string YelpConsumerKey = ConfigurationManager.AppSettings["YelpConsumerKey"];
private static readonly string YelpConsumerSecret = ConfigurationManager.AppSettings["YelpConsumerSecret"];
private static readonly string YelpToken = ConfigurationManager.AppSettings["YelpToken"];
private static readonly string YelpTokenSecret = ConfigurationManager.AppSettings["YelpTokenSecret"];
private static readonly InMemoryTokenManager tokenManager = new InMemoryTokenManager(YelpConsumerKey, YelpConsumerSecret, YelpToken, YelpTokenSecret);
private static readonly Uri YelpURLBase = new Uri("http://api.yelp.com/v2/");
private static readonly ServiceProviderDescription YelpServiceDescription = new ServiceProviderDescription {
RequestTokenEndpoint = null,
UserAuthorizationEndpoint = null,
AccessTokenEndpoint = null,
TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
};
private static dynamic SearchBase(string queryString)
{
if (string.IsNullOrEmpty(queryString))
throw new ArgumentNullException();
var searchEndpoint = new MessageReceivingEndpoint(new Uri(YelpURLBase, "search?" + queryString), HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);
var consumer = new WebConsumer(YelpServiceDescription, tokenManager);
try
{
using (IncomingWebResponse response = consumer.PrepareAuthorizedRequestAndSend(searchEndpoint, YelpToken))
{
string rs = response.GetResponseReader().ReadToEnd();
dynamic js = SimpleJson.SimpleJson.DeserializeObject(rs);
return js;
}
}
catch (Exception e)
{
ErrorSignal.FromCurrentContext().Raise(e);
return null;
}
}
internal class InMemoryTokenManager : IConsumerTokenManager
{
private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();
public InMemoryTokenManager(string consumerKey, string consumerSecret, string token, string secret)
{
if (String.IsNullOrEmpty(consumerKey))
{
throw new ArgumentNullException("consumerKey");
}
this.tokensAndSecrets[token] = secret;
this.ConsumerKey = consumerKey;
this.ConsumerSecret = consumerSecret;
}
public string ConsumerKey { get; private set; }
public string ConsumerSecret { get; private set; }
public string GetTokenSecret(string token)
{
return this.tokensAndSecrets[token];
}
public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response)
{
this.tokensAndSecrets[response.Token] = response.TokenSecret;
}
public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret)
{
this.tokensAndSecrets.Remove(requestToken);
this.tokensAndSecrets[accessToken] = accessTokenSecret;
}
public TokenType GetTokenType(string token)
{
throw new NotImplementedException();
}
}
}
If I pass in the following QueryString limit=5&category_filter=movietheaters,bars,cafe,museums,danceclubs,parks&ll=37.78364455,-122.464104, I get an exception saying "Precondition failed.: value != null" and a stacktrace of:
at System.Diagnostics.Contracts.__ContractsRuntime.Requires[TException](Boolean condition, String message, String conditionText)
at DotNetOpenAuth.Messaging.MessagingUtilities.EscapeUriDataStringRfc3986(String value)
at DotNetOpenAuth.OAuth.ChannelElements.SigningBindingElementBase.ConstructSignatureBaseString(ITamperResistantOAuthMessage message, MessageDictionary messageDictionary)
at DotNetOpenAuth.OAuth.ChannelElements.HmacSha1SigningBindingElement.GetSignature(ITamperResistantOAuthMessage message)
at DotNetOpenAuth.OAuth.ChannelElements.SigningBindingElementBase.ProcessOutgoingMessage(IProtocolMessage message)
at DotNetOpenAuth.OAuth.ChannelElements.SigningBindingElementChain.ProcessOutgoingMessage(IProtocolMessage message)
at DotNetOpenAuth.Messaging.Channel.ProcessOutgoingMessage(IProtocolMessage message)
at DotNetOpenAuth.OAuth.ChannelElements.OAuthChannel.InitializeRequest(IDirectedProtocolMessage request)
at DotNetOpenAuth.OAuth.ConsumerBase.PrepareAuthorizedRequestAndSend(MessageReceivingEndpoint endpoint, String accessToken)
at MeetPpl.Helpers.SocialConnectors.YelpConnector.SearchBase(String queryString)
Any suggestions? Am I on the right trail?
I struggled with this issue for 1 whole day before giving up on dotnetopenauth. I found a very simple way to search yelp using the oauth library of http://www.twitterizer.net/ . Simply download the lite version of twitterizer and use my sample code below.
Download link is http://www.twitterizer.net/files/Twitterizer2lite-2.3.2.zip
public static string search()
{
string yelpSearchURL = "http://api.yelp.com/v2/search?term=food&location=San+Francisco";
string yelpConsumerKey = "your key";
string yelpConsumerSecret = "your secret";
string yelpRequestToken = "your token";
string yelpRequestTokenSecret = "your token secret";
Twitterizer.OAuthTokens ot = new Twitterizer.OAuthTokens();
ot.AccessToken = yelpRequestToken;
ot.AccessTokenSecret = yelpRequestTokenSecret;
ot.ConsumerKey = yelpConsumerKey;
ot.ConsumerSecret = yelpConsumerSecret;
string formattedUri = String.Format(CultureInfo.InvariantCulture,
yelpSearchURL, "");
Uri url = new Uri(formattedUri);
Twitterizer.WebRequestBuilder wb = new Twitterizer.WebRequestBuilder(url, Twitterizer.HTTPVerb.GET, ot);
System.Net.HttpWebResponse wr = wb.ExecuteRequest();
StreamReader sr = new StreamReader(wr.GetResponseStream());
return sr.ReadToEnd();
}
You can use RestSharp api: https://github.com/JustinBeckwith/YelpSharp in combination with OAuthBase: http://oauth.googlecode.com/svn/code/csharp/OAuthBase.cs.
In YelpSharp implementation change Yelp.cs class method makeRequest with this:
protected string makeRequest(string area, string id, Dictionary<string, string> parameters)
{
// build the url with parameters
var url = area;
if (!String.IsNullOrEmpty(id)) url += "/" + HttpUtility.UrlEncode(id);
if (parameters != null)
{
bool firstp = true;
string[] keys = parameters.Keys.ToArray();
foreach (string _key in keys)
{
if (firstp) url += "?";
else url += "&";
firstp = false;
//Double URL encode "&" to prevent restsharp from treating the second half of the string as a new parameter
parameters[_key] = parameters[_key].Replace("&", "%26");
parameters[_key] = parameters[_key].Replace("+", "%2B");
parameters[_key] = parameters[_key].Replace(" ", "%2B");
url += _key + "=" + parameters[_key]; //HttpUtility.UrlEncode(parameters[_key]);
}
}
var client = new RestClient(rootUri);
var request = new RestRequest(Method.GET);
OAuthBase oAuth = new OAuthBase();
string nonce = oAuth.GenerateNonce();
string timeStamp = oAuth.GenerateTimeStamp();
string normalizedUrl;
string normalizedRequestParameters;
string sig = oAuth.GenerateSignature(new Uri(string.Format("{0}/{1}", client.BaseUrl, url)),
options.ConsumerKey, options.ConsumerSecret,
options.AccessToken, options.AccessTokenSecret,
"GET", timeStamp, nonce, out normalizedUrl, out normalizedRequestParameters);
sig = HttpUtility.UrlEncode(sig);
request.Resource = string.Format(area);
if (parameters != null)
{
foreach (var p in parameters)
{
request.AddParameter(p.Key, p.Value);
}
}
request.AddParameter("oauth_consumer_key", options.ConsumerKey);
request.AddParameter("oauth_token", options.AccessToken);
request.AddParameter("oauth_nonce", nonce);
request.AddParameter("oauth_timestamp", timeStamp);
request.AddParameter("oauth_signature_method", "HMAC-SHA1");
request.AddParameter("oauth_version", "1.0");
request.AddParameter("oauth_signature", sig);
var response = client.Execute(request);
return response.Content;
}
Test like this:
public void testYelp()
{
string _term = "food";
string _location = "San Francisco";
var o = Credentials.GetOptions();
var y = new Yelp(o);
var searchOptions = new SearchOptions();
searchOptions.GeneralOptions = new GeneralOptions()
{
term = _term
};
searchOptions.LocationOptions = new LocationOptions()
{
location = _location
};
var results = y.Search(searchOptions);
}
would anyone have a working example of an amazon ITEMLOOKUP ?>
i have the following code but it does not seem to work:
string ISBN = "0393326381";
string ASIN = "";
if (!(string.IsNullOrEmpty(ISBN) && string.IsNullOrEmpty(ASIN)))
{
AWSECommerceServicePortTypeChannel service = new AWSECommerceServicePortTypeChannel();
ItemLookup lookup = new ItemLookup();
ItemLookupRequest request = new ItemLookupRequest();
lookup.AssociateTag = secretKey;
lookup.AWSAccessKeyId = accessKeyId;
if (string.IsNullOrEmpty(ASIN))
{
request.IdType = ItemLookupRequestIdType.ISBN;
request.ItemId = new string[] { ISBN.Replace("-", "") };
}
else
{
request.IdType = ItemLookupRequestIdType.ASIN;
request.ItemId = new string[] { ASIN };
}
request.ResponseGroup = new string[] { "OfferSummary" };
lookup.Request = new ItemLookupRequest[] { request };
response = service.ItemLookup(lookup);
if (response.Items.Length > 0 && response.Items[0].Item.Length > 0)
{
Item item = response.Items[0].Item[0];
if (item.MediumImage == null)
{
//bookImageHyperlink.Visible = false;
}
else
{
//bookImageHyperlink.ImageUrl = item.MediumImage.URL;
}
//bookImageHyperlink.NavigateUrl = item.DetailPageURL;
//bookTitleHyperlink.Text = item.ItemAttributes.Title;
//bookTitleHyperlink.NavigateUrl = item.DetailPageURL;
if (item.OfferSummary.LowestNewPrice == null)
{
if (item.OfferSummary.LowestUsedPrice == null)
{
//priceHyperlink.Visible = false;
}
else
{
//priceHyperlink.Text = string.Format("Buy used {0}", item.OfferSummary.LowestUsedPrice.FormattedPrice);
//priceHyperlink.NavigateUrl = item.DetailPageURL;
}
}
else
{
//priceHyperlink.Text = string.Format("Buy new {0}", item.OfferSummary.LowestNewPrice.FormattedPrice);
//priceHyperlink.NavigateUrl = item.DetailPageURL;
}
if (item.ItemAttributes.Author != null)
{
//authorLabel.Text = string.Format("By {0}", string.Join(", ", item.ItemAttributes.Author));
}
else
{
//authorLabel.Text = string.Format("By {0}", string.Join(", ", item.ItemAttributes.Creator.Select(c => c.Value).ToArray()));
}
/*
ItemLink link = item.ItemLinks.Where(i => i.Description.Contains("Wishlist")).FirstOrDefault();
if (link == null)
{
//wishListHyperlink.Visible = false;
}
else
{
//wishListHyperlink.NavigateUrl = link.URL;
}
* */
}
}
}
the problem is with this:
thisshould be defined differently but i do not know how AWSECommerceServicePortTypeChannel service = new AWSECommerceServicePortTypeChannel();
Say, that code looks awful familiar. You're missing the Endpoint signing piece from when they switched over to requiring that you add message signing. You need to add a behavior on your client. Here's the change to your code above:
if (!(string.IsNullOrEmpty(ISBN) && string.IsNullOrEmpty(ASIN)))
{
AWSECommerceServicePortTypeClient client = new AWSECommerceServicePortTypeClient();
client.ChannelFactory.Endpoint.Behaviors.Add(
new Amazon.AmazonSigningEndpointBehavior(
accessKeyId,
secretKey);
ItemLookup lookup = new ItemLookup();
ItemLookupRequest request = new ItemLookupRequest();
lookup.AssociateTag = accessKeyId;
lookup.AWSAccessKeyId = secretKey;
//... etc.
And here's the Endpoint (I can't take credit for this, I wish I could remember who should):
namespace Amazon
{
public class AmazonSigningEndpointBehavior : IEndpointBehavior {
private string accessKeyId = "";
private string secretKey = "";
public AmazonSigningEndpointBehavior(string accessKeyId, string secretKey) {
this.accessKeyId = accessKeyId;
this.secretKey = secretKey;
}
public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime clientRuntime) {
clientRuntime.MessageInspectors.Add(new AmazonSigningMessageInspector(accessKeyId, secretKey));
}
public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher) { return; }
public void Validate(ServiceEndpoint serviceEndpoint) { return; }
public void AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection bindingParameters) { return; }
}
}
Oh. And you'll need the MessageInspector for that to work.
namespace Amazon
{
public class AmazonSigningMessageInspector : IClientMessageInspector {
private string accessKeyId = "";
private string secretKey = "";
public AmazonSigningMessageInspector(string accessKeyId, string secretKey) {
this.accessKeyId = accessKeyId;
this.secretKey = secretKey;
}
public object BeforeSendRequest(ref Message request, IClientChannel channel) {
// prepare the data to sign
string operation = Regex.Match(request.Headers.Action, "[^/]+$").ToString();
DateTime now = DateTime.UtcNow;
string timestamp = now.ToString("yyyy-MM-ddTHH:mm:ssZ");
string signMe = operation + timestamp;
byte[] bytesToSign = Encoding.UTF8.GetBytes(signMe);
// sign the data
byte[] secretKeyBytes = Encoding.UTF8.GetBytes(secretKey);
HMAC hmacSha256 = new HMACSHA256(secretKeyBytes);
byte[] hashBytes = hmacSha256.ComputeHash(bytesToSign);
string signature = Convert.ToBase64String(hashBytes);
// add the signature information to the request headers
request.Headers.Add(new AmazonHeader("AWSAccessKeyId", accessKeyId));
request.Headers.Add(new AmazonHeader("Timestamp", timestamp));
request.Headers.Add(new AmazonHeader("Signature", signature));
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState) { }
}
}
And finally, the Header:
namespace Amazon
{
public class AmazonHeader : MessageHeader
{
private string name;
private string value;
public AmazonHeader(string name, string value)
{
this.name = name;
this.value = value;
}
public override string Name { get { return name; } }
public override string Namespace { get { return "http://security.amazonaws.com/doc/2007-01-01/"; } }
protected override void OnWriteHeaderContents(XmlDictionaryWriter xmlDictionaryWriter, MessageVersion messageVersion)
{
xmlDictionaryWriter.WriteString(value);
}
}
}
Yes, they made it complicated when they started requiring message signing...
A simple and easy library is available on nuget.
PM> Install-Package Nager.AmazonProductAdvertising
Example
var authentication = new AmazonAuthentication("accesskey", "secretkey");
var client = new AmazonProductAdvertisingClient(authentication, AmazonEndpoint.US);
var result = await client.GetItemsAsync("B00BYPW00I");
To perform a lookup for anything other then an ASIN, you need to specify the "SearchIndex" property. You can simply set it to "All".
var request = new ItemLookupRequest();
request.ItemId = new[] {upcCode};
request.IdType = ItemLookupRequestIdType.UPC;
request.IdTypeSpecified = true;
request.SearchIndex = "All";
Here is a link to the documentation: http://docs.amazonwebservices.com/AWSECommerceService/2011-08-01/DG/index.html?ItemLookup.html. Note the description of the SearchIndex parameter:
Constraint:If ItemIdis an ASIN, a search index cannot be specified in
the request. Required for non-ASIN ItemIds.
I actually built a little wrapper around it so it hands you back a handy object graph. I have the source up on BitBucket and a little more about it on the C# Amazon ItemLookup page.
C# Amazon ItemLookup
You can make calls like:
var item = client.LookupByAsin("B0037X9N5U");
double? price = item.GetLowestPrice();