AbstractConnection.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.IO.Abstractions;
  5. using System.Net.Http;
  6. using System.Net.Http.Headers;
  7. using System.Threading.Tasks;
  8. using BO.AppServer.Metadata.Dto;
  9. using Quadarax.Foundation.Core.Data.Interface;
  10. using Quadarax.Foundation.Core.Data.Interface.Entity;
  11. using Quadarax.Foundation.Core.Data.Interface.Entity.Dto;
  12. using Quadarax.Foundation.Core.Json;
  13. using Quadarax.Foundation.Core.Logging;
  14. using Quadarax.Foundation.Core.Object;
  15. using Quadarax.Foundation.Core.Value.Extensions;
  16. namespace BO.Connector
  17. {
  18. public abstract class AbstractConnection : DisposableObject
  19. {
  20. #region *** Private fields ***
  21. private HttpClient _client;
  22. private Uri _uriApiBase;
  23. private ILogHandler _log;
  24. private TimeSpan _timeout;
  25. private string _ticket;
  26. private Binder _binder;
  27. private bool _isDumpContentEnabled;
  28. protected abstract string ApiName { get; }
  29. protected bool IsOpen => !string.IsNullOrEmpty(_ticket);
  30. protected string Ticket => _ticket;
  31. #endregion
  32. #region *** Constructors ***
  33. protected AbstractConnection(Uri uriApiBase, TimeSpan timeout, ILogHandler log = null, bool isDumpContentEnabled = false)
  34. {
  35. _uriApiBase = uriApiBase ?? throw new ArgumentNullException(nameof(uriApiBase));
  36. _uriApiBase = new Uri(_uriApiBase.AbsoluteUri + ApiName + "/");
  37. _log = log;
  38. _timeout = timeout;
  39. _binder = new Binder(new FileSystem());
  40. _binder.SerializerOptions.IgnoreReadOnlyProperties = true;
  41. _binder.SerializerOptions.IgnoreNullValues = true;
  42. _isDumpContentEnabled = isDumpContentEnabled;
  43. }
  44. #endregion
  45. #region *** Public Operations ***
  46. public void Open(string userName, string userPassword, string magicKey)
  47. {
  48. Log(LogSeverityEnum.Trace, $"Opening Connector '{ApiName}' open with magicKey='{magicKey}'");
  49. _client = CreateClient();
  50. InitTicket(magicKey);
  51. Login(userName, userPassword);
  52. OnOpening();
  53. Log(LogSeverityEnum.Debug, $"Connector '{ApiName}' open for user '{userName}' with timeout setting {_timeout}.");
  54. }
  55. public void Close()
  56. {
  57. _client?.CancelPendingRequests();
  58. _client?.Dispose();
  59. _client = null;
  60. _ticket = string.Empty;
  61. Log(LogSeverityEnum.Debug, $"Connector '{ApiName}' closed.");
  62. }
  63. public bool Ping(out TimeSpan duration, out string status)
  64. {
  65. CheckIsOpen();
  66. var start = DateTime.Now;
  67. var ping = Task.Run(() =>CallRequestAsync<ResultValueDto<PingDto>>($"{_ticket}/ping", RestCallTypeEnum.Get, null, null, true));
  68. ping.Wait();
  69. duration = DateTime.Now - start;
  70. if (ping.Result == null)
  71. {
  72. status = string.Empty;
  73. return false;
  74. }
  75. status = ping.Result.Value.Status;
  76. return ping.Result.IsSuccess;
  77. }
  78. #endregion
  79. #region *** Virtuals & Overrides ***
  80. protected override void OnDisposing()
  81. {
  82. _client?.Dispose();
  83. }
  84. #endregion
  85. #region *** Private Operations ***
  86. protected void CheckIsOpen()
  87. {
  88. if (!IsOpen)
  89. throw new InvalidOperationException(
  90. "Connector is not open to call specific operation. Call Open() first.");
  91. }
  92. protected void Log(LogSeverityEnum type, string message, Exception e = null)
  93. {
  94. _log?.Log(type, message, e);
  95. }
  96. protected void InitTicket(string magicKey)
  97. {
  98. var result = Task.Run(() => CallRequestAsync<ResultValueDto<AuthDto>>($"{magicKey}/auth", RestCallTypeEnum.Get));;
  99. result.Wait();
  100. if (!result.Result.IsSuccess)
  101. throw result.Result.ToAggregateException();
  102. _ticket = result.Result.Value.Ticket;
  103. }
  104. protected void Login(string userName, string userPassword)
  105. {
  106. var headerAtts = new Dictionary<string, string>();
  107. headerAtts.Add("userName",userName);
  108. headerAtts.Add("userPassword",userPassword);
  109. var result = Task.Run(() => CallRequestAsync<ResultValueDto<UserRDto>>($"{_ticket}/login", RestCallTypeEnum.Post, new ResultPlain(), headerAtts));
  110. result.Wait();
  111. if (!result.Result.IsSuccess)
  112. throw result.Result.ToAggregateException();
  113. Log(LogSeverityEnum.Info,$"User '{result.Result.Value.Name}' is logged in.");
  114. }
  115. protected IDictionary<string, string> GetPagingHeaderAttributes(PagingDto paging)
  116. {
  117. if (paging == null || paging.IsDisabled)
  118. return null;//paging = new PagingDto();
  119. var result = new Dictionary<string, string>
  120. {
  121. { "page", paging.Page.ToString() },
  122. { "pageSize", paging.PageSize.ToString() }
  123. };
  124. return result;
  125. }
  126. protected async Task<Stream> CallGetStreamAsync(string relativeUrlApiCall,
  127. IDictionary<string, string> headerAttributes = null, bool silent = false)
  128. {
  129. Log(LogSeverityEnum.Trace, $"Calling REST API GET STREAM {_uriApiBase.AbsoluteUri + relativeUrlApiCall}");
  130. if (headerAttributes != null)
  131. {
  132. foreach (var key in headerAttributes.Keys)
  133. {
  134. _client.DefaultRequestHeaders.Remove(key);
  135. _client.DefaultRequestHeaders.Add(key,headerAttributes[key]);
  136. }
  137. }
  138. return await _client.GetStreamAsync(relativeUrlApiCall);
  139. }
  140. protected async Task<TResult> CallRequestAsync<TResult>(string relativeUrlApiCall,RestCallTypeEnum method, IDto data = null,IDictionary<string,string> headerAttributes = null, bool silent = false)
  141. where TResult : ResultDto
  142. {
  143. if (data == null)
  144. if(method != RestCallTypeEnum.Get && method != RestCallTypeEnum.GetStream && method != RestCallTypeEnum.Delete)
  145. throw new ArgumentNullException(nameof(data), $"For REST API method '{method}' data must be specified!");
  146. Log(LogSeverityEnum.Trace, $"Calling REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall}");
  147. var start = DateTime.Now;
  148. HttpResponseMessage resp = null;
  149. switch (method)
  150. {
  151. case RestCallTypeEnum.Get:
  152. if (headerAttributes != null)
  153. {
  154. foreach (var key in headerAttributes.Keys)
  155. {
  156. _client.DefaultRequestHeaders.Remove(key);
  157. _client.DefaultRequestHeaders.Add(key,headerAttributes[key]);
  158. }
  159. }
  160. resp = await _client.GetAsync(relativeUrlApiCall);
  161. break;
  162. case RestCallTypeEnum.Post:
  163. var req = new StringContent(_binder.SaveToString(data));
  164. if (headerAttributes!=null)
  165. foreach (var key in headerAttributes.Keys)
  166. req.Headers.Add(key,headerAttributes[key]);
  167. req.Headers.ContentType = new MediaTypeHeaderValue("application/json");
  168. resp = await _client.PostAsync(relativeUrlApiCall, req);
  169. break;
  170. case RestCallTypeEnum.Put:
  171. req = new StringContent(_binder.SaveToString(data));
  172. if (headerAttributes!=null)
  173. foreach (var key in headerAttributes.Keys)
  174. req.Headers.Add(key,headerAttributes[key]);
  175. req.Headers.ContentType = new MediaTypeHeaderValue("application/json");
  176. resp = await _client.PutAsync(relativeUrlApiCall, req);
  177. break;
  178. case RestCallTypeEnum.Delete:
  179. resp = await _client.DeleteAsync(relativeUrlApiCall);
  180. break;
  181. default:
  182. throw new NotSupportedException($"{method} REST API operation is not implemented.");
  183. }
  184. // may remove paging headers after call
  185. /*
  186. if (headerAttributes != null)
  187. foreach (var key in headerAttributes.Keys)
  188. _client.DefaultRequestHeaders.Remove(key);
  189. */
  190. Log(LogSeverityEnum.Trace, $"REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall} done @ {(DateTime.Now - start).ToReadableString()}");
  191. if (!resp.IsSuccessStatusCode)
  192. {
  193. var message = $"Calling REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall} ends with result code '{resp.StatusCode}'";
  194. Log(LogSeverityEnum.Warn, message);
  195. throw new Exception(message);
  196. }
  197. var content = await resp.Content.ReadAsStringAsync();
  198. if (_isDumpContentEnabled)
  199. Log(LogSeverityEnum.Trace, content);
  200. var result = (TResult)_binder.LoadFromString(content, typeof(TResult));
  201. if (result.IsSuccess) return result;
  202. if (silent) return result;
  203. // handle errors
  204. var excps = new List<Exception>();
  205. foreach (var err in result.Errors)
  206. {
  207. var exc = new Exception($"[{err.Code}] {err.Message}");
  208. exc.Data.Add("Code", err.Code);
  209. excps.Add(exc);
  210. }
  211. throw new AggregateException(excps);
  212. }
  213. protected virtual void OnOpening()
  214. {
  215. }
  216. private HttpClient CreateClient()
  217. {
  218. var client = new HttpClient();
  219. client.BaseAddress = new Uri(_uriApiBase, ApiName);
  220. client.Timeout = _timeout;
  221. client.DefaultRequestHeaders.Accept.Clear();
  222. client.DefaultRequestHeaders.Add("User-Agent", "BO.Connector." + ApiName);
  223. // Add an Accept header for JSON format.
  224. client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  225. return client;
  226. }
  227. #endregion
  228. }
  229. }