forked from anthonyreilly/NetCoreForce
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJsonClient.cs
More file actions
261 lines (232 loc) · 9.46 KB
/
JsonClient.cs
File metadata and controls
261 lines (232 loc) · 9.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NetCoreForce.Client.Serializer;
using NetCoreForce.Client.Models;
namespace NetCoreForce.Client
{
public class JsonClient : IDisposable
{
private const string JsonMimeType = "application/json";
// private const string GZipEncoding = "gzip";
// private const string DeflateEncoding = "deflate";
//best practice is to reuse HttpClient
//https://github.com/mspnp/performance-optimization/blob/master/ImproperInstantiation/docs/ImproperInstantiation.md
//By default, and the ideal case, is using the static readonly HttpClient.
//Alternatively, for testing and special cases, a class instance instnace of an HttpClient can be used instead.
private static readonly HttpClient _SharedHttpClient;
private HttpClient _httpClient;
private HttpClient SharedHttpClient
{
get
{
//use the instance client when extant, otherwise use the default shared instance.
return _httpClient ?? _SharedHttpClient;
}
}
AuthenticationHeaderValue _authHeaderValue;
/// <summary>
/// JSON Client static constructor, initializes the default shared HttpClient instance.
/// </summary>
static JsonClient()
{
_SharedHttpClient = HttpClientFactory.CreateHttpClient();
}
/// <summary>
/// Intialize the JSON client.
/// <para>By default, uses a shared static HttpClient instance for best performance.</para>
/// </summary>
/// <param name="accessToken">API Access token</param>
/// <param name="httpClient">Optional custom HttpClient. Ideally this should be a shared static instance for best performance.</param>
public JsonClient(string accessToken, HttpClient httpClient = null)
{
_authHeaderValue = new AuthenticationHeaderValue("Bearer", accessToken);
if (httpClient != null)
{
_httpClient = httpClient;
}
}
public async Task<T> HttpGetAsync<T>(Uri uri, Dictionary<string, string> customHeaders = null)
{
//TODO: can this handle T = string?
try
{
return await HttpAsync<T>(uri, HttpMethod.Get, null, customHeaders);
}
catch (Exception ex)
{
throw ex;
}
}
public async Task<T> HttpPostAsync<T>(object inputObject, Uri uri, Dictionary<string, string> customHeaders = null)
{
var json = JsonSerializer.SerializeForCreate(inputObject);
try
{
var content = new StringContent(json, Encoding.UTF8, JsonMimeType);
HttpRequestMessage request = new HttpRequestMessage();
request.Headers.Authorization = _authHeaderValue;
request.RequestUri = uri;
request.Method = HttpMethod.Post;
request.Content = content;
return await GetResponse<T>(request, customHeaders);
}
catch (Exception ex)
{
throw ex;
}
}
public async Task<T> HttpPatchAsync<T>(object inputObject, Uri uri, Dictionary<string, string> customHeaders = null)
{
try
{
var json = JsonSerializer.SerializeForUpdate(inputObject);
var content = new StringContent(json, Encoding.UTF8, JsonMimeType);
return await HttpAsync<T>(uri, new HttpMethod("PATCH"), content, customHeaders);
}
catch (Exception ex)
{
throw ex;
}
}
public async Task<T> HttpDeleteAsync<T>(Uri uri, Dictionary<string, string> customHeaders = null)
{
try
{
HttpRequestMessage request = new HttpRequestMessage();
request.Headers.Authorization = _authHeaderValue;
request.RequestUri = uri;
request.Method = HttpMethod.Delete;
return await GetResponse<T>(request, customHeaders);
}
catch (Exception ex)
{
throw ex;
}
}
private async Task<T> HttpAsync<T>(Uri uri, HttpMethod httpMethod, HttpContent content = null, Dictionary<string, string> customHeaders = null)
{
try
{
HttpRequestMessage request = new HttpRequestMessage();
request.Headers.Authorization = _authHeaderValue;
request.RequestUri = uri;
request.Method = httpMethod;
if (content != null)
{
request.Content = content;
}
return await GetResponse<T>(request, customHeaders);
}
catch (Exception ex)
{
throw ex;
}
}
private async Task<T> GetResponse<T>(HttpRequestMessage request, Dictionary<string, string> customHeaders = null)
{
//const string ContentEncoding = "Content-Encoding";
if (customHeaders != null && customHeaders.Count > 0)
{
foreach (KeyValuePair<string, string> header in customHeaders)
{
request.Headers.Add(header.Key, header.Value);
}
}
HttpResponseMessage responseMessage = null;
try
{
responseMessage = await SharedHttpClient.SendAsync(request).ConfigureAwait(false);
}
catch (Exception ex)
{
string errMsg = "Error sending HTTP request:" + ex.Message;
if (ex.InnerException != null && !string.IsNullOrEmpty(ex.InnerException.Message))
{
errMsg += " " + ex.InnerException.Message;
}
Debug.WriteLine(errMsg);
throw new ForceApiException(errMsg);
}
#if DEBUG
//API usage response header
//e.g. "Sforce-Limit-Info: api-usage=90/15000"
const string SforceLimitInfoHeaderName = "Sforce-Limit-Info";
IEnumerable<string> limitValues = GetHeaderValues(responseMessage.Headers, SforceLimitInfoHeaderName);
if (limitValues != null)
{
Debug.WriteLine(string.Format("{0}: {1}", SforceLimitInfoHeaderName, limitValues.FirstOrDefault() ?? "none"));
}
#endif
if (responseMessage.StatusCode == HttpStatusCode.NoContent)
{
return JsonConvert.DeserializeObject<T>(string.Empty);
}
if (responseMessage.Content != null)
{
try
{
string responseContent = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
if (responseMessage.IsSuccessStatusCode)
{
if (string.IsNullOrEmpty(responseContent))
{
throw new ForceApiException("Response content was empty");
}
return JsonConvert.DeserializeObject<T>(responseContent);
}
else
{
var errors = JsonConvert.DeserializeObject<List<ErrorResponse>>(responseContent);
throw new ForceApiException(string.Format("Salesforce API returned {0}, see Errors for details.", responseMessage.StatusCode.ToString()), errors, responseMessage.StatusCode);
}
}
catch (Exception ex)
{
throw new ForceApiException(string.Format("Error parsing response content: {0}", ex.Message));
}
}
throw new ForceApiException(string.Format("Error processing response: returned {0} for {1}", responseMessage.ReasonPhrase, request.RequestUri.ToString()));
}
/// <summary>
/// Get values for a particular reponse header
/// </summary>
/// <param name="headers">HttpHeaders from the HttpResponseMessage</param>
/// <param name="headerName">Header Name</param>
/// <returns>IEnumerable{string} of header values, if any, Null if none found.</returns>
private IEnumerable<string> GetHeaderValues(HttpHeaders headers, string headerName)
{
if (headers != null)
{
IEnumerable<string> values;
if (headers.TryGetValues(headerName, out values))
{
return values;
}
}
Debug.WriteLine(string.Format("{0} header not found in response", headerName));
return null;
}
/// <summary>
/// Dispose client - only disposes instance HttpClient, if any. Shared static HttpClient is left as-is.
/// </summary>
public void Dispose()
{
//only dispose instance member, if any
if (_httpClient != null)
{
_httpClient.Dispose();
}
}
}
}