ServiceAgent.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. // -
  2. // <copyright file="ServiceAgent.cs" company="Microsoft Corporation">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // -
  6. namespace Microsoft.Hawaii
  7. {
  8. using System;
  9. using System.Diagnostics;
  10. using System.IO;
  11. using System.Net;
  12. using System.Runtime.Serialization;
  13. using System.Runtime.Serialization.Json;
  14. using System.Xml.Serialization;
  15. /// <summary>
  16. /// A base class for all Hawaii service agent classes.
  17. /// These agents are wrapping the communication tasks specific to each service.
  18. /// ServiceAgent provides functionality common to all these clases.
  19. /// </summary>
  20. /// <typeparam name="T">Generic Type</typeparam>
  21. public abstract class ServiceAgent<T> where T : ServiceResult, new()
  22. {
  23. /// <summary>
  24. /// The http request object.
  25. /// </summary>
  26. private HttpWebRequest request;
  27. /// <summary>
  28. /// Initializes a new instance of the ServiceAgent class.
  29. /// </summary>
  30. public ServiceAgent() :
  31. this(HttpMethod.Get, null)
  32. {
  33. }
  34. /// <summary>
  35. /// Initializes a new instance of the ServiceAgent class.
  36. /// </summary>
  37. /// <param name="requestMethod">Specifies a http request method.</param>
  38. /// <param name="stateObject">Specifies a user-defined object.</param>
  39. public ServiceAgent(HttpMethod requestMethod, object stateObject)
  40. {
  41. this.RequestMethod = requestMethod;
  42. this.Uri = null;
  43. this.ClientIdentity = null;
  44. this.Result = new T();
  45. this.Result.StateObject = stateObject;
  46. }
  47. /// <summary>
  48. /// OnCompleteDelegate delegate type definition.
  49. /// </summary>
  50. /// <param name="result">
  51. /// Returns nothing.
  52. /// </param>
  53. public delegate void OnCompleteDelegate(T result);
  54. /// <summary>
  55. /// Gets or sets OnComplete handler.
  56. /// </summary>
  57. protected OnCompleteDelegate OnComplete { get; set; }
  58. /// <summary>
  59. /// Gets or sets service result.
  60. /// </summary>
  61. protected T Result { get; set; }
  62. /// <summary>
  63. /// Gets or sets service Uri.
  64. /// </summary>
  65. protected Uri Uri { get; set; }
  66. /// <summary>
  67. /// Gets the request content type.
  68. /// </summary>
  69. /// <remarks>
  70. /// Default is application/xml for backwards compatibility.
  71. /// </remarks>
  72. protected virtual string RequestContentType
  73. {
  74. get
  75. {
  76. return "application/xml";
  77. }
  78. }
  79. /// <summary>
  80. /// Gets or sets the HTTP method (GET, POST, PUT or DELETE).
  81. /// </summary>
  82. protected HttpMethod RequestMethod { get; set; }
  83. /// <summary>
  84. /// Gets or sets the client identity.
  85. /// </summary>
  86. protected ClientIdentity ClientIdentity { get; set; }
  87. /// <summary>
  88. /// This method initiates the asynchronous service call.
  89. /// </summary>
  90. /// <param name="handlerMethod">
  91. /// The on complete" callback that will be invoked after the service call completes.
  92. /// </param>
  93. public void ProcessRequest(OnCompleteDelegate handlerMethod)
  94. {
  95. try
  96. {
  97. if (handlerMethod != null)
  98. {
  99. this.OnComplete = handlerMethod;
  100. }
  101. // Create the Http request.
  102. this.request = (HttpWebRequest)HttpWebRequest.Create(this.Uri);
  103. // Set http method.
  104. this.request.Method = this.RequestMethod.ToString().ToUpper();
  105. // Set expected format of response
  106. this.request.Accept = this.RequestContentType;
  107. if (this.ClientIdentity != null)
  108. {
  109. this.ClientIdentity.RetriveAccessToken(new Hawaii.ClientIdentity.RetriveAccessTokenComplete(this.ClientIdentity_RetriveAccessTokenCompleteEvent));
  110. }
  111. else
  112. {
  113. this.SendHttpRequest(string.Empty);
  114. }
  115. }
  116. catch (Exception ex)
  117. {
  118. this.HandleException(ex);
  119. }
  120. }
  121. /// <summary>
  122. /// Deserializes the response stream.
  123. /// </summary>
  124. /// <typeparam name="TResult">Result object.</typeparam>
  125. /// <param name="responseStream">Server response stream.</param>
  126. /// <returns>Deserailized object.</returns>
  127. protected static TResult DeserializeResponse<TResult>(Stream responseStream) where TResult : class
  128. {
  129. try
  130. {
  131. XmlSerializer serializer = new XmlSerializer(typeof(TResult));
  132. return serializer.Deserialize(responseStream) as TResult;
  133. }
  134. catch (Exception ex)
  135. {
  136. throw new SerializationException("Invalid response received from server.", ex);
  137. }
  138. }
  139. /// <summary>
  140. /// Deserializes the response stream using JSON serializer.
  141. /// </summary>
  142. /// <typeparam name="TResult">Result object.</typeparam>
  143. /// <param name="responseStream">Server response stream.</param>
  144. /// <returns>Deserailized object.</returns>
  145. protected static TResult DeserializeResponseJson<TResult>(Stream responseStream) where TResult : class
  146. {
  147. try
  148. {
  149. DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(TResult));
  150. TResult result = serializer.ReadObject(responseStream) as TResult;
  151. return result;
  152. }
  153. catch (Exception ex)
  154. {
  155. throw new SerializationException("Invalid response received from server.", ex);
  156. }
  157. }
  158. /// <summary>
  159. /// This method must be implemented by all classes that inherit from ServiceAgent.
  160. /// It will provide the POST data that has to be sent to the service if the Http Method used is POST.
  161. /// </summary>
  162. /// <returns>Returns the POST data in byte array format.</returns>
  163. protected virtual byte[] GetPostData()
  164. {
  165. return null;
  166. }
  167. /// <summary>
  168. /// This method is called after the response sent by the server is received by the client.
  169. /// It allows classes that inherit from ServiceAgent to do their own processing of
  170. /// the data received from the server.
  171. /// </summary>
  172. /// <param name="responseStream">
  173. /// The response stream containing response data that is received from the server.
  174. /// </param>
  175. protected virtual void ParseOutput(Stream responseStream)
  176. {
  177. }
  178. /// <summary>
  179. /// A virtual method. It is invoked after completing the service request.
  180. /// The implementation of this base class will invoke the client's "on complete" callback method.
  181. /// Classes that inherit from ServiceAgent can oveerite this method to further process the service
  182. /// call result before calling the client's "on complete" callback method.
  183. /// </summary>
  184. protected virtual void OnCompleteRequest()
  185. {
  186. if (this.OnComplete != null)
  187. {
  188. // Call the UI's on completion method.
  189. this.OnComplete(this.Result);
  190. }
  191. }
  192. /// <summary>
  193. /// The callback handler of ClientIdentity after get the access token.
  194. /// </summary>
  195. /// <param name="accessToken">The authorization token.</param>
  196. /// <param name="ex">Coressponding exception if failed to get the access token.</param>
  197. private void ClientIdentity_RetriveAccessTokenCompleteEvent(string accessToken, Exception ex)
  198. {
  199. if (ex == null)
  200. {
  201. this.SendHttpRequest(accessToken);
  202. }
  203. else
  204. {
  205. this.HandleException(ex);
  206. }
  207. }
  208. /// <summary>
  209. /// Send the http request.
  210. /// </summary>
  211. /// <param name="identityToken">The authorization token.</param>
  212. private void SendHttpRequest(string identityToken)
  213. {
  214. try
  215. {
  216. if (!string.IsNullOrEmpty(identityToken))
  217. {
  218. // if the identity token is not null, set the authorization header.
  219. this.request.Headers[HttpRequestHeader.Authorization] = identityToken;
  220. }
  221. if (this.RequestMethod != HttpMethod.Get)
  222. {
  223. // Set the content body type of the request
  224. this.request.ContentType = this.RequestContentType;
  225. this.request.BeginGetRequestStream(new AsyncCallback(this.RequestCallback), this.request);
  226. }
  227. else
  228. {
  229. this.request.BeginGetResponse(new AsyncCallback(this.ResponseCallback), this.request);
  230. }
  231. }
  232. catch (Exception ex)
  233. {
  234. this.HandleException(ex);
  235. }
  236. }
  237. /// <summary>
  238. /// Callback method called after request.BeginGetRequestStream completes.
  239. /// </summary>
  240. /// <param name="asyncResult">
  241. /// An asyncResult object.
  242. /// </param>
  243. private void RequestCallback(IAsyncResult asyncResult)
  244. {
  245. Debug.Assert(asyncResult != null, "IAsyncResult object is null");
  246. Debug.Assert(asyncResult.AsyncState != null, "IAsyncResult.AsyncState object is null");
  247. HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
  248. Debug.Assert(request != null, "HttpWebRequest object is null");
  249. try
  250. {
  251. using (Stream stream = request.EndGetRequestStream(asyncResult))
  252. {
  253. if (stream == null)
  254. {
  255. throw new Exception("Null/Invalid request stream received from server.");
  256. }
  257. // Get the input from the service client.
  258. byte[] inputBuffer = this.GetPostData();
  259. // Step 3: POST data, for a POST request.
  260. if (inputBuffer != null && inputBuffer.Length != 0)
  261. {
  262. stream.Write(inputBuffer, 0, inputBuffer.Length);
  263. }
  264. }
  265. }
  266. catch (Exception ex)
  267. {
  268. this.HandleException(ex);
  269. }
  270. request.BeginGetResponse(new AsyncCallback(this.ResponseCallback), request);
  271. }
  272. /// <summary>
  273. /// Callback method called after request.BeginGetResponse completes.
  274. /// </summary>
  275. /// <param name="asyncResult">
  276. /// An asyncResult object.
  277. /// </param>
  278. private void ResponseCallback(IAsyncResult asyncResult)
  279. {
  280. Debug.Assert(asyncResult != null, "IAsyncResult object is null");
  281. Debug.Assert(asyncResult.AsyncState != null, "IAsyncResult.AsyncState object is null");
  282. HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
  283. Debug.Assert(request != null, "HttpWebRequest object is null");
  284. try
  285. {
  286. using (HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult))
  287. {
  288. if (response == null)
  289. {
  290. // Create and set the error exception.
  291. throw new Exception("Invalid response from server.");
  292. }
  293. this.Result.Status = this.ConvertStatus(response.StatusCode);
  294. if (this.Result.Status != Status.Success)
  295. {
  296. // Create and set the error exception.
  297. throw new Exception("Invalid result status from server.");
  298. }
  299. // Calls client method to interprete the result buffer.
  300. this.ParseOutput(response.GetResponseStream());
  301. // The following code will be never hit, but kept it for safety.
  302. if (this.Result.Exception != null &&
  303. this.Result.Status == Status.Success)
  304. {
  305. // This is be an invalid combination.
  306. Debug.Assert(false, "Result status can't be success when exception indicates an error.");
  307. throw new Exception("Invalid response stream received from server.");
  308. }
  309. }
  310. }
  311. catch (Exception ex)
  312. {
  313. this.Result.Status = Status.Error;
  314. System.Net.WebException webException = ex as System.Net.WebException;
  315. if (webException == null || webException.Response == null)
  316. {
  317. this.Result.Exception = ex;
  318. }
  319. else
  320. {
  321. using (Stream stream = webException.Response.GetResponseStream())
  322. {
  323. try
  324. {
  325. ServiceFault fault = DeserializeResponseJson<ServiceFault>(stream);
  326. if (fault == null)
  327. {
  328. this.Result.Exception = ex;
  329. }
  330. else
  331. {
  332. this.Result.Exception = new WebException(fault.Message, ex);
  333. this.Result.RequestId = fault.RequestId;
  334. this.Result.ServerExceptionStack = fault.ExceptionStack;
  335. }
  336. }
  337. catch (SerializationException)
  338. {
  339. this.Result.Exception = ex;
  340. }
  341. }
  342. }
  343. }
  344. finally
  345. {
  346. // Calls client method to call service event hanlder.
  347. this.OnCompleteRequest();
  348. }
  349. }
  350. /// <summary>
  351. /// Handle unexpected exception and fires the UI callback.
  352. /// </summary>
  353. /// <param name="ex">The exception object.</param>
  354. private void HandleException(Exception ex)
  355. {
  356. this.Result.Status = Status.Error;
  357. this.Result.Exception = ex;
  358. // Call the OnCompleteRequest handler in case any error handling request.
  359. this.OnCompleteRequest();
  360. }
  361. /// <summary>
  362. /// Method converts HttpStatusCode to Status.
  363. /// </summary>
  364. /// <param name="statusCode">Service http status code.</param>
  365. /// <returns>Hawaii Status code.</returns>
  366. private Status ConvertStatus(HttpStatusCode statusCode)
  367. {
  368. switch (statusCode)
  369. {
  370. case HttpStatusCode.OK:
  371. case HttpStatusCode.Created:
  372. return Status.Success;
  373. case HttpStatusCode.InternalServerError:
  374. return Status.InternalServerError;
  375. default:
  376. return Status.Error;
  377. }
  378. }
  379. }
  380. }