99using System ;
1010using System . Collections . Generic ;
1111using System . Globalization ;
12+ using System . Linq ;
1213using System . Net ;
1314using System . Net . Http ;
1415using System . Net . Http . Headers ;
@@ -71,8 +72,8 @@ public async Task<List<MoodleLtiLineItem>> GetLineItemsAsync()
7172 string url = $ "{ _serviceUrl } /lineitems?{ _queryStringPrefix } ";
7273
7374 // Perform request and parse response body
74- string response = await DoGetRequestAsync ( url , "application/vnd.ims.lis.v2.lineitemcontainer+json" ) ;
75- var lineItems = JsonConvert . DeserializeObject < List < MoodleLtiLineItem > > ( response ) ;
75+ ( string responseBody , HttpStatusCode _ ) = await DoGetRequestAsync ( url , "application/vnd.ims.lis.v2.lineitemcontainer+json" , new [ ] { HttpStatusCode . OK } ) ;
76+ var lineItems = JsonConvert . DeserializeObject < List < MoodleLtiLineItem > > ( responseBody ) ;
7677
7778 // Generate numeric IDs
7879 foreach ( var lineItem in lineItems )
@@ -88,10 +89,10 @@ public async Task<int> CreateLineItemAsync(MoodleLtiLineItem lineItem)
8889
8990 // Serialize line item
9091 string serializedLineItem = JsonConvert . SerializeObject ( lineItem , new JsonSerializerSettings { NullValueHandling = NullValueHandling . Ignore } ) ;
91- string result = await DoPostRequestAsync ( url , serializedLineItem , "application/vnd.ims.lis.v2.lineitem+json" ) ;
92+ ( string responseBody , HttpStatusCode _ ) = await DoPostRequestAsync ( url , serializedLineItem , "application/vnd.ims.lis.v2.lineitem+json" , new [ ] { HttpStatusCode . Created } ) ;
9293
9394 // Try to deserialize result
94- MoodleLtiLineItem resultLineItem = JsonConvert . DeserializeObject < MoodleLtiLineItem > ( result ) ;
95+ MoodleLtiLineItem resultLineItem = JsonConvert . DeserializeObject < MoodleLtiLineItem > ( responseBody ) ;
9596
9697 // Extract ID
9798 return GetNumericLineItemId ( resultLineItem . StringId ) ;
@@ -103,8 +104,8 @@ public async Task<MoodleLtiLineItem> GetLineItemAsync(int id)
103104 string url = $ "{ _serviceUrl } /lineitems/{ id } /lineitem?{ _queryStringPrefix } ";
104105
105106 // Retrieve line item
106- string response = await DoGetRequestAsync ( url , "application/vnd.ims.lis.v2.lineitem+json" ) ;
107- var lineItem = JsonConvert . DeserializeObject < MoodleLtiLineItem > ( response ) ;
107+ ( string responseBody , HttpStatusCode _ ) = await DoGetRequestAsync ( url , "application/vnd.ims.lis.v2.lineitem+json" , new [ ] { HttpStatusCode . OK } ) ;
108+ var lineItem = JsonConvert . DeserializeObject < MoodleLtiLineItem > ( responseBody ) ;
108109 lineItem . Id = GetNumericLineItemId ( lineItem . StringId ) ;
109110
110111 return lineItem ;
@@ -118,9 +119,7 @@ public async Task UpdateLineItemAsync(MoodleLtiLineItem lineItem)
118119 // Serialize line item
119120 lineItem . StringId = url ;
120121 string serializedLineItem = JsonConvert . SerializeObject ( lineItem , new JsonSerializerSettings { NullValueHandling = NullValueHandling . Ignore } ) ;
121- string result = await DoPutRequestAsync ( url , serializedLineItem , "application/vnd.ims.lis.v2.lineitem+json" ) ;
122-
123- // TODO check result
122+ await DoPutRequestAsync ( url , serializedLineItem , "application/vnd.ims.lis.v2.lineitem+json" , new [ ] { HttpStatusCode . OK } ) ;
124123 }
125124
126125 public async Task DeleteLineItemAsync ( int id )
@@ -129,9 +128,7 @@ public async Task DeleteLineItemAsync(int id)
129128 string url = $ "{ _serviceUrl } /lineitems/{ id } /lineitem?{ _queryStringPrefix } ";
130129
131130 // Delete line item
132- string result = await DoDeleteRequestAsync ( url ) ;
133-
134- // TODO check result
131+ await DoDeleteRequestAsync ( url , new [ ] { HttpStatusCode . NoContent } ) ;
135132 }
136133
137134 public async Task UpdateScoreAsync ( int lineItemId , MoodleLtiScore score )
@@ -141,18 +138,17 @@ public async Task UpdateScoreAsync(int lineItemId, MoodleLtiScore score)
141138
142139 // Serialize and send score
143140 string serializedScore = JsonConvert . SerializeObject ( score , new JsonSerializerSettings { NullValueHandling = NullValueHandling . Ignore } ) ;
144- string result = await DoPostRequestAsync ( url , serializedScore , "application/vnd.ims.lis.v1.score+json" ) ;
145-
146- // TODO check result
141+ await DoPostRequestAsync ( url , serializedScore , "application/vnd.ims.lis.v1.score+json" , new [ ] { HttpStatusCode . OK } ) ;
147142 }
148143
149144 /// <summary>
150- /// Performs a GET request and returns the response body, if it was successful .
145+ /// Performs a GET request and returns the response body and its status code .
151146 /// </summary>
152147 /// <param name="url">The target URL.</param>
153148 /// <param name="expectedContentType">The expected response content type.</param>
154- /// <returns></returns>
155- private async Task < string > DoGetRequestAsync ( string url , string expectedContentType )
149+ /// <param name="expectedStatusCodes">The expected status codes. All other status codes trigger an exception.</param>
150+ /// <exception cref="MoodleLtiException">Thrown when an unexpected HTTP status code is returned.</exception>
151+ private async Task < ( string responseBody , HttpStatusCode statusCode ) > DoGetRequestAsync ( string url , string expectedContentType , HttpStatusCode [ ] expectedStatusCodes )
156152 {
157153 // Assign content type
158154 _httpClient . DefaultRequestHeaders . Accept . Clear ( ) ;
@@ -166,23 +162,31 @@ private async Task<string> DoGetRequestAsync(string url, string expectedContentT
166162 await SecuredClient . SignRequest ( _httpClient , reqMessage , _consumerKey , _sharedSecret , SignatureMethod . HmacSha1 ) ;
167163
168164 // Send HTTP request and retrieve response
169- // TODO exception handling
170- using ( var response = await _httpClient . SendAsync ( reqMessage ) )
171- {
172- if ( response . StatusCode == HttpStatusCode . OK )
173- return await response . ReadBody ( ) ;
174- }
175- return default ;
165+ using var response = await _httpClient . SendAsync ( reqMessage ) ;
166+ string responseBody = await response . ReadBody ( ) ;
167+ if ( expectedStatusCodes . Contains ( response . StatusCode ) )
168+ return ( responseBody , response . StatusCode ) ;
169+
170+ // An error occured
171+ throw CreateExceptionFromFailedRequest (
172+ response . StatusCode ,
173+ expectedStatusCodes ,
174+ response . RequestMessage . Headers . ToString ( ) ,
175+ string . Empty ,
176+ response . Headers . ToString ( ) ,
177+ responseBody
178+ ) ;
176179 }
177180
178181 /// <summary>
179- /// Performs a POST request and returns the response body, if it was successful .
182+ /// Performs a POST request and returns the response body and its status code .
180183 /// </summary>
181184 /// <param name="url">The target URL.</param>
182185 /// <param name="body">The POST body.</param>
183186 /// <param name="bodyContentType">The content type of the body.</param>
184- /// <returns></returns>
185- private async Task < string > DoPostRequestAsync ( string url , string body , string bodyContentType )
187+ /// <param name="expectedStatusCodes">The expected status codes. All other status codes trigger an exception.</param>
188+ /// <exception cref="MoodleLtiException">Thrown when an unexpected HTTP status code is returned.</exception>
189+ private async Task < ( string responseBody , HttpStatusCode statusCode ) > DoPostRequestAsync ( string url , string body , string bodyContentType , HttpStatusCode [ ] expectedStatusCodes )
186190 {
187191 // Sign request object
188192 var encodedBody = new StringContent ( body , Encoding . UTF8 , bodyContentType ) ;
@@ -193,19 +197,31 @@ private async Task<string> DoPostRequestAsync(string url, string body, string bo
193197 await SecuredClient . SignRequest ( _httpClient , reqMessage , _consumerKey , _sharedSecret , SignatureMethod . HmacSha1 ) ;
194198
195199 // Send HTTP request and retrieve response
196- // TODO exception handling
197200 using var response = await _httpClient . SendAsync ( reqMessage ) ;
198- return await response . ReadBody ( ) ;
201+ string responseBody = await response . ReadBody ( ) ;
202+ if ( expectedStatusCodes . Contains ( response . StatusCode ) )
203+ return ( responseBody , response . StatusCode ) ;
204+
205+ // An error occured
206+ throw CreateExceptionFromFailedRequest (
207+ response . StatusCode ,
208+ expectedStatusCodes ,
209+ response . RequestMessage . Headers . ToString ( ) ,
210+ body ,
211+ response . Headers . ToString ( ) ,
212+ responseBody
213+ ) ;
199214 }
200215
201216 /// <summary>
202- /// Performs a PUT request and returns the response body, if it was successful .
217+ /// Performs a PUT request and returns the response body and its status code .
203218 /// </summary>
204219 /// <param name="url">The target URL.</param>
205220 /// <param name="body">The PUT body.</param>
206221 /// <param name="bodyContentType">The content type of the body.</param>
207- /// <returns></returns>
208- private async Task < string > DoPutRequestAsync ( string url , string body , string bodyContentType )
222+ /// <param name="expectedStatusCodes">The expected status codes. All other status codes trigger an exception.</param>
223+ /// <exception cref="MoodleLtiException">Thrown when an unexpected HTTP status code is returned.</exception>
224+ private async Task < ( string responseBody , HttpStatusCode statusCode ) > DoPutRequestAsync ( string url , string body , string bodyContentType , HttpStatusCode [ ] expectedStatusCodes )
209225 {
210226 // Sign request object
211227 var encodedBody = new StringContent ( body , Encoding . UTF8 , bodyContentType ) ;
@@ -216,17 +232,29 @@ private async Task<string> DoPutRequestAsync(string url, string body, string bod
216232 await SecuredClient . SignRequest ( _httpClient , reqMessage , _consumerKey , _sharedSecret , SignatureMethod . HmacSha1 ) ;
217233
218234 // Send HTTP request and retrieve response
219- // TODO exception handling
220235 using var response = await _httpClient . SendAsync ( reqMessage ) ;
221- return await response . ReadBody ( ) ;
236+ string responseBody = await response . ReadBody ( ) ;
237+ if ( expectedStatusCodes . Contains ( response . StatusCode ) )
238+ return ( responseBody , response . StatusCode ) ;
239+
240+ // An error occured
241+ throw CreateExceptionFromFailedRequest (
242+ response . StatusCode ,
243+ expectedStatusCodes ,
244+ response . RequestMessage . Headers . ToString ( ) ,
245+ body ,
246+ response . Headers . ToString ( ) ,
247+ responseBody
248+ ) ;
222249 }
223250
224251 /// <summary>
225- /// Performs a DELETE request and returns the response body, if it was successful .
252+ /// Performs a DELETE request and returns the response body and its status code .
226253 /// </summary>
227254 /// <param name="url">The target URL.</param>
228- /// <returns></returns>
229- private async Task < string > DoDeleteRequestAsync ( string url )
255+ /// <param name="expectedStatusCodes">The expected status codes. All other status codes trigger an exception.</param>
256+ /// <exception cref="MoodleLtiException">Thrown when an unexpected HTTP status code is returned.</exception>
257+ private async Task < ( string responseBody , HttpStatusCode statusCode ) > DoDeleteRequestAsync ( string url , HttpStatusCode [ ] expectedStatusCodes )
230258 {
231259 // Sign request object
232260 var reqMessage = new HttpRequestMessage ( HttpMethod . Delete , url )
@@ -236,12 +264,49 @@ private async Task<string> DoDeleteRequestAsync(string url)
236264 await SecuredClient . SignRequest ( _httpClient , reqMessage , _consumerKey , _sharedSecret , SignatureMethod . HmacSha1 ) ;
237265
238266 // Send HTTP request and retrieve response
239- // TODO exception handling
240- using ( var response = await _httpClient . SendAsync ( reqMessage ) )
241- {
242- // Should return 204 NoContent
243- }
244- return default ;
267+ using var response = await _httpClient . SendAsync ( reqMessage ) ;
268+ string responseBody = await response . ReadBody ( ) ;
269+ if ( expectedStatusCodes . Contains ( response . StatusCode ) )
270+ return ( responseBody , response . StatusCode ) ;
271+
272+ // An error occured
273+ throw CreateExceptionFromFailedRequest (
274+ response . StatusCode ,
275+ expectedStatusCodes ,
276+ response . RequestMessage . Headers . ToString ( ) ,
277+ string . Empty ,
278+ response . Headers . ToString ( ) ,
279+ responseBody
280+ ) ;
281+ }
282+
283+ /// <summary>
284+ /// Creates a new <see cref="MoodleLtiException"/> from the given HTTP request data.
285+ /// </summary>
286+ /// <param name="statusCode">Server status code.</param>
287+ /// <param name="expectedStatusCodes">Expected server status codes.</param>
288+ /// <param name="requestHeaders">Request headers.</param>
289+ /// <param name="requestBody">Request body.</param>
290+ /// <param name="responseHeaders">Response headers.</param>
291+ /// <param name="responseBody">Response body.</param>
292+ /// <returns></returns>
293+ private static MoodleLtiException CreateExceptionFromFailedRequest (
294+ HttpStatusCode statusCode ,
295+ HttpStatusCode [ ] expectedStatusCodes ,
296+ string requestHeaders ,
297+ string requestBody ,
298+ string responseHeaders ,
299+ string responseBody )
300+ {
301+ string expectedStatusCodesString = string . Join ( ", " , expectedStatusCodes ) ;
302+ var exception = new MoodleLtiException ( $ "Unexpected HTTP status: { statusCode } (expected: { expectedStatusCodesString } )") ;
303+ exception . Data . Add ( "StatusCode" , statusCode ) ;
304+ exception . Data . Add ( "StatusCodesExpected" , expectedStatusCodesString ) ;
305+ exception . Data . Add ( "RequestHeaders" , requestHeaders ) ;
306+ exception . Data . Add ( "RequestBody" , requestBody ) ;
307+ exception . Data . Add ( "ResponseHeaders" , responseHeaders ) ;
308+ exception . Data . Add ( "ResponseBody" , responseBody ) ;
309+ return exception ;
245310 }
246311
247312 /// <summary>
0 commit comments