diff --git a/TinCan/ILRS.cs b/TinCan/ILRS.cs index 9b9c176..7d145f2 100644 --- a/TinCan/ILRS.cs +++ b/TinCan/ILRS.cs @@ -15,6 +15,7 @@ limitations under the License. */ using System; using System.Collections.Generic; +using System.Threading.Tasks; using TinCan.Documents; using TinCan.LRSResponses; @@ -22,30 +23,30 @@ namespace TinCan { public interface ILRS { - AboutLRSResponse About(); - - StatementLRSResponse SaveStatement(Statement statement); - StatementLRSResponse VoidStatement(Guid id, Agent agent); - StatementsResultLRSResponse SaveStatements(List statements); - StatementLRSResponse RetrieveStatement(Guid id); - StatementLRSResponse RetrieveVoidedStatement(Guid id); - StatementsResultLRSResponse QueryStatements(StatementsQuery query); - StatementsResultLRSResponse MoreStatements(StatementsResult result); - - ProfileKeysLRSResponse RetrieveStateIds(Activity activity, Agent agent, Nullable registration = null); - StateLRSResponse RetrieveState(String id, Activity activity, Agent agent, Nullable registration = null); - LRSResponse SaveState(StateDocument state); - LRSResponse DeleteState(StateDocument state); - LRSResponse ClearState(Activity activity, Agent agent, Nullable registration = null); - - ProfileKeysLRSResponse RetrieveActivityProfileIds(Activity activity); - ActivityProfileLRSResponse RetrieveActivityProfile(String id, Activity activity); - LRSResponse SaveActivityProfile(ActivityProfileDocument profile); - LRSResponse DeleteActivityProfile(ActivityProfileDocument profile); - - ProfileKeysLRSResponse RetrieveAgentProfileIds(Agent agent); - AgentProfileLRSResponse RetrieveAgentProfile(String id, Agent agent); - LRSResponse SaveAgentProfile(AgentProfileDocument profile); - LRSResponse DeleteAgentProfile(AgentProfileDocument profile); + Task About(); + + Task SaveStatement(Statement statement); + Task VoidStatement(Guid id, Agent agent); + Task SaveStatements(List statements); + Task RetrieveStatement(Guid id); + Task RetrieveVoidedStatement(Guid id); + Task QueryStatements(StatementsQuery query); + Task MoreStatements(StatementsResult result); + + Task RetrieveStateIds(Activity activity, Agent agent, Nullable registration = null); + Task RetrieveState(String id, Activity activity, Agent agent, Nullable registration = null); + Task SaveState(StateDocument state); + Task DeleteState(StateDocument state); + Task ClearState(Activity activity, Agent agent, Nullable registration = null); + + Task RetrieveActivityProfileIds(Activity activity); + Task RetrieveActivityProfile(String id, Activity activity); + Task SaveActivityProfile(ActivityProfileDocument profile); + Task DeleteActivityProfile(ActivityProfileDocument profile); + + Task RetrieveAgentProfileIds(Agent agent); + Task RetrieveAgentProfile(String id, Agent agent); + Task SaveAgentProfile(AgentProfileDocument profile); + Task DeleteAgentProfile(AgentProfileDocument profile); } } diff --git a/TinCan/LRSHttpRequest.cs b/TinCan/LRSHttpRequest.cs new file mode 100644 index 0000000..e994236 --- /dev/null +++ b/TinCan/LRSHttpRequest.cs @@ -0,0 +1,75 @@ +/* + Copyright 2014-2017 Rustici Software + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using System.Collections.Generic; +using System.Text; +using System.Net.Http; + +namespace TinCan +{ + /// + /// LRS HTTP request. + /// + sealed class LRSHttpRequest + { + /// + /// Gets or sets the method. + /// + /// The method. + public HttpMethod Method { get; internal set; } + + /// + /// Gets or sets the resource. + /// + /// The resource. + public string Resource { get; internal set; } + + /// + /// Gets or sets the query parameters. + /// + /// The query parameters. + public Dictionary QueryParams { get; internal set; } + + /// + /// Gets or sets the headers. + /// + /// The headers. + public Dictionary Headers { get; internal set; } + + /// + /// Gets or sets the type of the content. + /// + /// The type of the content. + public string ContentType { get; internal set; } + + /// + /// Gets or sets the content. + /// + /// The content. + public byte[] Content { get; internal set; } + + /// + /// Returns a that represents the current . + /// + /// A that represents the current . + public override string ToString() + { + return string.Format( + "[MyHTTPRequest: method={0}, resource={1}, queryParams={2}, headers={3}, contentType={4}, content={5}]", + Method, Resource, string.Join(";", QueryParams), Headers, ContentType, Encoding.UTF8.GetString(Content, 0, Content.Length)); + } + } +} diff --git a/TinCan/LRSHttpResponse.cs b/TinCan/LRSHttpResponse.cs new file mode 100644 index 0000000..f2e844c --- /dev/null +++ b/TinCan/LRSHttpResponse.cs @@ -0,0 +1,111 @@ +/* + Copyright 2014-2017 Rustici Software + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using System; +using System.Net; +using System.Net.Http; +using System.Text; + +namespace TinCan +{ + /// + /// LRS HTTP response. + /// + sealed class LRSHttpResponse + { + /// + /// Initializes a new instance of the class. + /// + public LRSHttpResponse() { } + + /// + /// Initializes a new instance of the class. + /// + /// Web resp. + public LRSHttpResponse(HttpResponseMessage response) + { + if (response == null) + { + throw new ArgumentNullException(nameof(response)); + } + + Status = response.StatusCode; + + if (response.Content?.Headers?.ContentType != null) + { + ContentType = response.Content.Headers.ContentType.ToString(); + } + + if (response.Headers.ETag != null) + { + Etag = response.Headers.ETag.ToString(); + } + + if (response.Content?.Headers?.LastModified != null) + { + LastModified = response.Content.Headers.LastModified.Value.LocalDateTime; + } + + Content = response.Content.ReadAsByteArrayAsync().Result; + } + + /// + /// Gets or sets the status. + /// + /// The status. + public HttpStatusCode Status { get; internal set; } + + /// + /// Gets or sets the type of the content. + /// + /// The type of the content. + public string ContentType { get; internal set; } + + /// + /// Gets or sets the content. + /// + /// The content. + public byte[] Content { get; internal set; } + + /// + /// Gets or sets the last modified. + /// + /// The last modified. + public DateTime LastModified { get; internal set; } + + /// + /// Gets or sets the etag. + /// + /// The etag. + public string Etag { get; internal set; } + + /// + /// Gets or sets the ex. + /// + /// The ex. + public Exception Exception { get; internal set; } + + /// + /// Returns a that represents the current . + /// + /// A that represents the current . + public override string ToString() + { + return string.Format("[MyHTTPResponse: Status={0}, ContentType={1}, Content={2}, LastModified={3}, Etag={4}, Exception={5}]", + Status, ContentType, Encoding.UTF8.GetString(Content, 0, Content.Length), LastModified, Etag, Exception); + } + } +} diff --git a/TinCan/RemoteLRS.cs b/TinCan/RemoteLRS.cs index af04955..85f4769 100644 --- a/TinCan/RemoteLRS.cs +++ b/TinCan/RemoteLRS.cs @@ -17,8 +17,10 @@ limitations under the License. using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; using System.Text; -using System.Web; +using System.Threading.Tasks; using Newtonsoft.Json.Linq; using TinCan.Documents; using TinCan.LRSResponses; @@ -27,6 +29,7 @@ namespace TinCan { public class RemoteLRS : ILRS { + readonly HttpClient client = new HttpClient(); public Uri endpoint { get; set; } public TCAPIVersion version { get; set; } public String auth { get; set; } @@ -47,132 +50,108 @@ public RemoteLRS(Uri endpoint, TCAPIVersion version, String username, String pas public RemoteLRS(String endpoint, TCAPIVersion version, String username, String password) : this(new Uri(endpoint), version, username, password) { } public RemoteLRS(String endpoint, String username, String password) : this(endpoint, TCAPIVersion.latest(), username, password) { } - private class MyHTTPRequest + async Task MakeRequest(LRSHttpRequest req) { - public String method { get; set; } - public String resource { get; set; } - public Dictionary queryParams { get; set; } - public Dictionary headers { get; set; } - public String contentType { get; set; } - public byte[] content { get; set; } - } - - private class MyHTTPResponse - { - public HttpStatusCode status { get; set; } - public String contentType { get; set; } - public byte[] content { get; set; } - public DateTime lastModified { get; set; } - public String etag { get; set; } - public Exception ex { get; set; } - - public MyHTTPResponse() { } - public MyHTTPResponse(HttpWebResponse webResp) - { - status = webResp.StatusCode; - contentType = webResp.ContentType; - etag = webResp.Headers.Get("Etag"); - lastModified = webResp.LastModified; - - using (var stream = webResp.GetResponseStream()) - { - content = ReadFully(stream, (int)webResp.ContentLength); - } + if (req == null) + { + throw new ArgumentNullException(nameof(req)); } - } - private MyHTTPResponse MakeSyncRequest(MyHTTPRequest req) - { - String url; - if (req.resource.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)) + string url; + + if (req.Resource.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)) { - url = req.resource; + url = req.Resource; } else { url = endpoint.ToString(); - if (! url.EndsWith("/") && ! req.resource.StartsWith("/")) { + + if (!url.EndsWith("/", StringComparison.Ordinal) && !req.Resource.StartsWith("/", StringComparison.Ordinal)) + { url += "/"; } - url += req.resource; + + url += req.Resource; } - if (req.queryParams != null) + if (req.QueryParams != null) { - String qs = ""; - foreach (KeyValuePair entry in req.queryParams) + var qs = string.Empty; + + foreach (var entry in req.QueryParams) { - if (qs != "") + if (qs != string.Empty) { qs += "&"; } - qs += HttpUtility.UrlEncode(entry.Key) + "=" + HttpUtility.UrlEncode(entry.Value); + + qs += string.Format("{0}={1}", WebUtility.UrlEncode(entry.Key), WebUtility.UrlEncode(entry.Value)); } - if (qs != "") + + if (qs != string.Empty) { url += "?" + qs; } } // TODO: handle special properties we recognize, such as content type, modified since, etc. - var webReq = (HttpWebRequest)WebRequest.Create(url); - webReq.Method = req.method; + var webReq = new HttpRequestMessage(req.Method, new Uri(url)); + + client.DefaultRequestHeaders.Clear(); + client.DefaultRequestHeaders.Add("X-Experience-API-Version", version.ToString()); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(req.ContentType ?? "application/content-stream")); - webReq.Headers.Add("X-Experience-API-Version", version.ToString()); if (auth != null) { - webReq.Headers.Add("Authorization", auth); + client.DefaultRequestHeaders.Add("Authorization", auth); } - if (req.headers != null) + if (req.Headers != null) { - foreach (KeyValuePair entry in req.headers) + foreach (var entry in req.Headers) { - webReq.Headers.Add(entry.Key, entry.Value); + client.DefaultRequestHeaders.Add(entry.Key, entry.Value); } } - if (req.contentType != null) + if (req.Content != null) { - webReq.ContentType = req.contentType; - } - else - { - webReq.ContentType = "application/octet-stream"; - } + webReq.Content = new ByteArrayContent(req.Content); + webReq.Content.Headers.Add("Content-Length", req.Content.Length.ToString()); - if (req.content != null) - { - webReq.ContentLength = req.content.Length; - using (var stream = webReq.GetRequestStream()) + if (!string.IsNullOrWhiteSpace(req.ContentType)) { - stream.Write(req.content, 0, req.content.Length); + webReq.Content.Headers.Add("Content-Type", req.ContentType); + } + else + { + webReq.Content.Headers.Add("Content-Type", "application/json"); } } - MyHTTPResponse resp; + LRSHttpResponse resp; try { - using (var webResp = (HttpWebResponse)webReq.GetResponse()) - { - resp = new MyHTTPResponse(webResp); - } + var theResponse = await client.SendAsync(webReq).ConfigureAwait(false); + resp = new LRSHttpResponse(theResponse); } catch (WebException ex) { + resp = new LRSHttpResponse(); + if (ex.Response != null) { - using (var webResp = (HttpWebResponse)ex.Response) + using (var stream = ex.Response.GetResponseStream()) { - resp = new MyHTTPResponse(webResp); + resp.Content = ReadFully(stream, (int)ex.Response.ContentLength); } } else { - resp = new MyHTTPResponse(); - resp.content = Encoding.UTF8.GetBytes("Web exception without '.Response'"); + resp.Content = Encoding.UTF8.GetBytes("Web exception without '.Response'"); } - resp.ex = ex; + resp.Exception = ex; } return resp; @@ -231,49 +210,56 @@ private static byte[] ReadFully(Stream stream, int initialLength) return ret; } - private MyHTTPResponse GetDocument(String resource, Dictionary queryParams, Document document) + private async Task GetDocument(String resource, Dictionary queryParams, Document document) { - var req = new MyHTTPRequest(); - req.method = "GET"; - req.resource = resource; - req.queryParams = queryParams; + var req = new LRSHttpRequest + { + Method = HttpMethod.Get, + Resource = resource, + QueryParams = queryParams + }; - var res = MakeSyncRequest(req); - if (res.status == HttpStatusCode.OK) + var res = await MakeRequest(req); + + if (res.Status == HttpStatusCode.OK) { - document.content = res.content; - document.contentType = res.contentType; - document.timestamp = res.lastModified; - document.etag = res.etag; + document.content = res.Content; + document.contentType = res.ContentType; + document.timestamp = res.LastModified; + document.etag = res.Etag; } return res; } - private ProfileKeysLRSResponse GetProfileKeys(String resource, Dictionary queryParams) + private async Task GetProfileKeys(String resource, Dictionary queryParams) { var r = new ProfileKeysLRSResponse(); - var req = new MyHTTPRequest(); - req.method = "GET"; - req.resource = resource; - req.queryParams = queryParams; + var req = new LRSHttpRequest + { + Method = HttpMethod.Get, + Resource = resource, + QueryParams = queryParams + }; - var res = MakeSyncRequest(req); - if (res.status != HttpStatusCode.OK) + var res = await MakeRequest(req); + if (res.Status != HttpStatusCode.OK) { r.success = false; - r.httpException = res.ex; - r.SetErrMsgFromBytes(res.content); + r.httpException = res.Exception; + r.SetErrMsgFromBytes(res.Content); return r; } r.success = true; - var keys = JArray.Parse(Encoding.UTF8.GetString(res.content)); - if (keys.Count > 0) { + var keys = JArray.Parse(Encoding.UTF8.GetString(res.Content)); + if (keys.Count > 0) + { r.content = new List(); - foreach (JToken key in keys) { + foreach (JToken key in keys) + { r.content.Add((String)key); } } @@ -281,23 +267,25 @@ private ProfileKeysLRSResponse GetProfileKeys(String resource, Dictionary queryParams, Document document) + private async Task SaveDocument(String resource, Dictionary queryParams, Document document) { var r = new LRSResponse(); - var req = new MyHTTPRequest(); - req.method = "PUT"; - req.resource = resource; - req.queryParams = queryParams; - req.contentType = document.contentType; - req.content = document.content; + var req = new LRSHttpRequest + { + Method = HttpMethod.Put, + Resource = resource, + QueryParams = queryParams, + ContentType = document.contentType, + Content = document.content + }; - var res = MakeSyncRequest(req); - if (res.status != HttpStatusCode.NoContent) + var res = await MakeRequest(req); + if (res.Status != HttpStatusCode.NoContent) { r.success = false; - r.httpException = res.ex; - r.SetErrMsgFromBytes(res.content); + r.httpException = res.Exception; + r.SetErrMsgFromBytes(res.Content); return r; } @@ -306,21 +294,23 @@ private LRSResponse SaveDocument(String resource, Dictionary que return r; } - private LRSResponse DeleteDocument(String resource, Dictionary queryParams) + private async Task DeleteDocument(String resource, Dictionary queryParams) { var r = new LRSResponse(); - var req = new MyHTTPRequest(); - req.method = "DELETE"; - req.resource = resource; - req.queryParams = queryParams; + var req = new LRSHttpRequest + { + Method = HttpMethod.Delete, + Resource = resource, + QueryParams = queryParams + }; - var res = MakeSyncRequest(req); - if (res.status != HttpStatusCode.NoContent) + var res = await MakeRequest(req); + if (res.Status != HttpStatusCode.NoContent) { r.success = false; - r.httpException = res.ex; - r.SetErrMsgFromBytes(res.content); + r.httpException = res.Exception; + r.SetErrMsgFromBytes(res.Content); return r; } @@ -329,93 +319,100 @@ private LRSResponse DeleteDocument(String resource, Dictionary q return r; } - private StatementLRSResponse GetStatement(Dictionary queryParams) + private async Task GetStatement(Dictionary queryParams) { var r = new StatementLRSResponse(); - var req = new MyHTTPRequest(); - req.method = "GET"; - req.resource = "statements"; - req.queryParams = queryParams; + var req = new LRSHttpRequest + { + Method = HttpMethod.Get, + Resource = "statements", + QueryParams = queryParams + }; - var res = MakeSyncRequest(req); - if (res.status != HttpStatusCode.OK) + var res = await MakeRequest(req); + if (res.Status != HttpStatusCode.OK) { r.success = false; - r.httpException = res.ex; - r.SetErrMsgFromBytes(res.content); + r.httpException = res.Exception; + r.SetErrMsgFromBytes(res.Content); return r; } r.success = true; - r.content = new Statement(new Json.StringOfJSON(Encoding.UTF8.GetString(res.content))); + r.content = new Statement(new Json.StringOfJSON(Encoding.UTF8.GetString(res.Content))); return r; } - public AboutLRSResponse About() + public async Task About() { var r = new AboutLRSResponse(); - var req = new MyHTTPRequest(); - req.method = "GET"; - req.resource = "about"; + var req = new LRSHttpRequest + { + Method = HttpMethod.Get, + Resource = "about" + }; - var res = MakeSyncRequest(req); - if (res.status != HttpStatusCode.OK) + var res = await MakeRequest(req); + if (res.Status != HttpStatusCode.OK) { r.success = false; - r.httpException = res.ex; - r.SetErrMsgFromBytes(res.content); + r.httpException = res.Exception; + r.SetErrMsgFromBytes(res.Content); return r; } r.success = true; - r.content = new About(Encoding.UTF8.GetString(res.content)); + r.content = new About(Encoding.UTF8.GetString(res.Content)); return r; } - public StatementLRSResponse SaveStatement(Statement statement) + public async Task SaveStatement(Statement statement) { var r = new StatementLRSResponse(); - var req = new MyHTTPRequest(); - req.queryParams = new Dictionary(); - req.resource = "statements"; + var req = new LRSHttpRequest + { + QueryParams = new Dictionary(), + Resource = "statements" + }; if (statement.id == null) { - req.method = "POST"; + req.Method = HttpMethod.Post; } else { - req.method = "PUT"; - req.queryParams.Add("statementId", statement.id.ToString()); + req.Method = HttpMethod.Put; + req.QueryParams.Add("statementId", statement.id.ToString()); } - req.contentType = "application/json"; - req.content = Encoding.UTF8.GetBytes(statement.ToJSON(version)); + req.ContentType = "application/json"; + req.Content = Encoding.UTF8.GetBytes(statement.ToJSON(version)); - var res = MakeSyncRequest(req); + var res = await MakeRequest(req); if (statement.id == null) { - if (res.status != HttpStatusCode.OK) + if (res.Status != HttpStatusCode.OK) { r.success = false; - r.httpException = res.ex; - r.SetErrMsgFromBytes(res.content); + r.httpException = res.Exception; + r.SetErrMsgFromBytes(res.Content); return r; } - var ids = JArray.Parse(Encoding.UTF8.GetString(res.content)); + var ids = JArray.Parse(Encoding.UTF8.GetString(res.Content)); statement.id = new Guid((String)ids[0]); } - else { - if (res.status != HttpStatusCode.NoContent) + else + { + if (res.Status != HttpStatusCode.NoContent) { r.success = false; - r.httpException = res.ex; - r.SetErrMsgFromBytes(res.content); + r.httpException = res.Exception; + r.SetErrMsgFromBytes(res.Content); return r; } } @@ -425,7 +422,7 @@ public StatementLRSResponse SaveStatement(Statement statement) return r; } - public StatementLRSResponse VoidStatement(Guid id, Agent agent) + public async Task VoidStatement(Guid id, Agent agent) { var voidStatement = new Statement { @@ -439,34 +436,36 @@ public StatementLRSResponse VoidStatement(Guid id, Agent agent) }; voidStatement.verb.display.Add("en-US", "voided"); - return SaveStatement(voidStatement); + return await SaveStatement(voidStatement); } - public StatementsResultLRSResponse SaveStatements(List statements) + public async Task SaveStatements(List statements) { var r = new StatementsResultLRSResponse(); - var req = new MyHTTPRequest(); - req.resource = "statements"; - req.method = "POST"; - req.contentType = "application/json"; + var req = new LRSHttpRequest + { + Resource = "statements", + Method = HttpMethod.Post, + ContentType = "application/json" + }; var jarray = new JArray(); foreach (Statement st in statements) { jarray.Add(st.ToJObject(version)); } - req.content = Encoding.UTF8.GetBytes(jarray.ToString()); + req.Content = Encoding.UTF8.GetBytes(jarray.ToString()); - var res = MakeSyncRequest(req); - if (res.status != HttpStatusCode.OK) + var res = await MakeRequest(req); + if (res.Status != HttpStatusCode.OK) { r.success = false; - r.httpException = res.ex; - r.SetErrMsgFromBytes(res.content); + r.httpException = res.Exception; + r.SetErrMsgFromBytes(res.Content); return r; } - var ids = JArray.Parse(Encoding.UTF8.GetString(res.content)); + var ids = JArray.Parse(Encoding.UTF8.GetString(res.Content)); for (int i = 0; i < ids.Count; i++) { statements[i].id = new Guid((String)ids[i]); @@ -477,72 +476,77 @@ public StatementsResultLRSResponse SaveStatements(List statements) return r; } - public StatementLRSResponse RetrieveStatement(Guid id) + public async Task RetrieveStatement(Guid id) { var queryParams = new Dictionary(); queryParams.Add("statementId", id.ToString()); - return GetStatement(queryParams); + return await GetStatement(queryParams); } - public StatementLRSResponse RetrieveVoidedStatement(Guid id) + public async Task RetrieveVoidedStatement(Guid id) { var queryParams = new Dictionary(); queryParams.Add("voidedStatementId", id.ToString()); - return GetStatement(queryParams); + return await GetStatement(queryParams); } - public StatementsResultLRSResponse QueryStatements(StatementsQuery query) + public async Task QueryStatements(StatementsQuery query) { var r = new StatementsResultLRSResponse(); - var req = new MyHTTPRequest(); - req.method = "GET"; - req.resource = "statements"; - req.queryParams = query.ToParameterMap(version); + var req = new LRSHttpRequest + { + Method = HttpMethod.Get, + Resource = "statements", + QueryParams = query.ToParameterMap(version) + }; - var res = MakeSyncRequest(req); - if (res.status != HttpStatusCode.OK) + var res = await MakeRequest(req); + if (res.Status != HttpStatusCode.OK) { r.success = false; - r.httpException = res.ex; - r.SetErrMsgFromBytes(res.content); + r.httpException = res.Exception; + r.SetErrMsgFromBytes(res.Content); return r; } r.success = true; - r.content = new StatementsResult(new Json.StringOfJSON(Encoding.UTF8.GetString(res.content))); + r.content = new StatementsResult(new Json.StringOfJSON(Encoding.UTF8.GetString(res.Content))); return r; } - public StatementsResultLRSResponse MoreStatements(StatementsResult result) + public async Task MoreStatements(StatementsResult result) { var r = new StatementsResultLRSResponse(); - var req = new MyHTTPRequest(); - req.method = "GET"; - req.resource = endpoint.GetLeftPart(UriPartial.Authority); - if (! req.resource.EndsWith("/")) { - req.resource += "/"; + var req = new LRSHttpRequest + { + Method = HttpMethod.Get, + Resource = endpoint.GetLeftPart(UriPartial.Authority) + }; + if (!req.Resource.EndsWith("/", StringComparison.Ordinal)) + { + req.Resource += "/"; } - req.resource += result.more; + req.Resource += result.more; - var res = MakeSyncRequest(req); - if (res.status != HttpStatusCode.OK) + var res = await MakeRequest(req); + if (res.Status != HttpStatusCode.OK) { r.success = false; - r.httpException = res.ex; - r.SetErrMsgFromBytes(res.content); + r.httpException = res.Exception; + r.SetErrMsgFromBytes(res.Content); return r; } r.success = true; - r.content = new StatementsResult(new Json.StringOfJSON(Encoding.UTF8.GetString(res.content))); + r.content = new StatementsResult(new Json.StringOfJSON(Encoding.UTF8.GetString(res.Content))); return r; } // TODO: since param - public ProfileKeysLRSResponse RetrieveStateIds(Activity activity, Agent agent, Nullable registration = null) + public async Task RetrieveStateIds(Activity activity, Agent agent, Nullable registration = null) { var queryParams = new Dictionary(); queryParams.Add("activityId", activity.id.ToString()); @@ -552,9 +556,9 @@ public ProfileKeysLRSResponse RetrieveStateIds(Activity activity, Agent agent, N queryParams.Add("registration", registration.ToString()); } - return GetProfileKeys("activities/state", queryParams); + return await GetProfileKeys("activities/state", queryParams); } - public StateLRSResponse RetrieveState(String id, Activity activity, Agent agent, Nullable registration = null) + public async Task RetrieveState(String id, Activity activity, Agent agent, Nullable registration = null) { var r = new StateLRSResponse(); @@ -574,12 +578,12 @@ public StateLRSResponse RetrieveState(String id, Activity activity, Agent agent, state.registration = registration; } - var resp = GetDocument("activities/state", queryParams, state); - if (resp.status != HttpStatusCode.OK && resp.status != HttpStatusCode.NotFound) + var resp = await GetDocument("activities/state", queryParams, state); + if (resp.Status != HttpStatusCode.OK && resp.Status != HttpStatusCode.NotFound) { r.success = false; - r.httpException = resp.ex; - r.SetErrMsgFromBytes(resp.content); + r.httpException = resp.Exception; + r.SetErrMsgFromBytes(resp.Content); return r; } r.success = true; @@ -587,7 +591,7 @@ public StateLRSResponse RetrieveState(String id, Activity activity, Agent agent, return r; } - public LRSResponse SaveState(StateDocument state) + public async Task SaveState(StateDocument state) { var queryParams = new Dictionary(); queryParams.Add("stateId", state.id); @@ -598,9 +602,9 @@ public LRSResponse SaveState(StateDocument state) queryParams.Add("registration", state.registration.ToString()); } - return SaveDocument("activities/state", queryParams, state); + return await SaveDocument("activities/state", queryParams, state); } - public LRSResponse DeleteState(StateDocument state) + public async Task DeleteState(StateDocument state) { var queryParams = new Dictionary(); queryParams.Add("stateId", state.id); @@ -611,9 +615,9 @@ public LRSResponse DeleteState(StateDocument state) queryParams.Add("registration", state.registration.ToString()); } - return DeleteDocument("activities/state", queryParams); + return await DeleteDocument("activities/state", queryParams); } - public LRSResponse ClearState(Activity activity, Agent agent, Nullable registration = null) + public async Task ClearState(Activity activity, Agent agent, Nullable registration = null) { var queryParams = new Dictionary(); queryParams.Add("activityId", activity.id.ToString()); @@ -623,18 +627,18 @@ public LRSResponse ClearState(Activity activity, Agent agent, Nullable reg queryParams.Add("registration", registration.ToString()); } - return DeleteDocument("activities/state", queryParams); + return await DeleteDocument("activities/state", queryParams); } // TODO: since param - public ProfileKeysLRSResponse RetrieveActivityProfileIds(Activity activity) + public async Task RetrieveActivityProfileIds(Activity activity) { var queryParams = new Dictionary(); queryParams.Add("activityId", activity.id.ToString()); - return GetProfileKeys("activities/profile", queryParams); + return await GetProfileKeys("activities/profile", queryParams); } - public ActivityProfileLRSResponse RetrieveActivityProfile(String id, Activity activity) + public async Task RetrieveActivityProfile(String id, Activity activity) { var r = new ActivityProfileLRSResponse(); @@ -646,12 +650,12 @@ public ActivityProfileLRSResponse RetrieveActivityProfile(String id, Activity ac profile.id = id; profile.activity = activity; - var resp = GetDocument("activities/profile", queryParams, profile); - if (resp.status != HttpStatusCode.OK && resp.status != HttpStatusCode.NotFound) + var resp = await GetDocument("activities/profile", queryParams, profile); + if (resp.Status != HttpStatusCode.OK && resp.Status != HttpStatusCode.NotFound) { r.success = false; - r.httpException = resp.ex; - r.SetErrMsgFromBytes(resp.content); + r.httpException = resp.Exception; + r.SetErrMsgFromBytes(resp.Content); return r; } r.success = true; @@ -659,33 +663,33 @@ public ActivityProfileLRSResponse RetrieveActivityProfile(String id, Activity ac return r; } - public LRSResponse SaveActivityProfile(ActivityProfileDocument profile) + public async Task SaveActivityProfile(ActivityProfileDocument profile) { var queryParams = new Dictionary(); queryParams.Add("profileId", profile.id); queryParams.Add("activityId", profile.activity.id.ToString()); - return SaveDocument("activities/profile", queryParams, profile); + return await SaveDocument("activities/profile", queryParams, profile); } - public LRSResponse DeleteActivityProfile(ActivityProfileDocument profile) + public async Task DeleteActivityProfile(ActivityProfileDocument profile) { var queryParams = new Dictionary(); queryParams.Add("profileId", profile.id); queryParams.Add("activityId", profile.activity.id.ToString()); // TODO: need to pass Etag? - return DeleteDocument("activities/profile", queryParams); + return await DeleteDocument("activities/profile", queryParams); } // TODO: since param - public ProfileKeysLRSResponse RetrieveAgentProfileIds(Agent agent) + public async Task RetrieveAgentProfileIds(Agent agent) { var queryParams = new Dictionary(); queryParams.Add("agent", agent.ToJSON(version)); - return GetProfileKeys("agents/profile", queryParams); + return await GetProfileKeys("agents/profile", queryParams); } - public AgentProfileLRSResponse RetrieveAgentProfile(String id, Agent agent) + public async Task RetrieveAgentProfile(String id, Agent agent) { var r = new AgentProfileLRSResponse(); @@ -697,12 +701,12 @@ public AgentProfileLRSResponse RetrieveAgentProfile(String id, Agent agent) profile.id = id; profile.agent = agent; - var resp = GetDocument("agents/profile", queryParams, profile); - if (resp.status != HttpStatusCode.OK && resp.status != HttpStatusCode.NotFound) + var resp = await GetDocument("agents/profile", queryParams, profile); + if (resp.Status != HttpStatusCode.OK && resp.Status != HttpStatusCode.NotFound) { r.success = false; - r.httpException = resp.ex; - r.SetErrMsgFromBytes(resp.content); + r.httpException = resp.Exception; + r.SetErrMsgFromBytes(resp.Content); return r; } r.success = true; @@ -710,22 +714,22 @@ public AgentProfileLRSResponse RetrieveAgentProfile(String id, Agent agent) return r; } - public LRSResponse SaveAgentProfile(AgentProfileDocument profile) + public async Task SaveAgentProfile(AgentProfileDocument profile) { var queryParams = new Dictionary(); queryParams.Add("profileId", profile.id); queryParams.Add("agent", profile.agent.ToJSON(version)); - return SaveDocument("agents/profile", queryParams, profile); + return await SaveDocument("agents/profile", queryParams, profile); } - public LRSResponse DeleteAgentProfile(AgentProfileDocument profile) + public async Task DeleteAgentProfile(AgentProfileDocument profile) { var queryParams = new Dictionary(); queryParams.Add("profileId", profile.id); queryParams.Add("agent", profile.agent.ToJSON(version)); // TODO: need to pass Etag? - return DeleteDocument("agents/profile", queryParams); + return await DeleteDocument("agents/profile", queryParams); } } } diff --git a/TinCan/TCAPIVersion.cs b/TinCan/TCAPIVersion.cs index cd0a80a..554c98e 100644 --- a/TinCan/TCAPIVersion.cs +++ b/TinCan/TCAPIVersion.cs @@ -20,6 +20,7 @@ namespace TinCan { public sealed class TCAPIVersion { + public static readonly TCAPIVersion V103 = new TCAPIVersion("1.0.3"); public static readonly TCAPIVersion V102 = new TCAPIVersion("1.0.2"); public static readonly TCAPIVersion V101 = new TCAPIVersion("1.0.1"); public static readonly TCAPIVersion V100 = new TCAPIVersion("1.0.0"); @@ -41,6 +42,7 @@ public static Dictionary GetKnown() } known = new Dictionary(); + known.Add("1.0.3", V103); known.Add("1.0.2", V102); known.Add("1.0.1", V101); known.Add("1.0.0", V100); @@ -57,6 +59,7 @@ public static Dictionary GetSupported() } supported = new Dictionary(); + supported.Add("1.0.3", V103); supported.Add("1.0.2", V102); supported.Add("1.0.1", V101); supported.Add("1.0.0", V100); diff --git a/TinCan/TinCan.csproj b/TinCan/TinCan.csproj index 1efe8f4..9743677 100644 --- a/TinCan/TinCan.csproj +++ b/TinCan/TinCan.csproj @@ -9,8 +9,8 @@ Properties TinCan TinCan - v3.5 512 + v4.5 true @@ -41,10 +41,6 @@ MinimumRecommendedRules.ruleset - - ..\packages\Newtonsoft.Json.8.0.3\lib\net35\Newtonsoft.Json.dll - True - @@ -52,6 +48,11 @@ + + ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + + @@ -94,6 +95,8 @@ + + diff --git a/TinCan/packages.config b/TinCan/packages.config index 7546d33..583acbd 100644 --- a/TinCan/packages.config +++ b/TinCan/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/TinCanTests/RemoteLRSResourceTest.cs b/TinCanTests/RemoteLRSResourceTest.cs index d6e0f58..bd14373 100644 --- a/TinCanTests/RemoteLRSResourceTest.cs +++ b/TinCanTests/RemoteLRSResourceTest.cs @@ -17,6 +17,7 @@ namespace TinCanTests { using System; using System.Collections.Generic; + using System.Threading.Tasks; using System.Xml; using NUnit.Framework; using Newtonsoft.Json.Linq; @@ -48,38 +49,38 @@ public void Init() } [Test] - public void TestAbout() + public async Task TestAbout() { - AboutLRSResponse lrsRes = lrs.About(); + AboutLRSResponse lrsRes = await lrs.About(); Assert.IsTrue(lrsRes.success); } [Test] - public void TestAboutFailure() + public async Task TestAboutFailure() { lrs.endpoint = new Uri("http://cloud.scorm.com/tc/3TQLAI9/sandbox/"); - AboutLRSResponse lrsRes = lrs.About(); + AboutLRSResponse lrsRes = await lrs.About(); Assert.IsFalse(lrsRes.success); Console.WriteLine("TestAboutFailure - errMsg: " + lrsRes.errMsg); } [Test] - public void TestSaveStatement() + public async Task TestSaveStatement() { var statement = new Statement(); statement.actor = Support.agent; statement.verb = Support.verb; statement.target = Support.activity; - StatementLRSResponse lrsRes = lrs.SaveStatement(statement); + StatementLRSResponse lrsRes = await lrs.SaveStatement(statement); Assert.IsTrue(lrsRes.success); Assert.AreEqual(statement, lrsRes.content); Assert.IsNotNull(lrsRes.content.id); } [Test] - public void TestSaveStatementWithID() + public async Task TestSaveStatementWithID() { var statement = new Statement(); statement.Stamp(); @@ -87,13 +88,13 @@ public void TestSaveStatementWithID() statement.verb = Support.verb; statement.target = Support.activity; - StatementLRSResponse lrsRes = lrs.SaveStatement(statement); + StatementLRSResponse lrsRes = await lrs.SaveStatement(statement); Assert.IsTrue(lrsRes.success); Assert.AreEqual(statement, lrsRes.content); } [Test] - public void TestSaveStatementStatementRef() + public async Task TestSaveStatementStatementRef() { var statement = new Statement(); statement.Stamp(); @@ -101,13 +102,13 @@ public void TestSaveStatementStatementRef() statement.verb = Support.verb; statement.target = Support.statementRef; - StatementLRSResponse lrsRes = lrs.SaveStatement(statement); + StatementLRSResponse lrsRes = await lrs.SaveStatement(statement); Assert.IsTrue(lrsRes.success); Assert.AreEqual(statement, lrsRes.content); } [Test] - public void TestSaveStatementSubStatement() + public async Task TestSaveStatementSubStatement() { var statement = new Statement(); statement.Stamp(); @@ -117,24 +118,24 @@ public void TestSaveStatementSubStatement() Console.WriteLine(statement.ToJSON(true)); - StatementLRSResponse lrsRes = lrs.SaveStatement(statement); + StatementLRSResponse lrsRes = await lrs.SaveStatement(statement); Assert.IsTrue(lrsRes.success); Assert.AreEqual(statement, lrsRes.content); } [Test] - public void TestVoidStatement() + public async Task TestVoidStatement() { Guid toVoid = Guid.NewGuid(); - StatementLRSResponse lrsRes = lrs.VoidStatement(toVoid, Support.agent); + StatementLRSResponse lrsRes = await lrs.VoidStatement(toVoid, Support.agent); Assert.IsTrue(lrsRes.success, "LRS response successful"); Assert.AreEqual(new Uri("http://adlnet.gov/expapi/verbs/voided"), lrsRes.content.verb.id, "voiding statement uses voided verb"); - Assert.AreEqual(toVoid, ((StatementRef) lrsRes.content.target).id, "voiding statement target correct id"); + Assert.AreEqual(toVoid, ((StatementRef)lrsRes.content.target).id, "voiding statement target correct id"); } [Test] - public void TestSaveStatements() + public async Task TestSaveStatements() { var statement1 = new Statement(); statement1.actor = Support.agent; @@ -151,13 +152,13 @@ public void TestSaveStatements() statements.Add(statement1); statements.Add(statement2); - StatementsResultLRSResponse lrsRes = lrs.SaveStatements(statements); + StatementsResultLRSResponse lrsRes = await lrs.SaveStatements(statements); Assert.IsTrue(lrsRes.success); // TODO: check statements match and ids not null } [Test] - public void TestRetrieveStatement() + public async Task TestRetrieveStatement() { var statement = new TinCan.Statement(); statement.Stamp(); @@ -167,10 +168,10 @@ public void TestRetrieveStatement() statement.context = Support.context; statement.result = Support.result; - StatementLRSResponse saveRes = lrs.SaveStatement(statement); + StatementLRSResponse saveRes = await lrs.SaveStatement(statement); if (saveRes.success) { - StatementLRSResponse retRes = lrs.RetrieveStatement(saveRes.content.id.Value); + StatementLRSResponse retRes = await lrs.RetrieveStatement(saveRes.content.id.Value); Assert.IsTrue(retRes.success); Console.WriteLine("TestRetrieveStatement - statement: " + retRes.content.ToJSON(true)); } @@ -181,7 +182,7 @@ public void TestRetrieveStatement() } [Test] - public void TestQueryStatements() + public async Task TestQueryStatements() { var query = new TinCan.StatementsQuery(); query.agent = Support.agent; @@ -192,22 +193,22 @@ public void TestQueryStatements() query.format = StatementsQueryResultFormat.IDS; query.limit = 10; - StatementsResultLRSResponse lrsRes = lrs.QueryStatements(query); + StatementsResultLRSResponse lrsRes = await lrs.QueryStatements(query); Assert.IsTrue(lrsRes.success); Console.WriteLine("TestQueryStatements - statement count: " + lrsRes.content.statements.Count); } [Test] - public void TestMoreStatements() + public async Task TestMoreStatements() { var query = new TinCan.StatementsQuery(); query.format = StatementsQueryResultFormat.IDS; query.limit = 2; - StatementsResultLRSResponse queryRes = lrs.QueryStatements(query); + StatementsResultLRSResponse queryRes = await lrs.QueryStatements(query); if (queryRes.success && queryRes.content.more != null) { - StatementsResultLRSResponse moreRes = lrs.MoreStatements(queryRes.content); + StatementsResultLRSResponse moreRes = await lrs.MoreStatements(queryRes.content); Assert.IsTrue(moreRes.success); Console.WriteLine("TestMoreStatements - statement count: " + moreRes.content.statements.Count); } @@ -218,22 +219,22 @@ public void TestMoreStatements() } [Test] - public void TestRetrieveStateIds() + public async Task TestRetrieveStateIds() { - ProfileKeysLRSResponse lrsRes = lrs.RetrieveStateIds(Support.activity, Support.agent); + ProfileKeysLRSResponse lrsRes = await lrs.RetrieveStateIds(Support.activity, Support.agent); Assert.IsTrue(lrsRes.success); } [Test] - public void TestRetrieveState() + public async Task TestRetrieveState() { - StateLRSResponse lrsRes = lrs.RetrieveState("test", Support.activity, Support.agent); + StateLRSResponse lrsRes = await lrs.RetrieveState("test", Support.activity, Support.agent); Assert.IsTrue(lrsRes.success); Assert.IsInstanceOf(lrsRes.content); } [Test] - public void TestSaveState() + public async Task TestSaveState() { var doc = new StateDocument(); doc.activity = Support.activity; @@ -241,102 +242,102 @@ public void TestSaveState() doc.id = "test"; doc.content = System.Text.Encoding.UTF8.GetBytes("Test value"); - LRSResponse lrsRes = lrs.SaveState(doc); + LRSResponse lrsRes = await lrs.SaveState(doc); Assert.IsTrue(lrsRes.success); } [Test] - public void TestDeleteState() + public async Task TestDeleteState() { var doc = new StateDocument(); doc.activity = Support.activity; doc.agent = Support.agent; doc.id = "test"; - LRSResponse lrsRes = lrs.DeleteState(doc); + LRSResponse lrsRes = await lrs.DeleteState(doc); Assert.IsTrue(lrsRes.success); } [Test] - public void TestClearState() + public async Task TestClearState() { - LRSResponse lrsRes = lrs.ClearState(Support.activity, Support.agent); + LRSResponse lrsRes = await lrs.ClearState(Support.activity, Support.agent); Assert.IsTrue(lrsRes.success); } [Test] - public void TestRetrieveActivityProfileIds() + public async Task TestRetrieveActivityProfileIds() { - ProfileKeysLRSResponse lrsRes = lrs.RetrieveActivityProfileIds(Support.activity); + ProfileKeysLRSResponse lrsRes = await lrs.RetrieveActivityProfileIds(Support.activity); Assert.IsTrue(lrsRes.success); } [Test] - public void TestRetrieveActivityProfile() + public async Task TestRetrieveActivityProfile() { - ActivityProfileLRSResponse lrsRes = lrs.RetrieveActivityProfile("test", Support.activity); + ActivityProfileLRSResponse lrsRes = await lrs.RetrieveActivityProfile("test", Support.activity); Assert.IsTrue(lrsRes.success); Assert.IsInstanceOf(lrsRes.content); } [Test] - public void TestSaveActivityProfile() + public async Task TestSaveActivityProfile() { var doc = new ActivityProfileDocument(); doc.activity = Support.activity; doc.id = "test"; doc.content = System.Text.Encoding.UTF8.GetBytes("Test value"); - LRSResponse lrsRes = lrs.SaveActivityProfile(doc); + LRSResponse lrsRes = await lrs.SaveActivityProfile(doc); Assert.IsTrue(lrsRes.success); } [Test] - public void TestDeleteActivityProfile() + public async Task TestDeleteActivityProfile() { var doc = new ActivityProfileDocument(); doc.activity = Support.activity; doc.id = "test"; - LRSResponse lrsRes = lrs.DeleteActivityProfile(doc); + LRSResponse lrsRes = await lrs.DeleteActivityProfile(doc); Assert.IsTrue(lrsRes.success); } [Test] - public void TestRetrieveAgentProfileIds() + public async Task TestRetrieveAgentProfileIds() { - ProfileKeysLRSResponse lrsRes = lrs.RetrieveAgentProfileIds(Support.agent); + ProfileKeysLRSResponse lrsRes = await lrs.RetrieveAgentProfileIds(Support.agent); Assert.IsTrue(lrsRes.success); } [Test] - public void TestRetrieveAgentProfile() + public async Task TestRetrieveAgentProfile() { - AgentProfileLRSResponse lrsRes = lrs.RetrieveAgentProfile("test", Support.agent); + AgentProfileLRSResponse lrsRes = await lrs.RetrieveAgentProfile("test", Support.agent); Assert.IsTrue(lrsRes.success); Assert.IsInstanceOf(lrsRes.content); } [Test] - public void TestSaveAgentProfile() + public async Task TestSaveAgentProfile() { var doc = new AgentProfileDocument(); doc.agent = Support.agent; doc.id = "test"; doc.content = System.Text.Encoding.UTF8.GetBytes("Test value"); - LRSResponse lrsRes = lrs.SaveAgentProfile(doc); + LRSResponse lrsRes = await lrs.SaveAgentProfile(doc); Assert.IsTrue(lrsRes.success); } [Test] - public void TestDeleteAgentProfile() + public async Task TestDeleteAgentProfile() { var doc = new AgentProfileDocument(); doc.agent = Support.agent; doc.id = "test"; - LRSResponse lrsRes = lrs.DeleteAgentProfile(doc); + LRSResponse lrsRes = await lrs.DeleteAgentProfile(doc); Assert.IsTrue(lrsRes.success); } } diff --git a/TinCanTests/TinCanTests.csproj b/TinCanTests/TinCanTests.csproj index 348ca50..34a7347 100644 --- a/TinCanTests/TinCanTests.csproj +++ b/TinCanTests/TinCanTests.csproj @@ -9,8 +9,8 @@ Properties TinCanTests TinCanTests - v3.5 512 + v4.5 AnyCPU @@ -44,16 +44,14 @@ MinimumRecommendedRules.ruleset - - ..\packages\Newtonsoft.Json.8.0.3\lib\net35\Newtonsoft.Json.dll - True - - - ..\packages\NUnit.3.2.0\lib\net20\nunit.framework.dll - True - + + ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + + ..\packages\NUnit.3.6.1\lib\net45\nunit.framework.dll + diff --git a/TinCanTests/packages.config b/TinCanTests/packages.config index e1f462b..173d24f 100644 --- a/TinCanTests/packages.config +++ b/TinCanTests/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file