Skip to content

Commit 57f2daa

Browse files
Refactored code to use HttpClient (#10)
* Refactored code to use Http client tomake requests * Added a tests for new changes * Readme and version changes * Resolved comemnts
1 parent a34098c commit 57f2daa

7 files changed

Lines changed: 254 additions & 39 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ Modifications done by Naresh Vadala (Questionmark)
2828
* Add public property Headers to RemoteLrs class to add custom headers to request.
2929
* Added optional property to SaveStatementsAsync which takes timestamp parameter to add it to the payload
3030

31+
Modifications done by Naresh Vadala (Questionmark)
32+
* Removed optional property to SaveStatementsAsync which takes timestamp parameter to add it to the payload
33+
* Refactored code to use HttpClient to make request rather WebRequest as it is obsolete
34+
3135
# Parent Project
3236

3337
No new updates in parent project. Last checked: 2nd July, 2022

TinCan/ILRS.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public interface ILrs
2828

2929
Task<StatementLrsResponse> SaveStatementAsync(Statement statement);
3030
Task<StatementLrsResponse> VoidStatementAsync(Guid id, Agent agent);
31-
Task<StatementsResultLrsResponse> SaveStatementsAsync(List<Statement> statements, string timestamp = null);
31+
Task<StatementsResultLrsResponse> SaveStatementsAsync(List<Statement> statements);
3232
Task<StatementLrsResponse> RetrieveStatementAsync(Guid id);
3333
Task<StatementLrsResponse> RetrieveVoidedStatementAsync(Guid id);
3434
Task<StatementsResultLrsResponse> QueryStatementsAsync(StatementsQuery query);

TinCan/IRemoteLrs.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace TinCan
5+
{
6+
public interface IRemoteLrs : ILrs
7+
{
8+
Uri Endpoint { get; set; }
9+
TCAPIVersion Version { get; set; }
10+
string Auth { get; set; }
11+
Dictionary<string, string> Extended { get; set; }
12+
Dictionary<string, string> Headers { get; set; }
13+
bool UseHttpClient { get; set; }
14+
string GetJsonStringFromStatements(List<Statement> statements);
15+
void SetAuth(string username, string password);
16+
}
17+
}

TinCan/LanguageMap.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public override string ToString()
7575

7676
public IEnumerator GetEnumerator()
7777
{
78-
throw new NotImplementedException();
78+
return _map.GetEnumerator();
7979
}
8080
}
8181
}

TinCan/RemoteLRS.cs

Lines changed: 196 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ limitations under the License.
2020
using System.IO;
2121
using System.Linq;
2222
using System.Net;
23+
using System.Net.Http;
2324
using System.Text;
2425
using System.Threading.Tasks;
2526
using System.Web;
@@ -28,18 +29,25 @@ limitations under the License.
2829

