-
Notifications
You must be signed in to change notification settings - Fork 5
/
CWHttpClient.cs
244 lines (220 loc) · 9.52 KB
/
CWHttpClient.cs
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
using ConnectWise.Http.Json;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConnectWise.Http
{
/// <summary>
/// Main HTTP Client used to interact with ConnectWise Manage API.
/// </summary>
public class CWHttpClient : IDisposable
{
/// <summary>
/// Information about your ConnectWise Manage Installation. This is populated the first time you make a request with CWHttpClient.
/// </summary>
public CWCompanyInfo Info { get; private set; }
private readonly string companyName;
private readonly string clientId;
private readonly string domain;
private readonly string cookieValue;
private readonly string version;
private readonly string accept;
//private string accept = "application/vnd.connectwise.com+json; version=3.0.0"; // This accept string was causing bugs on some endpoints
private readonly AuthenticationHeaderValue auth;
private readonly HttpClient client;
private readonly bool ownsClient;
/// <summary>
/// HttpClient used to make requests to a ConnectWise Manage API. This constructor will take a pre-instantiated System.Net.Http.HttpClient. Use this if you will be using the provided HttpClient to make requests to other APIs as well, or if you want to set different HttpClient settings. CWHttpClient will not make any changes to your provided HttpClient's default settings.
/// </summary>
public CWHttpClient(CWApiSettings settings, HttpClient client = null)
{
companyName = settings.CompanyName;
clientId = settings.ClientId;
domain = settings.Domain.TrimEnd('/');
cookieValue = settings.CookieValue;
version = settings.UriVersion;
accept = settings.Accept;
auth = settings.Authentication.BuildHeader(companyName);
// Passthrough
if (client != null)
this.client = client;
else
{
ownsClient = true;
this.client = new HttpClient();
}
}
/// <summary>
/// Method used to send a request to the configured Manage API.
/// </summary>
/// <param name="request">CWRequest object. Can be pre-build with various included modules or can be constructed manually.</param>
/// <param name="cancelToken">Cancellation Token if you would like to be able to cancel mid-request.</param>
/// <returns>A generic CWResponse object with no deserialization.</returns>
public async Task<CWResponse> SendAsync(CWRequest request, CancellationToken cancelToken = default)
{
// Check if we have Company Info
if (Info == null)
{
// Need Company Info
var infoResponse = await getCompanyInfoAsync(cancelToken).ConfigureAwait(false);
if (!infoResponse.IsSuccessful)
{
return infoResponse;
}
}
// Build Request
var httpRequest = buildRequest(request);
// Make Request
HttpResponseMessage response = null;
try
{
response = await client.SendAsync(httpRequest, cancelToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
return new CWResponse("Request cancelled.");
}
catch (Exception e)
{
return new CWResponse(e.ToString());
}
// Check Response
if (response != null)
{
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return new CWResponse(response, content);
}
return new CWResponse("There was an error making the request to the CW Manage API.");
}
/// <summary>
/// Method used to send a request to the configured Manage API.
/// </summary>
/// <typeparam name="T">The deserialization type. Must be a class.</typeparam>
/// <param name="request">CWRequest object. Can be pre-build with various included modules or can be constructed manually.</param>
/// <param name="cancelToken">Cancellation Token if you would like to be able to cancel mid-request.</param>
/// <returns>A CWResponse object that will attempt deserialization to the specified type.</returns>
public async Task<CWResponse<T>> SendAsync<T>(CWRequest request, CancellationToken cancelToken = default)
where T : class
{
// Check if we have Company Info
if (Info == null)
{
// Need Company Info
var infoResponse = await getCompanyInfoAsync(cancelToken).ConfigureAwait(false);
if (!infoResponse.IsSuccessful)
{
return new CWResponse<T>(infoResponse.Response, infoResponse.Result, false);
}
}
// Build Request
var httpRequest = buildRequest(request);
// Make Request
HttpResponseMessage response = null;
try
{
response = await client.SendAsync(httpRequest, cancelToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
return new CWResponse<T>("Request cancelled.");
}
catch (Exception e)
{
return new CWResponse<T>(e.ToString());
}
// Check Response
if (response != null)
{
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return new CWResponse<T>(response, content);
}
return new CWResponse<T>("There was an error making the request to the CW Manage API.");
}
private HttpRequestMessage buildRequest(CWRequest request)
{
// Build Request
var httpRequest = new HttpRequestMessage
{
RequestUri = new Uri($"{domain}/{Info.Codebase}/apis/{version}/{request.Endpoint}"),
};
// Content
if (request.Content != null)
httpRequest.Content = request.Content;
// Headers
httpRequest.Headers.Clear();
httpRequest.Headers.TryAddWithoutValidation("Accept", accept);
httpRequest.Headers.TryAddWithoutValidation("clientId", clientId);
httpRequest.Headers.Authorization = auth;
if (!string.IsNullOrWhiteSpace(cookieValue))
httpRequest.Headers.TryAddWithoutValidation("Cookie", string.Concat("cw-app-id=", cookieValue));
// Method
switch (request.Method)
{
case CWHttpMethod.Post:
httpRequest.Method = HttpMethod.Post;
break;
case CWHttpMethod.Put:
httpRequest.Method = HttpMethod.Put;
break;
case CWHttpMethod.Patch:
httpRequest.Method = new HttpMethod("PATCH");
break;
case CWHttpMethod.Delete:
httpRequest.Method = HttpMethod.Delete;
break;
default:
httpRequest.Method = HttpMethod.Get;
break;
}
return httpRequest;
}
private async Task<CWResponse> getCompanyInfoAsync(CancellationToken cancelToken)
{
// Build request
var request = new HttpRequestMessage
{
RequestUri = new Uri($"{domain}/login/companyInfo/{companyName}")
};
request.Headers.Clear();
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Make request
HttpResponseMessage response;
try
{
response = await client.SendAsync(request, cancelToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
return new CWResponse("Request cancelled.");
}
catch (Exception e)
{
return new CWResponse(e.ToString());
}
// Check response
if (response != null)
{
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
// Deserialize Company Info
Info = JsonConvert.DeserializeObject<CWCompanyInfo>(content, CWJsonSerializer.PrivateSetters);
return new CWResponse();
}
return new CWResponse(response, content);
}
return new CWResponse("Unable to pull CW Company Information required to form future CWRequests.");
}
public void Dispose()
{
if (this.ownsClient)
this.client?.Dispose();
}
}
}