Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Modifications done by Naresh Vadala (Questionmark)
* Add public property Headers to RemoteLrs class to add custom headers to request.
* Added optional property to SaveStatementsAsync which takes timestamp parameter to add it to the payload

Modifications done by Naresh Vadala (Questionmark)
* Removed optional property to SaveStatementsAsync which takes timestamp parameter to add it to the payload
* Refactored code to use HttpClient to make request rather WebRequest as it is obsolete

# Parent Project

No new updates in parent project. Last checked: 2nd July, 2022
Expand Down
2 changes: 1 addition & 1 deletion TinCan/ILRS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public interface ILrs

Task<StatementLrsResponse> SaveStatementAsync(Statement statement);
Task<StatementLrsResponse> VoidStatementAsync(Guid id, Agent agent);
Task<StatementsResultLrsResponse> SaveStatementsAsync(List<Statement> statements, string timestamp = null);
Task<StatementsResultLrsResponse> SaveStatementsAsync(List<Statement> statements);
Task<StatementLrsResponse> RetrieveStatementAsync(Guid id);
Task<StatementLrsResponse> RetrieveVoidedStatementAsync(Guid id);
Task<StatementsResultLrsResponse> QueryStatementsAsync(StatementsQuery query);
Expand Down
17 changes: 17 additions & 0 deletions TinCan/IRemoteLrs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;

namespace TinCan
{
public interface IRemoteLrs : ILrs
{
Uri Endpoint { get; set; }
TCAPIVersion Version { get; set; }
string Auth { get; set; }
Dictionary<string, string> Extended { get; set; }
Dictionary<string, string> Headers { get; set; }
bool UseHttpClient { get; set; }
string GetJsonStringFromStatements(List<Statement> statements);
void SetAuth(string username, string password);
}
}
2 changes: 1 addition & 1 deletion TinCan/LanguageMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public override string ToString()

public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
return _map.GetEnumerator();
}
}
}
230 changes: 196 additions & 34 deletions TinCan/RemoteLRS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ limitations under the License.
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
Expand All @@ -28,18 +29,25 @@ limitations under the License.

namespace TinCan
{
public class RemoteLrs : ILrs
public class RemoteLrs : IRemoteLrs
{
public Uri Endpoint { get; set; }
public TCAPIVersion Version { get; set; }
public string Auth { get; set; }
public Dictionary<string, string> Extended { get; set; } = new Dictionary<string, string>();
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
private HttpClient _httpClient { get; set; }
public bool UseHttpClient { get; set; }

public RemoteLrs()
{
}

public RemoteLrs(HttpClient httpClient)
{
_httpClient = httpClient;
}

public RemoteLrs(Uri endpoint, TCAPIVersion version, string username, string password)
{
Endpoint = endpoint;
Expand All @@ -60,19 +68,23 @@ public RemoteLrs(string endpoint, string username, string password) : this(endpo
private class MyHttpRequest
{
public string Method { get; set; }
public HttpMethod HttpMethod { get; set; }
public string Resource { get; set; }
public Dictionary<string, string> QueryParams { get; set; } = new Dictionary<string, string>();
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();

public string ContentType { get; set; }
public byte[] Content { get; set; }
public string StringContent { get; set; }

}

private class MyHttpResponse
{
public HttpStatusCode Status { get; }
public string ContentType { get; }
public byte[] Content { get; set; }
public string StringContent { get; set; }
public DateTime LastModified { get; }
public string Etag { get; }
public Exception Ex { get; set; }
Expand Down Expand Up @@ -101,6 +113,34 @@ public MyHttpResponse(HttpWebResponse webResp)
Content = ReadFully(stream, (int)webResp.ContentLength);
}
}

public MyHttpResponse(HttpResponseMessage httpResponseMessage)

{
Status = httpResponseMessage.StatusCode;
try
{
if (httpResponseMessage.Headers.Contains("Etag"))
{
Etag = httpResponseMessage.Headers.GetValues("Etag")?.FirstOrDefault()?.ToString();
}

if (httpResponseMessage.Content.Headers.TryGetValues("LastModified", out var values))
{
// Get the Last-Modified header value and parse it to DateTime
var lastModifiedString = values.ToString();

if (DateTime.TryParse(lastModifiedString, out DateTime lastModifiedDate))
{
LastModified = lastModifiedDate;
}
}
}
catch
{
//sometimes will throw an exception, just ignore
Comment thread
naresh-vadala-lrn marked this conversation as resolved.
}
}
}

private async Task<MyHttpResponse> MakeRequest(MyHttpRequest req)
Expand Down Expand Up @@ -158,6 +198,68 @@ private async Task<MyHttpResponse> MakeRequest(MyHttpRequest req)
return resp;
}

private async Task<MyHttpResponse> MakeHttpRequest(MyHttpRequest req)
{
var httpRequestMessage = BuildHttpRequestMessage(req);

try
{
if(_httpClient == null) _httpClient = new HttpClient();
var httpResponseMessage = await _httpClient.SendAsync(httpRequestMessage);
var response = new MyHttpResponse(httpResponseMessage);
response.StringContent = await httpResponseMessage.Content.ReadAsStringAsync();
return response;

}
catch (HttpRequestException ex)
{
var httpResponseMessage = new HttpResponseMessage()
{
StatusCode = ex.StatusCode.Value,
Content = new StringContent($"Request failed: {ex.Message}"),
};

var response = new MyHttpResponse(httpResponseMessage);
response.Ex = ex;
return response;
}
catch (Exception ex)
{

var httpResponseMessage = new HttpResponseMessage()
{
StatusCode = HttpStatusCode.InternalServerError,
Content = new StringContent($"Request failed: {ex.Message}"),
};

var response = new MyHttpResponse(httpResponseMessage);
response.Ex = ex;
return response;
}
}

private HttpRequestMessage BuildHttpRequestMessage(MyHttpRequest req)
{
string url = GetEndpointUrl(req.Resource);
url = AppendQueryStringParamsToUrl(url, req.QueryParams);

var httpRequestMessage = new HttpRequestMessage();
httpRequestMessage.RequestUri = new Uri(url);
httpRequestMessage.Method = req.HttpMethod;

AddHeadersToRequest(req.Headers, httpRequestMessage);

req.ContentType = req.ContentType ?? "application/octet-stream";

if (req.StringContent != null)
{
httpRequestMessage.Content = new StringContent(req.StringContent, UTF8Encoding.UTF8, req.ContentType);

}

return httpRequestMessage;
}

private string GetEndpointUrl(string resource)
{
string url;
Expand Down Expand Up @@ -207,6 +309,44 @@ private void AddHeadersToRequest(MyHttpRequest req, HttpWebRequest webReq)
}
}