2930
namespace TinCan
3031
{
31-
public class RemoteLrs : ILrs
32+
public class RemoteLrs : IRemoteLrs
3233
{
3334
public Uri Endpoint { get; set; }
3435
public TCAPIVersion Version { get; set; }
3536
public string Auth { get; set; }
3637
public Dictionary<string, string> Extended { get; set; } = new Dictionary<string, string>();
3738
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
39+
private HttpClient _httpClient { get; set; }
40+
public bool UseHttpClient { get; set; }
3841

3942
public RemoteLrs()
4043
{
4144
}
4245

46+
public RemoteLrs(HttpClient httpClient)
47+
{
48+
_httpClient = httpClient;
49+
}
50+
4351
public RemoteLrs(Uri endpoint, TCAPIVersion version, string username, string password)
4452
{
4553
Endpoint = endpoint;
@@ -60,19 +68,23 @@ public RemoteLrs(string endpoint, string username, string password) : this(endpo
6068
private class MyHttpRequest
6169
{
6270
public string Method { get; set; }
71+
public HttpMethod HttpMethod { get; set; }
6372
public string Resource { get; set; }
6473
public Dictionary<string, string> QueryParams { get; set; } = new Dictionary<string, string>();
6574
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
6675

6776
public string ContentType { get; set; }
6877
public byte[] Content { get; set; }
78+
public string StringContent { get; set; }
79+
6980
}
7081

7182
private class MyHttpResponse
7283
{
7384
public HttpStatusCode Status { get; }
7485
public string ContentType { get; }
7586
public byte[] Content { get; set; }
87+
public string StringContent { get; set; }
7688
public DateTime LastModified { get; }
7789
public string Etag { get; }
7890
public Exception Ex { get; set; }
@@ -101,6 +113,34 @@ public MyHttpResponse(HttpWebResponse webResp)
101113
Content = ReadFully(stream, (int)webResp.ContentLength);
102114
}
103115
}
116+
117+
public MyHttpResponse(HttpResponseMessage httpResponseMessage)
118+
119+
{
120+
Status = httpResponseMessage.StatusCode;
121+
try
122+
{
123+
if (httpResponseMessage.Headers.Contains("Etag"))
124+
{
125+
Etag = httpResponseMessage.Headers.GetValues("Etag")?.FirstOrDefault()?.ToString();
126+
}
127+
128+
if (httpResponseMessage.Content.Headers.TryGetValues("LastModified", out var values))
129+
{
130+
// Get the Last-Modified header value and parse it to DateTime
131+
var lastModifiedString = values.ToString();
132+
133+
if (DateTime.TryParse(lastModifiedString, out DateTime lastModifiedDate))
134+
{
135+
LastModified = lastModifiedDate;
136+
}
137+
}
138+
}
139+
catch
140+
{
141+
//sometimes will throw an exception, just ignore
142+
}
143+
}
104144
}
105145

106146
private async Task<MyHttpResponse> MakeRequest(MyHttpRequest req)
@@ -158,6 +198,68 @@ private async Task<MyHttpResponse> MakeRequest(MyHttpRequest req)
158198
return resp;
159199
}
160200

201+
private async Task<MyHttpResponse> MakeHttpRequest(MyHttpRequest req)
202+
{
203+
var httpRequestMessage = BuildHttpRequestMessage(req);
204+
205+
try
206+
{
207+
if(_httpClient == null) _httpClient = new HttpClient();
208+
var httpResponseMessage = await _httpClient.SendAsync(httpRequestMessage);
209+
var response = new MyHttpResponse(httpResponseMessage);
210+
response.StringContent = await httpResponseMessage.Content.ReadAsStringAsync();
211+
return response;
212+
213+
}
214+
catch (HttpRequestException ex)
215+
{
216+
var httpResponseMessage = new HttpResponseMessage()
217+
{
218+
StatusCode = ex.StatusCode.Value,
219+
Content = new StringContent($"Request failed: {ex.Message}"),
220+
};
221+
222+
var response = new MyHttpResponse(httpResponseMessage);
223+
response.Ex = ex;
224+
return response;
225+
}
226+
catch (Exception ex)
227+
{
228+
229+
var httpResponseMessage = new HttpResponseMessage()
230+
{
231+
StatusCode = HttpStatusCode.InternalServerError,
232+
Content = new StringContent($"Request failed: {ex.Message}"),
233+
};
234+
235+
var response = new MyHttpResponse(httpResponseMessage);
236+
response.Ex = ex;
237+
return response;
238+
}
239+
}
240+
241+
private HttpRequestMessage BuildHttpRequestMessage(MyHttpRequest req)
242+
{
243+
string url = GetEndpointUrl(req.Resource);
244+
url = AppendQueryStringParamsToUrl(url, req.QueryParams);
245+
246+
var httpRequestMessage = new HttpRequestMessage();
247+
httpRequestMessage.RequestUri = new Uri(url);
248+
httpRequestMessage.Method = req.HttpMethod;
249+
250+
AddHeadersToRequest(req.Headers, httpRequestMessage);
251+
252+
req.ContentType = req.ContentType ?? "application/octet-stream";
253+
254+
if (req.StringContent != null)
255+
{
256+
httpRequestMessage.Content = new StringContent(req.StringContent, UTF8Encoding.UTF8, req.ContentType);
257+
258+
}
259+
260+
return httpRequestMessage;
261+
}
262+
161263
private string GetEndpointUrl(string resource)
162264
{
163265
string url;
@@ -207,6 +309,44 @@ private void AddHeadersToRequest(MyHttpRequest req, HttpWebRequest webReq)
207309
}
208310
}
209311

312+
private void AddHeadersToRequest(Dictionary<string,string> requestHeaders, HttpRequestMessage httpRequestMessage)
313+
{
314+
httpRequestMessage.Headers.Clear();
315+
httpRequestMessage.Headers.Add("X-Experience-API-Version", Version.ToString());
316+
if (Auth != null)
317+
{
318+
httpRequestMessage.Headers.Add("Authorization", Auth);
319+
}
320+
321+
Headers.Concat(requestHeaders);
322+
foreach (var entry in Headers)
323+
{
324+
httpRequestMessage.Headers.Add(entry.Key, entry.Value);
325+
}
326+
}
327+
328+
private StatementsResultLrsResponse BuildStatementsResultLrsResponse(List<Statement> statements, MyHttpResponse responseMessage)
329+
{
330+
var resultLrsResponse = new StatementsResultLrsResponse();
331+
if (!IsSuccessStatusCode(responseMessage.Status))
332+
{
333+
resultLrsResponse.Success = false;
334+
resultLrsResponse.HttpException = responseMessage.Ex;
335+
resultLrsResponse.ErrMsg = responseMessage.StringContent;
336+
return resultLrsResponse;
337+
}
338+
339+
var ids = JArray.Parse(responseMessage.StringContent);
340+
for (var i = 0; i < ids.Count; i++)
341+
{
342+
statements[i].Id = new Guid((string)ids[i]);
343+
}
344+
345+
resultLrsResponse.Success = true;
346+
resultLrsResponse.Content = new StatementsResult(statements);
347+
return resultLrsResponse;
348+
}
349+
210350
/// <summary>
211351
/// See http://www.yoda.arachsys.com/csharp/readbinary.html no license found
212352
///
@@ -515,47 +655,59 @@ public async Task<StatementLrsResponse> VoidStatementAsync(Guid id, Agent agent)
515655
return await SaveStatementAsync(voidStatement);
516656
}
517657

518-
public async Task<StatementsResultLrsResponse> SaveStatementsAsync(List<Statement> statements,
519-
string timestamp = null)
658+
public async Task<StatementsResultLrsResponse> SaveStatementsAsync(List<Statement> statements)
520659
{
521-
var r = new StatementsResultLrsResponse();
522-
523-
var req = new MyHttpRequest
660+
if (UseHttpClient)
524661
{
525-
Resource = "statements",
526-
Method = "POST",
527-
ContentType = "application/json"
528-
};
529-
530-
var jarray = new JArray();
531-
if (!string.IsNullOrEmpty(timestamp))
532-
jarray.Add(JToken.Parse(timestamp + '|'));
533-
foreach (var st in statements)
534-
{
535-
jarray.Add(st.ToJObject(Version));
662+
var req = new MyHttpRequest
663+
{
664+
Resource = "statements",
665+
ContentType = "application/json",
666+
HttpMethod = HttpMethod.Post,
667+
StringContent = GetJsonStringFromStatements(statements)
668+
};
669+
var myHttpResponse = await MakeHttpRequest(req);
670+
return BuildStatementsResultLrsResponse(statements, myHttpResponse);
536671
}
672+
else
673+
{
674+
var r = new StatementsResultLrsResponse();
537675

538-
req.Content = Encoding.UTF8.GetBytes(jarray.ToString());
676+
var req = new MyHttpRequest
677+
{
678+
Resource = "statements",
679+
Method = "POST",
680+
ContentType = "application/json"
681+
};
539682

540-
var res = await MakeRequest(req);
541-
if (!IsSuccessStatusCode(res.Status))
542-
{
543-
r.Success = false;
544-
r.HttpException = res.Ex;
545-
r.SetErrMsgFromBytes(res.Content);
546-
return r;
547-
}
683+
var jarray = new JArray();
684+
foreach (var st in statements)
685+
{
686+
jarray.Add(st.ToJObject(Version));
687+
}
548688

549-
var ids = JArray.Parse(Encoding.UTF8.GetString(res.Content));
550-
for (var i = 0; i < ids.Count; i++)
551-
{
552-
statements[i].Id = new Guid((string)ids[i]);
553-
}
689+
req.Content = Encoding.UTF8.GetBytes(jarray.ToString());
554690

555-
r.Success = true;
556-
r.Content = new StatementsResult(statements);
691+
var res = await MakeRequest(req);
692+
if (!IsSuccessStatusCode(res.Status))
693+
{
694+
r.Success = false;
695+
r.HttpException = res.Ex;
696+
r.SetErrMsgFromBytes(res.Content);
697+
return r;
698+
}
557699

558-
return r;
700+
var ids = JArray.Parse(Encoding.UTF8.GetString(res.Content));
701+
for (var i = 0; i < ids.Count; i++)
702+
{
703+
statements[i].Id = new Guid((string)ids[i]);
704+
}
705+
706+
r.Success = true;
707+
r.Content = new StatementsResult(statements);
708+
709+
return r;
710+
}
559711
}
560712

561713
public async Task<StatementLrsResponse> RetrieveStatementAsync(Guid id)
@@ -875,6 +1027,16 @@ public async Task<LrsResponse> DeleteAgentProfileAsync(AgentProfileDocument prof
8751027
return await DeleteDocument("agents/profile", queryParams);
8761028
}
8771029

1030+
public string GetJsonStringFromStatements(List<Statement> statements)
1031+
{
1032+
var jarray = new JArray();
1033+
foreach (var st in statements)
1034+
{
1035+
jarray.Add(st.ToJObject(Version));
1036+
}
1037+
return jarray.ToString();
1038+
}
1039+
8781040
#endregion
8791041
}
8801042
}

TinCan/TinCan.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFramework>net8.0</TargetFramework>
4-
<Version>1.4.0.0</Version>
4+
<Version>1.5.0.0</Version>
55
<PackageId>TinCanStandard</PackageId>
66
<Product>TinCanStandard</Product>
77
<Title>TinCanCore</Title>
@@ -10,8 +10,8 @@
1010
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
1111
<RepositoryUrl>https://github.com/mayuragarwal-qm/TinCan.NET</RepositoryUrl>
1212
<RepositoryType>git</RepositoryType>
13-
<AssemblyVersion>1.4.0.0</AssemblyVersion>
14-
<FileVersion>1.4.0.0</FileVersion>
13+
<AssemblyVersion>1.5.0.0</AssemblyVersion>
14+
<FileVersion>1.5.0.0</FileVersion>
1515
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
1616
</PropertyGroup>
1717
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

0 commit comments

Comments
 (0)