private void AddHeadersToRequest(Dictionary<string,string> requestHeaders, HttpRequestMessage httpRequestMessage)
{
httpRequestMessage.Headers.Clear();
httpRequestMessage.Headers.Add("X-Experience-API-Version", Version.ToString());
if (Auth != null)
{
httpRequestMessage.Headers.Add("Authorization", Auth);
}

Headers.Concat(requestHeaders);
foreach (var entry in Headers)
{
httpRequestMessage.Headers.Add(entry.Key, entry.Value);
}
}

private StatementsResultLrsResponse BuildStatementsResultLrsResponse(List<Statement> statements, MyHttpResponse responseMessage)
{
var resultLrsResponse = new StatementsResultLrsResponse();
if (!IsSuccessStatusCode(responseMessage.Status))
{
resultLrsResponse.Success = false;
resultLrsResponse.HttpException = responseMessage.Ex;
resultLrsResponse.ErrMsg = responseMessage.StringContent;
return resultLrsResponse;
}

var ids = JArray.Parse(responseMessage.StringContent);
for (var i = 0; i < ids.Count; i++)
{
statements[i].Id = new Guid((string)ids[i]);
}

resultLrsResponse.Success = true;
resultLrsResponse.Content = new StatementsResult(statements);
return resultLrsResponse;
}

/// <summary>
/// See http://www.yoda.arachsys.com/csharp/readbinary.html no license found
///
Expand Down Expand Up @@ -515,47 +655,59 @@ public async Task<StatementLrsResponse> VoidStatementAsync(Guid id, Agent agent)
return await SaveStatementAsync(voidStatement);
}

public async Task<StatementsResultLrsResponse> SaveStatementsAsync(List<Statement> statements,
string timestamp = null)
public async Task<StatementsResultLrsResponse> SaveStatementsAsync(List<Statement> statements)
{
var r = new StatementsResultLrsResponse();

var req = new MyHttpRequest
if (UseHttpClient)
{
Resource = "statements",
Method = "POST",
ContentType = "application/json"
};

var jarray = new JArray();
if (!string.IsNullOrEmpty(timestamp))
jarray.Add(JToken.Parse(timestamp + '|'));
foreach (var st in statements)
{
jarray.Add(st.ToJObject(Version));
var req = new MyHttpRequest
{
Resource = "statements",
ContentType = "application/json",
HttpMethod = HttpMethod.Post,
StringContent = GetJsonStringFromStatements(statements)
};
var myHttpResponse = await MakeHttpRequest(req);
return BuildStatementsResultLrsResponse(statements, myHttpResponse);
}
else
{
var r = new StatementsResultLrsResponse();

req.Content = Encoding.UTF8.GetBytes(jarray.ToString());
var req = new MyHttpRequest
{
Resource = "statements",
Method = "POST",
ContentType = "application/json"
};

var res = await MakeRequest(req);
if (!IsSuccessStatusCode(res.Status))
{
r.Success = false;
r.HttpException = res.Ex;
r.SetErrMsgFromBytes(res.Content);
return r;
}
var jarray = new JArray();
foreach (var st in statements)
{
jarray.Add(st.ToJObject(Version));
}

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

r.Success = true;
r.Content = new StatementsResult(statements);
var res = await MakeRequest(req);
if (!IsSuccessStatusCode(res.Status))
{
r.Success = false;
r.HttpException = res.Ex;
r.SetErrMsgFromBytes(res.Content);
return r;
}

return r;
var ids = JArray.Parse(Encoding.UTF8.GetString(res.Content));
for (var i = 0; i < ids.Count; i++)
{
statements[i].Id = new Guid((string)ids[i]);
}

r.Success = true;
r.Content = new StatementsResult(statements);

return r;
}
}

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

public string GetJsonStringFromStatements(List<Statement> statements)
{
var jarray = new JArray();
foreach (var st in statements)
{
jarray.Add(st.ToJObject(Version));
}
return jarray.ToString();
}

#endregion
}
}
6 changes: 3 additions & 3 deletions TinCan/TinCan.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>1.4.0.0</Version>
<Version>1.5.0.0</Version>
<PackageId>TinCanStandard</PackageId>
<Product>TinCanStandard</Product>
<Title>TinCanCore</Title>
Expand All @@ -10,8 +10,8 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<RepositoryUrl>https://github.com/mayuragarwal-qm/TinCan.NET</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<AssemblyVersion>1.4.0.0</AssemblyVersion>
<FileVersion>1.4.0.0</FileVersion>
<AssemblyVersion>1.5.0.0</AssemblyVersion>
<FileVersion>1.5.0.0</FileVersion>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
Expand Down
Loading